From 9b96e297b3dc94fc5862ad042f8d5887dcb40213 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Wed, 18 Oct 2023 19:58:05 +0530 Subject: [PATCH] chore: created new issue peek overview component and implemented in project issues list view (#2481) * chore: created new issue peek overview component and implemented in project issues list view * build: default project props in project, cycles, modules and view layout --- .../issue-layouts/kanban/cycle-root.tsx | 25 ++ .../issues/issue-layouts/kanban/default.tsx | 215 +++++----- .../issue-layouts/kanban/module-root.tsx | 25 ++ .../kanban/profile-issues-root.tsx | 31 +- .../issues/issue-layouts/kanban/root.tsx | 28 +- .../issues/issue-layouts/kanban/swimlanes.tsx | 391 +++++++++++------- .../issues/issue-layouts/kanban/view-root.tsx | 25 ++ .../issues/issue-layouts/list/block.tsx | 26 +- .../roots/project-layout-root.tsx | 1 - .../issues/issue-peek-overview/index.ts | 1 + .../issue-peek-overview/issue-detail.tsx | 45 ++ .../issues/issue-peek-overview/properties.tsx | 141 +++++++ .../issues/issue-peek-overview/root.tsx | 50 +++ .../issues/issue-peek-overview/view.tsx | 206 +++++++++ web/store/issue/issue_detail.store.ts | 23 +- 15 files changed, 955 insertions(+), 278 deletions(-) create mode 100644 web/components/issues/issue-peek-overview/index.ts create mode 100644 web/components/issues/issue-peek-overview/issue-detail.tsx create mode 100644 web/components/issues/issue-peek-overview/properties.tsx create mode 100644 web/components/issues/issue-peek-overview/root.tsx create mode 100644 web/components/issues/issue-peek-overview/view.tsx diff --git a/web/components/issues/issue-layouts/kanban/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/cycle-root.tsx index 728d4f02f..1f00585f9 100644 --- a/web/components/issues/issue-layouts/kanban/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/cycle-root.tsx @@ -9,11 +9,14 @@ import { KanBan } from "./default"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +// constants +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface ICycleKanBanLayout {} export const CycleKanBanLayout: React.FC = observer(() => { const { + project: projectStore, cycleIssue: cycleIssueStore, issueFilter: issueFilterStore, cycleIssueKanBanView: cycleIssueKanBanViewStore, @@ -55,6 +58,14 @@ export const CycleKanBanLayout: React.FC = observer(() => { cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value); }; + const states = projectStore?.projectStates || null; + const priorities = ISSUE_PRIORITIES || null; + const labels = projectStore?.projectLabels || null; + const members = projectStore?.projectMembers || null; + const stateGroups = ISSUE_STATE_GROUPS || null; + const projects = projectStore?.projectStates || null; + const estimates = null; + return (
@@ -67,6 +78,13 @@ export const CycleKanBanLayout: React.FC = observer(() => { display_properties={display_properties} kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> ) : ( { display_properties={display_properties} kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index e1400ab7e..c9619fe87 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -114,10 +114,18 @@ export interface IKanBan { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; + + states: any; + stateGroups: any; + priorities: any; + labels: any; + members: any; + projects: any; + estimates: any; } -export const KanBan: React.FC = observer( - ({ +export const KanBan: React.FC = observer((props) => { + const { issues, sub_group_by, group_by, @@ -126,107 +134,114 @@ export const KanBan: React.FC = observer( display_properties, kanBanToggle, handleKanBanToggle, - }) => { - const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); + states, + stateGroups, + priorities, + labels, + members, + projects, + estimates, + } = props; - return ( -
- {group_by && group_by === "state" && ( - - )} + const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); - {group_by && group_by === "state_detail.group" && ( - - )} + return ( +
+ {group_by && group_by === "state" && ( + + )} - {group_by && group_by === "priority" && ( - - )} + {group_by && group_by === "state_detail.group" && ( + + )} - {group_by && group_by === "labels" && ( - - )} + {group_by && group_by === "priority" && ( + + )} - {group_by && group_by === "assignees" && ( - - )} + {group_by && group_by === "labels" && ( + + )} - {group_by && group_by === "created_by" && ( - - )} -
- ); - } -); + {group_by && group_by === "assignees" && ( + + )} + + {group_by && group_by === "created_by" && ( + + )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/kanban/module-root.tsx b/web/components/issues/issue-layouts/kanban/module-root.tsx index 5bdc09706..9e42bfe05 100644 --- a/web/components/issues/issue-layouts/kanban/module-root.tsx +++ b/web/components/issues/issue-layouts/kanban/module-root.tsx @@ -9,11 +9,14 @@ import { KanBan } from "./default"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +// constants +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface IModuleKanBanLayout {} export const ModuleKanBanLayout: React.FC = observer(() => { const { + project: projectStore, moduleIssue: moduleIssueStore, issueFilter: issueFilterStore, moduleIssueKanBanView: moduleIssueKanBanViewStore, @@ -55,6 +58,14 @@ export const ModuleKanBanLayout: React.FC = observer(() => { moduleIssueKanBanViewStore.handleKanBanToggle(toggle, value); }; + const states = projectStore?.projectStates || null; + const priorities = ISSUE_PRIORITIES || null; + const labels = projectStore?.projectLabels || null; + const members = projectStore?.projectMembers || null; + const stateGroups = ISSUE_STATE_GROUPS || null; + const projects = projectStore?.projectStates || null; + const estimates = null; + return (
@@ -67,6 +78,13 @@ export const ModuleKanBanLayout: React.FC = observer(() => { display_properties={display_properties} kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> ) : ( { display_properties={display_properties} kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} diff --git a/web/components/issues/issue-layouts/kanban/profile-issues-root.tsx b/web/components/issues/issue-layouts/kanban/profile-issues-root.tsx index 3efebfd8e..68089c78f 100644 --- a/web/components/issues/issue-layouts/kanban/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/kanban/profile-issues-root.tsx @@ -1,5 +1,4 @@ -import React from "react"; -// react beautiful dnd +import { FC } from "react"; import { DragDropContext } from "@hello-pangea/dnd"; // mobx import { observer } from "mobx-react-lite"; @@ -9,11 +8,15 @@ import { KanBan } from "./default"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +// constants +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface IProfileIssuesKanBanLayout {} -export const ProfileIssuesKanBanLayout: React.FC = observer(() => { +export const ProfileIssuesKanBanLayout: FC = observer(() => { const { + workspace: workspaceStore, + project: projectStore, profileIssues: profileIssuesStore, profileIssueFilters: profileIssueFiltersStore, issueKanBanView: issueKanBanViewStore, @@ -55,6 +58,14 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => { issueKanBanViewStore.handleKanBanToggle(toggle, value); }; + const states = projectStore?.projectStates || null; + const priorities = ISSUE_PRIORITIES || null; + const labels = workspaceStore.workspaceLabels || null; + const members = projectStore?.projectMembers || null; + const stateGroups = ISSUE_STATE_GROUPS || null; + const projects = projectStore?.workspaceProjects || null; + const estimates = null; + return (
@@ -67,6 +78,13 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => { display_properties={display_properties} kanBanToggle={issueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> ) : ( { display_properties={display_properties} kanBanToggle={issueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/root.tsx index d3aa148f8..d6d4e1625 100644 --- a/web/components/issues/issue-layouts/kanban/root.tsx +++ b/web/components/issues/issue-layouts/kanban/root.tsx @@ -76,13 +76,13 @@ export const KanBanLayout: FC = observer(() => { display_properties={display_properties} kanBanToggle={issueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} - // states={states} - // stateGroups={stateGroups} - // priorities={priorities} - // labels={labels} - // members={members} - // projects={projects} - // estimates={estimates} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> ) : ( { display_properties={display_properties} kanBanToggle={issueKanBanViewStore?.kanBanToggle} handleKanBanToggle={handleKanBanToggle} - // states={states} - // stateGroups={stateGroups} - // priorities={priorities} - // labels={labels} - // members={members} - // projects={projects} - // estimates={estimates} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index baf8b36b2..161b53ecb 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -64,9 +64,16 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; + states: any; + stateGroups: any; + priorities: any; + labels: any; + members: any; + projects: any; + estimates: any; } -const SubGroupSwimlane: React.FC = observer( - ({ +const SubGroupSwimlane: React.FC = observer((props) => { + const { issues, sub_group_by, group_by, @@ -76,55 +83,69 @@ const SubGroupSwimlane: React.FC = observer( display_properties, kanBanToggle, handleKanBanToggle, - }) => { - const calculateIssueCount = (column_id: string) => { - let issueCount = 0; - issues?.[column_id] && - Object.keys(issues?.[column_id])?.forEach((_list: any) => { - issueCount += issues?.[column_id]?.[_list]?.length || 0; - }); - return issueCount; - }; + states, + stateGroups, + priorities, + labels, + members, + projects, + estimates, + } = props; - return ( -
- {list && - list.length > 0 && - list.map((_list: any) => ( -
-
-
- -
-
+ const calculateIssueCount = (column_id: string) => { + let issueCount = 0; + issues?.[column_id] && + Object.keys(issues?.[column_id])?.forEach((_list: any) => { + issueCount += issues?.[column_id]?.[_list]?.length || 0; + }); + return issueCount; + }; + + return ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+
+
+
- {!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && ( -
- -
- )} +
- ))} -
- ); - } -); + {!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && ( +
+ +
+ )} +
+ ))} +
+ ); +}); export interface IKanBanSwimLanes { issues: any; @@ -134,172 +155,236 @@ export interface IKanBanSwimLanes { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; + states: any; + stateGroups: any; + priorities: any; + labels: any; + members: any; + projects: any; + estimates: any; } -export const KanBanSwimLanes: React.FC = observer( - ({ issues, sub_group_by, group_by, handleIssues, display_properties, kanBanToggle, handleKanBanToggle }) => { - const { project: projectStore }: RootStore = useMobxStore(); +export const KanBanSwimLanes: React.FC = observer((props) => { + const { + issues, + sub_group_by, + group_by, + handleIssues, + display_properties, + kanBanToggle, + handleKanBanToggle, + states, + stateGroups, + priorities, + labels, + members, + projects, + estimates, + } = props; - return ( -
-
- {group_by && group_by === "state" && ( - - )} + const { project: projectStore }: RootStore = useMobxStore(); - {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" && ( - +
+ {group_by && 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" && ( - )}
- ); - } -); + + {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/view-root.tsx b/web/components/issues/issue-layouts/kanban/view-root.tsx index 0f5e690d0..bdc10f1a1 100644 --- a/web/components/issues/issue-layouts/kanban/view-root.tsx +++ b/web/components/issues/issue-layouts/kanban/view-root.tsx @@ -9,11 +9,14 @@ import { KanBan } from "./default"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +// constants +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface IViewKanBanLayout {} export const ViewKanBanLayout: React.FC = observer(() => { const { + project: projectStore, issue: issueStore, issueFilter: issueFilterStore, issueKanBanView: issueKanBanViewStore, @@ -51,6 +54,14 @@ export const ViewKanBanLayout: React.FC = observer(() => { issueStore.updateIssueStructure(group_by, sub_group_by, issue); }; + const states = projectStore?.projectStates || null; + const priorities = ISSUE_PRIORITIES || null; + const labels = projectStore?.projectLabels || null; + const members = projectStore?.projectMembers || null; + const stateGroups = ISSUE_STATE_GROUPS || null; + const projects = projectStore?.projectStates || null; + const estimates = null; + return (
@@ -63,6 +74,13 @@ export const ViewKanBanLayout: React.FC = observer(() => { display_properties={display_properties} kanBanToggle={() => {}} handleKanBanToggle={() => {}} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> ) : ( { display_properties={display_properties} kanBanToggle={() => {}} handleKanBanToggle={() => {}} + states={states} + stateGroups={stateGroups} + priorities={priorities} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 8c5cbb473..a542f5983 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -1,6 +1,7 @@ import { FC } from "react"; // components import { KanBanProperties } from "./properties"; +import { IssuePeekOverview } from "components/issues/issue-peek-overview"; // ui import { Tooltip } from "@plane/ui"; @@ -18,6 +19,10 @@ interface IssueBlockProps { export const IssueBlock: FC = (props) => { const { columnId, issues, handleIssues, display_properties, states, labels, members, priorities } = props; + const handleIssue = (_issue: any) => { + if (_issue && handleIssues) handleIssues(!columnId && columnId === "null" ? null : columnId, _issue); + }; + return ( <> {issues && @@ -25,14 +30,25 @@ export const IssueBlock: FC = (props) => { issues.map((issue: any, index: any) => (
{display_properties && display_properties?.key && ( -
ONE-{issue.sequence_id}
+
+ {issue?.project_detail?.identifier}-{issue.sequence_id} +
)} - -
{issue.name}
-
+ + + +
{issue.name}
+
+
+
) => void; +} + +export const PeekOverviewIssueDetails: FC = (props) => { + const { workspaceSlug, issue, issueUpdate } = props; + + const debouncedIssueDescription = useDebouncedCallback(async (_data: any) => { + issueUpdate({ ...issue, description_html: _data }); + }, 1500); + + return ( +
+
+ {issue?.project_detail?.identifier}-{issue?.sequence_id} +
+ +
{issue?.name}
+ + { + debouncedIssueDescription(description_html); + }} + /> +
+ ); +}; diff --git a/web/components/issues/issue-peek-overview/properties.tsx b/web/components/issues/issue-peek-overview/properties.tsx new file mode 100644 index 000000000..f24a58302 --- /dev/null +++ b/web/components/issues/issue-peek-overview/properties.tsx @@ -0,0 +1,141 @@ +import { FC } from "react"; +// ui icons +import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui"; +import { CalendarDays, Signal } from "lucide-react"; +// components +import { IssuePropertyState } from "components/issues/issue-layouts/properties/state"; +import { IssuePropertyPriority } from "components/issues/issue-layouts/properties/priority"; +import { IssuePropertyAssignee } from "components/issues/issue-layouts/properties/assignee"; +import { IssuePropertyDate } from "components/issues/issue-layouts/properties/date"; +// types +import { IIssue } from "types"; + +interface IPeekOverviewProperties { + issue: IIssue; + issueUpdate: (issue: Partial) => void; + + states: any; + members: any; + priorities: any; +} + +export const PeekOverviewProperties: FC = (props) => { + const { issue, issueUpdate, states, members, priorities } = props; + + const handleState = (_state: string) => { + if (issueUpdate) issueUpdate({ ...issue, state: _state }); + }; + + const handlePriority = (_priority: any) => { + if (issueUpdate) issueUpdate({ ...issue, priority: _priority }); + }; + + const handleAssignee = (_assignees: string[]) => { + if (issueUpdate) issueUpdate({ ...issue, assignees: _assignees }); + }; + + const handleStartDate = (_startDate: string) => { + if (issueUpdate) issueUpdate({ ...issue, start_date: _startDate }); + }; + + const handleTargetDate = (_targetDate: string) => { + if (issueUpdate) issueUpdate({ ...issue, target_date: _targetDate }); + }; + + return ( +
+ {/* state */} +
+
+
+ +
+
State
+
+
+ handleState(id)} + disabled={false} + list={states} + /> +
+
+ + {/* assignees */} +
+
+
+ +
+
Assignees
+
+
+ handleAssignee(ids)} + disabled={false} + list={members} + /> +
+
+ + {/* priority */} +
+
+
+ +
+
Priority
+
+
+ handlePriority(id)} + disabled={false} + list={priorities} + /> +
+
+ + {/* start_date */} +
+
+
+ +
+
Start date
+
+
+ handleStartDate(date)} + disabled={false} + placeHolder={`Start date`} + /> +
+
+ + {/* target_date */} +
+
+
+ +
+
Target date
+
+
+ handleTargetDate(date)} + disabled={false} + placeHolder={`Target date`} + /> +
+
+
+ ); +}; diff --git a/web/components/issues/issue-peek-overview/root.tsx b/web/components/issues/issue-peek-overview/root.tsx new file mode 100644 index 000000000..c8ab92bdb --- /dev/null +++ b/web/components/issues/issue-peek-overview/root.tsx @@ -0,0 +1,50 @@ +import { FC, ReactNode } from "react"; +import { observer } from "mobx-react-lite"; +// components +import { IssueView } from "./view"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; +// types +import { IIssue } from "types"; +import { RootStore } from "store/root"; +// constants +import { ISSUE_PRIORITIES } from "constants/issue"; + +interface IIssuePeekOverview { + workspaceSlug: string; + projectId: string; + issueId: string; + handleIssue: (issue: Partial) => void; + children: ReactNode; +} + +export const IssuePeekOverview: FC = observer((props) => { + const { workspaceSlug, projectId, issueId, handleIssue, children } = props; + + const { project: projectStore, issueDetail: issueDetailStore }: RootStore = useMobxStore(); + + const states = projectStore?.projectStates || undefined; + const members = projectStore?.projectMembers || undefined; + const priorities = ISSUE_PRIORITIES || undefined; + + const issueUpdate = (_data: Partial) => { + if (handleIssue) { + handleIssue(_data); + issueDetailStore.updateIssue(workspaceSlug, projectId, issueId, _data, undefined); + } + }; + + return ( + + {children} + + ); +}); diff --git a/web/components/issues/issue-peek-overview/view.tsx b/web/components/issues/issue-peek-overview/view.tsx new file mode 100644 index 000000000..d3974a56b --- /dev/null +++ b/web/components/issues/issue-peek-overview/view.tsx @@ -0,0 +1,206 @@ +import { FC, ReactNode, useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { Maximize2, ArrowRight, Link, Trash, PanelRightOpen, Square, SquareCode } from "lucide-react"; +import { observer } from "mobx-react-lite"; +// components +import { PeekOverviewIssueDetails } from "./issue-detail"; +import { PeekOverviewProperties } from "./properties"; +// types +import { IIssue } from "types"; +import { RootStore } from "store/root"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; + +interface IIssueView { + workspaceSlug: string; + projectId: string; + issueId: string; + issueUpdate: (issue: Partial) => void; + states: any; + members: any; + priorities: any; + children: ReactNode; +} + +type TPeekModes = "side-peek" | "modal" | "full-screen"; + +const peekOptions: { key: TPeekModes; icon: any; title: string }[] = [ + { + key: "side-peek", + icon: PanelRightOpen, + title: "Side Peek", + }, + { + key: "modal", + icon: Square, + title: "Modal", + }, + { + key: "full-screen", + icon: SquareCode, + title: "Full Screen", + }, +]; + +export const IssueView: FC = observer((props) => { + const { workspaceSlug, projectId, issueId, issueUpdate, states, members, priorities, children } = props; + + const router = useRouter(); + const { peekIssueId } = router.query as { peekIssueId: string }; + + const { issueDetail: issueDetailStore }: RootStore = useMobxStore(); + + const [peekMode, setPeekMode] = useState("side-peek"); + const handlePeekMode = (_peek: TPeekModes) => { + if (peekMode != _peek) setPeekMode(_peek); + }; + + const updateRoutePeekId = () => { + if (issueId != peekIssueId) { + const { query } = router; + router.push({ + pathname: router.pathname, + query: { ...query, peekIssueId: issueId }, + }); + } + }; + const removeRoutePeekId = () => { + const { query } = router; + if (query.peekIssueId) { + delete query.peekIssueId; + router.push({ + pathname: router.pathname, + query: { ...query }, + }); + } + }; + + const redirectToIssueDetail = () => { + router.push({ + pathname: `/${workspaceSlug}/projects/${projectId}/issues/${issueId}`, + }); + }; + + useEffect(() => { + if (workspaceSlug && projectId && issueId && peekIssueId && issueId === peekIssueId) + issueDetailStore.fetchIssueDetails(workspaceSlug, projectId, issueId); + }, [workspaceSlug, projectId, issueId, peekIssueId, issueDetailStore]); + + const issue = issueDetailStore.getIssue; + + return ( +
+
+ {children} +
+ + {issueId === peekIssueId && ( +
+ {/* header */} +
+
+ +
+ +
+ +
+ +
+ {peekOptions.map((_option) => ( +
handlePeekMode(_option?.key)} + > + <_option.icon width={14} strokeWidth={2} /> +
{_option?.title}
+
+ ))} +
+ +
+
+ Subscribe +
+ +
+ +
+ +
+ +
+
+
+ + {/* content */} +
+ {issueDetailStore?.loader && !issue ? ( +
Loading...
+ ) : ( + issue && ( + <> + {["side-peek", "modal"].includes(peekMode) ? ( +
+ + + {/* reactions */} + + + + {/* activity */} +
+ ) : ( +
+
+ + + {/* reactions */} + + {/* activity */} +
+
+ +
+
+ )} + + ) + )} +
+
+ )} +
+ ); +}); diff --git a/web/store/issue/issue_detail.store.ts b/web/store/issue/issue_detail.store.ts index a88f9f14b..a1886012d 100644 --- a/web/store/issue/issue_detail.store.ts +++ b/web/store/issue/issue_detail.store.ts @@ -1,4 +1,4 @@ -import { observable, action, makeObservable, runInAction } from "mobx"; +import { observable, action, makeObservable, runInAction, computed } from "mobx"; // services import { IssueService } from "services/issue"; // types @@ -20,12 +20,22 @@ export interface IIssueDetailStore { setPeekId: (issueId: string | null) => void; setPeekMode: (issueId: IPeekMode | null) => void; + + // computed + getIssue: IIssue | null; + // fetch issue details fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => void; // creating issue createIssue: (workspaceSlug: string, projectId: string, data: Partial, user: IUser) => void; // updating issue - updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial, user: IUser) => void; + updateIssue: ( + workspaceId: string, + projectId: string, + issueId: string, + data: Partial, + user: IUser | undefined + ) => void; // deleting issue deleteIssue: (workspaceSlug: string, projectId: string, issueId: string, user: IUser) => void; } @@ -57,6 +67,8 @@ export class IssueDetailStore implements IIssueDetailStore { issues: observable.ref, + getIssue: computed, + setPeekId: action, setPeekMode: action, @@ -70,6 +82,12 @@ export class IssueDetailStore implements IIssueDetailStore { this.issueService = new IssueService(); } + get getIssue() { + if (!this.peekId) return null; + const _issue = this.issues[this.peekId]; + return _issue || null; + } + setPeekId = (issueId: string | null) => (this.peekId = issueId); setPeekMode = (mode: IPeekMode | null) => (this.peekMode = mode); @@ -78,6 +96,7 @@ export class IssueDetailStore implements IIssueDetailStore { try { this.loader = true; this.error = null; + this.peekId = issueId; const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId);