diff --git a/apps/app/components/project/cycles/BoardView/single-board.tsx b/apps/app/components/project/cycles/BoardView/single-board.tsx deleted file mode 100644 index 92ff37110..000000000 --- a/apps/app/components/project/cycles/BoardView/single-board.tsx +++ /dev/null @@ -1,662 +0,0 @@ -// react -import React, { useState } from "react"; -// next -import Link from "next/link"; -import Image from "next/image"; -// swr -import useSWR from "swr"; -// services -import cycleServices from "lib/services/cycles.service"; -// hooks -import useUser from "lib/hooks/useUser"; -// ui -import { Spinner } from "ui"; -// icons -import { - ArrowsPointingInIcon, - ArrowsPointingOutIcon, - CalendarDaysIcon, - PlusIcon, - EllipsisHorizontalIcon, - TrashIcon, -} from "@heroicons/react/24/outline"; -import User from "public/user.png"; -// types -import { - CycleIssueResponse, - ICycle, - IIssue, - IWorkspaceMember, - NestedKeyOf, - Properties, -} from "types"; -// constants -import { CYCLE_ISSUES, WORKSPACE_MEMBERS } from "constants/fetch-keys"; -import { - addSpaceIfCamelCase, - findHowManyDaysLeft, - renderShortNumericDateFormat, -} from "constants/common"; -import { Menu, Transition } from "@headlessui/react"; -import workspaceService from "lib/services/workspace.service"; - -type Props = { - properties: Properties; - groupedByIssues: { - [key: string]: IIssue[]; - }; - selectedGroup: NestedKeyOf | null; - groupTitle: string; - createdBy: string | null; - bgColor?: string; - openCreateIssueModal: ( - sprintId: string, - issue?: IIssue, - actionType?: "create" | "edit" | "delete" - ) => void; - openIssuesListModal: (cycleId: string) => void; - removeIssueFromCycle: (cycleId: string, bridgeId: string) => void; -}; - -const SingleCycleBoard: React.FC = ({ - properties, - groupedByIssues, - selectedGroup, - groupTitle, - createdBy, - bgColor, - openCreateIssueModal, - openIssuesListModal, - removeIssueFromCycle, -}) => { - // Collapse/Expand - const [show, setState] = useState(true); - - const { activeWorkspace, activeProject } = useUser(); - - if (selectedGroup === "priority") - groupTitle === "high" - ? (bgColor = "#dc2626") - : groupTitle === "medium" - ? (bgColor = "#f97316") - : groupTitle === "low" - ? (bgColor = "#22c55e") - : (bgColor = "#ff0000"); - - const { data: people } = useSWR( - activeWorkspace ? WORKSPACE_MEMBERS : null, - activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null - ); - - return ( -
-
-
-
-
- -

- {groupTitle === null || groupTitle === "null" - ? "None" - : createdBy - ? createdBy - : addSpaceIfCamelCase(groupTitle)} -

- - {groupedByIssues[groupTitle].length} - -
-
-
-
- {groupedByIssues[groupTitle].map((childIssue, index: number) => { - const assignees = [ - ...(childIssue?.assignees_list ?? []), - ...(childIssue?.assignees ?? []), - ]?.map((assignee) => { - const tempPerson = people?.find((p) => p.member.id === assignee)?.member; - - return { - avatar: tempPerson?.avatar, - first_name: tempPerson?.first_name, - email: tempPerson?.email, - }; - }); - - return ( -
-
-
- -
- - - {properties.key && ( -
- {activeProject?.identifier}-{childIssue.sequence_id} -
- )} -
- {childIssue.name} -
-
- -
- {properties.priority && ( -
- {/* {getPriorityIcon(childIssue.priority ?? "")} */} - {childIssue.priority ?? "None"} -
-
Priority
-
- {childIssue.priority ?? "None"} -
-
-
- )} - {properties.state && ( -
- - {addSpaceIfCamelCase(childIssue.state_detail.name)} -
-
State
-
{childIssue.state_detail.name}
-
-
- )} - {properties.start_date && ( -
- - {childIssue.start_date - ? renderShortNumericDateFormat(childIssue.start_date) - : "N/A"} -
-
Started at
-
{renderShortNumericDateFormat(childIssue.start_date ?? "")}
-
-
- )} - {properties.target_date && ( -
- - {childIssue.target_date - ? renderShortNumericDateFormat(childIssue.target_date) - : "N/A"} -
-
Target date
-
{renderShortNumericDateFormat(childIssue.target_date ?? "")}
-
- {childIssue.target_date && - (childIssue.target_date < new Date().toISOString() - ? `Target date has passed by ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : findHowManyDaysLeft(childIssue.target_date) <= 3 - ? `Target date is in ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : "Target date")} -
-
-
- )} - {properties.assignee && ( -
- {childIssue.assignee_details?.length > 0 ? ( - childIssue.assignee_details?.map((assignee, index: number) => ( -
- {assignee.avatar && assignee.avatar !== "" ? ( -
- {assignee.name} -
- ) : ( -
- {assignee.first_name.charAt(0)} -
- )} -
- )) - ) : ( -
- No user -
- )} -
-
Assigned to
-
- {childIssue.assignee_details?.length > 0 - ? childIssue.assignee_details - .map((assignee) => assignee.first_name) - .join(", ") - : "No one"} -
-
-
- )} -
-
-
- ); - })} - -
-
-
- ); - - // return ( - //
- //
- //
- //
- //
- //

- // {cycle.name} - //

