diff --git a/web/components/estimates/estimate-select.tsx b/web/components/estimates/estimate-select.tsx new file mode 100644 index 000000000..5ac283b83 --- /dev/null +++ b/web/components/estimates/estimate-select.tsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; +import { usePopper } from "react-popper"; +import { Combobox } from "@headlessui/react"; +import { Check, ChevronDown, Search, Triangle } from "lucide-react"; +// types +import { Tooltip } from "components/ui"; +import { Placement } from "@popperjs/core"; +// constants +import { IEstimatePoint } from "types"; + +type Props = { + value: number | null; + onChange: (value: number | null) => void; + estimatePoints: IEstimatePoint[] | undefined; + className?: string; + buttonClassName?: string; + optionsClassName?: string; + placement?: Placement; + hideDropdownArrow?: boolean; + disabled?: boolean; +}; + +export const EstimateSelect: React.FC = (props) => { + const { + value, + onChange, + estimatePoints, + className = "", + buttonClassName = "", + optionsClassName = "", + placement, + hideDropdownArrow = false, + disabled = false, + } = props; + + const [query, setQuery] = useState(""); + + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + const options: { value: number | null; query: string; content: any }[] | undefined = estimatePoints?.map( + (estimate) => ({ + value: estimate.key, + query: estimate.value, + content: ( +
+ + {estimate.value} +
+ ), + }) + ); + options?.unshift({ + value: null, + query: "none", + content: ( +
+ + None +
+ ), + }); + + const filteredOptions = + query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); + + const selectedEstimate = estimatePoints?.find((e) => e.key === value); + const label = ( + +
+ + {selectedEstimate?.value ?? "None"} +
+
+ ); + + return ( + onChange(val as number | null)} + disabled={disabled} + > + + + + +
+
+ + setQuery(e.target.value)} + placeholder="Search" + displayValue={(assigned: any) => assigned?.name} + /> +
+
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ + active ? "bg-custom-background-80" : "" + } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( + +

No matching results

+
+ ) + ) : ( +

Loading...

+ )} +
+
+
+
+ ); +}; diff --git a/web/components/estimates/index.tsx b/web/components/estimates/index.ts similarity index 77% rename from web/components/estimates/index.tsx rename to web/components/estimates/index.ts index f20c74780..b88ceaf03 100644 --- a/web/components/estimates/index.tsx +++ b/web/components/estimates/index.ts @@ -1,3 +1,4 @@ export * from "./create-update-estimate-modal"; -export * from "./single-estimate"; export * from "./delete-estimate-modal"; +export * from "./estimate-select"; +export * from "./single-estimate"; diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 34f0a550e..505da1da3 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -2,7 +2,7 @@ import { Draggable } from "@hello-pangea/dnd"; // components import { KanBanProperties } from "./properties"; // types -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite } from "types"; interface IssueBlockProps { sub_group_id: string; @@ -18,10 +18,27 @@ interface IssueBlockProps { ) => void; quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode; displayProperties: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + estimates: IEstimatePoint[] | null; } export const KanbanIssueBlock: React.FC = (props) => { - const { sub_group_id, columnId, index, issue, isDragDisabled, handleIssues, quickActions, displayProperties } = props; + const { + sub_group_id, + columnId, + index, + issue, + isDragDisabled, + handleIssues, + quickActions, + displayProperties, + states, + labels, + members, + estimates, + } = props; const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => { if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, "update"); @@ -54,7 +71,7 @@ export const KanbanIssueBlock: React.FC = (props) => { {issue.project_detail.identifier}-{issue.sequence_id} )} -
{issue.name}
+
{issue.name}
= (props) => { issue={issue} handleIssues={updateIssue} display_properties={displayProperties} + states={states} + labels={labels} + members={members} + estimates={estimates} />
diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx index aeee5c2fc..0e921638a 100644 --- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx +++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx @@ -1,6 +1,6 @@ // components import { KanbanIssueBlock } from "components/issues"; -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite } from "types"; interface IssueBlocksListProps { sub_group_id: string; @@ -15,10 +15,26 @@ interface IssueBlocksListProps { ) => void; quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode; display_properties: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + estimates: IEstimatePoint[] | null; } export const KanbanIssueBlocksList: React.FC = (props) => { - const { sub_group_id, columnId, issues, isDragDisabled, handleIssues, quickActions, display_properties } = props; + const { + sub_group_id, + columnId, + issues, + isDragDisabled, + handleIssues, + quickActions, + display_properties, + states, + labels, + members, + estimates, + } = props; return ( <> @@ -35,6 +51,10 @@ export const KanbanIssueBlocksList: React.FC = (props) => columnId={columnId} sub_group_id={sub_group_id} isDragDisabled={isDragDisabled} + states={states} + labels={labels} + members={members} + estimates={estimates} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 691e50fb9..dd781a289 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -7,7 +7,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { KanBanGroupByHeaderRoot } from "./headers/group-by-root"; import { KanbanIssueBlocksList } from "components/issues"; // types -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IProject, IState, IUserLite } from "types"; // constants import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES, getValueFromObject } from "constants/issue"; @@ -29,6 +29,11 @@ export interface IGroupByKanBan { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + priorities: any; + estimates: IEstimatePoint[] | null; } const GroupByKanBan: React.FC = observer((props) => { @@ -45,6 +50,11 @@ const GroupByKanBan: React.FC = observer((props) => { display_properties, kanBanToggle, handleKanBanToggle, + states, + labels, + members, + priorities, + estimates, } = props; const verticalAlignPosition = (_list: any) => @@ -93,6 +103,10 @@ const GroupByKanBan: React.FC = observer((props) => { handleIssues={handleIssues} quickActions={quickActions} display_properties={display_properties} + states={states} + labels={labels} + members={members} + estimates={estimates} /> ) : ( isDragDisabled && ( @@ -128,14 +142,13 @@ export interface IKanBan { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; - - states: any; + states: IState[] | null; stateGroups: any; priorities: any; - labels: any; - members: any; - projects: any; - estimates: any; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + projects: IProject[] | null; + estimates: IEstimatePoint[] | null; } export const KanBan: React.FC = observer((props) => { @@ -176,6 +189,11 @@ export const KanBan: React.FC = observer((props) => { display_properties={display_properties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + priorities={priorities} + estimates={estimates} /> )} @@ -193,6 +211,11 @@ export const KanBan: React.FC = observer((props) => { display_properties={display_properties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + priorities={priorities} + estimates={estimates} /> )} @@ -210,6 +233,11 @@ export const KanBan: React.FC = observer((props) => { display_properties={display_properties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + priorities={priorities} + estimates={estimates} /> )} @@ -227,6 +255,11 @@ export const KanBan: React.FC = observer((props) => { display_properties={display_properties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + priorities={priorities} + estimates={estimates} /> )} @@ -244,6 +277,11 @@ export const KanBan: React.FC = observer((props) => { display_properties={display_properties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + priorities={priorities} + estimates={estimates} /> )} @@ -261,6 +299,11 @@ export const KanBan: React.FC = observer((props) => { display_properties={display_properties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + priorities={priorities} + estimates={estimates} /> )} diff --git a/web/components/issues/issue-layouts/kanban/index.ts b/web/components/issues/issue-layouts/kanban/index.ts index 3adfe5c26..f84f7c8af 100644 --- a/web/components/issues/issue-layouts/kanban/index.ts +++ b/web/components/issues/issue-layouts/kanban/index.ts @@ -1,5 +1,3 @@ export * from "./block"; +export * from "./roots"; export * from "./blocks-list"; -export * from "./cycle-root"; -export * from "./module-root"; -export * from "./root"; diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx index 979ead23b..e321094d4 100644 --- a/web/components/issues/issue-layouts/kanban/properties.tsx +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -10,190 +10,196 @@ import { IssuePropertyAssignee } from "../properties/assignee"; import { IssuePropertyEstimates } from "../properties/estimates"; import { IssuePropertyDate } from "../properties/date"; import { Tooltip } from "@plane/ui"; +import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite, TIssuePriorities } from "types"; export interface IKanBanProperties { sub_group_id: string; columnId: string; - issue: any; - handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void; + issue: IIssue; + handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void; display_properties: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + estimates: IEstimatePoint[] | null; } -export const KanBanProperties: React.FC = observer( - ({ sub_group_id, columnId: group_id, issue, handleIssues, display_properties }) => { - const handleState = (id: string) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, state: id } - ); - }; +export const KanBanProperties: React.FC = observer((props) => { + const { + sub_group_id, + columnId: group_id, + issue, + handleIssues, + display_properties, + states, + labels, + members, + estimates, + } = props; - const handlePriority = (id: string) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, priority: id } - ); - }; - - const handleLabel = (ids: string[]) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, labels: ids } - ); - }; - - const handleAssignee = (ids: string[]) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, assignees: ids } - ); - }; - - const handleStartDate = (date: string) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, start_date: date } - ); - }; - - const handleTargetDate = (date: string) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, target_date: date } - ); - }; - - const handleEstimate = (id: string) => { - if (handleIssues) - handleIssues( - !sub_group_id && sub_group_id === "null" ? null : sub_group_id, - !group_id && group_id === "null" ? null : group_id, - { ...issue, estimate_point: id } - ); - }; - - return ( -
- {/* basic properties */} - {/* state */} - {display_properties && display_properties?.state && ( - handleState(id)} - disabled={false} - /> - )} - - {/* priority */} - {display_properties && display_properties?.priority && ( - handlePriority(id)} - disabled={false} - /> - )} - - {/* label */} - {display_properties && display_properties?.labels && ( - handleLabel(ids)} - disabled={false} - /> - )} - - {/* assignee */} - {display_properties && display_properties?.assignee && ( - handleAssignee(ids)} - disabled={false} - /> - )} - - {/* start date */} - {display_properties && display_properties?.start_date && ( - handleStartDate(date)} - disabled={false} - /> - )} - - {/* target/due date */} - {display_properties && display_properties?.due_date && ( - handleTargetDate(date)} - disabled={false} - /> - )} - - {/* estimates */} - {display_properties && display_properties?.estimate && ( - handleEstimate(id)} - disabled={false} - workspaceSlug={issue?.workspace_detail?.slug || null} - projectId={issue?.project_detail?.id || null} - /> - )} - - {/* extra render properties */} - {/* sub-issues */} - {display_properties && display_properties?.sub_issue_count && ( - -
-
- -
-
{issue.sub_issues_count}
-
-
- )} - - {/* attachments */} - {display_properties && display_properties?.attachment_count && ( - -
-
- -
-
{issue.attachment_count}
-
-
- )} - - {/* link */} - {display_properties && display_properties?.link && ( - -
-
- -
-
{issue.link_count}
-
-
- )} -
+ const handleState = (state: IState) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, state: state.id } ); - } -); + }; + + const handlePriority = (value: TIssuePriorities) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, priority: value } + ); + }; + + const handleLabel = (ids: string[]) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, labels_list: ids } + ); + }; + + const handleAssignee = (ids: string[]) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, assignees_list: ids } + ); + }; + + const handleStartDate = (date: string) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, start_date: date } + ); + }; + + const handleTargetDate = (date: string) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, target_date: date } + ); + }; + + const handleEstimate = (value: number | null) => { + handleIssues( + !sub_group_id && sub_group_id === "null" ? null : sub_group_id, + !group_id && group_id === "null" ? null : group_id, + { ...issue, estimate_point: value } + ); + }; + + return ( +
+ {/* basic properties */} + {/* state */} + {display_properties && display_properties?.state && ( + + )} + + {/* priority */} + {display_properties && display_properties?.priority && ( + + )} + + {/* label */} + {display_properties && display_properties?.labels && ( + + )} + + {/* assignee */} + {display_properties && display_properties?.assignee && ( + + )} + + {/* start date */} + {display_properties && display_properties?.start_date && ( + handleStartDate(date)} + disabled={false} + placeHolder="Start date" + /> + )} + + {/* target/due date */} + {display_properties && display_properties?.due_date && ( + handleTargetDate(date)} + disabled={false} + placeHolder="Target date" + /> + )} + + {/* estimates */} + {display_properties && display_properties?.estimate && ( + + )} + + {/* extra render properties */} + {/* sub-issues */} + {display_properties && display_properties?.sub_issue_count && ( + +
+ +
{issue.sub_issues_count}
+
+
+ )} + + {/* attachments */} + {display_properties && display_properties?.attachment_count && ( + +
+ +
{issue.attachment_count}
+
+
+ )} + + {/* link */} + {display_properties && display_properties?.link && ( + +
+ +
{issue.link_count}
+
+
+ )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/kanban/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx similarity index 83% rename from web/components/issues/issue-layouts/kanban/cycle-root.tsx rename to web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx index f4d09aada..188e27a68 100644 --- a/web/components/issues/issue-layouts/kanban/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx @@ -5,9 +5,11 @@ import { DragDropContext } from "@hello-pangea/dnd"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { KanBanSwimLanes } from "./swimlanes"; -import { KanBan } from "./default"; +import { KanBanSwimLanes } from "../swimlanes"; +import { KanBan } from "../default"; import { CycleIssueQuickActions } from "components/issues"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types import { IIssue } from "types"; // constants @@ -25,7 +27,7 @@ export const CycleKanBanLayout: React.FC = observer(() => { } = useMobxStore(); const router = useRouter(); - const { workspaceSlug, cycleId } = router.query; + const { workspaceSlug, projectId, cycleId } = router.query; const issues = cycleIssueStore?.getIssues; @@ -60,12 +62,12 @@ export const CycleKanBanLayout: React.FC = observer(() => { if (!workspaceSlug || !cycleId) return; if (action === "update") { - cycleIssueStore.updateIssueStructure(group_by, null, issue); + cycleIssueStore.updateIssueStructure(group_by, sub_group_by, issue); issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); } - if (action === "delete") cycleIssueStore.deleteIssue(group_by, null, issue); + if (action === "delete") cycleIssueStore.deleteIssue(group_by, sub_group_by, issue); if (action === "remove" && issue.bridge_id) { - cycleIssueStore.deleteIssue(group_by, null, issue); + cycleIssueStore.deleteIssue(group_by, sub_group_by, issue); cycleIssueStore.removeIssueFromCycle( workspaceSlug.toString(), issue.project, @@ -81,13 +83,18 @@ export const CycleKanBanLayout: React.FC = observer(() => { cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value); }; + const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null; + 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; + const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; + const estimates = + projectDetails?.estimate !== null + ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null + : null; return (
@@ -113,9 +120,9 @@ export const CycleKanBanLayout: React.FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} /> ) : ( { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} /> )} diff --git a/web/components/issues/issue-layouts/kanban/roots/index.ts b/web/components/issues/issue-layouts/kanban/roots/index.ts new file mode 100644 index 000000000..139c09a7a --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/roots/index.ts @@ -0,0 +1,5 @@ +export * from "./cycle-root"; +export * from "./module-root"; +export * from "./profile-issues-root"; +export * from "./project-root"; +export * from "./project-view-root"; diff --git a/web/components/issues/issue-layouts/kanban/module-root.tsx b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx similarity index 85% rename from web/components/issues/issue-layouts/kanban/module-root.tsx rename to web/components/issues/issue-layouts/kanban/roots/module-root.tsx index 594f15757..754693d11 100644 --- a/web/components/issues/issue-layouts/kanban/module-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx @@ -5,9 +5,11 @@ import { DragDropContext } from "@hello-pangea/dnd"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { KanBanSwimLanes } from "./swimlanes"; -import { KanBan } from "./default"; +import { KanBanSwimLanes } from "../swimlanes"; +import { KanBan } from "../default"; import { ModuleIssueQuickActions } from "components/issues"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types import { IIssue } from "types"; // constants @@ -25,7 +27,7 @@ export const ModuleKanBanLayout: React.FC = observer(() => { } = useMobxStore(); const router = useRouter(); - const { workspaceSlug, moduleId } = router.query; + const { workspaceSlug, projectId, moduleId } = router.query; const issues = moduleIssueStore?.getIssues; @@ -81,13 +83,18 @@ export const ModuleKanBanLayout: React.FC = observer(() => { moduleIssueKanBanViewStore.handleKanBanToggle(toggle, value); }; + const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null; + 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; + const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; + const estimates = + projectDetails?.estimate !== null + ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null + : null; return (
@@ -113,9 +120,9 @@ export const ModuleKanBanLayout: React.FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} /> ) : ( { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} /> )} diff --git a/web/components/issues/issue-layouts/kanban/profile-issues-root.tsx b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx similarity index 94% rename from web/components/issues/issue-layouts/kanban/profile-issues-root.tsx rename to web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx index badcd04aa..d2346120a 100644 --- a/web/components/issues/issue-layouts/kanban/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx @@ -5,8 +5,8 @@ import { DragDropContext } from "@hello-pangea/dnd"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { KanBanSwimLanes } from "./swimlanes"; -import { KanBan } from "./default"; +import { KanBanSwimLanes } from "../swimlanes"; +import { KanBan } from "../default"; import { ProjectIssueQuickActions } from "components/issues"; // constants import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; @@ -79,7 +79,6 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => { const members = projectStore?.projectMembers || null; const stateGroups = ISSUE_STATE_GROUPS || null; const projects = projectStore?.workspaceProjects || null; - const estimates = null; return (
@@ -104,9 +103,9 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={null} /> ) : ( { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={null} /> )} diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx similarity index 82% rename from web/components/issues/issue-layouts/kanban/root.tsx rename to web/components/issues/issue-layouts/kanban/roots/project-root.tsx index 741e878a0..fb375b382 100644 --- a/web/components/issues/issue-layouts/kanban/root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx @@ -1,13 +1,15 @@ -import { FC, useCallback } from "react"; +import { useCallback } from "react"; import { useRouter } from "next/router"; import { DragDropContext } from "@hello-pangea/dnd"; import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { KanBanSwimLanes } from "./swimlanes"; -import { KanBan } from "./default"; +import { KanBanSwimLanes } from "../swimlanes"; +import { KanBan } from "../default"; import { ProjectIssueQuickActions } from "components/issues"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types import { IIssue } from "types"; // constants @@ -15,9 +17,9 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface IKanBanLayout {} -export const KanBanLayout: FC = observer(() => { +export const KanBanLayout: React.FC = observer(() => { const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; const { project: projectStore, @@ -72,13 +74,18 @@ export const KanBanLayout: FC = observer(() => { issueKanBanViewStore.handleKanBanToggle(toggle, value); }; + const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null; + 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; + const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; + const estimates = + projectDetails?.estimate !== null + ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null + : null; return (
@@ -103,9 +110,9 @@ export const KanBanLayout: FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} /> ) : ( { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} /> )} diff --git a/web/components/issues/issue-layouts/kanban/view-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx similarity index 95% rename from web/components/issues/issue-layouts/kanban/view-root.tsx rename to web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx index 78117da80..583835ba3 100644 --- a/web/components/issues/issue-layouts/kanban/view-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx @@ -4,8 +4,8 @@ import { DragDropContext } from "@hello-pangea/dnd"; // mobx import { observer } from "mobx-react-lite"; // components -import { KanBanSwimLanes } from "./swimlanes"; -import { KanBan } from "./default"; +import { KanBanSwimLanes } from "../swimlanes"; +import { KanBan } from "../default"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -14,7 +14,7 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface IViewKanBanLayout {} -export const ViewKanBanLayout: React.FC = observer(() => { +export const ProjectViewKanBanLayout: React.FC = observer(() => { const { project: projectStore, issue: issueStore, diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 9090162c0..c46c6290b 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -7,7 +7,7 @@ import { KanBanGroupByHeaderRoot } from "./headers/group-by-root"; import { KanBanSubGroupByHeaderRoot } from "./headers/sub-group-by-root"; import { KanBan } from "./default"; // types -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IProject, IState, IUserLite } from "types"; // constants import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES, getValueFromObject } from "constants/issue"; @@ -19,6 +19,11 @@ interface ISubGroupSwimlaneHeader { listKey: string; kanBanToggle: any; handleKanBanToggle: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + projects: IProject[] | null; + estimates: IEstimatePoint[] | null; } const SubGroupSwimlaneHeader: React.FC = ({ issues, @@ -71,13 +76,13 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; - states: any; + states: IState[] | null; stateGroups: any; priorities: any; - labels: any; - members: any; - projects: any; - estimates: any; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + projects: IProject[] | null; + estimates: IEstimatePoint[] | null; } const SubGroupSwimlane: React.FC = observer((props) => { const { @@ -171,13 +176,13 @@ export interface IKanBanSwimLanes { display_properties: any; kanBanToggle: any; handleKanBanToggle: any; - states: any; + states: IState[] | null; stateGroups: any; priorities: any; - labels: any; - members: any; - projects: any; - estimates: any; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + projects: IProject[] | null; + estimates: IEstimatePoint[] | null; } export const KanBanSwimLanes: React.FC = observer((props) => { @@ -213,6 +218,11 @@ export const KanBanSwimLanes: React.FC = observer((props) => { listKey={`id`} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} @@ -225,6 +235,11 @@ export const KanBanSwimLanes: React.FC = observer((props) => { listKey={`key`} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} @@ -237,6 +252,11 @@ export const KanBanSwimLanes: React.FC = observer((props) => { listKey={`key`} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} @@ -249,6 +269,11 @@ export const KanBanSwimLanes: React.FC = observer((props) => { listKey={`id`} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} @@ -261,6 +286,11 @@ export const KanBanSwimLanes: React.FC = observer((props) => { listKey={`member.id`} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + labels={labels} + members={members} + projects={projects} + estimates={estimates} /> )} @@ -273,6 +303,11 @@ export const KanBanSwimLanes: React.FC = observer((props) => { listKey={`member.id`} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + states={states} + 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 47a1a38f0..9777f9ba5 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -4,7 +4,7 @@ import { IssuePeekOverview } from "components/issues/issue-peek-overview"; // ui import { Tooltip } from "@plane/ui"; // types -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite } from "types"; interface IssueBlockProps { columnId: string; @@ -12,18 +12,17 @@ interface IssueBlockProps { handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void; quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode; display_properties: any; - states: any; - labels: any; - members: any; - priorities: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + estimates: IEstimatePoint[] | null; } export const IssueBlock: React.FC = (props) => { - const { columnId, issue, handleIssues, quickActions, display_properties, states, labels, members, priorities } = - props; + const { columnId, issue, handleIssues, quickActions, display_properties, states, labels, members, estimates } = props; const updateIssue = (group_by: string | null, issueToUpdate: IIssue) => { - if (issueToUpdate && handleIssues) handleIssues(group_by, issueToUpdate, "update"); + handleIssues(group_by, issueToUpdate, "update"); }; return ( @@ -55,7 +54,7 @@ export const IssueBlock: React.FC = (props) => { states={states} labels={labels} members={members} - priorities={priorities} + estimates={estimates} /> {quickActions(!columnId && columnId === "null" ? null : columnId, issue)}
diff --git a/web/components/issues/issue-layouts/list/blocks-list.tsx b/web/components/issues/issue-layouts/list/blocks-list.tsx index 33618505f..3267e221c 100644 --- a/web/components/issues/issue-layouts/list/blocks-list.tsx +++ b/web/components/issues/issue-layouts/list/blocks-list.tsx @@ -2,7 +2,7 @@ import { FC } from "react"; // components import { IssueBlock } from "components/issues"; // types -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite } from "types"; interface Props { columnId: string; @@ -10,14 +10,14 @@ interface Props { handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void; quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode; display_properties: any; - states: any; - labels: any; - members: any; - priorities: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + estimates: IEstimatePoint[] | null; } export const IssueBlocksList: FC = (props) => { - const { columnId, issues, handleIssues, quickActions, display_properties, states, labels, members, priorities } = + const { columnId, issues, handleIssues, quickActions, display_properties, states, labels, members, estimates } = props; return ( @@ -35,7 +35,7 @@ export const IssueBlocksList: FC = (props) => { states={states} labels={labels} members={members} - priorities={priorities} + estimates={estimates} /> ))} diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index 008e625ae..e1c6caa16 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -2,11 +2,11 @@ import React from "react"; import { observer } from "mobx-react-lite"; // components import { ListGroupByHeaderRoot } from "./headers/group-by-root"; -import { IssueBlock } from "./block"; +import { IssueBlocksList } from "components/issues"; +// types +import { IEstimatePoint, IIssue, IIssueLabels, IProject, IState, IUserLite } from "types"; // constants import { getValueFromObject } from "constants/issue"; -import { IIssue } from "types"; -import { IssueBlocksList } from "./blocks-list"; export interface IGroupByList { issues: any; @@ -17,13 +17,13 @@ export interface IGroupByList { quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode; display_properties: any; is_list?: boolean; - states: any; - labels: any; - members: any; - projects: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + projects: IProject[] | null; stateGroups: any; priorities: any; - estimates: any; + estimates: IEstimatePoint[] | null; } const GroupByList: React.FC = observer((props) => { @@ -72,7 +72,7 @@ const GroupByList: React.FC = observer((props) => { states={states} labels={labels} members={members} - priorities={priorities} + estimates={estimates} /> )}
@@ -90,13 +90,13 @@ export interface IList { handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void; quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode; display_properties: any; - states: any; - labels: any; - members: any; - projects: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + projects: IProject[] | null; stateGroups: any; priorities: any; - estimates: any; + estimates: IEstimatePoint[] | null; } export const List: React.FC = observer((props) => { diff --git a/web/components/issues/issue-layouts/list/index.ts b/web/components/issues/issue-layouts/list/index.ts index 3adfe5c26..de9129b8f 100644 --- a/web/components/issues/issue-layouts/list/index.ts +++ b/web/components/issues/issue-layouts/list/index.ts @@ -1,5 +1,3 @@ +export * from "./roots"; export * from "./block"; export * from "./blocks-list"; -export * from "./cycle-root"; -export * from "./module-root"; -export * from "./root"; diff --git a/web/components/issues/issue-layouts/list/properties.tsx b/web/components/issues/issue-layouts/list/properties.tsx index 86af82656..9c70f9fdd 100644 --- a/web/components/issues/issue-layouts/list/properties.tsx +++ b/web/components/issues/issue-layouts/list/properties.tsx @@ -11,49 +11,48 @@ import { IssuePropertyDate } from "../properties/date"; // ui import { Tooltip } from "@plane/ui"; // types -import { IIssue } from "types"; +import { IEstimatePoint, IIssue, IIssueLabels, IState, IUserLite, TIssuePriorities } from "types"; export interface IKanBanProperties { columnId: string; - issue: any; - handleIssues?: (group_by: string | null, issue: IIssue) => void; + issue: IIssue; + handleIssues: (group_by: string | null, issue: IIssue) => void; display_properties: any; - states: any; - labels: any; - members: any; - priorities: any; + states: IState[] | null; + labels: IIssueLabels[] | null; + members: IUserLite[] | null; + estimates: IEstimatePoint[] | null; } export const KanBanProperties: FC = observer((props) => { - const { columnId: group_id, issue, handleIssues, display_properties, states, labels, members, priorities } = props; + const { columnId: group_id, issue, handleIssues, display_properties, states, labels, members, estimates } = props; - const handleState = (id: string) => { - if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, state: id }); + const handleState = (state: IState) => { + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, state: state.id }); }; - const handlePriority = (id: string) => { - if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, priority: id }); + const handlePriority = (value: TIssuePriorities) => { + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, priority: value }); }; const handleLabel = (ids: string[]) => { - if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, labels: ids }); + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, labels_list: ids }); }; const handleAssignee = (ids: string[]) => { - if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, assignees: ids }); + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, assignees_list: ids }); }; const handleStartDate = (date: string) => { - if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, start_date: date }); + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, start_date: date }); }; const handleTargetDate = (date: string) => { - if (handleIssues) handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, target_date: date }); + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, target_date: date }); }; - const handleEstimate = (id: string) => { - if (handleIssues) - handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, estimate_point: id }); + const handleEstimate = (value: number | null) => { + handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, estimate_point: value }); }; return ( @@ -62,22 +61,21 @@ export const KanBanProperties: FC = observer((props) => { {/* state */} {display_properties && display_properties?.state && states && ( handleState(id)} + value={issue?.state_detail || null} + hideDropdownArrow={true} + onChange={handleState} disabled={false} - list={states} + states={states} /> )} {/* priority */} - {display_properties && display_properties?.priority && priorities && ( + {display_properties && display_properties?.priority && ( handlePriority(id)} + onChange={handlePriority} disabled={false} - list={priorities} + hideDropdownArrow={true} /> )} @@ -85,10 +83,10 @@ export const KanBanProperties: FC = observer((props) => { {display_properties && display_properties?.labels && labels && ( handleLabel(ids)} + onChange={handleLabel} + labels={labels} disabled={false} - list={labels} + hideDropdownArrow={true} /> )} @@ -96,10 +94,10 @@ export const KanBanProperties: FC = observer((props) => { {display_properties && display_properties?.assignee && members && ( handleAssignee(ids)} + hideDropdownArrow={true} + onChange={handleAssignee} disabled={false} - list={members} + members={members} /> )} @@ -109,7 +107,7 @@ export const KanBanProperties: FC = observer((props) => { value={issue?.start_date || null} onChange={(date: string) => handleStartDate(date)} disabled={false} - placeHolder={`Start date`} + placeHolder="Start date" /> )} @@ -119,31 +117,28 @@ export const KanBanProperties: FC = observer((props) => { value={issue?.target_date || null} onChange={(date: string) => handleTargetDate(date)} disabled={false} - placeHolder={`Target date`} + placeHolder="Target date" /> )} {/* estimates */} {display_properties && display_properties?.estimate && ( handleEstimate(id)} + value={issue?.estimate_point || null} + estimatePoints={estimates} + hideDropdownArrow={true} + onChange={handleEstimate} disabled={false} - workspaceSlug={issue?.workspace_detail?.slug || null} - projectId={issue?.project_detail?.id || null} /> )} {/* extra render properties */} {/* sub-issues */} {display_properties && display_properties?.sub_issue_count && ( - -
-
- -
-
{issue.sub_issues_count}
+ +
+ +
{issue.sub_issues_count}
)} @@ -151,11 +146,9 @@ export const KanBanProperties: FC = observer((props) => { {/* attachments */} {display_properties && display_properties?.attachment_count && ( -
-
- -
-
{issue.attachment_count}
+
+ +
{issue.attachment_count}
)} @@ -163,11 +156,9 @@ export const KanBanProperties: FC = observer((props) => { {/* link */} {display_properties && display_properties?.link && ( -
-
- -
-
{issue.link_count}
+
+ +
{issue.link_count}
)} diff --git a/web/components/issues/issue-layouts/list/cycle-root.tsx b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx similarity index 80% rename from web/components/issues/issue-layouts/list/cycle-root.tsx rename to web/components/issues/issue-layouts/list/roots/cycle-root.tsx index 511336531..771186c84 100644 --- a/web/components/issues/issue-layouts/list/cycle-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx @@ -4,8 +4,10 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { List } from "./default"; +import { List } from "../default"; import { CycleIssueQuickActions } from "components/issues"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types import { IIssue } from "types"; // constants @@ -15,7 +17,7 @@ export interface ICycleListLayout {} export const CycleListLayout: React.FC = observer(() => { const router = useRouter(); - const { workspaceSlug, cycleId } = router.query; + const { workspaceSlug, projectId, cycleId } = router.query; const { project: projectStore, @@ -52,13 +54,18 @@ export const CycleListLayout: React.FC = observer(() => { [cycleIssueStore, issueDetailStore, cycleId, workspaceSlug] ); + const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null; + 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; + const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; + const estimates = + projectDetails?.estimate !== null + ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null + : null; return (
@@ -79,9 +86,9 @@ export const CycleListLayout: React.FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} />
); diff --git a/web/components/issues/issue-layouts/list/roots/index.ts b/web/components/issues/issue-layouts/list/roots/index.ts new file mode 100644 index 000000000..139c09a7a --- /dev/null +++ b/web/components/issues/issue-layouts/list/roots/index.ts @@ -0,0 +1,5 @@ +export * from "./cycle-root"; +export * from "./module-root"; +export * from "./profile-issues-root"; +export * from "./project-root"; +export * from "./project-view-root"; diff --git a/web/components/issues/issue-layouts/list/module-root.tsx b/web/components/issues/issue-layouts/list/roots/module-root.tsx similarity index 80% rename from web/components/issues/issue-layouts/list/module-root.tsx rename to web/components/issues/issue-layouts/list/roots/module-root.tsx index 485e4e908..daa12e64a 100644 --- a/web/components/issues/issue-layouts/list/module-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/module-root.tsx @@ -4,8 +4,10 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { List } from "./default"; +import { List } from "../default"; import { ModuleIssueQuickActions } from "components/issues"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types import { IIssue } from "types"; // constants @@ -15,7 +17,7 @@ export interface IModuleListLayout {} export const ModuleListLayout: React.FC = observer(() => { const router = useRouter(); - const { workspaceSlug, moduleId } = router.query; + const { workspaceSlug, projectId, moduleId } = router.query; const { project: projectStore, @@ -52,13 +54,18 @@ export const ModuleListLayout: React.FC = observer(() => { [moduleIssueStore, issueDetailStore, moduleId, workspaceSlug] ); + const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null; + 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; + const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; + const estimates = + projectDetails?.estimate !== null + ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null + : null; return (
@@ -79,9 +86,9 @@ export const ModuleListLayout: React.FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} />
); diff --git a/web/components/issues/issue-layouts/list/profile-issues-root.tsx b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx similarity index 95% rename from web/components/issues/issue-layouts/list/profile-issues-root.tsx rename to web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx index b1fb86c6a..9e4937ffd 100644 --- a/web/components/issues/issue-layouts/list/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { List } from "./default"; +import { List } from "../default"; import { ProjectIssueQuickActions } from "components/issues"; // types import { IIssue } from "types"; @@ -50,7 +50,6 @@ export const ProfileIssuesListLayout: FC = observer(() => { const members = projectStore?.projectMembers || null; const stateGroups = ISSUE_STATE_GROUPS || null; const projects = projectStore?.workspaceProjects || null; - const estimates = null; return (
@@ -70,9 +69,9 @@ export const ProfileIssuesListLayout: FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={null} />
); diff --git a/web/components/issues/issue-layouts/list/root.tsx b/web/components/issues/issue-layouts/list/roots/project-root.tsx similarity index 77% rename from web/components/issues/issue-layouts/list/root.tsx rename to web/components/issues/issue-layouts/list/roots/project-root.tsx index a5b1baa64..677e2f11c 100644 --- a/web/components/issues/issue-layouts/list/root.tsx +++ b/web/components/issues/issue-layouts/list/roots/project-root.tsx @@ -4,8 +4,10 @@ import { observer } from "mobx-react-lite"; // hooks import { useMobxStore } from "lib/mobx/store-provider"; // components -import { List } from "./default"; +import { List } from "../default"; import { ProjectIssueQuickActions } from "components/issues"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // types import { IIssue } from "types"; // constants @@ -13,7 +15,7 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export const ListLayout: FC = observer(() => { const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; const { project: projectStore, @@ -41,13 +43,18 @@ export const ListLayout: FC = observer(() => { [issueStore, issueDetailStore, workspaceSlug] ); + const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null; + 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; + const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; + const estimates = + projectDetails?.estimate !== null + ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null + : null; return (
@@ -67,9 +74,9 @@ export const ListLayout: FC = observer(() => { stateGroups={stateGroups} priorities={priorities} labels={labels} - members={members} + members={members?.map((m) => m.member) ?? null} projects={projects} - estimates={estimates} + estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} />
); diff --git a/web/components/issues/issue-layouts/list/view-root.tsx b/web/components/issues/issue-layouts/list/roots/project-view-root.tsx similarity index 94% rename from web/components/issues/issue-layouts/list/view-root.tsx rename to web/components/issues/issue-layouts/list/roots/project-view-root.tsx index aa7ab563a..85b8177b3 100644 --- a/web/components/issues/issue-layouts/list/view-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/project-view-root.tsx @@ -1,7 +1,7 @@ import React from "react"; import { observer } from "mobx-react-lite"; // components -import { List } from "./default"; +import { List } from "../default"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -10,7 +10,7 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; export interface IViewListLayout {} -export const ViewListLayout: React.FC = observer(() => { +export const ProjectViewListLayout: React.FC = observer(() => { const { project: projectStore, issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); const issues = issueStore?.getIssues; diff --git a/web/components/issues/issue-layouts/properties/assignee.tsx b/web/components/issues/issue-layouts/properties/assignee.tsx index a235ec5a2..6a209f3b6 100644 --- a/web/components/issues/issue-layouts/properties/assignee.tsx +++ b/web/components/issues/issue-layouts/properties/assignee.tsx @@ -1,252 +1,28 @@ -import { FC, useRef, useState } from "react"; -import { Combobox } from "@headlessui/react"; -import { ChevronDown, Search, X, Check } from "lucide-react"; import { observer } from "mobx-react-lite"; // components -import { Tooltip } from "@plane/ui"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; - -interface IFiltersOption { - id: string; - title: string; - avatar: string; -} +import { MembersSelect } from "components/project"; +// types +import { IUserLite } from "types"; export interface IIssuePropertyAssignee { - value?: any; - onChange?: (id: any, data: any) => void; + value: string[]; + onChange: (data: string[]) => void; + members: IUserLite[] | null; disabled?: boolean; - list?: any; - - className?: string; - buttonClassName?: string; - optionsClassName?: string; - dropdownArrow?: boolean; + hideDropdownArrow?: boolean; } -export const IssuePropertyAssignee: FC = observer((props) => { - const { value, onChange, disabled, list, className, buttonClassName, optionsClassName, dropdownArrow = true } = props; - - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const [search, setSearch] = useState(""); - - const options: IFiltersOption[] | [] = - (list && - list?.length > 0 && - list.map((_member: any) => ({ - id: _member?.member?.id, - title: _member?.member?.display_name, - avatar: _member?.member?.avatar && _member?.member?.avatar !== "" ? _member?.member?.avatar : null, - }))) || - []; - - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - - const selectedOption: IFiltersOption[] = - (value && value?.length > 0 && options.filter((_member: IFiltersOption) => value.includes(_member.id))) || []; - - const filteredOptions: IFiltersOption[] = - search === "" - ? options && options.length > 0 - ? options - : [] - : options && options.length > 0 - ? options.filter((_member: IFiltersOption) => - _member.title.toLowerCase().replace(/\s+/g, "").includes(search.toLowerCase().replace(/\s+/g, "")) - ) - : []; - - const assigneeRenderLength = 5; +export const IssuePropertyAssignee: React.FC = observer((props) => { + const { value, onChange, members, disabled = false, hideDropdownArrow = false } = props; return ( - _member.id) as string[]} - onChange={(data: string[]) => { - if (onChange && selectedOption) onChange(data, selectedOption); - }} + - {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); - - return ( - <> - - {selectedOption && selectedOption?.length > 0 ? ( - <> - {selectedOption?.length > 1 ? ( - _label.title) || []).join(", ")} - > -
- {selectedOption.slice(0, assigneeRenderLength).map((_assignee) => ( -
- {_assignee && _assignee.avatar ? ( - {_assignee.title} - ) : ( - _assignee.title[0] - )} -
- ))} - {selectedOption.length > assigneeRenderLength && ( -
- +{selectedOption?.length - assigneeRenderLength} -
- )} -
-
- ) : ( - _label.title) || []).join(", ")} - > -
-
- {selectedOption[0] && selectedOption[0].avatar ? ( - {selectedOption[0].title} - ) : ( -
- {selectedOption[0].title[0]} -
- )} -
-
{selectedOption[0].title}
-
-
- )} - - ) : ( - -
Select Assignees
-
- )} - - {dropdownArrow && !disabled && ( -
- -
- )} -
- -
- - {options && options.length > 0 ? ( - <> -
-
- -
- -
- setSearch(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
- - {search && search.length > 0 && ( -
setSearch("")} - className="flex-shrink-0 flex justify-center items-center w-[16px] h-[16px] rounded-sm cursor-pointer hover:bg-custom-background-80" - > - -
- )} -
- -
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || (value && value.length > 0 && value.includes(option?.id)) - ? "bg-custom-background-80" - : "" - } ${ - value && value.length > 0 && value.includes(option?.id) - ? "text-custom-text-100" - : "text-custom-text-200" - }` - } - > -
-
- {option && option.avatar ? ( - {option.title} - ) : ( -
- {option.title[0]} -
- )} -
-
{option.title}
- {value && value.length > 0 && value.includes(option?.id) && ( -
- -
- )} -
-
- )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
- - ) : ( -

No options available.

- )} -
-
- - ); - }} -
+ hideDropdownArrow={hideDropdownArrow} + multiple + /> ); }); diff --git a/web/components/issues/issue-layouts/properties/date.tsx b/web/components/issues/issue-layouts/properties/date.tsx index 48b6de507..dbcbc0eac 100644 --- a/web/components/issues/issue-layouts/properties/date.tsx +++ b/web/components/issues/issue-layouts/properties/date.tsx @@ -15,85 +15,81 @@ import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import { renderDateFormat } from "helpers/date-time.helper"; export interface IIssuePropertyDate { - value?: any; - onChange?: (date: any) => void; + value: any; + onChange: (date: any) => void; disabled?: boolean; placeHolder?: string; } -export const IssuePropertyDate: React.FC = observer( - ({ value, onChange, disabled, placeHolder }) => { - const dropdownBtn = React.useRef(null); - const dropdownOptions = React.useRef(null); +export const IssuePropertyDate: React.FC = observer((props) => { + const { value, onChange, disabled, placeHolder } = props; - const [isOpen, setIsOpen] = React.useState(false); + const dropdownBtn = React.useRef(null); + const dropdownOptions = React.useRef(null); - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); + const [isOpen, setIsOpen] = React.useState(false); - return ( - - {({ open }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); + useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - return ( - <> - - -
-
- -
- {value ? ( - <> -
{value}
-
{ - if (onChange) onChange(null); - }} - > - -
- - ) : ( -
{placeHolder ? placeHolder : `Select date`}
- )} -
-
-
+ return ( + + {({ open }) => { + if (open) { + if (!isOpen) setIsOpen(true); + } else if (isOpen) setIsOpen(false); -
- - {({ close }) => ( - { - if (onChange && val) { - onChange(renderDateFormat(val)); - close(); - } - }} - dateFormat="dd-MM-yyyy" - calendarClassName="h-full" - inline - /> + return ( + <> + + +
+ + {value && ( + <> +
{value}
+
{ + if (onChange) onChange(null); + }} + > + +
+ )} - -
- - ); - }} - - ); - } -); +
+ + + +
+ + {({ close }) => ( + { + if (onChange && val) { + onChange(renderDateFormat(val)); + close(); + } + }} + dateFormat="dd-MM-yyyy" + calendarClassName="h-full" + inline + /> + )} + +
+ + ); + }} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/properties/estimates.tsx b/web/components/issues/issue-layouts/properties/estimates.tsx index 80b3d9615..83de934cb 100644 --- a/web/components/issues/issue-layouts/properties/estimates.tsx +++ b/web/components/issues/issue-layouts/properties/estimates.tsx @@ -1,217 +1,28 @@ -import React from "react"; -// headless ui -import { Combobox } from "@headlessui/react"; -// lucide icons -import { ChevronDown, Search, X, Check, Triangle } from "lucide-react"; -// mobx import { observer } from "mobx-react-lite"; // components -import { Tooltip } from "@plane/ui"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; - -interface IFiltersOption { - id: string; - title: string; - key: string; -} +import { EstimateSelect } from "components/estimates"; +// types +import { IEstimatePoint } from "types"; export interface IIssuePropertyEstimates { - value?: any; - onChange?: (id: any) => void; + value: number | null; + onChange: (value: number | null) => void; + estimatePoints: IEstimatePoint[] | null; disabled?: boolean; - - workspaceSlug?: string; - projectId?: string; - - className?: string; - buttonClassName?: string; - optionsClassName?: string; - dropdownArrow?: boolean; + hideDropdownArrow?: boolean; } -export const IssuePropertyEstimates: React.FC = observer( - ({ - value, - onChange, - disabled, +export const IssuePropertyEstimates: React.FC = observer((props) => { + const { value, onChange, estimatePoints, disabled, hideDropdownArrow = false } = props; - workspaceSlug, - projectId, - - className, - buttonClassName, - optionsClassName, - dropdownArrow = true, - }) => { - const { project: projectStore }: RootStore = useMobxStore(); - - const dropdownBtn = React.useRef(null); - const dropdownOptions = React.useRef(null); - - const [isOpen, setIsOpen] = React.useState(false); - const [search, setSearch] = React.useState(""); - - const projectDetail = - (workspaceSlug && projectId && projectStore?.getProjectById(workspaceSlug, projectId)) || null; - const projectEstimateId = (projectDetail && projectDetail?.estimate) || null; - const estimates = (projectEstimateId && projectStore?.getProjectEstimateById(projectEstimateId)) || null; - - const options: IFiltersOption[] | [] = - (estimates && - estimates.points && - estimates.points.length > 0 && - estimates.points.map((_estimate) => ({ - id: _estimate?.id, - title: _estimate?.value, - key: _estimate?.key.toString(), - }))) || - []; - - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - - const selectedOption: IFiltersOption | null | undefined = - (value && options.find((_estimate: IFiltersOption) => _estimate.key === value)) || null; - - const filteredOptions: IFiltersOption[] = - search === "" - ? options && options.length > 0 - ? options - : [] - : options && options.length > 0 - ? options.filter((_estimate: IFiltersOption) => - _estimate.title.toLowerCase().replace(/\s+/g, "").includes(search.toLowerCase().replace(/\s+/g, "")) - ) - : []; - - return ( - { - if (onChange) onChange(data); - }} - disabled={disabled} - > - {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); - - return ( - <> - - {selectedOption ? ( - -
-
- -
-
{selectedOption?.title}
-
-
- ) : ( - -
Select Estimates
-
- )} - - {dropdownArrow && !disabled && ( -
- -
- )} -
- -
- - {options && options.length > 0 ? ( - <> -
-
- -
- -
- setSearch(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
- - {search && search.length > 0 && ( -
setSearch("")} - className="flex-shrink-0 flex justify-center items-center w-[16px] h-[16px] rounded-sm cursor-pointer hover:bg-custom-background-80" - > - -
- )} -
- -
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || selected ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( -
-
- -
-
{option.title}
- {selected && ( -
- -
- )} -
- )} -
- )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
- - ) : ( -

No options available.

- )} -
-
- - ); - }} -
- ); - } -); + return ( + + ); +}); diff --git a/web/components/issues/issue-layouts/properties/labels.tsx b/web/components/issues/issue-layouts/properties/labels.tsx index bc9930c72..dcc884b19 100644 --- a/web/components/issues/issue-layouts/properties/labels.tsx +++ b/web/components/issues/issue-layouts/properties/labels.tsx @@ -1,230 +1,28 @@ -import { FC, useRef, useState } from "react"; -import { Combobox } from "@headlessui/react"; -import { ChevronDown, Search, X, Check } from "lucide-react"; import { observer } from "mobx-react-lite"; // components -import { Tooltip } from "@plane/ui"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; - -interface IFiltersOption { - id: string; - title: string; - color: string | null; -} +import { LabelSelect } from "components/project"; +// types +import { IIssueLabels } from "types"; export interface IIssuePropertyLabels { - value?: any; - onChange?: (id: any, data: any) => void; + value: string[]; + onChange: (data: string[]) => void; + labels: IIssueLabels[] | null; disabled?: boolean; - list?: any; - - className?: string; - buttonClassName?: string; - optionsClassName?: string; - dropdownArrow?: boolean; + hideDropdownArrow?: boolean; } -export const IssuePropertyLabels: FC = observer((props) => { - const { - value, - onChange, - disabled, - list, - - className, - buttonClassName, - optionsClassName, - dropdownArrow = true, - } = props; - - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const [search, setSearch] = useState(""); - - const options: IFiltersOption[] | [] = - (list && - list?.length > 0 && - list.map((_label: any) => ({ - id: _label?.id, - title: _label?.name, - color: _label?.color || null, - }))) || - []; - - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - - const selectedOption: IFiltersOption[] = - (value && value?.length > 0 && options.filter((_label: IFiltersOption) => value.includes(_label.id))) || []; - - const filteredOptions: IFiltersOption[] = - search === "" - ? options && options.length > 0 - ? options - : [] - : options && options.length > 0 - ? options.filter((_label: IFiltersOption) => - _label.title.toLowerCase().replace(/\s+/g, "").includes(search.toLowerCase().replace(/\s+/g, "")) - ) - : []; +export const IssuePropertyLabels: React.FC = observer((props) => { + const { value, onChange, labels, disabled, hideDropdownArrow = false } = props; return ( - _label.id) as string[]} - onChange={(data: string[]) => { - if (onChange && selectedOption) onChange(data, selectedOption); - }} + - {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); - - return ( - <> - - {selectedOption && selectedOption?.length > 0 ? ( - <> - {selectedOption?.length === 1 ? ( - _label.title) || []).join(", ")} - > -
-
-
{selectedOption[0]?.title}
-
- - ) : ( - _label.title) || []).join(", ")} - > -
-
-
{selectedOption?.length} Labels
-
- - )} - - ) : ( - -
Select Labels
-
- )} - - {dropdownArrow && !disabled && ( -
- -
- )} - - -
- - {options && options.length > 0 ? ( - <> -
-
- -
- -
- setSearch(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
- - {search && search.length > 0 && ( -
setSearch("")} - className="flex-shrink-0 flex justify-center items-center w-[16px] h-[16px] rounded-sm cursor-pointer hover:bg-custom-background-80" - > - -
- )} -
- -
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || (value && value.length > 0 && value.includes(option?.id)) - ? "bg-custom-background-80" - : "" - } ${ - value && value.length > 0 && value.includes(option?.id) - ? "text-custom-text-100" - : "text-custom-text-200" - }` - } - > -
-
-
{option.title}
- {value && value.length > 0 && value.includes(option?.id) && ( -
- -
- )} -
- - )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
- - ) : ( -

No options available.

- )} - -
- - ); - }} - + hideDropdownArrow={hideDropdownArrow} + /> ); }); diff --git a/web/components/issues/issue-layouts/properties/priority.tsx b/web/components/issues/issue-layouts/properties/priority.tsx index 37404c11b..cbf6602eb 100644 --- a/web/components/issues/issue-layouts/properties/priority.tsx +++ b/web/components/issues/issue-layouts/properties/priority.tsx @@ -1,223 +1,25 @@ -import { FC, useRef, useState } from "react"; -import { Combobox } from "@headlessui/react"; -import { ChevronDown, Search, X, Check, AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; +import { PrioritySelect } from "components/project"; import { observer } from "mobx-react-lite"; -// components -import { Tooltip } from "@plane/ui"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; - -interface IFiltersOption { - id: string; - title: string; -} +// types +import { TIssuePriorities } from "types"; export interface IIssuePropertyPriority { - value?: any; - onChange?: (id: any, data: IFiltersOption) => void; + value: TIssuePriorities; + onChange: (value: TIssuePriorities) => void; disabled?: boolean; - list?: any; - - className?: string; - buttonClassName?: string; - optionsClassName?: string; - dropdownArrow?: boolean; + hideDropdownArrow?: boolean; } -const Icon = ({ priority }: any) => ( -
- {priority === "urgent" ? ( -
- -
- ) : priority === "high" ? ( -
- -
- ) : priority === "medium" ? ( -
- -
- ) : priority === "low" ? ( -
- -
- ) : ( -
- -
- )} -
-); - -export const IssuePropertyPriority: FC = observer((props) => { - const { - value, - onChange, - disabled, - list, - - className, - buttonClassName, - optionsClassName, - dropdownArrow = true, - } = props; - - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const [search, setSearch] = useState(""); - - const options: IFiltersOption[] | [] = - (list && - list?.length > 0 && - list.map((_priority: any) => ({ - id: _priority?.key, - title: _priority?.title, - }))) || - []; - - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - - const selectedOption: IFiltersOption | null | undefined = - (value && options.find((_priority: IFiltersOption) => _priority.id === value)) || null; - - const filteredOptions: IFiltersOption[] = - search === "" - ? options && options.length > 0 - ? options - : [] - : options && options.length > 0 - ? options.filter((_priority: IFiltersOption) => - _priority.title.toLowerCase().replace(/\s+/g, "").includes(search.toLowerCase().replace(/\s+/g, "")) - ) - : []; +export const IssuePropertyPriority: React.FC = observer((props) => { + const { value, onChange, disabled, hideDropdownArrow = false } = props; return ( - { - if (onChange && selectedOption) onChange(data, selectedOption); - }} + - {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); - - return ( - <> - - {selectedOption ? ( - -
-
- -
-
{selectedOption?.title}
-
-
- ) : ( - -
Select Priority
-
- )} - - {dropdownArrow && !disabled && ( -
- -
- )} -
- -
- - {options && options.length > 0 ? ( - <> -
-
- -
- -
- setSearch(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
- - {search && search.length > 0 && ( -
setSearch("")} - className="flex-shrink-0 flex justify-center items-center w-[16px] h-[16px] rounded-sm cursor-pointer hover:bg-custom-background-80" - > - -
- )} -
- -
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || selected ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( -
-
- -
-
{option.title}
- {selected && ( -
- -
- )} -
- )} -
- )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
- - ) : ( -

No options available.

- )} -
-
- - ); - }} -
+ hideDropdownArrow={hideDropdownArrow} + /> ); }); diff --git a/web/components/issues/issue-layouts/properties/state.tsx b/web/components/issues/issue-layouts/properties/state.tsx index 05cb375ad..9264b3084 100644 --- a/web/components/issues/issue-layouts/properties/state.tsx +++ b/web/components/issues/issue-layouts/properties/state.tsx @@ -1,214 +1,28 @@ -import { FC, useRef, useState } from "react"; -import { Combobox } from "@headlessui/react"; -import { ChevronDown, Search, X, Check } from "lucide-react"; import { observer } from "mobx-react-lite"; // components -import { Tooltip, StateGroupIcon } from "@plane/ui"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; - +import { StateSelect } from "components/states"; // types import { IState } from "types"; -interface IFiltersOption { - id: string; - title: string; - group: string; - color: string | null; -} - export interface IIssuePropertyState { - value?: any; - onChange?: (id: any, data: IFiltersOption) => void; + value: IState; + onChange: (state: IState) => void; + states: IState[] | null; disabled?: boolean; - list?: any; - - className?: string; - buttonClassName?: string; - optionsClassName?: string; - dropdownArrow?: boolean; + hideDropdownArrow?: boolean; } -export const IssuePropertyState: FC = observer((props) => { - const { - value, - onChange, - disabled, - list, - - className, - buttonClassName, - optionsClassName, - dropdownArrow = true, - } = props; - - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const [search, setSearch] = useState(""); - - const options: IFiltersOption[] | [] = - (list && - list?.length > 0 && - list.map((_state: IState) => ({ - id: _state?.id, - title: _state?.name, - group: _state?.group, - color: _state?.color || null, - }))) || - []; - - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - - const selectedOption: IFiltersOption | null | undefined = - (value && options.find((_state: IFiltersOption) => _state.id === value)) || null; - - const filteredOptions: IFiltersOption[] = - search === "" - ? options && options.length > 0 - ? options - : [] - : options && options.length > 0 - ? options.filter((_state: IFiltersOption) => - _state.title.toLowerCase().replace(/\s+/g, "").includes(search.toLowerCase().replace(/\s+/g, "")) - ) - : []; +export const IssuePropertyState: React.FC = observer((props) => { + const { value, onChange, states, disabled, hideDropdownArrow = false } = props; return ( - { - if (onChange && selectedOption) onChange(data, selectedOption); - }} + - {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); - - return ( - <> - - {selectedOption ? ( - -
-
- -
-
{selectedOption?.title}
-
-
- ) : ( - -
Select State
-
- )} - - {dropdownArrow && !disabled && ( -
- -
- )} -
- -
- - {options && options.length > 0 ? ( - <> -
-
- -
- -
- setSearch(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
- - {search && search.length > 0 && ( -
setSearch("")} - className="flex-shrink-0 flex justify-center items-center w-[16px] h-[16px] rounded-sm cursor-pointer hover:bg-custom-background-80" - > - -
- )} -
- -
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || selected ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( -
-
- -
-
{option.title}
- {selected && ( -
- -
- )} -
- )} -
- )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
- - ) : ( -

No options available.

- )} -
-
- - ); - }} -
+ hideDropdownArrow={hideDropdownArrow} + /> ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx index 7873ebe02..81f45d04f 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx @@ -4,6 +4,8 @@ import React from "react"; import { StateSelect } from "components/states"; // hooks import useSubIssue from "hooks/use-sub-issue"; +// helpers +import { getStatesList } from "helpers/state.helper"; // types import { IIssue, IStateResponse } from "types"; @@ -22,12 +24,14 @@ export const SpreadsheetStateColumn: React.FC = (props) => { const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const statesList = getStatesList(states); + return ( <> onChange({ state: data.id, state_detail: data })} - stateGroups={states} + states={statesList} buttonClassName="!shadow-none !border-0" hideDropdownArrow disabled={disabled} diff --git a/web/components/issues/issue-peek-overview/properties.tsx b/web/components/issues/issue-peek-overview/properties.tsx index f24a58302..bd087e10b 100644 --- a/web/components/issues/issue-peek-overview/properties.tsx +++ b/web/components/issues/issue-peek-overview/properties.tsx @@ -8,38 +8,37 @@ import { IssuePropertyPriority } from "components/issues/issue-layouts/propertie import { IssuePropertyAssignee } from "components/issues/issue-layouts/properties/assignee"; import { IssuePropertyDate } from "components/issues/issue-layouts/properties/date"; // types -import { IIssue } from "types"; +import { IIssue, IState, IUserLite, TIssuePriorities } from "types"; interface IPeekOverviewProperties { issue: IIssue; issueUpdate: (issue: Partial) => void; - - states: any; - members: any; + states: IState[] | null; + members: IUserLite[] | null; 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 handleState = (_state: IState) => { + issueUpdate({ ...issue, state: _state.id }); }; - const handlePriority = (_priority: any) => { - if (issueUpdate) issueUpdate({ ...issue, priority: _priority }); + const handlePriority = (_priority: TIssuePriorities) => { + issueUpdate({ ...issue, priority: _priority }); }; const handleAssignee = (_assignees: string[]) => { - if (issueUpdate) issueUpdate({ ...issue, assignees: _assignees }); + issueUpdate({ ...issue, assignees: _assignees }); }; const handleStartDate = (_startDate: string) => { - if (issueUpdate) issueUpdate({ ...issue, start_date: _startDate }); + issueUpdate({ ...issue, start_date: _startDate }); }; const handleTargetDate = (_targetDate: string) => { - if (issueUpdate) issueUpdate({ ...issue, target_date: _targetDate }); + issueUpdate({ ...issue, target_date: _targetDate }); }; return ( @@ -54,11 +53,11 @@ export const PeekOverviewProperties: FC = (props) => {
handleState(id)} + value={issue?.state_detail || null} + onChange={handleState} + states={states} disabled={false} - list={states} + hideDropdownArrow={true} />
@@ -74,10 +73,10 @@ export const PeekOverviewProperties: FC = (props) => {
handleAssignee(ids)} disabled={false} - list={members} + hideDropdownArrow={true} + members={members} />
@@ -93,10 +92,9 @@ export const PeekOverviewProperties: FC = (props) => {
handlePriority(id)} + onChange={handlePriority} disabled={false} - list={priorities} + hideDropdownArrow={true} />
diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx index ca0a08600..ce84d92c5 100644 --- a/web/components/issues/sub-issues/properties.tsx +++ b/web/components/issues/sub-issues/properties.tsx @@ -10,6 +10,8 @@ import { TrackEventService } from "services/track_event.service"; import { ViewDueDateSelect, ViewStartDateSelect } from "components/issues"; import { MembersSelect, PrioritySelect } from "components/project"; import { StateSelect } from "components/states"; +// helpers +import { getStatesList } from "helpers/state.helper"; // types import { IUser, IIssue, IState } from "types"; // fetch-keys @@ -115,6 +117,8 @@ export const IssueProperty: React.FC = observer((props) => { ); }; + const statesList = getStatesList(projectStore.states?.[issue.project]); + return (
{displayProperties.priority && ( @@ -132,7 +136,7 @@ export const IssueProperty: React.FC = observer((props) => {
handleStateChange(data)} hideDropdownArrow disabled={!editable} diff --git a/web/components/project/label-select.tsx b/web/components/project/label-select.tsx index f715793cb..ebf7f4776 100644 --- a/web/components/project/label-select.tsx +++ b/web/components/project/label-select.tsx @@ -3,9 +3,6 @@ import { usePopper } from "react-popper"; import { Placement } from "@popperjs/core"; import { Combobox } from "@headlessui/react"; import { Check, ChevronDown, PlusIcon, Search } from "lucide-react"; - -// components -import { CreateLabelModal } from "components/labels"; // ui import { Tooltip } from "components/ui"; // types @@ -14,7 +11,7 @@ import { IIssueLabels } from "types"; type Props = { value: string[]; onChange: (data: string[]) => void; - labels: IIssueLabels[]; + labels: IIssueLabels[] | undefined; className?: string; buttonClassName?: string; optionsClassName?: string; @@ -41,10 +38,16 @@ export const LabelSelect: React.FC = ({ const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); - const [labelModal, setLabelModal] = useState(false); - const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], }); const options = labels?.map((label) => ({ @@ -66,149 +69,126 @@ export const LabelSelect: React.FC = ({ const filteredOptions = query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); - const footerOption = ( - - ); - return ( - <> - {/* TODO: update this logic */} - {/* {projectId && ( - setLabelModal(false)} - projectId={projectId} - user={user} - /> - )} */} - - - - - - -
-
- - setQuery(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active && !selected ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
- {footerOption} +
+ value.includes(l.id)) + .map((l) => l.name) + .join(", ")} + > +
+ + {`${value.length} Labels`} +
+
+
+ ) + ) : ( +
+ Select labels +
+ )}
-
-
- + {!hideDropdownArrow && !disabled &&