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:
guru_sainath 2023-09-26 13:18:42 +05:30 committed by GitHub
parent 310a2ca904
commit 43404bfcdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 967 additions and 229 deletions

View File

@ -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.
// <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>
)}
</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>
);
};

View File

@ -0,0 +1 @@
export * from "./root";

View 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>
);
};

View File

@ -0,0 +1,2 @@
export * from "./kanban";
export * from "./calandar";

View 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>
)}
</>
);
};

View 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>
);
});

View 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 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 || ""} />}</>;
});

View 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>
);
};

View 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 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 || ""} />}</>;
});

View File

@ -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} />}
</>
);
});

View 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 || ""} />}</>;
});

View File

@ -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 || ""} />}</>;
});

View File

@ -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 || ""} />}</>;
});

View 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 || ""} />}</>;
});

View File

@ -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} />}
</>
);
});

View File

@ -0,0 +1 @@
export * from "./root";

View 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>
);
});

View 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>
);
});

View File

@ -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" },

View File

@ -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",

View File

@ -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,
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
this.rootStore.project.setProjectId(projectId);
// 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);
const issueType = this.getIssueType;
if (issueType != null) {
const _issues = {
...this.issues,
[projectId]: {
...this.rootStore.issue.issues[projectId],
ungrouped: newUngroupedIssues,
...this.issues[projectId],
[issueType]: issueResponse,
},
};
});
};
deleteIssueFromIssuesStore = (projectId: string, issueId: string) => {
const newUngroupedIssues = this.rootStore.issue.issues[projectId].ungrouped.filter((i) => i.id !== issueId);
runInAction(() => {
this.rootStore.issue.issues = {
...this.rootStore.issue.issues,
[projectId]: {
...this.rootStore.issue.issues[projectId],
ungrouped: newUngroupedIssues,
},
};
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;
}
};
}

View File

@ -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,
this.rootStore.issue.issues = {
...this.rootStore?.issue.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?.[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
// );
}
};
}

View File

@ -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;
}

View File

@ -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==