- // {cycleIssues?.length} - //
- //
- - //
- // - // - // - // - // - - // - // - //
- // - // {(active) => ( - // - // )} - // - // - // {(active) => ( - // - // )} - // - //
- //
- //
- //
- //
- //
- //
- // {cycleIssues ? ( - // cycleIssues.map((issue, index: number) => ( - //
- //
- //
- // - // - // - // - // - // - //
- // - //
- //
- // - //
- // - //
- //
- //
- //
- //
- // - // - // {properties.key && ( - //
- // {activeProject?.identifier}-{childIssue.sequence_id} - //
- // )} - //
- // {childIssue.name} - //
- //
- // - //
- // {properties.priority && ( - //
- // {/* {getPriorityIcon(childIssue.priority ?? "")} */} - // {childIssue.priority ?? "None"} - //
- //
Priority
- //
- // {childIssue.priority ?? "None"} - //
- //
- //
- // )} - // {properties.state && ( - //
- // - // {addSpaceIfCamelCase(childIssue.state_detail.name)} - //
- //
State
- //
{childIssue.state_detail.name}
- //
- //
- // )} - // {properties.start_date && ( - //
- // - // {childIssue.start_date - // ? renderShortNumericDateFormat(childIssue.start_date) - // : "N/A"} - //
- //
Started at
- //
- // {renderShortNumericDateFormat(childIssue.start_date ?? "")} - //
- //
- //
- // )} - // {properties.target_date && ( - //
- // - // {childIssue.target_date - // ? renderShortNumericDateFormat(childIssue.target_date) - // : "N/A"} - //
- //
Target date
- //
- // {renderShortNumericDateFormat(childIssue.target_date ?? "")} - //
- //
- // {childIssue.target_date && - // (childIssue.target_date < new Date().toISOString() - // ? `Target date has passed by ${findHowManyDaysLeft( - // childIssue.target_date - // )} days` - // : findHowManyDaysLeft(childIssue.target_date) <= 3 - // ? `Target date is in ${findHowManyDaysLeft( - // childIssue.target_date - // )} days` - // : "Target date")} - //
- //
- //
- // )} - // {properties.assignee && ( - //
- // {childIssue.assignee_details?.length > 0 ? ( - // childIssue.assignee_details?.map((assignee, index: number) => ( - //
- // {assignee.avatar && assignee.avatar !== "" ? ( - //
- // {assignee.name} - //
- // ) : ( - //
- // {assignee.first_name.charAt(0)} - //
- // )} - //
- // )) - // ) : ( - //
- // No user - //
- // )} - //
- //
Assigned to
- //
- // {childIssue.assignee_details?.length > 0 - // ? childIssue.assignee_details - // .map((assignee) => assignee.first_name) - // .join(", ") - // : "No one"} - //
- //
- //
- // )} - //
- //
- //
- // )) - // ) : ( - //
- // - //
- // )} - // - //
- //
- //
- // ); -}; - -export default SingleCycleBoard; diff --git a/apps/app/components/project/cycles/ListView/index.tsx b/apps/app/components/project/cycles/ListView/index.tsx deleted file mode 100644 index 5add9687a..000000000 --- a/apps/app/components/project/cycles/ListView/index.tsx +++ /dev/null @@ -1,714 +0,0 @@ -// react -import React from "react"; -// next -import Link from "next/link"; -// swr -import useSWR from "swr"; -// headless ui -import { Disclosure, Transition, Menu } from "@headlessui/react"; -// services -import cycleServices from "lib/services/cycles.service"; -// hooks -import useUser from "lib/hooks/useUser"; -// ui -import { Spinner } from "ui"; -// icons -import { PlusIcon, EllipsisHorizontalIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; -import { CalendarDaysIcon } from "@heroicons/react/24/outline"; -// types -import { IIssue, IWorkspaceMember, NestedKeyOf, Properties, SelectSprintType } from "types"; -// fetch keys -import { CYCLE_ISSUES, WORKSPACE_MEMBERS } from "constants/fetch-keys"; -// constants -import { - addSpaceIfCamelCase, - findHowManyDaysLeft, - renderShortNumericDateFormat, -} from "constants/common"; -import workspaceService from "lib/services/workspace.service"; - -type Props = { - groupedByIssues: { - [key: string]: IIssue[]; - }; - properties: Properties; - selectedGroup: NestedKeyOf | null; - openCreateIssueModal: ( - sprintId: string, - issue?: IIssue, - actionType?: "create" | "edit" | "delete" - ) => void; - openIssuesListModal: (cycleId: string) => void; - removeIssueFromCycle: (cycleId: string, bridgeId: string) => void; -}; - -const CyclesListView: React.FC = ({ - groupedByIssues, - selectedGroup, - openCreateIssueModal, - openIssuesListModal, - properties, - removeIssueFromCycle, -}) => { - const { activeWorkspace, activeProject } = useUser(); - - const { data: people } = useSWR( - activeWorkspace ? WORKSPACE_MEMBERS : null, - activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null - ); - - return ( -
- {Object.keys(groupedByIssues).map((singleGroup) => ( - - {({ open }) => ( -
-
- -
- - - - {selectedGroup !== null ? ( -

- {singleGroup === null || singleGroup === "null" - ? selectedGroup === "priority" && "No priority" - : addSpaceIfCamelCase(singleGroup)} -

- ) : ( -

All Issues

- )} -

- {groupedByIssues[singleGroup as keyof IIssue].length} -

-
-
-
- - -
- {groupedByIssues[singleGroup] ? ( - groupedByIssues[singleGroup].length > 0 ? ( - groupedByIssues[singleGroup].map((issue: IIssue) => { - const assignees = [ - ...(issue?.assignees_list ?? []), - ...(issue?.assignees ?? []), - ]?.map((assignee) => { - const tempPerson = people?.find( - (p) => p.member.id === assignee - )?.member; - - return { - avatar: tempPerson?.avatar, - first_name: tempPerson?.first_name, - email: tempPerson?.email, - }; - }); - - return ( -
- -
- {properties.priority && ( -
- {/* {getPriorityIcon(issue.priority ?? "")} */} - {issue.priority ?? "None"} -
-
Priority
-
- {issue.priority ?? "None"} -
-
-
- )} - {properties.state && ( -
- - {addSpaceIfCamelCase(issue?.state_detail.name)} -
-
State
-
{issue?.state_detail.name}
-
-
- )} - {properties.start_date && ( -
- - {issue.start_date - ? renderShortNumericDateFormat(issue.start_date) - : "N/A"} -
-
Started at
-
- {renderShortNumericDateFormat(issue.start_date ?? "")} -
-
-
- )} - {properties.target_date && ( -
- - {issue.target_date - ? renderShortNumericDateFormat(issue.target_date) - : "N/A"} -
-
- Target date -
-
- {renderShortNumericDateFormat(issue.target_date ?? "")} -
-
- {issue.target_date && - (issue.target_date < new Date().toISOString() - ? `Target date has passed by ${findHowManyDaysLeft( - issue.target_date - )} days` - : findHowManyDaysLeft(issue.target_date) <= 3 - ? `Target date is in ${findHowManyDaysLeft( - issue.target_date - )} days` - : "Target date")} -
-
-
- )} - - - - - - - - - -
- -
-
- -
- -
-
-
-
-
-
- ); - }) - ) : ( -

No issues.

- ) - ) : ( -
- -
- )} -
-
-
-
- -
-
- )} -
- ))} -
- ); - - // return ( - // <> - // - // {({ open }) => ( - //
- //
- // - - // - // - // - // - // - // - // - // - // - // - // - // - // - //
- // - // - // - // {(provided) => ( - //
- // {cycleIssues ? ( - // cycleIssues.length > 0 ? ( - // cycleIssues.map((issue, index) => ( - // - // {(provided, snapshot) => ( - //
- // - //
- // {properties.priority && ( - //
- // {/* {getPriorityIcon(issue.priority ?? "")} */} - // {issue.priority ?? "None"} - //
- //
- // Priority - //
- //
- // {issue.priority ?? "None"} - //
- //
- //
- // )} - // {properties.state && ( - //
- // - // {addSpaceIfCamelCase( - // issue?.state_detail.name - // )} - //
- //
State
- //
{issue?.state_detail.name}
- //
- //
- // )} - // {properties.start_date && ( - //
- // - // {issue.start_date - // ? renderShortNumericDateFormat( - // issue.start_date - // ) - // : "N/A"} - //
- //
Started at
- //
- // {renderShortNumericDateFormat( - // issue.start_date ?? "" - // )} - //
- //
- //
- // )} - // {properties.target_date && ( - //
- // - // {issue.target_date - // ? renderShortNumericDateFormat( - // issue.target_date - // ) - // : "N/A"} - //
- //
- // Target date - //
- //
- // {renderShortNumericDateFormat( - // issue.target_date ?? "" - // )} - //
- //
- // {issue.target_date && - // (issue.target_date < - // new Date().toISOString() - // ? `Target date has passed by ${findHowManyDaysLeft( - // issue.target_date - // )} days` - // : findHowManyDaysLeft( - // issue.target_date - // ) <= 3 - // ? `Target date is in ${findHowManyDaysLeft( - // issue.target_date - // )} days` - // : "Target date")} - //
- //
- //
- // )} - // - // - // - // - // - // - // - // - // - //
- // - //
- //
- // - //
- // - //
- //
- //
- //
- //
- //
- // )} - //
- // )) - // ) : ( - //

- // This cycle has no issue. - //

