diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index 4d5b886da..a75ac67d4 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -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 = ({ 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(null); @@ -97,118 +106,135 @@ export const AllViews: React.FC = ({ [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 ( - - - {(provided, snapshot) => ( -
- - Drop here to delete the issue. -
- )} -
- {groupedIssues ? ( - !isEmpty || - displayFilters?.layout === "kanban" || - displayFilters?.layout === "calendar" || - displayFilters?.layout === "gantt_chart" ? ( - <> - {displayFilters?.layout === "list" ? ( - - ) : displayFilters?.layout === "kanban" ? ( - - ) : displayFilters?.layout === "calendar" ? ( - - ) : displayFilters?.layout === "spreadsheet" ? ( - - ) : ( - displayFilters?.layout === "gantt_chart" && - )} - - ) : router.pathname.includes("archived-issues") ? ( - { - router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); - }, - }} - /> - ) : ( - - ) - ) : ( -
- -
- )} -
+ // + // + // {(provided, snapshot) => ( + //
+ // + // Drop here to delete the issue. + //
+ // )} + //
+ // {groupedIssues ? ( + // !isEmpty || + // displayFilters?.layout === "kanban" || + // displayFilters?.layout === "calendar" || + // displayFilters?.layout === "gantt_chart" ? ( + // <> + // {displayFilters?.layout === "list" ? ( + // + // ) : displayFilters?.layout === "kanban" ? ( + // + // ) : displayFilters?.layout === "calendar" ? ( + // + // ) : displayFilters?.layout === "spreadsheet" ? ( + // + // ) : ( + // displayFilters?.layout === "gantt_chart" && + // )} + // + // ) : router.pathname.includes("archived-issues") ? ( + // { + // router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); + // }, + // }} + // /> + // ) : ( + // + // ) + // ) : ( + //
+ // + //
+ // )} + //
+
+ +
); }; diff --git a/web/components/issues/issue-layouts/calandar/index.ts b/web/components/issues/issue-layouts/calandar/index.ts new file mode 100644 index 000000000..1efe34c51 --- /dev/null +++ b/web/components/issues/issue-layouts/calandar/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/web/components/issues/issue-layouts/calandar/root.tsx b/web/components/issues/issue-layouts/calandar/root.tsx new file mode 100644 index 000000000..c0ea15d9d --- /dev/null +++ b/web/components/issues/issue-layouts/calandar/root.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export interface ICalendarLayout { + issues: any; + handleDragDrop: () => void; +} + +export const CalendarLayout: React.FC = ({}) => { + console.log("kanaban layout"); + return ( +
+
header
+
content
+
footer
+
+ ); +}; diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts new file mode 100644 index 000000000..f8337cc77 --- /dev/null +++ b/web/components/issues/issue-layouts/index.ts @@ -0,0 +1,2 @@ +export * from "./kanban"; +export * from "./calandar"; diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx new file mode 100644 index 000000000..6900e9782 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -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) => ( + + {(provided: any, snapshot: any) => ( +
+
+
ONE-{issue.sequence_id}
+
{issue.name}
+
Footer
+
+
+ )} +
+ ))} + + ) : ( +
No issues are available.
+ )} + + ); +}; diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx new file mode 100644 index 000000000..321105635 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -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 = 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 ( +
+ {group_by && group_by === "state" && ( +
+ {projectStore?.projectStates && + projectStore?.projectStates.length > 0 && + projectStore?.projectStates.map((state) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} + +
+ + {(provided: any, snapshot: any) => ( +
+ {issues && ( + + )} + {provided.placeholder} +
+ )} +
+
+
+ ))} +
+ )} + + {group_by && group_by === "state_detail.group" && ( +
+ {ISSUE_STATE_GROUPS && + ISSUE_STATE_GROUPS.length > 0 && + ISSUE_STATE_GROUPS.map((stateGroup) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} +
content
+
+ ))} +
+ )} + + {group_by && group_by === "priority" && ( +
+ {ISSUE_PRIORITIES && + ISSUE_PRIORITIES.length > 0 && + ISSUE_PRIORITIES.map((priority) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} +
content
+
+ ))} +
+ )} + + {group_by && group_by === "labels" && ( +
+ {projectStore?.projectLabels && + projectStore?.projectLabels.length > 0 && + projectStore?.projectLabels.map((label) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} +
content
+
+ ))} +
+ )} + + {group_by && group_by === "assignees" && ( +
+ {projectStore?.projectMembers && + projectStore?.projectMembers.length > 0 && + projectStore?.projectMembers.map((member) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} +
content
+
+ ))} +
+ )} + + {group_by && group_by === "created_by" && ( +
+ {projectStore?.projectMembers && + projectStore?.projectMembers.length > 0 && + projectStore?.projectMembers.map((member) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} +
content
+
+ ))} +
+ )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx new file mode 100644 index 000000000..6824f1853 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -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 = observer(({ column_id }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const assignee = (column_id && projectStore?.getProjectMemberById(column_id)) ?? null; + + return <>{assignee && }; +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/card.tsx b/web/components/issues/issue-layouts/kanban/headers/card.tsx new file mode 100644 index 000000000..8c35aa99d --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/card.tsx @@ -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 ( +
+
+ {icon ? icon : } +
+ +
+
{title}
+
(0)
+
+ +
+ {position ? : } +
+ +
+ +
+
+ ); +}; diff --git a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx new file mode 100644 index 000000000..3b71bbf67 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx @@ -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 = observer(({ column_id }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const createdBy = (column_id && projectStore?.getProjectMemberById(column_id)) ?? null; + + return <>{createdBy && }; +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx new file mode 100644 index 000000000..9412d04f3 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx @@ -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 = observer(({ column_id }) => { + const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + return ( + <> + {group_by && group_by === "state" && } + {group_by && group_by === "state_detail.group" && } + {group_by && group_by === "priority" && } + {group_by && group_by === "labels" && } + {group_by && group_by === "assignees" && } + {group_by && group_by === "created_by" && } + + ); +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx new file mode 100644 index 000000000..00223e515 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -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 = observer(({ column_id }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null; + + return <>{label && }; +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/priority.tsx b/web/components/issues/issue-layouts/kanban/headers/priority.tsx new file mode 100644 index 000000000..5493f1a36 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/priority.tsx @@ -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 = observer(({ column_id }) => { + const {}: RootStore = useMobxStore(); + + const stateGroup = column_id && issuePriorityByKey(column_id); + + return <>{stateGroup && }; +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx new file mode 100644 index 000000000..2a406ac94 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx @@ -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 = observer(({ column_id }) => { + const {}: RootStore = useMobxStore(); + + const stateGroup = column_id && issueStateGroupByKey(column_id); + + return <>{stateGroup && }; +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/state.tsx b/web/components/issues/issue-layouts/kanban/headers/state.tsx new file mode 100644 index 000000000..4ed95a07c --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/state.tsx @@ -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 = observer(({ column_id }) => { + const { project: projectStore }: RootStore = useMobxStore(); + + const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null; + + return <>{state && }; +}); diff --git a/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx new file mode 100644 index 000000000..f78970fd7 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx @@ -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 = 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" && } + {sub_group_by && sub_group_by === "state_detail.group" && } + {sub_group_by && sub_group_by === "priority" && } + {sub_group_by && sub_group_by === "labels" && } + {sub_group_by && sub_group_by === "assignees" && } + {sub_group_by && sub_group_by === "created_by" && } + + ); +}); diff --git a/web/components/issues/issue-layouts/kanban/index.ts b/web/components/issues/issue-layouts/kanban/index.ts new file mode 100644 index 000000000..1efe34c51 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/root.tsx new file mode 100644 index 000000000..4b3bb5de9 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/root.tsx @@ -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 = 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 ( +
+ + {currentKanBanView === "default" ? : } + +
+ ); +}); diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx new file mode 100644 index 000000000..7ac4a4743 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -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) => ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+ +
+ ))} +
+); + +const SubGroupSwimlane = ({ issues, list, _key }: any) => ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+
+
+ +
+
+
+ +
+ +
+
+ ))} +
+); + +export const KanBanSwimLanes: React.FC = 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 ( +
+
+ {group_by && group_by === "state" && } + {group_by && group_by === "state_detail.group" && ( + + )} + {group_by && group_by === "priority" && } + {group_by && group_by === "labels" && } + {group_by && group_by === "assignees" && ( + + )} + {group_by && group_by === "created_by" && ( + + )} +
+ + {sub_group_by && sub_group_by === "state" && ( + + )} + + {sub_group_by && sub_group_by === "state_detail.group" && ( + + )} + + {sub_group_by && sub_group_by === "priority" && ( + + )} + + {sub_group_by && sub_group_by === "labels" && ( + + )} + + {sub_group_by && sub_group_by === "assignees" && ( + + )} + + {sub_group_by && sub_group_by === "created_by" && ( + + )} +
+ ); +}); diff --git a/web/constants/issue.ts b/web/constants/issue.ts index f7209ad49..eaec04999 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -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" }, diff --git a/web/package.json b/web/package.json index a3b3ba92b..6a261159e 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/store/issue.ts b/web/store/issue.ts index f91f8af05..00c3fa46a 100644 --- a/web/store/issue.ts +++ b/web/store/issue.ts @@ -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; + 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; + } }; } diff --git a/web/store/kanban_view.ts b/web/store/kanban_view.ts index 0289e9d62..c0d15227a 100644 --- a/web/store/kanban_view.ts +++ b/web/store/kanban_view.ts @@ -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 + // ); } }; } diff --git a/web/styles/globals.css b/web/styles/globals.css index 3de1e2c57..dc2bbed5e 100644 --- a/web/styles/globals.css +++ b/web/styles/globals.css @@ -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; +} diff --git a/yarn.lock b/yarn.lock index d357987f0..ace724162 100644 --- a/yarn.lock +++ b/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==