forked from github/plane
feat: Mobx integration, List and Kanban boards implementation in plane space (#1844)
* feat: init mobx and issue filter * feat: Implemented list and kanban views in plane space and integrated mobx. * feat: updated store type check
This commit is contained in:
parent
ad4cdcc512
commit
cd5e5b96da
@ -1 +1 @@
|
||||
NEXT_PUBLIC_VERCEL_ENV=local
|
||||
NEXT_PUBLIC_API_BASE_URL=''
|
33
apps/space/app/[workspace_slug]/[project_slug]/layout.tsx
Normal file
33
apps/space/app/[workspace_slug]/[project_slug]/layout.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
// next imports
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
// components
|
||||
import IssueNavbar from "components/issues/navbar";
|
||||
import IssueFilter from "components/issues/filters-render";
|
||||
|
||||
const RootLayout = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="relative w-screen min-h-[500px] h-screen overflow-hidden flex flex-col">
|
||||
<div className="flex-shrink-0 h-[60px] border-b border-gray-300 relative flex items-center bg-white select-none">
|
||||
<IssueNavbar />
|
||||
</div>
|
||||
{/* <div className="flex-shrink-0 min-h-[50px] h-auto py-1.5 border-b border-gray-300 relative flex items-center shadow-md bg-white select-none">
|
||||
<IssueFilter />
|
||||
</div> */}
|
||||
<div className="w-full h-full relative bg-gray-100/50 overflow-hidden">{children}</div>
|
||||
|
||||
<div className="absolute z-[99999] bottom-[10px] right-[10px] bg-white rounded-sm shadow-lg border border-gray-100">
|
||||
<Link href="https://plane.so" className="p-1 px-2 flex items-center gap-1" target="_blank">
|
||||
<div className="w-[24px] h-[24px] relative flex justify-center items-center">
|
||||
<Image src="/plane-logo.webp" alt="plane logo" className="w-[24px] h-[24px]" height="24" width="24" />
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
Powered by <b>Plane Deploy</b>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default RootLayout;
|
84
apps/space/app/[workspace_slug]/[project_slug]/page.tsx
Normal file
84
apps/space/app/[workspace_slug]/[project_slug]/page.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
// next imports
|
||||
import { useRouter, useParams, useSearchParams } from "next/navigation";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueListView } from "components/issues/board-views/list";
|
||||
import { IssueKanbanView } from "components/issues/board-views/kanban";
|
||||
import { IssueCalendarView } from "components/issues/board-views/calendar";
|
||||
import { IssueSpreadsheetView } from "components/issues/board-views/spreadsheet";
|
||||
import { IssueGanttView } from "components/issues/board-views/gantt";
|
||||
// mobx store
|
||||
import { RootStore } from "store/root";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// types
|
||||
import { TIssueBoardKeys } from "store/types";
|
||||
|
||||
const WorkspaceProjectPage = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const router = useRouter();
|
||||
const routerParams = useParams();
|
||||
const routerSearchparams = useSearchParams();
|
||||
|
||||
const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string };
|
||||
const board = routerSearchparams.get("board") as TIssueBoardKeys | "";
|
||||
|
||||
// updating default board view when we are in the issues page
|
||||
useEffect(() => {
|
||||
if (workspace_slug && project_slug) {
|
||||
if (!board) {
|
||||
store.issue.setCurrentIssueBoardView("list");
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${store?.issue?.currentIssueBoardView}`);
|
||||
} else {
|
||||
if (board != store?.issue?.currentIssueBoardView) store.issue.setCurrentIssueBoardView(board);
|
||||
}
|
||||
}
|
||||
}, [workspace_slug, project_slug, board, router, store?.issue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace_slug && project_slug) {
|
||||
store?.project?.getProjectSettingsAsync(workspace_slug, project_slug);
|
||||
store?.issue?.getIssuesAsync(workspace_slug, project_slug);
|
||||
}
|
||||
}, [workspace_slug, project_slug, store?.project, store?.issue]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
{store?.issue?.loader && !store.issue.issues ? (
|
||||
<div className="text-sm text-center py-10 text-gray-500">Loading...</div>
|
||||
) : (
|
||||
<>
|
||||
{store?.issue?.error ? (
|
||||
<div className="text-sm text-center py-10 text-gray-500">Something went wrong.</div>
|
||||
) : (
|
||||
store?.issue?.currentIssueBoardView && (
|
||||
<>
|
||||
{store?.issue?.currentIssueBoardView === "list" && (
|
||||
<div className="relative w-full h-full overflow-y-auto">
|
||||
<div className="container mx-auto px-5 py-3">
|
||||
<IssueListView />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{store?.issue?.currentIssueBoardView === "kanban" && (
|
||||
<div className="relative w-full h-full mx-auto px-5">
|
||||
<IssueKanbanView />
|
||||
</div>
|
||||
)}
|
||||
{store?.issue?.currentIssueBoardView === "calendar" && <IssueCalendarView />}
|
||||
{store?.issue?.currentIssueBoardView === "spreadsheet" && <IssueSpreadsheetView />}
|
||||
{store?.issue?.currentIssueBoardView === "gantt" && <IssueGanttView />}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default WorkspaceProjectPage;
|
@ -1,9 +1,7 @@
|
||||
import React from "react";
|
||||
"use client";
|
||||
|
||||
const WorkspaceProjectPage = () => (
|
||||
<div className="relative w-screen h-screen flex justify-center items-center text-5xl">
|
||||
Plane Workspace project Space
|
||||
</div>
|
||||
<div className="relative w-screen h-screen flex justify-center items-center text-5xl">Plane Workspace Space</div>
|
||||
);
|
||||
|
||||
export default WorkspaceProjectPage;
|
@ -1,10 +1,18 @@
|
||||
"use client";
|
||||
|
||||
// root styles
|
||||
import "styles/globals.css";
|
||||
// mobx store provider
|
||||
import { MobxStoreProvider } from "lib/mobx/store-provider";
|
||||
import MobxStoreInit from "lib/mobx/store-init";
|
||||
|
||||
const RootLayout = ({ children }: { children: React.ReactNode }) => (
|
||||
<html lang="en">
|
||||
<body className="antialiased w-100">
|
||||
<main>{children}</main>
|
||||
<MobxStoreProvider>
|
||||
<MobxStoreInit />
|
||||
<main>{children}</main>
|
||||
</MobxStoreProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
const HomePage = () => (
|
||||
|
5
apps/space/components/icons/index.ts
Normal file
5
apps/space/components/icons/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./issue-group/backlog-state-icon";
|
||||
export * from "./issue-group/unstarted-state-icon";
|
||||
export * from "./issue-group/started-state-icon";
|
||||
export * from "./issue-group/completed-state-icon";
|
||||
export * from "./issue-group/cancelled-state-icon";
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const BacklogStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["backlog"],
|
||||
}) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="10" cy="10" r="9" stroke={color} strokeLinecap="round" strokeDasharray="4 4" />
|
||||
</svg>
|
||||
);
|
@ -0,0 +1,74 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const CancelledStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["cancelled"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.36 84.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
|
||||
/>
|
||||
<circle className="cls-2" fill={color} cx="42.18" cy="42.18" r="31.04" />
|
||||
<path
|
||||
className="cls-3"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke="#ffffff"
|
||||
strokeLinecap="square"
|
||||
strokeMiterlimit={10}
|
||||
d="M32.64,32.44q9.54,9.75,19.09,19.48"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke="#ffffff"
|
||||
strokeLinecap="square"
|
||||
strokeMiterlimit={10}
|
||||
d="M32.64,51.92,51.73,32.44"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const CompletedStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["completed"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.36 84.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
|
||||
/>
|
||||
<circle className="cls-2" fill={color} cx="42.18" cy="42.18" r="31.04" />
|
||||
<path
|
||||
className="cls-3"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke="#ffffff"
|
||||
strokeLinecap="square"
|
||||
strokeMiterlimit={10}
|
||||
d="M30.45,43.75l6.61,6.61L53.92,34"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const StartedStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["started"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 83.36 83.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M20,7.19a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M76.17,20a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M63.42,76.17A39.78,39.78,0,0,1,20,75.64"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M7.19,63.42A39.75,39.75,0,0,1,7.73,20"
|
||||
/>
|
||||
<path
|
||||
className="cls-2"
|
||||
fill={color}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M42.32,41.21q9.57-14.45,19.13-28.9a35.8,35.8,0,0,0-39.09,0Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-2"
|
||||
fill={color}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M42.32,41.7,61.45,70.6a35.75,35.75,0,0,1-39.09,0Z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const UnstartedStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["unstarted"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.36 84.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
6
apps/space/components/icons/types.d.ts
vendored
Normal file
6
apps/space/components/icons/types.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export type Props = {
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
color?: string;
|
||||
className?: string;
|
||||
};
|
32
apps/space/components/issues/board-views/block-due-date.tsx
Normal file
32
apps/space/components/issues/board-views/block-due-date.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
// helpers
|
||||
import { renderDateFormat } from "constants/helpers";
|
||||
|
||||
export const findHowManyDaysLeft = (date: string | Date) => {
|
||||
const today = new Date();
|
||||
const eventDate = new Date(date);
|
||||
const timeDiff = Math.abs(eventDate.getTime() - today.getTime());
|
||||
return Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||
};
|
||||
|
||||
const validDate = (date: any, state: string): string => {
|
||||
if (date === null || ["backlog", "unstarted", "cancelled"].includes(state))
|
||||
return `bg-gray-500/10 text-gray-500 border-gray-500/50`;
|
||||
else {
|
||||
const today = new Date();
|
||||
const dueDate = new Date(date);
|
||||
|
||||
if (dueDate < today) return `bg-red-500/10 text-red-500 border-red-500/50`;
|
||||
else return `bg-green-500/10 text-green-500 border-green-500/50`;
|
||||
}
|
||||
};
|
||||
|
||||
export const IssueBlockDueDate = ({ due_date, state }: any) => (
|
||||
<div
|
||||
className={`h-[24px] rounded-sm flex px-2 items-center border border-gray-300 gap-1 text-gray-700 text-xs font-medium
|
||||
${validDate(due_date, state)}`}
|
||||
>
|
||||
{renderDateFormat(due_date)}
|
||||
</div>
|
||||
);
|
17
apps/space/components/issues/board-views/block-labels.tsx
Normal file
17
apps/space/components/issues/board-views/block-labels.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
export const IssueBlockLabels = ({ labels }: any) => (
|
||||
<div className="relative flex items-center flex-wrap gap-1">
|
||||
{labels &&
|
||||
labels.length > 0 &&
|
||||
labels.map((_label: any) => (
|
||||
<div
|
||||
className={`h-[24px] rounded-sm flex px-1 items-center border gap-1 !bg-transparent !text-gray-700`}
|
||||
style={{ backgroundColor: `${_label?.color}10`, borderColor: `${_label?.color}50` }}
|
||||
>
|
||||
<div className="w-[10px] h-[10px] rounded-full" style={{ backgroundColor: `${_label?.color}` }} />
|
||||
<div className="text-sm">{_label?.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
17
apps/space/components/issues/board-views/block-priority.tsx
Normal file
17
apps/space/components/issues/board-views/block-priority.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
// types
|
||||
import { TIssuePriorityKey } from "store/types/issue";
|
||||
// constants
|
||||
import { issuePriorityFilter } from "constants/data";
|
||||
|
||||
export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorityKey | null }) => {
|
||||
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
|
||||
|
||||
if (priority_detail === null) return <></>;
|
||||
return (
|
||||
<div className={`w-[24px] h-[24px] rounded-sm flex justify-center items-center ${priority_detail?.className}`}>
|
||||
<span className="material-symbols-rounded text-[16px]">{priority_detail?.icon}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
18
apps/space/components/issues/board-views/block-state.tsx
Normal file
18
apps/space/components/issues/board-views/block-state.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
|
||||
export const IssueBlockState = ({ state }: any) => {
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
if (stateGroup === null) return <></>;
|
||||
return (
|
||||
<div
|
||||
className={`h-[24px] rounded-sm flex px-1 items-center border ${stateGroup?.className} gap-1 !bg-transparent !text-gray-700`}
|
||||
>
|
||||
<stateGroup.icon />
|
||||
<div className="text-sm">{state?.name}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export const IssueCalendarView = () => <div> </div>;
|
1
apps/space/components/issues/board-views/gantt/index.tsx
Normal file
1
apps/space/components/issues/board-views/gantt/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export const IssueGanttView = () => <div> </div>;
|
57
apps/space/components/issues/board-views/kanban/block.tsx
Normal file
57
apps/space/components/issues/board-views/kanban/block.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueBlockPriority } from "components/issues/board-views/block-priority";
|
||||
import { IssueBlockState } from "components/issues/board-views/block-state";
|
||||
import { IssueBlockLabels } from "components/issues/board-views/block-labels";
|
||||
import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// interfaces
|
||||
import { IIssue } from "store/types/issue";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="p-2 px-3 bg-white space-y-2 rounded-sm shadow">
|
||||
{/* id */}
|
||||
<div className="flex-shrink-0 text-sm text-gray-600 w-[60px]">
|
||||
{store?.project?.project?.identifier}-{issue?.sequence_id}
|
||||
</div>
|
||||
|
||||
{/* name */}
|
||||
<div className="font-medium text-gray-800 h-full line-clamp-2">{issue.name}</div>
|
||||
|
||||
{/* priority */}
|
||||
<div className="relative flex items-center gap-3 w-full">
|
||||
{issue?.priority && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockPriority priority={issue?.priority} />
|
||||
</div>
|
||||
)}
|
||||
{/* state */}
|
||||
{issue?.state_detail && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockState state={issue?.state_detail} />
|
||||
</div>
|
||||
)}
|
||||
{/* labels */}
|
||||
{issue?.label_details && issue?.label_details.length > 0 && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockLabels labels={issue?.label_details} />
|
||||
</div>
|
||||
)}
|
||||
{/* due date */}
|
||||
{issue?.target_date && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockDueDate due_date={issue?.target_date} group={issue?.state_detail?.group} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
31
apps/space/components/issues/board-views/kanban/header.tsx
Normal file
31
apps/space/components/issues/board-views/kanban/header.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// interfaces
|
||||
import { IIssueState } from "store/types/issue";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
if (stateGroup === null) return <></>;
|
||||
|
||||
return (
|
||||
<div className="py-2 flex items-center gap-2">
|
||||
<div className="w-[28px] h-[28px] flex justify-center items-center">
|
||||
<stateGroup.icon />
|
||||
</div>
|
||||
<div className="font-medium capitalize">{state?.name}</div>
|
||||
<div className="bg-gray-200/50 text-gray-700 font-medium text-xs w-full max-w-[26px] h-[20px] flex justify-center items-center rounded-full">
|
||||
{store.issue.getCountOfIssuesByState(state.id)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
44
apps/space/components/issues/board-views/kanban/index.tsx
Normal file
44
apps/space/components/issues/board-views/kanban/index.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueListHeader } from "components/issues/board-views/kanban/header";
|
||||
import { IssueListBlock } from "components/issues/board-views/kanban/block";
|
||||
// interfaces
|
||||
import { IIssueState, IIssue } from "store/types/issue";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const IssueKanbanView = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full overflow-hidden overflow-x-auto flex gap-3">
|
||||
{store?.issue?.states &&
|
||||
store?.issue?.states.length > 0 &&
|
||||
store?.issue?.states.map((_state: IIssueState) => (
|
||||
<div className="flex-shrink-0 relative w-[340px] h-full flex flex-col">
|
||||
<div className="flex-shrink-0">
|
||||
<IssueListHeader state={_state} />
|
||||
</div>
|
||||
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
||||
{store.issue.getFilteredIssuesByState(_state.id) &&
|
||||
store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
|
||||
<div className="space-y-3 pb-2">
|
||||
{store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
||||
<IssueListBlock issue={_issue} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative w-full h-full flex justify-center items-center p-10 text-center text-sm text-gray-600">
|
||||
No Issues are available.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
59
apps/space/components/issues/board-views/list/block.tsx
Normal file
59
apps/space/components/issues/board-views/list/block.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueBlockPriority } from "components/issues/board-views/block-priority";
|
||||
import { IssueBlockState } from "components/issues/board-views/block-state";
|
||||
import { IssueBlockLabels } from "components/issues/board-views/block-labels";
|
||||
import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// interfaces
|
||||
import { IIssue } from "store/types/issue";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="p-2 px-3 relative flex items-center gap-3">
|
||||
<div className="relative flex items-center gap-3 w-full">
|
||||
{/* id */}
|
||||
<div className="flex-shrink-0 text-sm text-gray-600 w-[60px]">
|
||||
{store?.project?.project?.identifier}-{issue?.sequence_id}
|
||||
</div>
|
||||
{/* name */}
|
||||
<div className="font-medium text-gray-800 h-full line-clamp-1">{issue.name}</div>
|
||||
</div>
|
||||
|
||||
{/* priority */}
|
||||
{issue?.priority && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockPriority priority={issue?.priority} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* state */}
|
||||
{issue?.state_detail && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockState state={issue?.state_detail} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* labels */}
|
||||
{issue?.label_details && issue?.label_details.length > 0 && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockLabels labels={issue?.label_details} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* due date */}
|
||||
{issue?.target_date && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockDueDate due_date={issue?.target_date} group={issue?.state_detail?.group} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
31
apps/space/components/issues/board-views/list/header.tsx
Normal file
31
apps/space/components/issues/board-views/list/header.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// interfaces
|
||||
import { IIssueState } from "store/types/issue";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
if (stateGroup === null) return <></>;
|
||||
|
||||
return (
|
||||
<div className="py-2 px-3 flex items-center gap-2">
|
||||
<div className="w-[28px] h-[28px] flex justify-center items-center">
|
||||
<stateGroup.icon />
|
||||
</div>
|
||||
<div className="font-medium capitalize">{state?.name}</div>
|
||||
<div className="bg-gray-200/50 text-gray-700 font-medium text-xs w-full max-w-[26px] h-[20px] flex justify-center items-center rounded-full">
|
||||
{store.issue.getCountOfIssuesByState(state.id)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
38
apps/space/components/issues/board-views/list/index.tsx
Normal file
38
apps/space/components/issues/board-views/list/index.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueListHeader } from "components/issues/board-views/list/header";
|
||||
import { IssueListBlock } from "components/issues/board-views/list/block";
|
||||
// interfaces
|
||||
import { IIssueState, IIssue } from "store/types/issue";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const IssueListView = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{store?.issue?.states &&
|
||||
store?.issue?.states.length > 0 &&
|
||||
store?.issue?.states.map((_state: IIssueState) => (
|
||||
<div className="relative w-full">
|
||||
<IssueListHeader state={_state} />
|
||||
{store.issue.getFilteredIssuesByState(_state.id) &&
|
||||
store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
|
||||
<div className="bg-white divide-y">
|
||||
{store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
||||
<IssueListBlock issue={_issue} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white p-5 text-sm text-gray-600">No Issues are available.</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -0,0 +1 @@
|
||||
export const IssueSpreadsheetView = () => <div> </div>;
|
38
apps/space/components/issues/filters-render/date.tsx
Normal file
38
apps/space/components/issues/filters-render/date.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const IssueDateFilter = observer(() => {
|
||||
const store = useMobxStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 border border-gray-300 px-2 py-1 pr-1 rounded">
|
||||
<div className="flex-shrink-0 font-medium">Due Date</div>
|
||||
<div className="relative flex flex-wrap items-center gap-1">
|
||||
{/* <div className="flex items-center gap-1 border border-gray-300 px-[2px] py-0.5 rounded-full">
|
||||
<div className="w-[18px] h-[18px] cursor-pointer flex justify-center items-center overflow-hidden rounded-full border border-gray-300">
|
||||
<span className={`material-symbols-rounded text-[16px]`}>close</span>
|
||||
</div>
|
||||
<div>Backlog</div>
|
||||
<div
|
||||
className={`w-[18px] h-[18px] cursor-pointer flex justify-center items-center overflow-hidden rounded-full text-gray-500 hover:bg-gray-200/60 hover:text-gray-600`}
|
||||
>
|
||||
<span className={`material-symbols-rounded text-[16px]`}>close</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
<div
|
||||
className={`w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-sm text-gray-500 hover:bg-gray-200/60 hover:text-gray-600`}
|
||||
>
|
||||
<span className={`material-symbols-rounded text-[16px]`}>close</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssueDateFilter;
|
40
apps/space/components/issues/filters-render/index.tsx
Normal file
40
apps/space/components/issues/filters-render/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import IssueStateFilter from "./state";
|
||||
import IssueLabelFilter from "./label";
|
||||
import IssuePriorityFilter from "./priority";
|
||||
import IssueDateFilter from "./date";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const IssueFilter = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const clearAllFilters = () => {};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-5 flex justify-start items-center flex-wrap gap-2 text-sm">
|
||||
{/* state */}
|
||||
{store?.issue?.states && <IssueStateFilter />}
|
||||
{/* labels */}
|
||||
{store?.issue?.labels && <IssueLabelFilter />}
|
||||
{/* priority */}
|
||||
<IssuePriorityFilter />
|
||||
{/* due date */}
|
||||
<IssueDateFilter />
|
||||
{/* clear all filters */}
|
||||
<div
|
||||
className="flex items-center gap-2 border border-gray-300 px-2 py-1 pr-1 rounded cursor-pointer hover:bg-gray-200/60"
|
||||
onClick={clearAllFilters}
|
||||
>
|
||||
<div>Clear all filters</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssueFilter;
|
@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// interfaces
|
||||
import { IIssueLabel } from "store/types/issue";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
|
||||
export const RenderIssueLabel = observer(({ label }: { label: IIssueLabel }) => {
|
||||
const store = useMobxStore();
|
||||
|
||||
const removeLabelFromFilter = () => {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-shrink-0 relative flex items-center flex-wrap gap-1 border px-[2px] py-0.5 rounded-full select-none"
|
||||
style={{ color: label?.color, backgroundColor: `${label?.color}10`, borderColor: `${label?.color}50` }}
|
||||
>
|
||||
<div className="flex-shrink-0 w-[20px] h-[20px] flex justify-center items-center overflow-hidden rounded-full">
|
||||
<div className="w-[10px] h-[10px] rounded-full" style={{ backgroundColor: `${label?.color}` }} />
|
||||
</div>
|
||||
<div className="text-sm font-medium whitespace-nowrap">{label?.name}</div>
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-full text-gray-500 hover:bg-gray-200/60 hover:text-gray-600"
|
||||
onClick={removeLabelFromFilter}
|
||||
>
|
||||
<span className="material-symbols-rounded text-[14px]">close</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
37
apps/space/components/issues/filters-render/label/index.tsx
Normal file
37
apps/space/components/issues/filters-render/label/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { RenderIssueLabel } from "./filter-label-block";
|
||||
// interfaces
|
||||
import { IIssueLabel } from "store/types/issue";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const IssueLabelFilter = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const clearLabelFilters = () => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 border border-gray-300 px-2 py-1 pr-1 rounded">
|
||||
<div className="flex-shrink-0 font-medium">Labels</div>
|
||||
<div className="relative flex flex-wrap items-center gap-1">
|
||||
{store?.issue?.labels &&
|
||||
store?.issue?.labels.map((_label: IIssueLabel, _index: number) => <RenderIssueLabel label={_label} />)}
|
||||
</div>
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-sm text-gray-500 hover:bg-gray-200/60 hover:text-gray-600"
|
||||
onClick={clearLabelFilters}
|
||||
>
|
||||
<span className="material-symbols-rounded text-[16px]">close</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssueLabelFilter;
|
@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// interfaces
|
||||
import { IIssuePriorityFilters } from "store/types/issue";
|
||||
|
||||
export const RenderIssuePriority = observer(({ priority }: { priority: IIssuePriorityFilters }) => {
|
||||
const store = useMobxStore();
|
||||
|
||||
const removePriorityFromFilter = () => {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex-shrink-0 relative flex items-center flex-wrap gap-1 border px-[2px] py-0.5 rounded-full select-none ${
|
||||
priority.className || ``
|
||||
}`}
|
||||
>
|
||||
<div className="flex-shrink-0 w-[20px] h-[20px] flex justify-center items-center overflow-hidden rounded-full">
|
||||
<span className="material-symbols-rounded text-[14px]">{priority?.icon}</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium whitespace-nowrap">{priority?.title}</div>
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-full text-gray-500 hover:bg-gray-200/60 hover:text-gray-600"
|
||||
onClick={removePriorityFromFilter}
|
||||
>
|
||||
<span className="material-symbols-rounded text-[14px]">close</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { RenderIssuePriority } from "./filter-priority-block";
|
||||
// interfaces
|
||||
import { IIssuePriorityFilters } from "store/types/issue";
|
||||
// constants
|
||||
import { issuePriorityFilters } from "constants/data";
|
||||
|
||||
const IssuePriorityFilter = observer(() => {
|
||||
const store = useMobxStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 border border-gray-300 px-2 py-1 pr-1 rounded">
|
||||
<div className="flex-shrink-0 font-medium">Priority</div>
|
||||
<div className="relative flex flex-wrap items-center gap-1">
|
||||
{issuePriorityFilters.map((_priority: IIssuePriorityFilters, _index: number) => (
|
||||
<RenderIssuePriority priority={_priority} />
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={`w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-sm text-gray-500 hover:bg-gray-200/60 hover:text-gray-600`}
|
||||
>
|
||||
<span className={`material-symbols-rounded text-[16px]`}>close</span>
|
||||
</div>
|
||||
</div>{" "}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssuePriorityFilter;
|
@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// interfaces
|
||||
import { IIssueState } from "store/types/issue";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
|
||||
export const RenderIssueState = observer(({ state }: { state: IIssueState }) => {
|
||||
const store = useMobxStore();
|
||||
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
const removeStateFromFilter = () => {};
|
||||
|
||||
if (stateGroup === null) return <></>;
|
||||
return (
|
||||
<div
|
||||
className={`flex-shrink-0 relative flex items-center flex-wrap gap-1 border px-[2px] py-0.5 rounded-full select-none ${
|
||||
stateGroup.className || ``
|
||||
}`}
|
||||
>
|
||||
<div className="flex-shrink-0 w-[20px] h-[20px] flex justify-center items-center overflow-hidden rounded-full">
|
||||
<stateGroup.icon />
|
||||
</div>
|
||||
<div className="text-sm font-medium whitespace-nowrap">{state?.name}</div>
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-full text-gray-500 hover:bg-gray-200/60 hover:text-gray-600"
|
||||
onClick={removeStateFromFilter}
|
||||
>
|
||||
<span className="material-symbols-rounded text-[14px]">close</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
37
apps/space/components/issues/filters-render/state/index.tsx
Normal file
37
apps/space/components/issues/filters-render/state/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { RenderIssueState } from "./filter-state-block";
|
||||
// interfaces
|
||||
import { IIssueState } from "store/types/issue";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const IssueStateFilter = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const clearStateFilters = () => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 border border-gray-300 px-2 py-1 pr-1 rounded">
|
||||
<div className="flex-shrink-0 font-medium">State</div>
|
||||
<div className="relative flex flex-wrap items-center gap-1">
|
||||
{store?.issue?.states &&
|
||||
store?.issue?.states.map((_state: IIssueState, _index: number) => <RenderIssueState state={_state} />)}
|
||||
</div>
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] cursor-pointer flex justify-center items-center overflow-hidden rounded-sm text-gray-500 hover:bg-gray-200/60 hover:text-gray-600"
|
||||
onClick={clearStateFilters}
|
||||
>
|
||||
<span className="material-symbols-rounded text-[16px]">close</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssueStateFilter;
|
54
apps/space/components/issues/navbar/index.tsx
Normal file
54
apps/space/components/issues/navbar/index.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
// components
|
||||
import { NavbarSearch } from "./search";
|
||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||
import { NavbarIssueFilter } from "./issue-filter";
|
||||
import { NavbarIssueView } from "./issue-view";
|
||||
import { NavbarTheme } from "./theme";
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const IssueNavbar = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="px-5 relative w-full flex items-center gap-4">
|
||||
{/* project detail */}
|
||||
<div className="flex-shrink-0 flex items-center gap-2">
|
||||
<div className="w-[32px] h-[32px] rounded-sm flex justify-center items-center bg-gray-100 text-[24px]">
|
||||
{store?.project?.project && store?.project?.project?.icon ? store?.project?.project?.icon : "😊"}
|
||||
</div>
|
||||
<div className="font-medium text-lg max-w-[300px] line-clamp-1 overflow-hidden">
|
||||
{store?.project?.project?.name || `...`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* issue search bar */}
|
||||
<div className="w-full">
|
||||
<NavbarSearch />
|
||||
</div>
|
||||
|
||||
{/* issue views */}
|
||||
<div className="flex-shrink-0 relative flex items-center gap-1 transition-all ease-in-out delay-150">
|
||||
<NavbarIssueBoardView />
|
||||
</div>
|
||||
|
||||
{/* issue filters */}
|
||||
{/* <div className="flex-shrink-0 relative flex items-center gap-2">
|
||||
<NavbarIssueFilter />
|
||||
<NavbarIssueView />
|
||||
</div> */}
|
||||
|
||||
{/* theming */}
|
||||
{/* <div className="flex-shrink-0 relative">
|
||||
<NavbarTheme />
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssueNavbar;
|
54
apps/space/components/issues/navbar/issue-board-view.tsx
Normal file
54
apps/space/components/issues/navbar/issue-board-view.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
// next imports
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// constants
|
||||
import { issueViews } from "constants/data";
|
||||
// interfaces
|
||||
import { TIssueBoardKeys } from "store/types";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const NavbarIssueBoardView = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const router = useRouter();
|
||||
const routerParams = useParams();
|
||||
|
||||
const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string };
|
||||
|
||||
const handleCurrentBoardView = (boardView: TIssueBoardKeys) => {
|
||||
store?.issue?.setCurrentIssueBoardView(boardView);
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${boardView}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{store?.project?.workspaceProjectSettings &&
|
||||
issueViews &&
|
||||
issueViews.length > 0 &&
|
||||
issueViews.map(
|
||||
(_view) =>
|
||||
store?.project?.workspaceProjectSettings?.views[_view?.key] && (
|
||||
<div
|
||||
key={_view?.key}
|
||||
className={`w-[28px] h-[28px] flex justify-center items-center rounded-sm cursor-pointer text-gray-500 ${
|
||||
_view?.key === store?.issue?.currentIssueBoardView
|
||||
? `bg-gray-200/60 text-gray-800`
|
||||
: `hover:bg-gray-200/60 text-gray-600`
|
||||
}`}
|
||||
onClick={() => handleCurrentBoardView(_view?.key)}
|
||||
title={_view?.title}
|
||||
>
|
||||
<span className={`material-symbols-rounded text-[18px] ${_view?.className ? _view?.className : ``}`}>
|
||||
{_view?.icon}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
13
apps/space/components/issues/navbar/issue-filter.tsx
Normal file
13
apps/space/components/issues/navbar/issue-filter.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const NavbarIssueFilter = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return <div>Filter</div>;
|
||||
});
|
13
apps/space/components/issues/navbar/issue-view.tsx
Normal file
13
apps/space/components/issues/navbar/issue-view.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const NavbarIssueView = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return <div>View</div>;
|
||||
});
|
13
apps/space/components/issues/navbar/search.tsx
Normal file
13
apps/space/components/issues/navbar/search.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const NavbarSearch = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
return <div> </div>;
|
||||
});
|
28
apps/space/components/issues/navbar/theme.tsx
Normal file
28
apps/space/components/issues/navbar/theme.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export const NavbarTheme = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const handleTheme = () => {
|
||||
store?.theme?.setTheme(store?.theme?.theme === "light" ? "dark" : "light");
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-[28px] h-[28px] flex justify-center items-center rounded-sm cursor-pointer bg-gray-100 hover:bg-gray-200 hover:bg-gray-200/60 text-gray-600 transition-all"
|
||||
onClick={handleTheme}
|
||||
>
|
||||
{store?.theme?.theme === "light" ? (
|
||||
<span className={`material-symbols-rounded text-[18px]`}>dark_mode</span>
|
||||
) : (
|
||||
<span className={`material-symbols-rounded text-[18px]`}>light_mode</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
153
apps/space/constants/data.ts
Normal file
153
apps/space/constants/data.ts
Normal file
@ -0,0 +1,153 @@
|
||||
// interfaces
|
||||
import {
|
||||
IIssueBoardViews,
|
||||
// priority
|
||||
TIssuePriorityKey,
|
||||
// state groups
|
||||
TIssueGroupKey,
|
||||
IIssuePriorityFilters,
|
||||
IIssueGroup,
|
||||
} from "store/types/issue";
|
||||
// icons
|
||||
import {
|
||||
BacklogStateIcon,
|
||||
UnstartedStateIcon,
|
||||
StartedStateIcon,
|
||||
CompletedStateIcon,
|
||||
CancelledStateIcon,
|
||||
} from "components/icons";
|
||||
|
||||
// all issue views
|
||||
export const issueViews: IIssueBoardViews[] = [
|
||||
{
|
||||
key: "list",
|
||||
title: "List View",
|
||||
icon: "format_list_bulleted",
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
key: "kanban",
|
||||
title: "Board View",
|
||||
icon: "grid_view",
|
||||
className: "",
|
||||
},
|
||||
// {
|
||||
// key: "calendar",
|
||||
// title: "Calendar View",
|
||||
// icon: "calendar_month",
|
||||
// className: "",
|
||||
// },
|
||||
// {
|
||||
// key: "spreadsheet",
|
||||
// title: "Spreadsheet View",
|
||||
// icon: "table_chart",
|
||||
// className: "",
|
||||
// },
|
||||
// {
|
||||
// key: "gantt",
|
||||
// title: "Gantt Chart View",
|
||||
// icon: "waterfall_chart",
|
||||
// className: "rotate-90",
|
||||
// },
|
||||
];
|
||||
|
||||
// issue priority filters
|
||||
export const issuePriorityFilters: IIssuePriorityFilters[] = [
|
||||
{
|
||||
key: "urgent",
|
||||
title: "Urgent",
|
||||
className: "border border-red-500/50 bg-red-500/20 text-red-500",
|
||||
icon: "error",
|
||||
},
|
||||
{
|
||||
key: "high",
|
||||
title: "High",
|
||||
className: "border border-orange-500/50 bg-orange-500/20 text-orange-500",
|
||||
icon: "signal_cellular_alt",
|
||||
},
|
||||
{
|
||||
key: "medium",
|
||||
title: "Medium",
|
||||
className: "border border-yellow-500/50 bg-yellow-500/20 text-yellow-500",
|
||||
icon: "signal_cellular_alt_2_bar",
|
||||
},
|
||||
{
|
||||
key: "low",
|
||||
title: "Low",
|
||||
className: "border border-green-500/50 bg-green-500/20 text-green-500",
|
||||
icon: "signal_cellular_alt_1_bar",
|
||||
},
|
||||
{
|
||||
key: "none",
|
||||
title: "None",
|
||||
className: "border border-gray-500/50 bg-gray-500/20 text-gray-500",
|
||||
icon: "block",
|
||||
},
|
||||
];
|
||||
|
||||
export const issuePriorityFilter = (priorityKey: TIssuePriorityKey): IIssuePriorityFilters | null => {
|
||||
const currentIssuePriority: IIssuePriorityFilters | undefined | null =
|
||||
issuePriorityFilters && issuePriorityFilters.length > 0
|
||||
? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
|
||||
: null;
|
||||
|
||||
if (currentIssuePriority === undefined || currentIssuePriority === null) return null;
|
||||
return { ...currentIssuePriority };
|
||||
};
|
||||
|
||||
// issue group filters
|
||||
export const issueGroupColors: {
|
||||
[key: string]: string;
|
||||
} = {
|
||||
backlog: "#d9d9d9",
|
||||
unstarted: "#3f76ff",
|
||||
started: "#f59e0b",
|
||||
completed: "#16a34a",
|
||||
cancelled: "#dc2626",
|
||||
};
|
||||
|
||||
export const issueGroups: IIssueGroup[] = [
|
||||
{
|
||||
key: "backlog",
|
||||
title: "Backlog",
|
||||
color: "#d9d9d9",
|
||||
className: `border-[#d9d9d9]/50 text-[#d9d9d9] bg-[#d9d9d9]/10`,
|
||||
icon: BacklogStateIcon,
|
||||
},
|
||||
{
|
||||
key: "unstarted",
|
||||
title: "Unstarted",
|
||||
color: "#3f76ff",
|
||||
className: `border-[#3f76ff]/50 text-[#3f76ff] bg-[#3f76ff]/10`,
|
||||
icon: UnstartedStateIcon,
|
||||
},
|
||||
{
|
||||
key: "started",
|
||||
title: "Started",
|
||||
color: "#f59e0b",
|
||||
className: `border-[#f59e0b]/50 text-[#f59e0b] bg-[#f59e0b]/10`,
|
||||
icon: StartedStateIcon,
|
||||
},
|
||||
{
|
||||
key: "completed",
|
||||
title: "Completed",
|
||||
color: "#16a34a",
|
||||
className: `border-[#16a34a]/50 text-[#16a34a] bg-[#16a34a]/10`,
|
||||
icon: CompletedStateIcon,
|
||||
},
|
||||
{
|
||||
key: "cancelled",
|
||||
title: "Cancelled",
|
||||
color: "#dc2626",
|
||||
className: `border-[#dc2626]/50 text-[#dc2626] bg-[#dc2626]/10`,
|
||||
icon: CancelledStateIcon,
|
||||
},
|
||||
];
|
||||
|
||||
export const issueGroupFilter = (issueKey: TIssueGroupKey): IIssueGroup | null => {
|
||||
const currentIssueStateGroup: IIssueGroup | undefined | null =
|
||||
issueGroups && issueGroups.length > 0 ? issueGroups.find((group) => group.key === issueKey) : null;
|
||||
|
||||
if (currentIssueStateGroup === undefined || currentIssueStateGroup === null) return null;
|
||||
return { ...currentIssueStateGroup };
|
||||
};
|
13
apps/space/constants/helpers.ts
Normal file
13
apps/space/constants/helpers.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const renderDateFormat = (date: string | Date | null) => {
|
||||
if (!date) return "N/A";
|
||||
|
||||
var d = new Date(date),
|
||||
month = "" + (d.getMonth() + 1),
|
||||
day = "" + d.getDate(),
|
||||
year = d.getFullYear();
|
||||
|
||||
if (month.length < 2) month = "0" + month;
|
||||
if (day.length < 2) day = "0" + day;
|
||||
|
||||
return [year, month, day].join("-");
|
||||
};
|
35
apps/space/lib/mobx/store-init.tsx
Normal file
35
apps/space/lib/mobx/store-init.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
// next imports
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// interface
|
||||
import { TIssueBoardKeys } from "store/types";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const MobxStoreInit = () => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
// search params
|
||||
const routerSearchparams = useSearchParams();
|
||||
|
||||
const board = routerSearchparams.get("board") as TIssueBoardKeys;
|
||||
|
||||
useEffect(() => {
|
||||
// theme
|
||||
const _theme = localStorage && localStorage.getItem("app_theme") ? localStorage.getItem("app_theme") : "light";
|
||||
if (_theme && store?.theme?.theme != _theme) store.theme.setTheme(_theme);
|
||||
else localStorage.setItem("app_theme", _theme && _theme != "light" ? "dark" : "light");
|
||||
}, [store?.theme]);
|
||||
|
||||
// updating default board view when we are in the issues page
|
||||
useEffect(() => {
|
||||
if (board && board != store?.issue?.currentIssueBoardView) store.issue.setCurrentIssueBoardView(board);
|
||||
}, [board, store?.issue]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default MobxStoreInit;
|
28
apps/space/lib/mobx/store-provider.tsx
Normal file
28
apps/space/lib/mobx/store-provider.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
// mobx store
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
let rootStore: RootStore = new RootStore();
|
||||
|
||||
export const MobxStoreContext = createContext<RootStore>(rootStore);
|
||||
|
||||
const initializeStore = () => {
|
||||
const _rootStore: RootStore = rootStore ?? new RootStore();
|
||||
if (typeof window === "undefined") return _rootStore;
|
||||
if (!rootStore) rootStore = _rootStore;
|
||||
return _rootStore;
|
||||
};
|
||||
|
||||
export const MobxStoreProvider = ({ children }: any) => {
|
||||
const store: RootStore = initializeStore();
|
||||
return <MobxStoreContext.Provider value={store}>{children}</MobxStoreContext.Provider>;
|
||||
};
|
||||
|
||||
// hook
|
||||
export const useMobxStore = () => {
|
||||
const context = useContext(MobxStoreContext);
|
||||
if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider");
|
||||
return context;
|
||||
};
|
@ -18,6 +18,8 @@
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-next": "13.2.1",
|
||||
"js-cookie": "^3.0.1",
|
||||
"mobx": "^6.10.0",
|
||||
"mobx-react-lite": "^4.0.3",
|
||||
"next": "^13.4.13",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^18.2.0",
|
||||
|
BIN
apps/space/public/plane-logo.webp
Normal file
BIN
apps/space/public/plane-logo.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 566 B |
100
apps/space/services/api.service.ts
Normal file
100
apps/space/services/api.service.ts
Normal file
@ -0,0 +1,100 @@
|
||||
// axios
|
||||
import axios from "axios";
|
||||
// js cookie
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const base_url: string | null = "https://boarding.plane.so";
|
||||
|
||||
abstract class APIService {
|
||||
protected baseURL: string;
|
||||
protected headers: any = {};
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.baseURL = base_url ? base_url : baseURL;
|
||||
}
|
||||
|
||||
setRefreshToken(token: string) {
|
||||
Cookies.set("refreshToken", token);
|
||||
}
|
||||
|
||||
getRefreshToken() {
|
||||
return Cookies.get("refreshToken");
|
||||
}
|
||||
|
||||
purgeRefreshToken() {
|
||||
Cookies.remove("refreshToken", { path: "/" });
|
||||
}
|
||||
|
||||
setAccessToken(token: string) {
|
||||
Cookies.set("accessToken", token);
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return Cookies.get("accessToken");
|
||||
}
|
||||
|
||||
purgeAccessToken() {
|
||||
Cookies.remove("accessToken", { path: "/" });
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
return {
|
||||
Authorization: `Bearer ${this.getAccessToken()}`,
|
||||
};
|
||||
}
|
||||
|
||||
get(url: string, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "get",
|
||||
url: this.baseURL + url,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
post(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "post",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
put(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "put",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
patch(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "patch",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
delete(url: string, data?: any, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "delete",
|
||||
url: this.baseURL + url,
|
||||
data: data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
request(config = {}) {
|
||||
return axios(config);
|
||||
}
|
||||
}
|
||||
|
||||
export default APIService;
|
20
apps/space/services/issue.service.ts
Normal file
20
apps/space/services/issue.service.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class IssueService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getPublicIssues(workspace_slug: string, project_slug: string): Promise<any> {
|
||||
return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/issues/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default IssueService;
|
20
apps/space/services/project.service.ts
Normal file
20
apps/space/services/project.service.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class ProjectService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getProjectSettingsAsync(workspace_slug: string, project_slug: string): Promise<any> {
|
||||
return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/settings/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectService;
|
20
apps/space/services/user.service.ts
Normal file
20
apps/space/services/user.service.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class UserService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async currentUser(): Promise<any> {
|
||||
return this.get("/api/users/me/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UserService;
|
91
apps/space/store/issue.ts
Normal file
91
apps/space/store/issue.ts
Normal file
@ -0,0 +1,91 @@
|
||||
// mobx
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// service
|
||||
import IssueService from "services/issue.service";
|
||||
// types
|
||||
import { TIssueBoardKeys } from "store/types/issue";
|
||||
import { IIssueStore, IIssue, IIssueState, IIssueLabel } from "./types";
|
||||
|
||||
class IssueStore implements IIssueStore {
|
||||
currentIssueBoardView: TIssueBoardKeys | null = null;
|
||||
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
|
||||
states: IIssueState[] | null = null;
|
||||
labels: IIssueLabel[] | null = null;
|
||||
issues: IIssue[] | null = null;
|
||||
|
||||
userSelectedStates: string[] = [];
|
||||
userSelectedLabels: string[] = [];
|
||||
// root store
|
||||
rootStore;
|
||||
// service
|
||||
issueService;
|
||||
|
||||
constructor(_rootStore: any) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
currentIssueBoardView: observable,
|
||||
|
||||
loader: observable,
|
||||
error: observable,
|
||||
|
||||
states: observable.ref,
|
||||
labels: observable.ref,
|
||||
issues: observable.ref,
|
||||
|
||||
userSelectedStates: observable,
|
||||
userSelectedLabels: observable,
|
||||
// action
|
||||
setCurrentIssueBoardView: action,
|
||||
getIssuesAsync: action,
|
||||
// computed
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
this.issueService = new IssueService();
|
||||
}
|
||||
|
||||
// computed
|
||||
getCountOfIssuesByState(state_id: string): number {
|
||||
return this.issues?.filter((issue) => issue.state == state_id).length || 0;
|
||||
}
|
||||
|
||||
getFilteredIssuesByState(state_id: string): IIssue[] | [] {
|
||||
return this.issues?.filter((issue) => issue.state == state_id) || [];
|
||||
}
|
||||
|
||||
// action
|
||||
setCurrentIssueBoardView = async (view: TIssueBoardKeys) => {
|
||||
this.currentIssueBoardView = view;
|
||||
};
|
||||
|
||||
getIssuesAsync = async (workspace_slug: string, project_slug: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
this.error = null;
|
||||
|
||||
const response = await this.issueService.getPublicIssues(workspace_slug, project_slug);
|
||||
|
||||
if (response) {
|
||||
const _states: IIssueState[] = [...response?.states];
|
||||
const _labels: IIssueLabel[] = [...response?.labels];
|
||||
const _issues: IIssue[] = [...response?.issues];
|
||||
runInAction(() => {
|
||||
this.states = _states;
|
||||
this.labels = _labels;
|
||||
this.issues = _issues;
|
||||
this.loader = false;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
} catch (error) {
|
||||
this.loader = false;
|
||||
this.error = error;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default IssueStore;
|
69
apps/space/store/project.ts
Normal file
69
apps/space/store/project.ts
Normal file
@ -0,0 +1,69 @@
|
||||
// mobx
|
||||
import { observable, action, makeObservable, runInAction } from "mobx";
|
||||
// service
|
||||
import ProjectService from "services/project.service";
|
||||
// types
|
||||
import { IProjectStore, IWorkspace, IProject, IProjectSettings } from "./types";
|
||||
|
||||
class ProjectStore implements IProjectStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
|
||||
workspace: IWorkspace | null = null;
|
||||
project: IProject | null = null;
|
||||
workspaceProjectSettings: IProjectSettings | null = null;
|
||||
// root store
|
||||
rootStore;
|
||||
// service
|
||||
projectService;
|
||||
|
||||
constructor(_rootStore: any | null = null) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
workspace: observable.ref,
|
||||
project: observable.ref,
|
||||
workspaceProjectSettings: observable.ref,
|
||||
loader: observable,
|
||||
error: observable.ref,
|
||||
// action
|
||||
getProjectSettingsAsync: action,
|
||||
// computed
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
this.projectService = new ProjectService();
|
||||
}
|
||||
|
||||
getProjectSettingsAsync = async (workspace_slug: string, project_slug: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
this.error = null;
|
||||
|
||||
const response = await this.projectService.getProjectSettingsAsync(workspace_slug, project_slug);
|
||||
|
||||
if (response) {
|
||||
const _project: IProject = { ...response?.project_details };
|
||||
const _workspace: IWorkspace = { ...response?.workspace_detail };
|
||||
const _workspaceProjectSettings: IProjectSettings = {
|
||||
comments: response?.comments,
|
||||
reactions: response?.reactions,
|
||||
votes: response?.votes,
|
||||
views: { ...response?.views },
|
||||
};
|
||||
runInAction(() => {
|
||||
this.project = _project;
|
||||
this.workspace = _workspace;
|
||||
this.workspaceProjectSettings = _workspaceProjectSettings;
|
||||
this.loader = false;
|
||||
});
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
this.loader = false;
|
||||
this.error = error;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ProjectStore;
|
@ -1 +1,25 @@
|
||||
export const init = {};
|
||||
// mobx lite
|
||||
import { enableStaticRendering } from "mobx-react-lite";
|
||||
// store imports
|
||||
import UserStore from "./user";
|
||||
import ThemeStore from "./theme";
|
||||
import IssueStore from "./issue";
|
||||
import ProjectStore from "./project";
|
||||
// types
|
||||
import { IIssueStore, IProjectStore, IThemeStore, IUserStore } from "./types";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
|
||||
export class RootStore {
|
||||
user: IUserStore;
|
||||
theme: IThemeStore;
|
||||
issue: IIssueStore;
|
||||
project: IProjectStore;
|
||||
|
||||
constructor() {
|
||||
this.user = new UserStore(this);
|
||||
this.theme = new ThemeStore(this);
|
||||
this.issue = new IssueStore(this);
|
||||
this.project = new ProjectStore(this);
|
||||
}
|
||||
}
|
||||
|
33
apps/space/store/theme.ts
Normal file
33
apps/space/store/theme.ts
Normal file
@ -0,0 +1,33 @@
|
||||
// mobx
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// types
|
||||
import { IThemeStore } from "./types";
|
||||
|
||||
class ThemeStore implements IThemeStore {
|
||||
theme: "light" | "dark" = "light";
|
||||
// root store
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: any | null = null) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
theme: observable,
|
||||
// action
|
||||
setTheme: action,
|
||||
// computed
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
}
|
||||
|
||||
setTheme = async (_theme: "light" | "dark" | string) => {
|
||||
try {
|
||||
localStorage.setItem("app_theme", _theme);
|
||||
this.theme = _theme === "light" ? "light" : "dark";
|
||||
} catch (error) {
|
||||
console.error("setting user theme error", error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ThemeStore;
|
4
apps/space/store/types/index.ts
Normal file
4
apps/space/store/types/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./user";
|
||||
export * from "./theme";
|
||||
export * from "./project";
|
||||
export * from "./issue";
|
72
apps/space/store/types/issue.ts
Normal file
72
apps/space/store/types/issue.ts
Normal file
@ -0,0 +1,72 @@
|
||||
export type TIssueBoardKeys = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
|
||||
|
||||
export interface IIssueBoardViews {
|
||||
key: TIssueBoardKeys;
|
||||
title: string;
|
||||
icon: string;
|
||||
className: string;
|
||||
}
|
||||
|
||||
export type TIssuePriorityKey = "urgent" | "high" | "medium" | "low" | "none";
|
||||
export type TIssuePriorityTitle = "Urgent" | "High" | "Medium" | "Low" | "None";
|
||||
export interface IIssuePriorityFilters {
|
||||
key: TIssuePriorityKey;
|
||||
title: TIssuePriorityTitle;
|
||||
className: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export type TIssueGroupKey = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
||||
export type TIssueGroupTitle = "Backlog" | "Unstarted" | "Started" | "Completed" | "Cancelled";
|
||||
|
||||
export interface IIssueGroup {
|
||||
key: TIssueGroupKey;
|
||||
title: TIssueGroupTitle;
|
||||
color: string;
|
||||
className: string;
|
||||
icon: React.FC;
|
||||
}
|
||||
|
||||
export interface IIssue {
|
||||
id: string;
|
||||
sequence_id: number;
|
||||
name: string;
|
||||
description_html: string;
|
||||
priority: TIssuePriorityKey | null;
|
||||
state: string;
|
||||
state_detail: any;
|
||||
label_details: any;
|
||||
target_date: any;
|
||||
}
|
||||
|
||||
export interface IIssueState {
|
||||
id: string;
|
||||
name: string;
|
||||
group: TIssueGroupKey;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface IIssueLabel {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface IIssueStore {
|
||||
currentIssueBoardView: TIssueBoardKeys | null;
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
|
||||
states: IIssueState[] | null;
|
||||
labels: IIssueLabel[] | null;
|
||||
issues: IIssue[] | null;
|
||||
|
||||
userSelectedStates: string[];
|
||||
userSelectedLabels: string[];
|
||||
|
||||
getCountOfIssuesByState: (state: string) => number;
|
||||
getFilteredIssuesByState: (state: string) => IIssue[];
|
||||
|
||||
setCurrentIssueBoardView: (view: TIssueBoardKeys) => void;
|
||||
getIssuesAsync: (workspace_slug: string, project_slug: string) => Promise<void>;
|
||||
}
|
39
apps/space/store/types/project.ts
Normal file
39
apps/space/store/types/project.ts
Normal file
@ -0,0 +1,39 @@
|
||||
export interface IWorkspace {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export interface IProject {
|
||||
id: string;
|
||||
identifier: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
cover_image: string | null;
|
||||
icon_prop: string | null;
|
||||
emoji: string | null;
|
||||
}
|
||||
|
||||
export interface IProjectSettings {
|
||||
comments: boolean;
|
||||
reactions: boolean;
|
||||
votes: boolean;
|
||||
views: {
|
||||
list: boolean;
|
||||
gantt: boolean;
|
||||
kanban: boolean;
|
||||
calendar: boolean;
|
||||
spreadsheet: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IProjectStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
|
||||
workspace: IWorkspace | null;
|
||||
project: IProject | null;
|
||||
workspaceProjectSettings: IProjectSettings | null;
|
||||
|
||||
getProjectSettingsAsync: (workspace_slug: string, project_slug: string) => Promise<void>;
|
||||
}
|
4
apps/space/store/types/theme.ts
Normal file
4
apps/space/store/types/theme.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface IThemeStore {
|
||||
theme: string;
|
||||
setTheme: (theme: "light" | "dark" | string) => void;
|
||||
}
|
4
apps/space/store/types/user.ts
Normal file
4
apps/space/store/types/user.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface IUserStore {
|
||||
currentUser: any | null;
|
||||
getUserAsync: () => void;
|
||||
}
|
43
apps/space/store/user.ts
Normal file
43
apps/space/store/user.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// mobx
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// service
|
||||
import UserService from "services/user.service";
|
||||
// types
|
||||
import { IUserStore } from "./types";
|
||||
|
||||
class UserStore implements IUserStore {
|
||||
currentUser: any | null = null;
|
||||
// root store
|
||||
rootStore;
|
||||
// service
|
||||
userService;
|
||||
|
||||
constructor(_rootStore: any) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
currentUser: observable,
|
||||
// actions
|
||||
// computed
|
||||
});
|
||||
this.rootStore = _rootStore;
|
||||
this.userService = new UserService();
|
||||
}
|
||||
|
||||
getUserAsync = async () => {
|
||||
try {
|
||||
const response = this.userService.currentUser();
|
||||
if (response) {
|
||||
runInAction(() => {
|
||||
this.currentUser = response;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("error", error);
|
||||
runInAction(() => {
|
||||
// render error actions
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default UserStore;
|
@ -6,6 +6,7 @@ module.exports = {
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./layouts/**/*.tsx",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
"./constants/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
|
100
yarn.lock
100
yarn.lock
@ -3617,6 +3617,14 @@
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
||||
integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
a11y-status@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/a11y-status/-/a11y-status-2.0.1.tgz#a7883105910b9e3cd09ea90e5acf8404dc01b47e"
|
||||
@ -3641,6 +3649,11 @@ acorn@^8.8.2, acorn@^8.9.0:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
|
||||
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
|
||||
|
||||
acorn@^8.9.0:
|
||||
version "8.10.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
|
||||
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
@ -5180,6 +5193,56 @@ eslint@8.34.0:
|
||||
strip-json-comments "^3.1.0"
|
||||
text-table "^0.2.0"
|
||||
|
||||
eslint-visitor-keys@^3.4.1:
|
||||
version "3.4.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f"
|
||||
integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==
|
||||
|
||||
eslint@8.34.0:
|
||||
version "8.34.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6"
|
||||
integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==
|
||||
dependencies:
|
||||
"@eslint/eslintrc" "^1.4.1"
|
||||
"@humanwhocodes/config-array" "^0.11.8"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
ajv "^6.10.0"
|
||||
chalk "^4.0.0"
|
||||
cross-spawn "^7.0.2"
|
||||
debug "^4.3.2"
|
||||
doctrine "^3.0.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
eslint-scope "^7.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
espree "^9.4.0"
|
||||
esquery "^1.4.0"
|
||||
esutils "^2.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
file-entry-cache "^6.0.1"
|
||||
find-up "^5.0.0"
|
||||
glob-parent "^6.0.2"
|
||||
globals "^13.19.0"
|
||||
grapheme-splitter "^1.0.4"
|
||||
ignore "^5.2.0"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
is-glob "^4.0.0"
|
||||
is-path-inside "^3.0.3"
|
||||
js-sdsl "^4.1.4"
|
||||
js-yaml "^4.1.0"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash.merge "^4.6.2"
|
||||
minimatch "^3.1.2"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.1"
|
||||
regexpp "^3.2.0"
|
||||
strip-ansi "^6.0.1"
|
||||
strip-json-comments "^3.1.0"
|
||||
text-table "^0.2.0"
|
||||
|
||||
eslint@^7.23.0, eslint@^7.32.0:
|
||||
version "7.32.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
|
||||
@ -5397,6 +5460,17 @@ fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0:
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-glob@^3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
|
||||
integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
@ -6247,6 +6321,13 @@ is-wsl@^2.2.0:
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
|
||||
is-wsl@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
||||
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
|
||||
isarray@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
@ -7569,6 +7650,15 @@ postcss@^8.4.14, postcss@^8.4.21, postcss@^8.4.23:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.21:
|
||||
version "8.4.27"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
|
||||
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.6"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
prebuild-install@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
|
||||
@ -8619,6 +8709,11 @@ streamx@^2.15.0:
|
||||
fast-fifo "^1.1.0"
|
||||
queue-tick "^1.0.1"
|
||||
|
||||
streamsearch@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||
|
||||
string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
@ -9049,6 +9144,11 @@ tslib@~2.5.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
|
||||
integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
|
||||
|
||||
tslib@^2.5.0, tslib@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
|
||||
integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
|
Loading…
Reference in New Issue
Block a user