- // ) - // ) : ( - //
- // - //
- // )} - // {provided.placeholder} - //
- // )} - //
- //
- //
- //
- // - // - // - // Add issue - // - - // - // - //
- // - // {(active) => ( - // - // )} - // - // - // {(active) => ( - // - // )} - // - //
- //
- //
- //
- //
- //
- // )} - //
- // - // ); -}; - -export default CyclesListView; diff --git a/apps/app/components/project/cycles/BoardView/index.tsx b/apps/app/components/project/cycles/board-view/index.tsx similarity index 91% rename from apps/app/components/project/cycles/BoardView/index.tsx rename to apps/app/components/project/cycles/board-view/index.tsx index 8026786c4..07d68f20d 100644 --- a/apps/app/components/project/cycles/BoardView/index.tsx +++ b/apps/app/components/project/cycles/board-view/index.tsx @@ -1,5 +1,5 @@ // components -import SingleBoard from "components/project/cycles/BoardView/single-board"; +import SingleBoard from "components/project/cycles/board-view/single-board"; // ui import { Spinner } from "ui"; // types @@ -13,11 +13,7 @@ type Props = { properties: Properties; selectedGroup: NestedKeyOf | null; members: IProjectMember[] | undefined; - openCreateIssueModal: ( - sprintId: string, - issue?: IIssue, - actionType?: "create" | "edit" | "delete" - ) => void; + openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openIssuesListModal: (cycleId: string) => void; removeIssueFromCycle: (cycleId: string, bridgeId: string) => void; }; diff --git a/apps/app/components/project/cycles/board-view/single-board.tsx b/apps/app/components/project/cycles/board-view/single-board.tsx new file mode 100644 index 000000000..c437824e4 --- /dev/null +++ b/apps/app/components/project/cycles/board-view/single-board.tsx @@ -0,0 +1,377 @@ +// react +import React, { useState } from "react"; +// next +import Link from "next/link"; +import Image from "next/image"; +// swr +import useSWR from "swr"; +// services +import cycleServices from "lib/services/cycles.service"; +// hooks +import useUser from "lib/hooks/useUser"; +// ui +import { Spinner } from "ui"; +// icons +import { + ArrowsPointingInIcon, + ArrowsPointingOutIcon, + CalendarDaysIcon, + PlusIcon, + EllipsisHorizontalIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; +import User from "public/user.png"; +// types +import { + CycleIssueResponse, + ICycle, + IIssue, + IWorkspaceMember, + NestedKeyOf, + Properties, +} from "types"; +// constants +import { CYCLE_ISSUES, WORKSPACE_MEMBERS } from "constants/fetch-keys"; +import { + addSpaceIfCamelCase, + findHowManyDaysLeft, + renderShortNumericDateFormat, +} from "constants/common"; +import { Menu, Transition } from "@headlessui/react"; +import workspaceService from "lib/services/workspace.service"; + +type Props = { + properties: Properties; + groupedByIssues: { + [key: string]: IIssue[]; + }; + selectedGroup: NestedKeyOf | null; + groupTitle: string; + createdBy: string | null; + bgColor?: string; + openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; + openIssuesListModal: (cycleId: string) => void; + removeIssueFromCycle: (cycleId: string, bridgeId: string) => void; +}; + +const SingleCycleBoard: React.FC = ({ + properties, + groupedByIssues, + selectedGroup, + groupTitle, + createdBy, + bgColor, + openCreateIssueModal, + openIssuesListModal, + removeIssueFromCycle, +}) => { + // Collapse/Expand + const [show, setState] = useState(true); + + const { activeWorkspace, activeProject } = useUser(); + + if (selectedGroup === "priority") + groupTitle === "high" + ? (bgColor = "#dc2626") + : groupTitle === "medium" + ? (bgColor = "#f97316") + : groupTitle === "low" + ? (bgColor = "#22c55e") + : (bgColor = "#ff0000"); + + const { data: people } = useSWR( + activeWorkspace ? WORKSPACE_MEMBERS : null, + activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null + ); + + return ( +
+
+
+
+
+ +

+ {groupTitle === null || groupTitle === "null" + ? "None" + : createdBy + ? createdBy + : addSpaceIfCamelCase(groupTitle)} +

