mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Implemented swimlanes and kanban view (#2262)
* chore: issue store for kanban and calendar * chore: updated ui for kanba and swimlanes * chore: yarn.lock updated
This commit is contained in:
parent
310a2ca904
commit
43404bfcdf
@ -14,6 +14,7 @@ import useUser from "hooks/use-user";
|
||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||
// components
|
||||
import { AllLists, AllBoards, CalendarView, SpreadsheetView, GanttChartView } from "components/core";
|
||||
import { KanBanLayout } from "components/issues/issue-layouts";
|
||||
// ui
|
||||
import { EmptyState, Spinner } from "components/ui";
|
||||
// icons
|
||||
@ -27,6 +28,9 @@ import { getStatesList } from "helpers/state.helper";
|
||||
import { IIssue, IIssueViewProps } from "types";
|
||||
// fetch-keys
|
||||
import { STATES_LIST } from "constants/fetch-keys";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
type Props = {
|
||||
addIssueToDate: (date: string) => void;
|
||||
@ -71,7 +75,12 @@ export const AllViews: React.FC<Props> = ({
|
||||
viewProps,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
cycleId: string;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
const [myIssueProjectId, setMyIssueProjectId] = useState<string | null>(null);
|
||||
|
||||
@ -97,118 +106,135 @@ export const AllViews: React.FC<Props> = ({
|
||||
[trashBox, setTrashBox]
|
||||
);
|
||||
|
||||
const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
|
||||
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES` : null, async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId);
|
||||
|
||||
await projectStore.fetchProjectStates(workspaceSlug, projectId);
|
||||
await projectStore.fetchProjectLabels(workspaceSlug, projectId);
|
||||
await projectStore.fetchProjectMembers(workspaceSlug, projectId);
|
||||
|
||||
await issueStore.fetchIssues(workspaceSlug, projectId);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||
<StrictModeDroppable droppableId="trashBox">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className={`${
|
||||
trashBox ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
||||
} fixed top-4 left-1/2 -translate-x-1/2 z-40 w-72 flex items-center justify-center gap-2 rounded border-2 border-red-500/20 bg-custom-background-100 px-3 py-5 text-xs font-medium italic text-red-500 ${
|
||||
snapshot.isDraggingOver ? "bg-red-500 blur-2xl opacity-70" : ""
|
||||
} transition duration-300`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
Drop here to delete the issue.
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
{groupedIssues ? (
|
||||
!isEmpty ||
|
||||
displayFilters?.layout === "kanban" ||
|
||||
displayFilters?.layout === "calendar" ||
|
||||
displayFilters?.layout === "gantt_chart" ? (
|
||||
<>
|
||||
{displayFilters?.layout === "list" ? (
|
||||
<AllLists
|
||||
states={states}
|
||||
addIssueToGroup={addIssueToGroup}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
removeIssue={removeIssue}
|
||||
myIssueProjectId={myIssueProjectId}
|
||||
handleMyIssueOpen={handleMyIssueOpen}
|
||||
disableUserActions={disableUserActions}
|
||||
disableAddIssueOption={disableAddIssueOption}
|
||||
user={user}
|
||||
userAuth={memberRole}
|
||||
viewProps={viewProps}
|
||||
/>
|
||||
) : displayFilters?.layout === "kanban" ? (
|
||||
<AllBoards
|
||||
addIssueToGroup={addIssueToGroup}
|
||||
disableUserActions={disableUserActions}
|
||||
disableAddIssueOption={disableAddIssueOption}
|
||||
dragDisabled={dragDisabled}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
handleTrashBox={handleTrashBox}
|
||||
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
myIssueProjectId={myIssueProjectId}
|
||||
handleMyIssueOpen={handleMyIssueOpen}
|
||||
removeIssue={removeIssue}
|
||||
states={states}
|
||||
user={user}
|
||||
userAuth={memberRole}
|
||||
viewProps={viewProps}
|
||||
/>
|
||||
) : displayFilters?.layout === "calendar" ? (
|
||||
<CalendarView
|
||||
handleIssueAction={handleIssueAction}
|
||||
addIssueToDate={addIssueToDate}
|
||||
disableUserActions={disableUserActions}
|
||||
user={user}
|
||||
userAuth={memberRole}
|
||||
/>
|
||||
) : displayFilters?.layout === "spreadsheet" ? (
|
||||
<SpreadsheetView
|
||||
handleIssueAction={handleIssueAction}
|
||||
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
disableUserActions={disableUserActions}
|
||||
user={user}
|
||||
userAuth={memberRole}
|
||||
/>
|
||||
) : (
|
||||
displayFilters?.layout === "gantt_chart" && <GanttChartView disableUserActions={disableUserActions} />
|
||||
)}
|
||||
</>
|
||||
) : router.pathname.includes("archived-issues") ? (
|
||||
<EmptyState
|
||||
title="Archived Issues will be shown here"
|
||||
description="All the issues that have been in the completed or canceled groups for the configured period of time can be viewed here."
|
||||
image={emptyIssueArchive}
|
||||
primaryButton={{
|
||||
text: "Go to Automation Settings",
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
title={emptyState.title}
|
||||
description={emptyState.description}
|
||||
image={emptyIssue}
|
||||
primaryButton={
|
||||
emptyState.primaryButton
|
||||
? {
|
||||
icon: emptyState.primaryButton.icon,
|
||||
text: emptyState.primaryButton.text,
|
||||
onClick: emptyState.primaryButton.onClick,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
secondaryButton={emptyState.secondaryButton}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</DragDropContext>
|
||||
// <DragDropContext onDragEnd={handleOnDragEnd}>
|
||||
// <StrictModeDroppable droppableId="trashBox">
|
||||
// {(provided, snapshot) => (
|
||||
// <div
|
||||
// className={`${
|
||||
// trashBox ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
||||
// } fixed top-4 left-1/2 -translate-x-1/2 z-40 w-72 flex items-center justify-center gap-2 rounded border-2 border-red-500/20 bg-custom-background-100 px-3 py-5 text-xs font-medium italic text-red-500 ${
|
||||
// snapshot.isDraggingOver ? "bg-red-500 blur-2xl opacity-70" : ""
|
||||
// } transition duration-300`}
|
||||
// ref={provided.innerRef}
|
||||
// {...provided.droppableProps}
|
||||
// >
|
||||
// <TrashIcon className="h-4 w-4" />
|
||||
// Drop here to delete the issue.
|
||||
// </div>
|
||||
// )}
|
||||
// </StrictModeDroppable>
|
||||
// {groupedIssues ? (
|
||||
// !isEmpty ||
|
||||
// displayFilters?.layout === "kanban" ||
|
||||
// displayFilters?.layout === "calendar" ||
|
||||
// displayFilters?.layout === "gantt_chart" ? (
|
||||
// <>
|
||||
// {displayFilters?.layout === "list" ? (
|
||||
// <AllLists
|
||||
// states={states}
|
||||
// addIssueToGroup={addIssueToGroup}
|
||||
// handleIssueAction={handleIssueAction}
|
||||
// handleDraftIssueAction={handleDraftIssueAction}
|
||||
// openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
// removeIssue={removeIssue}
|
||||
// myIssueProjectId={myIssueProjectId}
|
||||
// handleMyIssueOpen={handleMyIssueOpen}
|
||||
// disableUserActions={disableUserActions}
|
||||
// disableAddIssueOption={disableAddIssueOption}
|
||||
// user={user}
|
||||
// userAuth={memberRole}
|
||||
// viewProps={viewProps}
|
||||
// />
|
||||
// ) : displayFilters?.layout === "kanban" ? (
|
||||
// <AllBoards
|
||||
// addIssueToGroup={addIssueToGroup}
|
||||
// disableUserActions={disableUserActions}
|
||||
// disableAddIssueOption={disableAddIssueOption}
|
||||
// dragDisabled={dragDisabled}
|
||||
// handleIssueAction={handleIssueAction}
|
||||
// handleDraftIssueAction={handleDraftIssueAction}
|
||||
// handleTrashBox={handleTrashBox}
|
||||
// openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
// myIssueProjectId={myIssueProjectId}
|
||||
// handleMyIssueOpen={handleMyIssueOpen}
|
||||
// removeIssue={removeIssue}
|
||||
// states={states}
|
||||
// user={user}
|
||||
// userAuth={memberRole}
|
||||
// viewProps={viewProps}
|
||||
// />
|
||||
// ) : displayFilters?.layout === "calendar" ? (
|
||||
// <CalendarView
|
||||
// handleIssueAction={handleIssueAction}
|
||||
// addIssueToDate={addIssueToDate}
|
||||
// disableUserActions={disableUserActions}
|
||||
// user={user}
|
||||
// userAuth={memberRole}
|
||||
// />
|
||||
// ) : displayFilters?.layout === "spreadsheet" ? (
|
||||
// <SpreadsheetView
|
||||
// handleIssueAction={handleIssueAction}
|
||||
// openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
// disableUserActions={disableUserActions}
|
||||
// user={user}
|
||||
// userAuth={memberRole}
|
||||
// />
|
||||
// ) : (
|
||||
// displayFilters?.layout === "gantt_chart" && <GanttChartView disableUserActions={disableUserActions} />
|
||||
// )}
|
||||
// </>
|
||||
// ) : router.pathname.includes("archived-issues") ? (
|
||||
// <EmptyState
|
||||
// title="Archived Issues will be shown here"
|
||||
// description="All the issues that have been in the completed or canceled groups for the configured period of time can be viewed here."
|
||||
// image={emptyIssueArchive}
|
||||
// primaryButton={{
|
||||
// text: "Go to Automation Settings",
|
||||
// onClick: () => {
|
||||
// router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`);
|
||||
// },
|
||||
// }}
|
||||
// />
|
||||
// ) : (
|
||||
// <EmptyState
|
||||
// title={emptyState.title}
|
||||
// description={emptyState.description}
|
||||
// image={emptyIssue}
|
||||
// primaryButton={
|
||||
// emptyState.primaryButton
|
||||
// ? {
|
||||
// icon: emptyState.primaryButton.icon,
|
||||
// text: emptyState.primaryButton.text,
|
||||
// onClick: emptyState.primaryButton.onClick,
|
||||
// }
|
||||
// : undefined
|
||||
// }
|
||||
// secondaryButton={emptyState.secondaryButton}
|
||||
// />
|
||||
// )
|
||||
// ) : (
|
||||
// <div className="flex h-full w-full items-center justify-center">
|
||||
// <Spinner />
|
||||
// </div>
|
||||
// )}
|
||||
// </DragDropContext>
|
||||
<div className="relative w-full h-full overflow-auto">
|
||||
<KanBanLayout />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
1
web/components/issues/issue-layouts/calandar/index.ts
Normal file
1
web/components/issues/issue-layouts/calandar/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./root";
|
17
web/components/issues/issue-layouts/calandar/root.tsx
Normal file
17
web/components/issues/issue-layouts/calandar/root.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
export interface ICalendarLayout {
|
||||
issues: any;
|
||||
handleDragDrop: () => void;
|
||||
}
|
||||
|
||||
export const CalendarLayout: React.FC<ICalendarLayout> = ({}) => {
|
||||
console.log("kanaban layout");
|
||||
return (
|
||||
<div>
|
||||
<div>header</div>
|
||||
<div>content</div>
|
||||
<div>footer</div>
|
||||
</div>
|
||||
);
|
||||
};
|
2
web/components/issues/issue-layouts/index.ts
Normal file
2
web/components/issues/issue-layouts/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./kanban";
|
||||
export * from "./calandar";
|
50
web/components/issues/issue-layouts/kanban/block.tsx
Normal file
50
web/components/issues/issue-layouts/kanban/block.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
// react beautiful dnd
|
||||
import { Draggable } from "@hello-pangea/dnd";
|
||||
|
||||
interface IssueBlockProps {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
issues: any;
|
||||
}
|
||||
|
||||
export const IssueBlock = ({ sub_group_id, columnId, issues }: IssueBlockProps) => {
|
||||
console.log();
|
||||
|
||||
return (
|
||||
<>
|
||||
{issues && issues.length > 0 ? (
|
||||
<>
|
||||
{issues.map((issue: any, index: any) => (
|
||||
<Draggable
|
||||
draggableId={`${sub_group_id}-${columnId}-${issue.id}`}
|
||||
index={index}
|
||||
key={`issue-blocks-${sub_group_id}-${columnId}-${issue.id}`}
|
||||
>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
key={issue.id}
|
||||
className="p-1.5 hover:cursor-default"
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<div
|
||||
className={`min-h-[106px] text-sm rounded p-2 px-3 shadow-custom-shadow-2xs space-y-[4px] border transition-all bg-custom-background-100 hover:cursor-grab ${
|
||||
snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`
|
||||
}`}
|
||||
>
|
||||
<div className="text-xs line-clamp-1 text-custom-text-300">ONE-{issue.sequence_id}</div>
|
||||
<div className="line-clamp-2 h-[40px] text-sm font-medium text-custom-text-100">{issue.name}</div>
|
||||
<div className="h-[22px]">Footer</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div>No issues are available.</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
151
web/components/issues/issue-layouts/kanban/default.tsx
Normal file
151
web/components/issues/issue-layouts/kanban/default.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React from "react";
|
||||
// react beautiful dnd
|
||||
import { Droppable } from "@hello-pangea/dnd";
|
||||
// components
|
||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { IssueBlock } from "./block";
|
||||
// constants
|
||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IKanBan {
|
||||
issues?: any;
|
||||
handleIssues?: () => void;
|
||||
handleDragDrop?: (result: any) => void | undefined;
|
||||
sub_group_id?: string;
|
||||
}
|
||||
|
||||
export const KanBan: React.FC<IKanBan> = observer(({ issues, sub_group_id = "null" }) => {
|
||||
const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
{group_by && group_by === "state" && (
|
||||
<div className="relative w-full h-full flex">
|
||||
{projectStore?.projectStates &&
|
||||
projectStore?.projectStates.length > 0 &&
|
||||
projectStore?.projectStates.map((state) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[340px]">
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full bg-custom-background-90 py-1 sticky top-0 z-[2]">
|
||||
<KanBanGroupByHeaderRoot column_id={state?.id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full h-full">
|
||||
<Droppable droppableId={`${sub_group_id}-${state?.id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`w-full h-full relative transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issues && (
|
||||
<IssueBlock sub_group_id={sub_group_id} columnId={state?.id} issues={issues[state?.id]} />
|
||||
)}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<div className="relative w-full h-full flex">
|
||||
{ISSUE_STATE_GROUPS &&
|
||||
ISSUE_STATE_GROUPS.length > 0 &&
|
||||
ISSUE_STATE_GROUPS.map((stateGroup) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[300px] h-full">
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full">
|
||||
<KanBanGroupByHeaderRoot column_id={stateGroup?.key} />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full h-full">content</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "priority" && (
|
||||
<div className="relative w-full h-full flex">
|
||||
{ISSUE_PRIORITIES &&
|
||||
ISSUE_PRIORITIES.length > 0 &&
|
||||
ISSUE_PRIORITIES.map((priority) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[300px] h-full">
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full">
|
||||
<KanBanGroupByHeaderRoot column_id={priority?.key} />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full h-full">content</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "labels" && (
|
||||
<div className="relative w-full h-full flex">
|
||||
{projectStore?.projectLabels &&
|
||||
projectStore?.projectLabels.length > 0 &&
|
||||
projectStore?.projectLabels.map((label) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[300px] h-full">
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full">
|
||||
<KanBanGroupByHeaderRoot column_id={label?.id} />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full h-full">content</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "assignees" && (
|
||||
<div className="relative w-full h-full flex">
|
||||
{projectStore?.projectMembers &&
|
||||
projectStore?.projectMembers.length > 0 &&
|
||||
projectStore?.projectMembers.map((member) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[300px] h-full">
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full">
|
||||
<KanBanGroupByHeaderRoot column_id={member?.id} />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full h-full">content</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "created_by" && (
|
||||
<div className="relative w-full h-full flex">
|
||||
{projectStore?.projectMembers &&
|
||||
projectStore?.projectMembers.length > 0 &&
|
||||
projectStore?.projectMembers.map((member) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[300px] h-full">
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full">
|
||||
<KanBanGroupByHeaderRoot column_id={member?.id} />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full h-full">content</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
// components
|
||||
import { HeaderCard } from "./card";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IAssigneesHeader {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const AssigneesHeader: React.FC<IAssigneesHeader> = observer(({ column_id }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const assignee = (column_id && projectStore?.getProjectMemberById(column_id)) ?? null;
|
||||
|
||||
return <>{assignee && <HeaderCard title={assignee?.member?.display_name || ""} />}</>;
|
||||
});
|
39
web/components/issues/issue-layouts/kanban/headers/card.tsx
Normal file
39
web/components/issues/issue-layouts/kanban/headers/card.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
// lucide icons
|
||||
import { Plus, Minimize2, Maximize2, Circle } from "lucide-react";
|
||||
|
||||
interface IHeaderCard {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const HeaderCard = ({ icon, title }: IHeaderCard) => {
|
||||
const position = false;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex-shrink-0 relative flex gap-0.5 rounded-sm ${
|
||||
position
|
||||
? `flex-col items-center w-[44px] border border-custom-border-100 bg-custom-background-80 shadow-custom-shadow-sm`
|
||||
: `flex-row items-center w-full`
|
||||
}`}
|
||||
>
|
||||
<div className="flex-shrink-0 w-[26px] h-[26px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
|
||||
{icon ? icon : <Circle width={14} strokeWidth={2} />}
|
||||
</div>
|
||||
|
||||
<div className={`capitalize flex items-center gap-1 ${position ? `flex-col` : `flex-row w-full`}`}>
|
||||
<div className={`font-medium line-clamp-1 ${position ? `vertical-lr` : ``}`}>{title}</div>
|
||||
<div className="text-xs">(0)</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 w-[26px] h-[26px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
|
||||
{position ? <Maximize2 width={14} strokeWidth={2} /> : <Minimize2 width={14} strokeWidth={2} />}
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 w-[26px] h-[26px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
|
||||
<Plus width={14} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
// components
|
||||
import { HeaderCard } from "./card";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface ICreatedByHeader {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const CreatedByHeader: React.FC<ICreatedByHeader> = observer(({ column_id }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const createdBy = (column_id && projectStore?.getProjectMemberById(column_id)) ?? null;
|
||||
|
||||
return <>{createdBy && <HeaderCard title={createdBy?.member?.display_name || ""} />}</>;
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
// components
|
||||
import { StateHeader } from "./state";
|
||||
import { StateGroupHeader } from "./state-group";
|
||||
import { AssigneesHeader } from "./assignee";
|
||||
import { PriorityHeader } from "./priority";
|
||||
import { LabelHeader } from "./label";
|
||||
import { CreatedByHeader } from "./created_by";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IKanBanGroupByHeaderRoot {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(({ column_id }) => {
|
||||
const { issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{group_by && group_by === "state" && <StateHeader column_id={column_id} />}
|
||||
{group_by && group_by === "state_detail.group" && <StateGroupHeader column_id={column_id} />}
|
||||
{group_by && group_by === "priority" && <PriorityHeader column_id={column_id} />}
|
||||
{group_by && group_by === "labels" && <LabelHeader column_id={column_id} />}
|
||||
{group_by && group_by === "assignees" && <AssigneesHeader column_id={column_id} />}
|
||||
{group_by && group_by === "created_by" && <CreatedByHeader column_id={column_id} />}
|
||||
</>
|
||||
);
|
||||
});
|
19
web/components/issues/issue-layouts/kanban/headers/label.tsx
Normal file
19
web/components/issues/issue-layouts/kanban/headers/label.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
// components
|
||||
import { HeaderCard } from "./card";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface ILabelHeader {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const LabelHeader: React.FC<ILabelHeader> = observer(({ column_id }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null;
|
||||
|
||||
return <>{label && <HeaderCard title={label?.name || ""} />}</>;
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
// components
|
||||
import { HeaderCard } from "./card";
|
||||
// constants
|
||||
import { issuePriorityByKey } from "constants/issue";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IPriorityHeader {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const PriorityHeader: React.FC<IPriorityHeader> = observer(({ column_id }) => {
|
||||
const {}: RootStore = useMobxStore();
|
||||
|
||||
const stateGroup = column_id && issuePriorityByKey(column_id);
|
||||
|
||||
return <>{stateGroup && <HeaderCard title={stateGroup?.title || ""} />}</>;
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
// components
|
||||
import { HeaderCard } from "./card";
|
||||
// constants
|
||||
import { issueStateGroupByKey } from "constants/issue";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IStateGroupHeader {
|
||||
column_id: string;
|
||||
swimlanes?: boolean;
|
||||
}
|
||||
|
||||
export const StateGroupHeader: React.FC<IStateGroupHeader> = observer(({ column_id }) => {
|
||||
const {}: RootStore = useMobxStore();
|
||||
|
||||
const stateGroup = column_id && issueStateGroupByKey(column_id);
|
||||
|
||||
return <>{stateGroup && <HeaderCard title={stateGroup?.title || ""} />}</>;
|
||||
});
|
19
web/components/issues/issue-layouts/kanban/headers/state.tsx
Normal file
19
web/components/issues/issue-layouts/kanban/headers/state.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
// components
|
||||
import { HeaderCard } from "./card";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IStateHeader {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const StateHeader: React.FC<IStateHeader> = observer(({ column_id }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null;
|
||||
|
||||
return <>{state && <HeaderCard title={state?.name || ""} />}</>;
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
// components
|
||||
import { StateHeader } from "./state";
|
||||
import { StateGroupHeader } from "./state-group";
|
||||
import { AssigneesHeader } from "./assignee";
|
||||
import { PriorityHeader } from "./priority";
|
||||
import { LabelHeader } from "./label";
|
||||
import { CreatedByHeader } from "./created_by";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IKanBanSubGroupByHeaderRoot {
|
||||
column_id: string;
|
||||
}
|
||||
|
||||
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer(({ column_id }) => {
|
||||
const { issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{sub_group_by && sub_group_by === "state" && <StateHeader column_id={column_id} />}
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && <StateGroupHeader column_id={column_id} />}
|
||||
{sub_group_by && sub_group_by === "priority" && <PriorityHeader column_id={column_id} />}
|
||||
{sub_group_by && sub_group_by === "labels" && <LabelHeader column_id={column_id} />}
|
||||
{sub_group_by && sub_group_by === "assignees" && <AssigneesHeader column_id={column_id} />}
|
||||
{sub_group_by && sub_group_by === "created_by" && <CreatedByHeader column_id={column_id} />}
|
||||
</>
|
||||
);
|
||||
});
|
1
web/components/issues/issue-layouts/kanban/index.ts
Normal file
1
web/components/issues/issue-layouts/kanban/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./root";
|
48
web/components/issues/issue-layouts/kanban/root.tsx
Normal file
48
web/components/issues/issue-layouts/kanban/root.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
// react beautiful dnd
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { KanBan } from "./default";
|
||||
|
||||
export interface IKanBanLayout {
|
||||
issues?: any;
|
||||
handleIssues?: () => void;
|
||||
handleDragDrop?: (result: any) => void;
|
||||
}
|
||||
|
||||
export const KanBanLayout: React.FC<IKanBanLayout> = observer(({}) => {
|
||||
const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
result.destination &&
|
||||
result.source &&
|
||||
result.destination.droppableId === result.source.droppableId &&
|
||||
result.destination.index === result.source.index
|
||||
)
|
||||
return;
|
||||
|
||||
console.log("result", result);
|
||||
// issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? <KanBan issues={issues} /> : <KanBanSwimLanes issues={issues} />}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
});
|
103
web/components/issues/issue-layouts/kanban/swimlanes.tsx
Normal file
103
web/components/issues/issue-layouts/kanban/swimlanes.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import React from "react";
|
||||
// components
|
||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { KanBanSubGroupByHeaderRoot } from "./headers/sub-group-by-root";
|
||||
import { KanBan } from "./default";
|
||||
// constants
|
||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IKanBanSwimLanes {
|
||||
issues?: any;
|
||||
handleIssues?: () => void;
|
||||
handleDragDrop?: () => void;
|
||||
}
|
||||
|
||||
const SubGroupSwimlaneHeader = ({ list, _key }: any) => (
|
||||
<div className="relative w-full min-h-full h-max flex items-center">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div className="flex-shrink-0 flex flex-col w-[340px]">
|
||||
<KanBanGroupByHeaderRoot column_id={_list?.[_key]} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const SubGroupSwimlane = ({ issues, list, _key }: any) => (
|
||||
<div className="relative w-full min-h-full h-max">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div className="flex-shrink-0 flex flex-col">
|
||||
<div className="sticky top-[30px] w-full z-[1] bg-custom-background-90 flex items-center py-1">
|
||||
<div className="flex-shrink-0 sticky left-0 bg-custom-background-90 pr-2">
|
||||
<KanBanSubGroupByHeaderRoot column_id={_list?.[_key]} />
|
||||
</div>
|
||||
<div className="w-full border-b border-custom-border-400 border-dashed" />
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<KanBan issues={issues} sub_group_id={_list?.[_key]} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(({ issues }) => {
|
||||
const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
console.log("sub_group_by", sub_group_by);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="sticky top-0 z-[2] bg-custom-background-90 h-[30px]">
|
||||
{group_by && group_by === "state" && <SubGroupSwimlaneHeader list={projectStore?.projectStates} _key={"id"} />}
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<SubGroupSwimlaneHeader list={ISSUE_STATE_GROUPS} _key={"key"} />
|
||||
)}
|
||||
{group_by && group_by === "priority" && <SubGroupSwimlaneHeader list={ISSUE_PRIORITIES} _key={"key"} />}
|
||||
{group_by && group_by === "labels" && <SubGroupSwimlaneHeader list={projectStore?.projectLabels} _key={"id"} />}
|
||||
{group_by && group_by === "assignees" && (
|
||||
<SubGroupSwimlaneHeader list={projectStore?.projectMembers} _key={"id"} />
|
||||
)}
|
||||
{group_by && group_by === "created_by" && (
|
||||
<SubGroupSwimlaneHeader list={projectStore?.projectMembers} _key={"id"} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<SubGroupSwimlane issues={issues} list={projectStore?.projectStates} _key={"id"} />
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||
<SubGroupSwimlane issues={issues} list={ISSUE_STATE_GROUPS} _key={"key"} />
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "priority" && (
|
||||
<SubGroupSwimlane issues={issues} list={ISSUE_PRIORITIES} _key={"key"} />
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "labels" && (
|
||||
<SubGroupSwimlane issues={issues} list={projectStore?.projectLabels} _key={"id"} />
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "assignees" && (
|
||||
<SubGroupSwimlane issues={issues} list={projectStore?.projectMembers} _key={"id"} />
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "created_by" && (
|
||||
<SubGroupSwimlane issues={issues} list={projectStore?.projectMembers} _key={"id"} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -23,6 +23,8 @@ export const ISSUE_PRIORITIES: {
|
||||
{ key: "none", title: "None" },
|
||||
];
|
||||
|
||||
export const issuePriorityByKey = (key: string) => ISSUE_PRIORITIES.find((item) => item.key === key) || null;
|
||||
|
||||
export const ISSUE_STATE_GROUPS: {
|
||||
key: TStateGroups;
|
||||
title: string;
|
||||
@ -34,6 +36,8 @@ export const ISSUE_STATE_GROUPS: {
|
||||
{ key: "cancelled", title: "Cancelled" },
|
||||
];
|
||||
|
||||
export const issueStateGroupByKey = (key: string) => ISSUE_STATE_GROUPS.find((item) => item.key === key) || null;
|
||||
|
||||
export const ISSUE_START_DATE_OPTIONS = [
|
||||
{ key: "last_week", title: "Last Week" },
|
||||
{ key: "2_weeks_from_now", title: "2 weeks from now" },
|
||||
|
@ -14,6 +14,7 @@
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@hello-pangea/dnd": "^16.3.0",
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@jitsu/nextjs": "^3.1.5",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
|
@ -1,11 +1,42 @@
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
import { IIssue } from "types";
|
||||
// store
|
||||
import { RootStore } from "./root";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// services
|
||||
import { IssueService } from "services/issue.service";
|
||||
|
||||
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
|
||||
export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
|
||||
export type IIssueGroupWithSubGroupsStructure = {
|
||||
[group_id: string]: {
|
||||
[sub_group_id: string]: IIssue[];
|
||||
};
|
||||
};
|
||||
export type IIssueUnGroupedStructure = IIssue[];
|
||||
|
||||
export interface IIssueStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
// issues
|
||||
issues: {
|
||||
[project_id: string]: {
|
||||
grouped: IIssueGroupedStructure;
|
||||
groupWithSubGroups: IIssueGroupWithSubGroupsStructure;
|
||||
ungrouped: IIssueUnGroupedStructure;
|
||||
};
|
||||
};
|
||||
// computed
|
||||
getIssueType: IIssueType | null;
|
||||
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
|
||||
// action
|
||||
fetchIssues: (workspaceSlug: string, projectId: string) => Promise<any>;
|
||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
||||
}
|
||||
|
||||
class IssueStore implements IIssueStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
issues: {
|
||||
[project_id: string]: {
|
||||
grouped: {
|
||||
@ -18,30 +49,9 @@ export interface IIssueStore {
|
||||
};
|
||||
ungrouped: IIssue[];
|
||||
};
|
||||
};
|
||||
|
||||
addIssueToIssuesStore: (projectId: string, issue: IIssue) => void;
|
||||
updateIssueInIssuesStore: (projectId: string, issue: IIssue) => void;
|
||||
deleteIssueFromIssuesStore: (projectId: string, issueId: string) => void;
|
||||
}
|
||||
|
||||
class IssueStore implements IIssueStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
issues: {
|
||||
[project_id: string]: {
|
||||
grouped: {
|
||||
[issueId: string]: IIssue[];
|
||||
};
|
||||
groupWithSubGroups: {
|
||||
[group_id: string]: {
|
||||
[sub_group_id: string]: IIssue[];
|
||||
};
|
||||
};
|
||||
ungrouped: IIssue[];
|
||||
};
|
||||
} = {};
|
||||
|
||||
// service
|
||||
issueService;
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: RootStore) {
|
||||
@ -50,55 +60,115 @@ class IssueStore implements IIssueStore {
|
||||
loader: observable.ref,
|
||||
error: observable.ref,
|
||||
issues: observable.ref,
|
||||
|
||||
addIssueToIssuesStore: action,
|
||||
updateIssueInIssuesStore: action,
|
||||
deleteIssueFromIssuesStore: action,
|
||||
// computed
|
||||
getIssueType: computed,
|
||||
getIssues: computed,
|
||||
// actions
|
||||
fetchIssues: action,
|
||||
updateIssueStructure: action,
|
||||
});
|
||||
this.rootStore = _rootStore;
|
||||
this.issueService = new IssueService();
|
||||
}
|
||||
|
||||
addIssueToIssuesStore = (projectId: string, issue: IIssue) => {
|
||||
runInAction(() => {
|
||||
this.rootStore.issue.issues = {
|
||||
...this.rootStore.issue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore.issue.issues[projectId],
|
||||
ungrouped: [...this.rootStore.issue.issues[projectId].ungrouped, issue],
|
||||
get getIssueType() {
|
||||
const groupedLayouts = ["kanban", "list"];
|
||||
const ungroupedLayouts = ["calendar", "spreadsheet", "gantt_chart"];
|
||||
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
|
||||
if (!issueLayout) return null;
|
||||
|
||||
const _issueState = groupedLayouts.includes(issueLayout)
|
||||
? issueSubGroup
|
||||
? "groupWithSubGroups"
|
||||
: "grouped"
|
||||
: ungroupedLayouts.includes(issueLayout)
|
||||
? "ungrouped"
|
||||
: null;
|
||||
|
||||
return _issueState || null;
|
||||
}
|
||||
|
||||
get getIssues() {
|
||||
const projectId: string | null = this.rootStore?.project?.projectId;
|
||||
const issueType = this.getIssueType;
|
||||
if (!projectId || !issueType) return null;
|
||||
|
||||
return this.issues?.[projectId]?.[issueType] || null;
|
||||
}
|
||||
|
||||
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
||||
const projectId: string | null = issue?.project;
|
||||
const issueType: IIssueType | null = this.getIssueType;
|
||||
if (!projectId || !issueType) return null;
|
||||
|
||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
||||
this.getIssues;
|
||||
if (!issues) return null;
|
||||
|
||||
if (issueType === "grouped" && group_id) {
|
||||
issues = issues as IIssueGroupedStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? issue : i)),
|
||||
};
|
||||
}
|
||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[sub_group_id]: {
|
||||
...issues[sub_group_id],
|
||||
[group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? issue : i)),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (issueType === "ungrouped") {
|
||||
issues = issues as IIssueUnGroupedStructure;
|
||||
issues = issues.map((i: IIssue) => (i?.id === issue?.id ? issue : i));
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } };
|
||||
});
|
||||
};
|
||||
|
||||
updateIssueInIssuesStore = (projectId: string, issue: IIssue) => {
|
||||
const newUngroupedIssues = this.rootStore.issue.issues[projectId].ungrouped.map((i) => ({
|
||||
...i,
|
||||
...(i.id === issue.id ? issue : {}),
|
||||
}));
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
this.error = null;
|
||||
|
||||
runInAction(() => {
|
||||
this.rootStore.issue.issues = {
|
||||
...this.rootStore.issue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore.issue.issues[projectId],
|
||||
ungrouped: newUngroupedIssues,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
|
||||
this.rootStore.project.setProjectId(projectId);
|
||||
|
||||
deleteIssueFromIssuesStore = (projectId: string, issueId: string) => {
|
||||
const newUngroupedIssues = this.rootStore.issue.issues[projectId].ungrouped.filter((i) => i.id !== issueId);
|
||||
// TODO: replace this once the issue filter is completed
|
||||
const params = { group_by: "state", order_by: "-created_at" };
|
||||
const issueResponse = await this.issueService.getIssuesWithParams(workspaceSlug, projectId, params);
|
||||
|
||||
runInAction(() => {
|
||||
this.rootStore.issue.issues = {
|
||||
...this.rootStore.issue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore.issue.issues[projectId],
|
||||
ungrouped: newUngroupedIssues,
|
||||
},
|
||||
};
|
||||
});
|
||||
const issueType = this.getIssueType;
|
||||
if (issueType != null) {
|
||||
const _issues = {
|
||||
...this.issues,
|
||||
[projectId]: {
|
||||
...this.issues[projectId],
|
||||
[issueType]: issueResponse,
|
||||
},
|
||||
};
|
||||
runInAction(() => {
|
||||
this.issues = _issues;
|
||||
this.loader = false;
|
||||
this.error = null;
|
||||
});
|
||||
}
|
||||
|
||||
return issueResponse;
|
||||
} catch (error) {
|
||||
console.error("Error: Fetching error in issues", error);
|
||||
this.loader = false;
|
||||
this.error = error;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { action, computed, makeObservable } from "mobx";
|
||||
// types
|
||||
import { RootStore } from "./root";
|
||||
import { IIssueType } from "./issue";
|
||||
|
||||
export interface IIssueKanBanViewStore {
|
||||
handleDragDrop: (source: any, destination: any) => void;
|
||||
@ -26,12 +27,10 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
|
||||
get canUserDragDrop() {
|
||||
if (
|
||||
this.rootStore?.issueFilters?.issueView &&
|
||||
this.rootStore?.issueFilters?.userFilters?.display_filters?.group_by &&
|
||||
this.rootStore?.issueFilters?.userFilters?.display_filters?.order_by &&
|
||||
!["my_issues"].includes(this.rootStore?.issueFilters?.issueView) &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilters?.userFilters?.display_filters?.group_by) &&
|
||||
this.rootStore?.issueFilters?.userFilters?.display_filters?.order_by === "sort_order"
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.order_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) &&
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@ -45,26 +44,18 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
}
|
||||
|
||||
handleDragDrop = async (source: any, destination: any) => {
|
||||
const workspaceId = this.rootStore?.issueFilters?.workspaceId;
|
||||
const projectId = this.rootStore?.issueFilters?.projectId;
|
||||
const issueView = this.rootStore?.issueFilters?.issueView;
|
||||
const issueLayout = this.rootStore?.issueFilters?.userFilters?.display_filters?.layout;
|
||||
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||
const projectId = this.rootStore?.project?.projectId;
|
||||
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
|
||||
const sortOrderDefaultValue = 10000;
|
||||
|
||||
if (
|
||||
this.rootStore?.issueView?.getIssues &&
|
||||
workspaceId &&
|
||||
projectId &&
|
||||
issueView &&
|
||||
issueLayout &&
|
||||
issueView != "my_issues"
|
||||
) {
|
||||
const projectSortedIssues: any =
|
||||
this.rootStore?.issueView.issues?.[workspaceId]?.project_issues?.[projectId]?.[issueView]?.[issueLayout];
|
||||
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && this.rootStore.issue.getIssues) {
|
||||
const currentIssues: any = this.rootStore.issue.getIssues;
|
||||
|
||||
let updateIssue: any = {
|
||||
workspaceId: workspaceId,
|
||||
workspaceSlug: workspaceSlug,
|
||||
projectId: projectId,
|
||||
};
|
||||
|
||||
@ -73,7 +64,7 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
// vertical
|
||||
if (source.droppableId === destination.droppableId) {
|
||||
const _columnId = source.droppableId;
|
||||
const _issues = projectSortedIssues[_columnId];
|
||||
const _issues = currentIssues[_columnId];
|
||||
|
||||
// update the sort order
|
||||
if (destination.index === 0) {
|
||||
@ -92,7 +83,7 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
projectSortedIssues[_columnId] = _issues;
|
||||
currentIssues[_columnId] = _issues;
|
||||
}
|
||||
|
||||
// horizontal
|
||||
@ -100,8 +91,8 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
const _sourceColumnId = source.droppableId;
|
||||
const _destinationColumnId = destination.droppableId;
|
||||
|
||||
const _sourceIssues = projectSortedIssues[_sourceColumnId];
|
||||
const _destinationIssues = projectSortedIssues[_destinationColumnId];
|
||||
const _sourceIssues = currentIssues[_sourceColumnId];
|
||||
const _destinationIssues = currentIssues[_destinationColumnId];
|
||||
|
||||
if (_destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
@ -142,8 +133,8 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
});
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
projectSortedIssues[_sourceColumnId] = _sourceIssues;
|
||||
projectSortedIssues[_destinationColumnId] = _destinationIssues;
|
||||
currentIssues[_sourceColumnId] = _sourceIssues;
|
||||
currentIssues[_destinationColumnId] = _destinationIssues;
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,29 +146,23 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
if (this.canUserDragDropHorizontally && source.droppableId != destination.droppableId) {
|
||||
}
|
||||
|
||||
this.rootStore.issueView.issues = {
|
||||
...this.rootStore?.issueView.issues,
|
||||
[workspaceId]: {
|
||||
...this.rootStore?.issueView.issues?.[workspaceId],
|
||||
project_issues: {
|
||||
...this.rootStore?.issueView.issues?.[workspaceId]?.project_issues,
|
||||
[projectId]: {
|
||||
...this.rootStore?.issueView.issues?.[workspaceId]?.project_issues?.[projectId],
|
||||
[issueView]: {
|
||||
...this.rootStore?.issueView.issues?.[workspaceId]?.project_issues?.[projectId]?.[issueView],
|
||||
[issueLayout]: projectSortedIssues,
|
||||
},
|
||||
},
|
||||
this.rootStore.issue.issues = {
|
||||
...this.rootStore?.issue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore?.issue.issues?.[projectId],
|
||||
[issueType]: {
|
||||
...this.rootStore?.issue.issues?.[projectId]?.[issueType],
|
||||
[issueType]: currentIssues,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
this.rootStore.issueDetail?.updateIssueAsync(
|
||||
updateIssue.workspaceId,
|
||||
updateIssue.projectId,
|
||||
updateIssue.issueId,
|
||||
updateIssue
|
||||
);
|
||||
// this.rootStore.issueDetail?.updateIssueAsync(
|
||||
// updateIssue.workspaceSlug,
|
||||
// updateIssue.projectId,
|
||||
// updateIssue.issueId,
|
||||
// updateIssue
|
||||
// );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -58,8 +58,8 @@
|
||||
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
||||
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
||||
|
||||
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06),
|
||||
0px 1px 2px 0px rgba(23, 23, 23, 0.06), 0px 1px 2px 0px rgba(23, 23, 23, 0.14);
|
||||
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06), 0px 1px 2px 0px rgba(23, 23, 23, 0.06),
|
||||
0px 1px 2px 0px rgba(23, 23, 23, 0.14);
|
||||
--color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16), 0px 2px 4px 0px rgba(16, 24, 40, 0.12),
|
||||
0px 1px 8px -1px rgba(16, 24, 40, 0.1);
|
||||
--color-shadow-sm: 0px 1px 4px 0px rgba(0, 0, 0, 0.01), 0px 4px 8px 0px rgba(0, 0, 0, 0.02),
|
||||
@ -72,8 +72,8 @@
|
||||
0px 1px 24px 0px rgba(16, 24, 40, 0.12);
|
||||
--color-shadow-xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.16), 0px 0px 24px 0px rgba(16, 24, 40, 0.16),
|
||||
0px 0px 52px 0px rgba(16, 24, 40, 0.16);
|
||||
--color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12),
|
||||
0px 12px 24px 0px rgba(16, 24, 40, 0.12), 0px 1px 32px 0px rgba(16, 24, 40, 0.12);
|
||||
--color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12), 0px 12px 24px 0px rgba(16, 24, 40, 0.12),
|
||||
0px 1px 32px 0px rgba(16, 24, 40, 0.12);
|
||||
--color-shadow-3xl: 0px 12px 24px 0px rgba(0, 0, 0, 0.12), 0px 16px 32px 0px rgba(0, 0, 0, 0.12),
|
||||
0px 1px 48px 0px rgba(16, 24, 40, 0.12);
|
||||
|
||||
@ -359,3 +359,8 @@ body {
|
||||
.disable-scroll {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.vertical-lr {
|
||||
-webkit-writing-mode: vertical-lr;
|
||||
-ms-writing-mode: vertical-lr;
|
||||
}
|
||||
|
60
yarn.lock
60
yarn.lock
@ -926,6 +926,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.12.1", "@babel/runtime@^7.22.5":
|
||||
version "7.23.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d"
|
||||
integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.22.5":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||
@ -1243,6 +1250,19 @@
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
|
||||
"@hello-pangea/dnd@^16.3.0":
|
||||
version "16.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6"
|
||||
integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.22.5"
|
||||
css-box-model "^1.2.1"
|
||||
memoize-one "^6.0.0"
|
||||
raf-schd "^4.0.3"
|
||||
react-redux "^8.1.1"
|
||||
redux "^4.2.1"
|
||||
use-memo-one "^1.1.3"
|
||||
|
||||
"@heroicons/react@^2.0.12":
|
||||
version "2.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.18.tgz#f80301907c243df03c7e9fd76c0286e95361f7c1"
|
||||
@ -2462,6 +2482,14 @@
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.1":
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a"
|
||||
integrity sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/js-cookie@^3.0.2", "@types/js-cookie@^3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e"
|
||||
@ -2693,6 +2721,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c"
|
||||
integrity sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==
|
||||
|
||||
"@types/use-sync-external-store@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
|
||||
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
|
||||
|
||||
"@types/uuid@^8.3.4":
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
@ -3486,7 +3519,7 @@ crypto-random-string@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||
|
||||
css-box-model@^1.2.0:
|
||||
css-box-model@^1.2.0, css-box-model@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||
@ -5705,6 +5738,11 @@ memoize-one@^5.1.1:
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
|
||||
memoize-one@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
@ -6731,7 +6769,7 @@ queue-tick@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
|
||||
integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
|
||||
|
||||
raf-schd@^4.0.2:
|
||||
raf-schd@^4.0.2, raf-schd@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||
@ -6924,6 +6962,18 @@ react-redux@^7.2.0:
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
react-redux@^8.1.1:
|
||||
version "8.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188"
|
||||
integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.1"
|
||||
"@types/hoist-non-react-statics" "^3.3.1"
|
||||
"@types/use-sync-external-store" "^0.0.3"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
react-is "^18.0.0"
|
||||
use-sync-external-store "^1.0.0"
|
||||
|
||||
react-remove-scroll-bar@^2.3.3:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9"
|
||||
@ -7006,7 +7056,7 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
redux@^4.0.0, redux@^4.0.4:
|
||||
redux@^4.0.0, redux@^4.0.4, redux@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
|
||||
@ -8158,7 +8208,7 @@ use-debounce@^9.0.4:
|
||||
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
|
||||
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
|
||||
|
||||
use-memo-one@^1.1.1:
|
||||
use-memo-one@^1.1.1, use-memo-one@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
|
||||
integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
|
||||
@ -8171,7 +8221,7 @@ use-sidecar@^1.1.2:
|
||||
detect-node-es "^1.1.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
|
||||
use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
Loading…
Reference in New Issue
Block a user