+ + {groupedByIssues[groupTitle].length} + +
+ + + + + + + + +
+ + {(active) => ( + + )} + + + {(active) => ( + + )} + +
+
+
+
+
+
+
+ {groupedByIssues[groupTitle].map((childIssue, index: number) => { + const assignees = [ + ...(childIssue?.assignees_list ?? []), + ...(childIssue?.assignees ?? []), + ]?.map((assignee) => { + const tempPerson = people?.find((p) => p.member.id === assignee)?.member; + + return { + avatar: tempPerson?.avatar, + first_name: tempPerson?.first_name, + email: tempPerson?.email, + }; + }); + + return ( +
+
+ + + {properties.key && ( +
+ {activeProject?.identifier}-{childIssue.sequence_id} +
+ )} +
+ {childIssue.name} +
+
+ +
+ {properties.priority && ( +
+ {/* {getPriorityIcon(childIssue.priority ?? "")} */} + {childIssue.priority ?? "None"} +
+
Priority
+
+ {childIssue.priority ?? "None"} +
+
+
+ )} + {properties.state && ( +
+ + {addSpaceIfCamelCase(childIssue.state_detail.name)} +
+
State
+
{childIssue.state_detail.name}
+
+
+ )} + {properties.start_date && ( +
+ + {childIssue.start_date + ? renderShortNumericDateFormat(childIssue.start_date) + : "N/A"} +
+
Started at
+
{renderShortNumericDateFormat(childIssue.start_date ?? "")}
+
+
+ )} + {properties.target_date && ( +
+ + {childIssue.target_date + ? renderShortNumericDateFormat(childIssue.target_date) + : "N/A"} +
+
Target date
+
{renderShortNumericDateFormat(childIssue.target_date ?? "")}
+
+ {childIssue.target_date && + (childIssue.target_date < new Date().toISOString() + ? `Target date has passed by ${findHowManyDaysLeft( + childIssue.target_date + )} days` + : findHowManyDaysLeft(childIssue.target_date) <= 3 + ? `Target date is in ${findHowManyDaysLeft( + childIssue.target_date + )} days` + : "Target date")} +
+
+
+ )} + {properties.assignee && ( +
+ {childIssue.assignee_details?.length > 0 ? ( + childIssue.assignee_details?.map((assignee, index: number) => ( +
+ {assignee.avatar && assignee.avatar !== "" ? ( +
+ {assignee.name} +
+ ) : ( +
+ {assignee.first_name.charAt(0)} +
+ )} +
+ )) + ) : ( +
+ No user +
+ )} +
+
Assigned to
+
+ {childIssue.assignee_details?.length > 0 + ? childIssue.assignee_details + .map((assignee) => assignee.first_name) + .join(", ") + : "No one"} +
+
+
+ )} +
+
+
+ ); + })} + +
+
+
+ ); +}; + +export default SingleCycleBoard; diff --git a/apps/app/components/project/cycles/list-view/index.tsx b/apps/app/components/project/cycles/list-view/index.tsx new file mode 100644 index 000000000..2ec0c6731 --- /dev/null +++ b/apps/app/components/project/cycles/list-view/index.tsx @@ -0,0 +1,352 @@ +// react +import React from "react"; +// next +import Link from "next/link"; +// swr +import useSWR from "swr"; +// headless ui +import { Disclosure, Transition, Menu } from "@headlessui/react"; +// services +import cycleServices from "lib/services/cycles.service"; +// hooks +import useUser from "lib/hooks/useUser"; +// ui +import { Spinner } from "ui"; +// icons +import { PlusIcon, EllipsisHorizontalIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; +import { CalendarDaysIcon } from "@heroicons/react/24/outline"; +// types +import { IIssue, IWorkspaceMember, NestedKeyOf, Properties, SelectSprintType } from "types"; +// fetch keys +import { CYCLE_ISSUES, WORKSPACE_MEMBERS } from "constants/fetch-keys"; +// constants +import { + addSpaceIfCamelCase, + findHowManyDaysLeft, + renderShortNumericDateFormat, +} from "constants/common"; +import workspaceService from "lib/services/workspace.service"; + +type Props = { + groupedByIssues: { + [key: string]: IIssue[]; + }; + properties: Properties; + selectedGroup: NestedKeyOf | null; + openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; + openIssuesListModal: (cycleId: string) => void; + removeIssueFromCycle: (cycleId: string, bridgeId: string) => void; +}; + +const CyclesListView: React.FC = ({ + groupedByIssues, + selectedGroup, + openCreateIssueModal, + openIssuesListModal, + properties, + removeIssueFromCycle, +}) => { + const { activeWorkspace, activeProject } = useUser(); + + const { data: people } = useSWR( + activeWorkspace ? WORKSPACE_MEMBERS : null, + activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null + ); + + return ( +
+ {Object.keys(groupedByIssues).map((singleGroup) => ( + + {({ open }) => ( +
+
+ +
+ + + + {selectedGroup !== null ? ( +

+ {singleGroup === null || singleGroup === "null" + ? selectedGroup === "priority" && "No priority" + : addSpaceIfCamelCase(singleGroup)} +

+ ) : ( +

All Issues

+ )} +

+ {groupedByIssues[singleGroup as keyof IIssue].length} +

+
+
+
+ + +
+ {groupedByIssues[singleGroup] ? ( + groupedByIssues[singleGroup].length > 0 ? ( + groupedByIssues[singleGroup].map((issue: IIssue) => { + const assignees = [ + ...(issue?.assignees_list ?? []), + ...(issue?.assignees ?? []), + ]?.map((assignee) => { + const tempPerson = people?.find( + (p) => p.member.id === assignee + )?.member; + + return { + avatar: tempPerson?.avatar, + first_name: tempPerson?.first_name, + email: tempPerson?.email, + }; + }); + + return ( +
+ +
+ {properties.priority && ( +
+ {/* {getPriorityIcon(issue.priority ?? "")} */} + {issue.priority ?? "None"} +
+
Priority
+
+ {issue.priority ?? "None"} +
+
+
+ )} + {properties.state && ( +
+ + {addSpaceIfCamelCase(issue?.state_detail.name)} +
+
State
+
{issue?.state_detail.name}
+
+
+ )} + {properties.start_date && ( +
+ + {issue.start_date + ? renderShortNumericDateFormat(issue.start_date) + : "N/A"} +
+
Started at
+
+ {renderShortNumericDateFormat(issue.start_date ?? "")} +
+
+
+ )} + {properties.target_date && ( +
+ + {issue.target_date + ? renderShortNumericDateFormat(issue.target_date) + : "N/A"} +
+
+ Target date +
+
+ {renderShortNumericDateFormat(issue.target_date ?? "")} +
+
+ {issue.target_date && + (issue.target_date < new Date().toISOString() + ? `Target date has passed by ${findHowManyDaysLeft( + issue.target_date + )} days` + : findHowManyDaysLeft(issue.target_date) <= 3 + ? `Target date is in ${findHowManyDaysLeft( + issue.target_date + )} days` + : "Target date")} +
+
+
+ )} + + + + + + + + + +
+ +
+
+ +
+ +
+
+
+
+
+
+ ); + }) + ) : ( +

No issues.

+ ) + ) : ( +
+ +
+ )} +
+
+
+
+ +
+
+ )} +
+ ))} +
+ // + // + // + // + // = ({ cycles }) => { + return ( + <> + {cycles.map((cycle) => ( + + ))} + + ); +}; + +export default CycleStatsView; diff --git a/apps/app/components/project/cycles/stats-view/single-stat.tsx b/apps/app/components/project/cycles/stats-view/single-stat.tsx new file mode 100644 index 000000000..ae5301fe9 --- /dev/null +++ b/apps/app/components/project/cycles/stats-view/single-stat.tsx @@ -0,0 +1,102 @@ +// next +import Link from "next/link"; +// swr +import useSWR from "swr"; +// services +import cyclesService from "lib/services/cycles.service"; +// hooks +import useUser from "lib/hooks/useUser"; +// types +import { CycleIssueResponse, ICycle } from "types"; +// fetch-keys +import { CYCLE_ISSUES } from "constants/fetch-keys"; +import { groupBy, renderShortNumericDateFormat } from "constants/common"; +import { + CheckIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, +} from "@heroicons/react/24/outline"; + +type Props = { cycle: ICycle }; + +const stateGroupIcons: { + [key: string]: JSX.Element; +} = { + backlog: , + unstarted: , + started: , + cancelled: , + completed: , +}; + +const SingleStat: React.FC = ({ cycle }) => { + const { activeWorkspace, activeProject } = useUser(); + + const { data: cycleIssues } = useSWR( + activeWorkspace && activeProject && cycle.id ? CYCLE_ISSUES(cycle.id as string) : null, + activeWorkspace && activeProject && cycle.id + ? () => + cyclesService.getCycleIssues(activeWorkspace?.slug, activeProject?.id, cycle.id as string) + : null + ); + const groupedIssues = { + backlog: [], + unstarted: [], + started: [], + cancelled: [], + completed: [], + ...groupBy(cycleIssues ?? [], "issue_details.state_detail.group"), + }; + + // status calculator + const startDate = new Date(cycle.start_date ?? ""); + const endDate = new Date(cycle.end_date ?? ""); + const today = new Date(); + + return ( + <> +
+
+
+ + {cycle.name} + +
+ {renderShortNumericDateFormat(startDate)} + {" - "} + {renderShortNumericDateFormat(endDate)} +
+
+
+ {today.getDate() < startDate.getDate() + ? "Not started" + : today.getDate() > endDate.getDate() + ? "Over" + : "Active"} +
+
+
+
+ {Object.keys(groupedIssues).map((group) => { + return ( +
+
+
+ {stateGroupIcons[group]} +
+
+
+
{group}
+ {groupedIssues[group].length} +
+
+ ); + })} +
+
+
+ + ); +}; + +export default SingleStat; diff --git a/apps/app/components/project/issues/IssuesListModal.tsx b/apps/app/components/project/issues/IssuesListModal.tsx index aba9a0c34..b635a16b6 100644 --- a/apps/app/components/project/issues/IssuesListModal.tsx +++ b/apps/app/components/project/issues/IssuesListModal.tsx @@ -19,6 +19,7 @@ type Props = { issues: IIssue[]; title?: string; multiple?: boolean; + customDisplay?: JSX.Element; }; const IssuesListModal: React.FC = ({ @@ -29,6 +30,7 @@ const IssuesListModal: React.FC = ({ issues, title = "Issues", multiple = false, + customDisplay, }) => { const [query, setQuery] = useState(""); const [values, setValues] = useState([]); @@ -90,9 +92,10 @@ const IssuesListModal: React.FC = ({ className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm outline-none" placeholder="Search..." onChange={(e) => setQuery(e.target.value)} + displayValue={() => ""} /> - +
{customDisplay}
; @@ -71,25 +71,12 @@ const IssueDetailSidebar: React.FC = ({ }) => { const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false); const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); - const [isParentModalOpen, setIsParentModalOpen] = useState(false); const [createLabelForm, setCreateLabelForm] = useState(false); const { activeWorkspace, activeProject, cycles, issues } = useUser(); const { setToastAlert } = useToast(); - const { data: states } = useSWR( - activeWorkspace && activeProject ? STATE_LIST(activeProject.id) : null, - activeWorkspace && activeProject - ? () => stateServices.getStates(activeWorkspace.slug, activeProject.id) - : null - ); - - const { data: people } = useSWR( - activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null, - activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null - ); - const { data: issueLabels, mutate: issueLabelMutate } = useSWR( activeProject && activeWorkspace ? PROJECT_ISSUE_LABELS(activeProject.id) : null, activeProject && activeWorkspace @@ -108,7 +95,7 @@ const IssueDetailSidebar: React.FC = ({ defaultValues, }); - const onSubmit = (formData: any) => { + const handleNewLabel = (formData: any) => { if (!activeWorkspace || !activeProject || isSubmitting) return; issuesServices .createIssueLabel(activeWorkspace.slug, activeProject.id, formData) @@ -133,58 +120,6 @@ const IssueDetailSidebar: React.FC = ({ }> > = [ [ - { - label: "Status", - name: "state", - canSelectMultipleOptions: false, - icon: Squares2X2Icon, - options: states?.map((state) => ({ - label: state.name, - value: state.id, - color: state.color, - })), - modal: false, - }, - { - label: "Assignees", - name: "assignees_list", - canSelectMultipleOptions: true, - icon: UserGroupIcon, - options: people?.map((person) => ({ - label: person.member.first_name, - value: person.member.id, - })), - modal: false, - }, - { - label: "Priority", - name: "priority", - canSelectMultipleOptions: false, - icon: ChartBarIcon, - options: PRIORITIES.map((property) => ({ - label: property, - value: property, - })), - modal: false, - }, - ], - [ - { - label: "Parent", - name: "parent", - canSelectMultipleOptions: false, - icon: UserIcon, - issuesList: - issues?.results.filter( - (i) => - i.id !== issueDetail?.id && - i.id !== issueDetail?.parent && - i.parent !== issueDetail?.id - ) ?? [], - modal: true, - isOpen: isParentModalOpen, - setIsOpen: setIsParentModalOpen, - }, // { // label: "Blocker", // name: "blockers_list", @@ -205,26 +140,6 @@ const IssueDetailSidebar: React.FC = ({ // isOpen: isBlockedModalOpen, // setIsOpen: setIsBlockedModalOpen, // }, - { - label: "Target Date", - name: "target_date", - canSelectMultipleOptions: true, - icon: CalendarDaysIcon, - modal: false, - }, - ], - [ - { - label: "Cycle", - name: "cycle", - canSelectMultipleOptions: false, - icon: ArrowPathIcon, - options: cycles?.map((cycle) => ({ - label: cycle.name, - value: cycle.id, - })), - modal: false, - }, ], ]; @@ -297,7 +212,69 @@ const IssueDetailSidebar: React.FC = ({
- {sidebarSections.map((section, index) => ( +
+ + + +
+
+ + i.id !== issueDetail?.id && + i.id !== issueDetail?.parent && + i.parent !== issueDetail?.id + ) ?? [] + } + customDisplay={ + issueDetail?.parent_detail ? ( + + ) : ( +
+ No parent selected +
+ ) + } + watchIssue={watchIssue} + /> +
+
+ +

Due date

+
+
+ ( + { + submitChanges({ target_date: e.target.value }); + onChange(e.target.value); + }} + className="hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 w-full" + /> + )} + /> +
+
+
+
+ +
+ {/* {sidebarSections.map((section, index) => (
{section.map((item) => (
@@ -306,154 +283,63 @@ const IssueDetailSidebar: React.FC = ({

{item.label}

- {item.name === "target_date" ? ( - ( - { - submitChanges({ target_date: e.target.value }); - onChange(e.target.value); + ( + <> + item.setIsOpen && item.setIsOpen(false)} + onChange={(val) => { + console.log(val); + submitChanges({ [item.name]: val }); + onChange(val); }} - className="hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 w-full" + issues={item?.issuesList ?? []} + title={`Select ${item.label}`} + multiple={item.canSelectMultipleOptions} + value={value} + customDisplay={ + issueDetail?.parent_detail ? ( + + ) : ( +
+ No parent selected +
+ ) + } /> - )} - /> - ) : item.modal ? ( - ( - <> - item.setIsOpen && item.setIsOpen(false)} - onChange={(val) => { - console.log(val); - // submitChanges({ [item.name]: val }); - onChange(val); - }} - issues={item?.issuesList ?? []} - title={`Select ${item.label}`} - multiple={item.canSelectMultipleOptions} - value={value} - /> - - - )} - /> - ) : ( - ( - { - if (item.name === "cycle") handleCycleChange(value); - else submitChanges({ [item.name]: value }); - }} - className="flex-shrink-0" - > - {({ open }) => ( -
- - - {value - ? Array.isArray(value) - ? value - .map( - (i: any) => - item.options?.find((option) => option.value === i) - ?.label - ) - .join(", ") || item.label - : item.options?.find((option) => option.value === value) - ?.label - : "None"} - - - - - - -
- {item.options ? ( - item.options.length > 0 ? ( - item.options.map((option) => ( - - `${ - active || selected - ? "text-white bg-theme" - : "text-gray-900" - } ${ - item.label === "Priority" && "capitalize" - } flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate` - } - value={option.value} - > - {option.color && ( - - )} - {option.label} - - )) - ) : ( -
No {item.label}s found
- ) - ) : ( - - )} -
-
-
-
- )} -
- )} - /> - )} + : `Select ${item.label}`} + + + )} + />
))}
- ))} + ))} */}
@@ -466,18 +352,20 @@ const IssueDetailSidebar: React.FC = ({ {issueDetail?.label_details.map((label) => ( - // submitChanges({ - // labels_list: issueDetail?.labels_list.filter((l) => l !== label.id), - // }) - // } + className="group flex items-center gap-1 border rounded-2xl text-xs px-1 py-0.5 hover:bg-red-50 hover:border-red-500 cursor-pointer" + onClick={() => { + const updatedLabels = issueDetail?.labels.filter((l) => l !== label.id); + submitChanges({ + labels_list: updatedLabels, + }); + }} > {label.name} + ))} = ({ as="div" value={value} multiple - onChange={(value: any) => submitChanges({ labels_list: value })} + onChange={(val) => submitChanges({ labels_list: val })} className="flex-shrink-0" > {({ open }) => ( <> Label
- - - Select Label - + + Select Label = ({ )} /> +
-
- -
{createLabelForm && ( -
+
{({ open }) => ( diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-assignee.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-assignee.tsx new file mode 100644 index 000000000..0fff8006b --- /dev/null +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-assignee.tsx @@ -0,0 +1,181 @@ +// react +import React from "react"; +// next +import Image from "next/image"; +// swr +import useSWR from "swr"; +// react-hook-form +import { Control, Controller } from "react-hook-form"; +// services +import workspaceService from "lib/services/workspace.service"; +// hooks +import useUser from "lib/hooks/useUser"; +// headless ui +import { Listbox, Transition } from "@headlessui/react"; +// ui +import { Spinner } from "ui"; +// icons +import { ArrowPathIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; +import User from "public/user.png"; +// types +import { IIssue } from "types"; +// constants +import { classNames } from "constants/common"; +import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; + +type Props = { + control: Control; + submitChanges: (formData: Partial) => void; +}; + +const SelectAssignee: React.FC = ({ control, submitChanges }) => { + const { activeWorkspace } = useUser(); + + const { data: people } = useSWR( + activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null, + activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null + ); + + return ( +
+
+ +

Assignees

+
+
+ ( + { + submitChanges({ assignees_list: value }); + }} + className="flex-shrink-0" + > + {({ open }) => ( +
+ + +
+ {value && Array.isArray(value) ? ( + <> + {value.length > 0 ? ( + value.map((assignee, index: number) => { + const person = people?.find( + (p) => p.member.id === assignee + )?.member; + + return ( +
+ {person && person.avatar && person.avatar !== "" ? ( +
+ {person.first_name} +
+ ) : ( +
+ {person?.first_name.charAt(0)} +
+ )} +
+ ); + }) + ) : ( +
+ No user +
+ )} + + ) : null} +
+
+
+ + + +
+ {people ? ( + people.length > 0 ? ( + people.map((option) => ( + + `${ + active || selected ? "text-white bg-theme" : "text-gray-900" + } flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate` + } + value={option.member.id} + > + {option.member.avatar && option.member.avatar !== "" ? ( +
+ avatar +
+ ) : ( +
+ {option.member.first_name && option.member.first_name !== "" + ? option.member.first_name.charAt(0) + : option.member.email.charAt(0)} +
+ )} + {option.member.first_name} +
+ )) + ) : ( +
No assignees found
+ ) + ) : ( + + )} +
+
+
+
+ )} +
+ )} + /> +
+
+ ); +}; + +export default SelectAssignee; diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-cycle.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-cycle.tsx new file mode 100644 index 000000000..0e36c54af --- /dev/null +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-cycle.tsx @@ -0,0 +1,98 @@ +// react-hook-form +import { Control, Controller } from "react-hook-form"; +// hooks +import useUser from "lib/hooks/useUser"; +// headless ui +import { Listbox, Transition } from "@headlessui/react"; +// types +import { IIssue } from "types"; +import { classNames } from "constants/common"; +import { Spinner } from "ui"; +import React from "react"; +import { ArrowPathIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; + +type Props = { + control: Control; + handleCycleChange: (cycleId: string) => void; +}; + +const SelectCycle: React.FC = ({ control, handleCycleChange }) => { + const { cycles } = useUser(); + + return ( +
+
+ +

Cycle

+
+
+ ( + { + handleCycleChange(value); + }} + className="flex-shrink-0" + > + {({ open }) => ( +
+ + + {value ? cycles?.find((c) => c.id === value)?.name : "None"} + + + + + + +
+ {cycles ? ( + cycles.length > 0 ? ( + cycles.map((option) => ( + + `${ + active || selected ? "text-white bg-theme" : "text-gray-900" + } flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate` + } + value={option.id} + > + {option.name} + + )) + ) : ( +
No cycles found
+ ) + ) : ( + + )} +
+
+
+
+ )} +
+ )} + /> +
+
+ ); +}; + +export default SelectCycle; diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-parent.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-parent.tsx new file mode 100644 index 000000000..03cf0be4e --- /dev/null +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-parent.tsx @@ -0,0 +1,74 @@ +// react +import React, { useState } from "react"; +// react-hook-form +import { Control, Controller, UseFormWatch } from "react-hook-form"; +// hooks +import useUser from "lib/hooks/useUser"; +// components +import IssuesListModal from "components/project/issues/IssuesListModal"; +// icons +import { UserIcon } from "@heroicons/react/24/outline"; +// types +import { IIssue } from "types"; + +type Props = { + control: Control; + submitChanges: (formData: Partial) => void; + issuesList: IIssue[]; + customDisplay: JSX.Element; + watchIssue: UseFormWatch; +}; + +const SelectParent: React.FC = ({ + control, + submitChanges, + issuesList, + customDisplay, + watchIssue, +}) => { + const [isParentModalOpen, setIsParentModalOpen] = useState(false); + + const { activeProject, issues } = useUser(); + + return ( +
+
+ +

Parent

+
+
+ ( + setIsParentModalOpen(false)} + onChange={(val) => { + submitChanges({ parent: val }); + onChange(val); + }} + issues={issuesList} + title="Select Parent" + value={value} + customDisplay={customDisplay} + /> + )} + /> + +
+
+ ); +}; + +export default SelectParent; diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-priority.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-priority.tsx new file mode 100644 index 000000000..e76beeb25 --- /dev/null +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-priority.tsx @@ -0,0 +1,84 @@ +// react +import React from "react"; +// react-hook-form +import { Control, Controller } from "react-hook-form"; +// headless ui +import { Listbox, Transition } from "@headlessui/react"; +// icons +import { ChevronDownIcon, ChartBarIcon } from "@heroicons/react/24/outline"; +// types +import { IIssue } from "types"; +// constants +import { classNames } from "constants/common"; +import { PRIORITIES } from "constants/"; + +type Props = { + control: Control; + submitChanges: (formData: Partial) => void; +}; + +const SelectPriority: React.FC = ({ control, submitChanges }) => { + return ( +
+
+ +

Priority

+
+
+ ( + { + submitChanges({ priority: value }); + }} + className="flex-shrink-0" + > + {({ open }) => ( +
+ + + {value} + + + + + + +
+ {PRIORITIES.map((option) => ( + + `${ + active || selected ? "text-white bg-theme" : "text-gray-900" + } flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate capitalize` + } + value={option} + > + {option} + + ))} +
+
+
+
+ )} +
+ )} + /> +
+
+ ); +}; + +export default SelectPriority; diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-state.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-state.tsx new file mode 100644 index 000000000..cc129c536 --- /dev/null +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-state.tsx @@ -0,0 +1,116 @@ +// react-hook-form +import { Control, Controller } from "react-hook-form"; +// hooks +import useUser from "lib/hooks/useUser"; +// headless ui +import { Listbox, Transition } from "@headlessui/react"; +// types +import { IIssue } from "types"; +import { classNames } from "constants/common"; +import { Spinner } from "ui"; +import React from "react"; +import { ChevronDownIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; + +type Props = { + control: Control; + submitChanges: (formData: Partial) => void; +}; + +const SelectState: React.FC = ({ control, submitChanges }) => { + const { states } = useUser(); + + return ( +
+
+ +

State

+
+
+ ( + { + submitChanges({ state: value }); + }} + className="flex-shrink-0" + > + {({ open }) => ( +
+ + + {value ? ( + <> + option.id === value)?.color, + }} + > + {states?.find((option) => option.id === value)?.name} + + ) : ( + "None" + )} + + + + + + +
+ {states ? ( + states.length > 0 ? ( + states.map((option) => ( + + `${ + active || selected ? "text-white bg-theme" : "text-gray-900" + } flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate` + } + value={option.id} + > + {option.color && ( + + )} + {option.name} + + )) + ) : ( +
No states found
+ ) + ) : ( + + )} +
+
+
+
+ )} +
+ )} + /> +
+
+ ); +}; + +export default SelectState; diff --git a/apps/app/components/sidebar/workspace-options.tsx b/apps/app/components/sidebar/workspace-options.tsx index b7760a49e..749eba17d 100644 --- a/apps/app/components/sidebar/workspace-options.tsx +++ b/apps/app/components/sidebar/workspace-options.tsx @@ -82,9 +82,7 @@ const WorkspaceOptions: React.FC = ({ sidebarCollapse }) => { return (
-
+
= ({ sidebarCollapse }) => { alt="Workspace Logo" layout="fill" objectFit="cover" + className="rounded" /> ) : ( activeWorkspace?.name?.charAt(0) ?? "N" )}
{!sidebarCollapse && ( -

- {activeWorkspace?.name ?? "Loading..."} +

+ {activeWorkspace?.name + ? activeWorkspace.name.length > 17 + ? `${activeWorkspace.name.substring(0, 17)}...` + : activeWorkspace.name + : "Loading..."}

)}
@@ -131,74 +134,124 @@ const WorkspaceOptions: React.FC = ({ sidebarCollapse }) => { leaveTo="transform opacity-0 scale-95" > -
- {workspaces ? ( - <> - {workspaces.length > 0 ? ( - workspaces.map((workspace: any) => ( - - {({ active }) => ( - - )} - - )) - ) : ( -

No workspace found!

- )} - { - router.push("/create-workspace"); - }} - className="w-full" - > - {({ active }) => ( - - - Create Workspace - +
+
+ + {user?.email} + +
+
+ {workspaces ? ( + <> + {workspaces.length > 0 ? ( + workspaces.map((workspace: any) => ( + + {({ active }) => ( + + )} + + )) + ) : ( +

No workspace found!

)} + { + router.push("/create-workspace"); + }} + className="w-full text-xs flex items-center gap-2 px-2 py-1 text-left rounded hover:bg-gray-100" + > + + Create Workspace + + + ) : ( +
+ +
+ )} +
+
+ {userLinks.map((link, index) => ( + + + + {link.name} + + - - ) : ( -
- -
- )} + ))} + { + await authenticationService + .signOut({ + refresh_token: authenticationService.getRefreshToken(), + }) + .then((response) => { + console.log("user signed out", response); + }) + .catch((error) => { + console.log("Failed to sign out", error); + }) + .finally(() => { + mutateUser(); + router.push("/signin"); + }); + }} + > + Sign out + +
- {!sidebarCollapse && ( + {/* {!sidebarCollapse && (
@@ -261,7 +314,7 @@ const WorkspaceOptions: React.FC = ({ sidebarCollapse }) => {
- )} + )} */}
{workspaceLinks.map((link, index) => ( diff --git a/apps/app/layouts/app-layout.tsx b/apps/app/layouts/app-layout.tsx index fd0f0b134..9f1500a6d 100644 --- a/apps/app/layouts/app-layout.tsx +++ b/apps/app/layouts/app-layout.tsx @@ -18,7 +18,9 @@ const AppLayout: React.FC = ({ children, noPadding = false, bg = "primary", + noHeader = false, breadcrumbs, + left, right, }) => { const [isOpen, setIsOpen] = useState(false); @@ -37,7 +39,7 @@ const AppLayout: React.FC = ({
-
+ {noHeader ? null :
}
{ - const [selectedWorkspace, setSelectedWorkspace] = useState(null); + const { activeWorkspace, user, states } = useUser(); - const { user, workspaces, activeWorkspace } = useUser(); + console.log(states); const { data: myIssues, mutate: mutateMyIssues } = useSWR( user && activeWorkspace ? USER_ISSUE(activeWorkspace.slug) : null, user && activeWorkspace ? () => userService.userIssues(activeWorkspace.slug) : null ); + const { data: people } = useSWR( + activeWorkspace ? WORKSPACE_MEMBERS : null, + activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null + ); + + const [properties, setProperties] = useIssuesProperties( + activeWorkspace?.slug, + "21b5fab2-cb0c-4875-9496-619134bf1f32" + ); + const updateMyIssues = ( workspaceSlug: string, projectId: string, @@ -79,98 +105,515 @@ const MyIssues: NextPage = () => { } right={ - { - const e = new KeyboardEvent("keydown", { - key: "i", - ctrlKey: true, - }); - document.dispatchEvent(e); - }} - /> +
+ + {({ open }) => ( + <> + + View + + + + +
+
+

Properties

+
+ {Object.keys(properties).map((key) => ( + + ))} +
+
+
+
+
+ + )} +
+ { + const e = new KeyboardEvent("keydown", { + key: "i", + ctrlKey: true, + }); + document.dispatchEvent(e); + }} + /> +
} >
{myIssues ? ( <> {myIssues.length > 0 ? ( -
-
-
-
- - - - - - - - - - - - {myIssues.map((myIssue, index) => ( - - - - - - - - ))} - -
- NAME - - DESCRIPTION - - PROJECT - - PRIORITY - - STATUS -
- - {myIssue.name} - - - {/* {myIssue.description} */} - - {myIssue.project_detail?.name} -
- {`(${myIssue.project_detail?.identifier}-${myIssue.sequence_id})`} -
{myIssue.priority} - -
+
+ + {({ open }) => ( +
+
+ +
+ + + +

My Issues

+

{myIssues.length}

+
+
+
+ + +
+ {myIssues.map((issue: IIssue) => { + const assignees = [ + ...(issue?.assignees_list ?? []), + ...(issue?.assignees ?? []), + ]?.map((assignee) => { + const tempPerson = people?.find( + (p) => p.member.id === assignee + )?.member; + + return { + avatar: tempPerson?.avatar, + first_name: tempPerson?.first_name, + email: tempPerson?.email, + }; + }); + + return ( +
+ +
+ {properties.priority && ( + { + // partialUpdateIssue({ priority: data }, issue.id); + }} + className="group relative flex-shrink-0" + > + {({ open }) => ( + <> +
+ + {issue.priority ?? "None"} + + + + + {PRIORITIES?.map((priority) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer capitalize select-none px-3 py-2" + ) + } + value={priority} + > + {priority} + + ))} + + +
+
+
+ Priority +
+
+ {issue.priority ?? "None"} +
+
+ + )} +
+ )} + {properties.state && ( + { + // partialUpdateIssue({ state: data }, issue.id); + }} + className="group relative flex-shrink-0" + > + {({ open }) => ( + <> +
+ + + {addSpaceIfCamelCase(issue.state_detail.name)} + + + + + {states?.map((state) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer select-none px-3 py-2" + ) + } + value={state.id} + > + {addSpaceIfCamelCase(state.name)} + + ))} + + +
+
+
State
+
{issue.state_detail.name}
+
+ + )} +
+ )} + {properties.start_date && ( +
+ + {issue.start_date + ? renderShortNumericDateFormat(issue.start_date) + : "N/A"} +
+
Started at
+
+ {renderShortNumericDateFormat(issue.start_date ?? "")} +
+
+
+ )} + {properties.target_date && ( +
+ + {issue.target_date + ? renderShortNumericDateFormat(issue.target_date) + : "N/A"} +
+
+ Target date +
+
+ {renderShortNumericDateFormat(issue.target_date ?? "")} +
+
+ {issue.target_date && + (issue.target_date < new Date().toISOString() + ? `Target date has passed by ${findHowManyDaysLeft( + issue.target_date + )} days` + : findHowManyDaysLeft(issue.target_date) <= 3 + ? `Target date is in ${findHowManyDaysLeft( + issue.target_date + )} days` + : "Target date")} +
+
+
+ )} + {properties.assignee && ( + { + const newData = issue.assignees ?? []; + if (newData.includes(data)) { + newData.splice(newData.indexOf(data), 1); + } else { + newData.push(data); + } + // partialUpdateIssue({ assignees_list: newData }, issue.id); + }} + className="group relative flex-shrink-0" + > + {({ open }) => ( + <> +
+ +
+ {assignees.length > 0 ? ( + assignees.map((assignee, index: number) => ( +
+ {assignee.avatar && + assignee.avatar !== "" ? ( +
+ {assignee?.first_name} +
+ ) : ( +
+ {assignee.first_name?.charAt(0)} +
+ )} +
+ )) + ) : ( +
+ No user +
+ )} +
+
+ + + + {people?.map((person) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer select-none p-2" + ) + } + value={person.member.id} + > +
+ {person.member.avatar && + person.member.avatar !== "" ? ( +
+ avatar +
+ ) : ( +
+ {person.member.first_name && + person.member.first_name !== "" + ? person.member.first_name.charAt(0) + : person.member.email.charAt(0)} +
+ )} +

+ {person.member.first_name && + person.member.first_name !== "" + ? person.member.first_name + : person.member.email} +

+
+
+ ))} +
+
+
+
+
Assigned to
+
+ {issue.assignee_details?.length > 0 + ? issue.assignee_details + .map((assignee) => assignee.first_name) + .join(", ") + : "No one"} +
+
+ + )} +
+ )} + + + + + + + + + +
+ +
+
+
+
+
+
+ ); + })} +
+
+
-
-
+ )} +
) : (
diff --git a/apps/app/pages/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/projects/[projectId]/cycles/[cycleId].tsx index f1dc51c6b..1f339bec1 100644 --- a/apps/app/pages/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/projects/[projectId]/cycles/[cycleId].tsx @@ -1,6 +1,7 @@ // react -import React from "react"; +import React, { useState } from "react"; // next +import Link from "next/link"; import { useRouter } from "next/router"; // swr import useSWR, { mutate } from "swr"; @@ -9,8 +10,8 @@ import { DropResult } from "react-beautiful-dnd"; // layouots import AppLayout from "layouts/app-layout"; // components -import CyclesListView from "components/project/cycles/ListView"; -import CyclesBoardView from "components/project/cycles/BoardView"; +import CyclesListView from "components/project/cycles/list-view"; +import CyclesBoardView from "components/project/cycles/board-view"; // services import issuesServices from "lib/services/issues.service"; import cycleServices from "lib/services/cycles.service"; @@ -25,19 +26,21 @@ import { Menu, Popover, Transition } from "@headlessui/react"; import { BreadcrumbItem, Breadcrumbs, CustomMenu } from "ui"; // icons import { Squares2X2Icon } from "@heroicons/react/20/solid"; -import { - ArrowPathIcon, - ChevronDownIcon, - EllipsisHorizontalIcon, - ListBulletIcon, -} from "@heroicons/react/24/outline"; +import { ArrowPathIcon, ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline"; // types -import { CycleIssueResponse, IIssue, NestedKeyOf, Properties } from "types"; +import { + CycleIssueResponse, + IIssue, + NestedKeyOf, + Properties, + SelectIssue, + SelectSprintType, +} from "types"; // fetch-keys import { CYCLE_ISSUES, PROJECT_MEMBERS } from "constants/fetch-keys"; // constants import { classNames, replaceUnderscoreIfSnakeCase } from "constants/common"; -import Link from "next/link"; +import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal"; const groupByOptions: Array<{ name: string; key: NestedKeyOf | null }> = [ { name: "State", key: "state_detail.name" }, @@ -73,6 +76,10 @@ const filterIssueOptions: Array<{ type Props = {}; const SingleCycle: React.FC = () => { + const [isIssueModalOpen, setIsIssueModalOpen] = useState(false); + const [selectedCycle, setSelectedCycle] = useState(); + const [selectedIssues, setSelectedIssues] = useState(); + const { activeWorkspace, activeProject, cycles } = useUser(); const router = useRouter(); @@ -121,6 +128,21 @@ const SingleCycle: React.FC = () => { filterIssue, } = useIssuesFilter(cycleIssuesArray ?? []); + const openCreateIssueModal = ( + issue?: IIssue, + actionType: "create" | "edit" | "delete" = "create" + ) => { + const cycle = cycles?.find((cycle) => cycle.id === cycleId); + if (cycle) { + setSelectedCycle({ + ...cycle, + actionType: "create-issue", + }); + if (issue) setSelectedIssues({ ...issue, actionType }); + setIsIssueModalOpen(true); + } + }; + const addIssueToCycle = (cycleId: string, issueId: string) => { if (!activeWorkspace || !activeProject?.id) return; @@ -200,18 +222,32 @@ const SingleCycle: React.FC = () => { }; return ( - - - {/* c.id === cycleId)?.name ?? "Cycle"} `} /> */} + <> + + + + + } + left={ - + - Cycle + {cycles?.find((c) => c.id === cycleId)?.name} = () => { - - } - right={ -
-
- - -
- - {({ open }) => ( - <> - - View - + } + right={ +
+
+ + +
+ + {({ open }) => ( + <> + + View + - - -
-
-

Group by

- option.key === groupByProperty)?.name ?? - "Select" - } - > - {groupByOptions.map((option) => ( - setGroupByProperty(option.key)} - > - {option.name} - - ))} - -
-
-

Order by

- option.key === orderBy)?.name ?? - "Select" - } - > - {orderByOptions.map((option) => - groupByProperty === "priority" && option.key === "priority" ? null : ( + + +
+
+

Group by

+ option.key === groupByProperty) + ?.name ?? "Select" + } + > + {groupByOptions.map((option) => ( setOrderBy(option.key)} + onClick={() => setGroupByProperty(option.key)} > {option.name} - ) - )} - -
-
-

Issue type

- option.key === filterIssue)?.name ?? - "Select" - } - > - {filterIssueOptions.map((option) => ( - setFilterIssue(option.key)} - > - {option.name} - - ))} - -
-
-
-

Properties

-
- {Object.keys(properties).map((key) => ( - - ))} + ))} + +
+
+

Order by

+ option.key === orderBy)?.name ?? + "Select" + } + > + {orderByOptions.map((option) => + groupByProperty === "priority" && option.key === "priority" ? null : ( + setOrderBy(option.key)} + > + {option.name} + + ) + )} + +
+
+

Issue type

+ option.key === filterIssue) + ?.name ?? "Select" + } + > + {filterIssueOptions.map((option) => ( + setFilterIssue(option.key)} + > + {option.name} + + ))} + +
+
+
+

Properties

+
+ {Object.keys(properties).map((key) => ( + + ))} +
-
-
-
- - )} - -
- } - > - {issueView === "list" ? ( - { - return; - }} - openIssuesListModal={() => { - return; - }} - removeIssueFromCycle={removeIssueFromCycle} - /> - ) : ( -
- + + + )} + +
+ } + > + {issueView === "list" ? ( + { - return; - }} + properties={properties} + openCreateIssueModal={openCreateIssueModal} openIssuesListModal={() => { return; }} + removeIssueFromCycle={removeIssueFromCycle} /> -
- )} - + ) : ( +
+ { + return; + }} + /> +
+ )} + + ); }; diff --git a/apps/app/pages/projects/[projectId]/cycles/index.tsx b/apps/app/pages/projects/[projectId]/cycles/index.tsx index 9a2709500..16d3d2227 100644 --- a/apps/app/pages/projects/[projectId]/cycles/index.tsx +++ b/apps/app/pages/projects/[projectId]/cycles/index.tsx @@ -3,11 +3,9 @@ import React, { useEffect, useState } from "react"; // next import { useRouter } from "next/router"; import type { NextPage } from "next"; -import Link from "next/link"; // swr import useSWR from "swr"; // services -import issuesServices from "lib/services/issues.service"; import sprintService from "lib/services/cycles.service"; // hooks import useUser from "lib/hooks/useUser"; @@ -24,6 +22,7 @@ import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deleti import ConfirmSprintDeletion from "components/project/cycles/ConfirmCycleDeletion"; import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal"; import CreateUpdateSprintsModal from "components/project/cycles/CreateUpdateCyclesModal"; +import CycleStatsView from "components/project/cycles/stats-view"; // headless ui import { Popover, Transition } from "@headlessui/react"; // ui @@ -204,11 +203,7 @@ const ProjectSprints: NextPage = () => { {cycles ? ( cycles.length > 0 ? (
- {cycles.map((cycle) => ( - - {cycle.name} - - ))} +
) : (
diff --git a/apps/app/pages/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/projects/[projectId]/issues/[issueId].tsx index e4eb8d2a2..3940e3a1e 100644 --- a/apps/app/pages/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/projects/[projectId]/issues/[issueId].tsx @@ -1,4 +1,5 @@ // next +import Link from "next/link"; import type { NextPage } from "next"; import { useRouter } from "next/router"; import dynamic from "next/dynamic"; @@ -27,10 +28,12 @@ import AppLayout from "layouts/app-layout"; // components import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal"; import IssueCommentSection from "components/project/issues/issue-detail/comment/IssueCommentSection"; +import AddAsSubIssue from "components/command-palette/addAsSubIssue"; +import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion"; // common import { debounce } from "constants/common"; // components -import IssueDetailSidebar from "components/project/issues/issue-detail/IssueDetailSidebar"; +import IssueDetailSidebar from "components/project/issues/issue-detail/issue-detail-sidebar"; // activites import IssueActivitySection from "components/project/issues/issue-detail/activity"; // ui @@ -46,9 +49,6 @@ import { EllipsisHorizontalIcon, PlusIcon, } from "@heroicons/react/24/outline"; -import Link from "next/link"; -import AddAsSubIssue from "components/command-palette/addAsSubIssue"; -import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion"; const RichTextEditor = dynamic(() => import("components/lexical/editor"), { ssr: false, diff --git a/apps/app/pages/projects/[projectId]/settings.tsx b/apps/app/pages/projects/[projectId]/settings.tsx index 6e7800d0e..18d5ed567 100644 --- a/apps/app/pages/projects/[projectId]/settings.tsx +++ b/apps/app/pages/projects/[projectId]/settings.tsx @@ -13,6 +13,7 @@ import { Tab } from "@headlessui/react"; import withAuth from "lib/hoc/withAuthWrapper"; // layouts import SettingsLayout from "layouts/settings-layout"; +import AppLayout from "layouts/app-layout"; // service import projectServices from "lib/services/project.service"; // hooks @@ -155,14 +156,14 @@ const ProjectSettings: NextPage = () => { ]; return ( - } - links={sidebarLinks} + // links={sidebarLinks} > {projectDetails ? (
@@ -209,7 +210,7 @@ const ProjectSettings: NextPage = () => {
)} -
+ ); }; diff --git a/apps/app/pages/workspace/index.tsx b/apps/app/pages/workspace/index.tsx index 94921eaf2..3c671e5f7 100644 --- a/apps/app/pages/workspace/index.tsx +++ b/apps/app/pages/workspace/index.tsx @@ -18,9 +18,14 @@ import userService from "lib/services/user.service"; // ui import { Spinner } from "ui"; // icons -import { ArrowRightIcon } from "@heroicons/react/24/outline"; +import { ArrowRightIcon, CalendarDaysIcon } from "@heroicons/react/24/outline"; // types import type { IIssue } from "types"; +import { + addSpaceIfCamelCase, + findHowManyDaysLeft, + renderShortNumericDateFormat, +} from "constants/common"; const Workspace: NextPage = () => { const { user, activeWorkspace, projects } = useUser(); @@ -46,7 +51,7 @@ const Workspace: NextPage = () => { const hours = new Date().getHours(); return ( - +
{user ? (
@@ -78,49 +83,105 @@ const Workspace: NextPage = () => {
{myIssues ? ( myIssues.length > 0 ? ( - - - - - - - - - - {myIssues?.map((issue, index) => ( - - - - - - ))} - -
- ISSUE - - KEY - - STATUS -
- - {issue.name} - - - {issue.project_detail?.identifier}-{issue.sequence_id} - - - {issue.state_detail.name ?? "None"} - -
+
+
+
+
+

My Issues

+

{myIssues.length}

+
+
+
+ {myIssues.map((issue) => ( +
+ +
+
+ {issue.priority ?? "None"} +
+ +
+ + {addSpaceIfCamelCase(issue.state_detail.name)} +
+
+ + {issue.target_date + ? renderShortNumericDateFormat(issue.target_date) + : "N/A"} +
+
Target date
+
{renderShortNumericDateFormat(issue.target_date ?? "")}
+
+ {issue.target_date && + (issue.target_date < new Date().toISOString() + ? `Target date has passed by ${findHowManyDaysLeft( + issue.target_date + )} days` + : findHowManyDaysLeft(issue.target_date) <= 3 + ? `Target date is in ${findHowManyDaysLeft( + issue.target_date + )} days` + : "Target date")} +
+
+
+
+
+ ))} +
+
+
) : (

No Issues Found