issues rendering in all issue layouts fir profile and project issues and global issues store implementation (#2886)

* dev: draft and archived issue store

* connect draft and archived issues

* kanban for draft issues

* fix filter store for calendar and kanban

* dev: profile issues store and draft issues filters in header

* disble issue creation for draft issues

* dev: profile issues store filters

* disable kanban properties in draft issues

* dev: profile issues store filters

* dev: seperated adding issues to the cycle and module as seperate methds in cycle and module store

* dev: workspace profile issues store

* dev: sub group issues in the swimlanes

* profile issues and create issue connection

* fix profile issues

* fix spreadsheet issues

* fix dissapearing project from create issue modal

* page level modifications

* fix additional bugs

* dev: issues profile and global iisues and filters update

* fix issue related bugs

* fix project views for list and kanban

* fix build errors

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
guru_sainath 2023-11-27 14:15:33 +05:30 committed by sriram veeraghanta
parent 70994d1da7
commit f79bd9df60
116 changed files with 3187 additions and 912 deletions

View File

@ -55,6 +55,8 @@ export const CommandPalette: FC = observer(() => {
toggleBulkDeleteIssueModal, toggleBulkDeleteIssueModal,
isDeleteIssueModalOpen, isDeleteIssueModalOpen,
toggleDeleteIssueModal, toggleDeleteIssueModal,
createIssueStoreType,
} = commandPalette; } = commandPalette;
const isAnyModalOpen = Boolean( const isAnyModalOpen = Boolean(
@ -224,6 +226,7 @@ export const CommandPalette: FC = observer(() => {
prePopulateData={ prePopulateData={
cycleId ? { cycle: cycleId.toString() } : moduleId ? { module: moduleId.toString() } : undefined cycleId ? { cycle: cycleId.toString() } : moduleId ? { module: moduleId.toString() } : undefined
} }
currentStore={createIssueStoreType}
/> />
{issueId && issueDetails && ( {issueId && issueDetails && (

View File

@ -20,6 +20,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
import { EFilterType } from "store/issues/types"; import { EFilterType } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store";
export const CycleIssuesHeader: React.FC = observer(() => { export const CycleIssuesHeader: React.FC = observer(() => {
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
@ -33,7 +34,6 @@ export const CycleIssuesHeader: React.FC = observer(() => {
const { const {
cycle: cycleStore, cycle: cycleStore,
cycleIssueFilters: cycleIssueFiltersStore,
projectIssuesFilter: projectIssueFiltersStore, projectIssuesFilter: projectIssueFiltersStore,
project: { currentProjectDetails }, project: { currentProjectDetails },
projectMember: { projectMembers }, projectMember: { projectMembers },
@ -190,11 +190,14 @@ export const CycleIssuesHeader: React.FC = observer(() => {
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm"> <Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics Analytics
</Button> </Button>
<Button onClick={() => { <Button
setTrackElement("CYCLE_PAGE_HEADER") onClick={() => {
commandPaletteStore.toggleCreateIssueModal(true) setTrackElement("CYCLE_PAGE_HEADER");
} commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE);
} size="sm" prependIcon={<Plus />}> }}
size="sm"
prependIcon={<Plus />}
>
Add Issue Add Issue
</Button> </Button>
<button <button

View File

@ -20,6 +20,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
import { EFilterType } from "store/issues/types"; import { EFilterType } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store";
export const ModuleIssuesHeader: React.FC = observer(() => { export const ModuleIssuesHeader: React.FC = observer(() => {
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
@ -33,7 +34,6 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
const { const {
module: moduleStore, module: moduleStore,
projectIssuesFilter: projectIssueFiltersStore,
project: projectStore, project: projectStore,
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore, projectState: projectStateStore,
@ -191,11 +191,14 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm"> <Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics Analytics
</Button> </Button>
<Button onClick={() => { <Button
setTrackElement("MODULE_PAGE_HEADER") onClick={() => {
commandPaletteStore.toggleCreateIssueModal(true) setTrackElement("MODULE_PAGE_HEADER");
} commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.MODULE);
} size="sm" prependIcon={<Plus />}> }}
size="sm"
prependIcon={<Plus />}
>
Add Issue Add Issue
</Button> </Button>
<button <button

View File

@ -1,20 +1,74 @@
import { FC } from "react"; import { FC, useCallback } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
// ui // ui
import { Breadcrumbs, LayersIcon } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// helper // helper
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
import { EFilterType } from "store/issues/types";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
export const ProjectDraftIssueHeader: FC = observer(() => { export const ProjectDraftIssueHeader: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { project: projectStore } = useMobxStore(); const {
const { currentProjectDetails } = projectStore; project: { currentProjectDetails },
projectLabel: { projectLabels },
projectMember: { projectMembers },
projectState: projectStateStore,
projectDraftIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore();
const activeLayout = issueFilters?.displayFilters?.layout;
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { [key]: newValues });
},
[workspaceSlug, projectId, issueFilters, updateFilters]
);
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, { layout: layout });
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_PROPERTIES, property);
},
[workspaceSlug, projectId, updateFilters]
);
return ( return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
@ -44,6 +98,37 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
/> />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
<div className="ml-auto flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end">
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels ?? undefined}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId ?? ""] ?? undefined}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
</FiltersDropdown>
</div>
</div> </div>
</div> </div>
); );

View File

@ -17,6 +17,7 @@ import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
// helper // helper
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
import { EFilterType } from "store/issues/types"; import { EFilterType } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store";
export const ProjectIssuesHeader: React.FC = observer(() => { export const ProjectIssuesHeader: React.FC = observer(() => {
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
@ -199,11 +200,14 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm"> <Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics Analytics
</Button> </Button>
<Button onClick={() => { <Button
setTrackElement("PROJECT_PAGE_HEADER"); onClick={() => {
commandPaletteStore.toggleCreateIssueModal(true) setTrackElement("PROJECT_PAGE_HEADER");
} commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
} size="sm" prependIcon={<Plus />}> }}
size="sm"
prependIcon={<Plus />}
>
Add Issue Add Issue
</Button> </Button>
</div> </div>

View File

@ -72,7 +72,7 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
<p className="text-sm text-custom-text-200"> <p className="text-sm text-custom-text-200">
Are you sure you want to delete issue{" "} Are you sure you want to delete issue{" "}
<span className="break-words font-medium text-custom-text-100"> <span className="break-words font-medium text-custom-text-100">
{data?.project_detail.identifier}-{data?.sequence_id} {data?.project_detail?.identifier}-{data?.sequence_id}
</span> </span>
{""}? All of the data related to the issue will be permanently removed. This action cannot be {""}? All of the data related to the issue will be permanently removed. This action cannot be
undone. undone.

View File

@ -228,7 +228,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
...defaultValues, ...defaultValues,
...initialData, ...initialData,
}); });
}, [setFocus, initialData, reset]); }, [setFocus, reset]);
// update projectId in form when projectId changes // update projectId in form when projectId changes
useEffect(() => { useEffect(() => {

View File

@ -7,14 +7,28 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { CalendarChart } from "components/issues"; import { CalendarChart } from "components/issues";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { ICycleIssuesStore, IModuleIssuesStore, IProjectIssuesStore, IViewIssuesStore } from "store/issues"; import {
import { IIssueCalendarViewStore, IssueStore } from "store/issue"; ICycleIssuesFilterStore,
ICycleIssuesStore,
IModuleIssuesFilterStore,
IModuleIssuesStore,
IProjectIssuesFilterStore,
IProjectIssuesStore,
IViewIssuesFilterStore,
IViewIssuesStore,
} from "store/issues";
import { IIssueCalendarViewStore } from "store/issue";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { IGroupedIssues } from "store/issues/types"; import { IGroupedIssues } from "store/issues/types";
interface IBaseCalendarRoot { interface IBaseCalendarRoot {
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore; issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
issuesFilterStore:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore;
calendarViewStore: IIssueCalendarViewStore; calendarViewStore: IIssueCalendarViewStore;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: { issueActions: {
@ -26,10 +40,9 @@ interface IBaseCalendarRoot {
} }
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { issueStore, calendarViewStore, QuickActions, issueActions, viewId } = props; const { issueStore, issuesFilterStore, calendarViewStore, QuickActions, issueActions, viewId } = props;
const { projectIssuesFilter: issueFilterStore } = useMobxStore();
const displayFilters = issueFilterStore.issueFilters?.displayFilters; const displayFilters = issuesFilterStore.issueFilters?.displayFilters;
const issues = issueStore.getIssues; const issues = issueStore.getIssues;
const groupedIssueIds = (issueStore.getIssuesIds ?? {}) as IGroupedIssues; const groupedIssueIds = (issueStore.getIssuesIds ?? {}) as IGroupedIssues;
@ -75,7 +88,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
} }
handleRemoveFromView={ handleRemoveFromView={
issueActions[EIssueActions.REMOVE] issueActions[EIssueActions.REMOVE]
? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.UPDATE) ? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.REMOVE)
: undefined : undefined
} }
/> />

View File

@ -10,7 +10,11 @@ import { EIssueActions } from "../../types";
import { BaseCalendarRoot } from "../base-calendar-root"; import { BaseCalendarRoot } from "../base-calendar-root";
export const CycleCalendarLayout: React.FC = observer(() => { export const CycleCalendarLayout: React.FC = observer(() => {
const { cycleIssues: cycleIssueStore, cycleIssueCalendarView: cycleIssueCalendarViewStore } = useMobxStore(); const {
cycleIssues: cycleIssueStore,
cycleIssuesFilter: cycleIssueFilterStore,
cycleIssueCalendarView: cycleIssueCalendarViewStore,
} = useMobxStore();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
@ -34,6 +38,7 @@ export const CycleCalendarLayout: React.FC = observer(() => {
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={cycleIssueStore} issueStore={cycleIssueStore}
issuesFilterStore={cycleIssueFilterStore}
calendarViewStore={cycleIssueCalendarViewStore} calendarViewStore={cycleIssueCalendarViewStore}
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}

View File

@ -10,7 +10,11 @@ import { EIssueActions } from "../../types";
import { BaseCalendarRoot } from "../base-calendar-root"; import { BaseCalendarRoot } from "../base-calendar-root";
export const ModuleCalendarLayout: React.FC = observer(() => { export const ModuleCalendarLayout: React.FC = observer(() => {
const { moduleIssues: moduleIssueStore, moduleIssueCalendarView: moduleIssueCalendarViewStore } = useMobxStore(); const {
moduleIssues: moduleIssueStore,
moduleIssuesFilter: moduleIssueFilterStore,
moduleIssueCalendarView: moduleIssueCalendarViewStore,
} = useMobxStore();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string }; const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string };
@ -33,6 +37,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={moduleIssueStore} issueStore={moduleIssueStore}
issuesFilterStore={moduleIssueFilterStore}
calendarViewStore={moduleIssueCalendarViewStore} calendarViewStore={moduleIssueCalendarViewStore}
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}

View File

@ -15,25 +15,26 @@ export const CalendarLayout: React.FC = observer(() => {
const { const {
projectIssues: issueStore, projectIssues: issueStore,
issueCalendarView: issueCalendarViewStore, issueCalendarView: issueCalendarViewStore,
issueDetail: issueDetailStore, projectIssuesFilter: projectIssueFiltersStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id); issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
}, },
}; };
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={issueStore} issueStore={issueStore}
issuesFilterStore={projectIssueFiltersStore}
calendarViewStore={issueCalendarViewStore} calendarViewStore={issueCalendarViewStore}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}

View File

@ -1,11 +1,9 @@
import { useCallback } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { DragDropContext, DropResult } from "@hello-pangea/dnd";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { CalendarChart, ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
@ -14,7 +12,7 @@ import { BaseCalendarRoot } from "../base-calendar-root";
export const ProjectViewCalendarLayout: React.FC = observer(() => { export const ProjectViewCalendarLayout: React.FC = observer(() => {
const { const {
viewIssues: projectViewIssuesStore, viewIssues: projectViewIssuesStore,
issueDetail: issueDetailStore, viewIssuesFilter: projectIssueViewFiltersStore,
projectViewIssueCalendarView: projectViewIssueCalendarViewStore, projectViewIssueCalendarView: projectViewIssueCalendarViewStore,
} = useMobxStore(); } = useMobxStore();
@ -25,18 +23,19 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); projectViewIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id); projectViewIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
}, },
}; };
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={projectViewIssuesStore} issueStore={projectViewIssuesStore}
issuesFilterStore={projectIssueViewFiltersStore}
calendarViewStore={projectViewIssueCalendarViewStore} calendarViewStore={projectViewIssueCalendarViewStore}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}

View File

@ -14,6 +14,7 @@ import { Button } from "@plane/ui";
import emptyIssue from "public/empty-state/issue.svg"; import emptyIssue from "public/empty-state/issue.svg";
// types // types
import { ISearchIssueResponse } from "types"; import { ISearchIssueResponse } from "types";
import { EProjectStore } from "store/command-palette.store";
type Props = { type Props = {
workspaceSlug: string | undefined; workspaceSlug: string | undefined;
@ -26,7 +27,11 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
// states // states
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const { cycleIssue: cycleIssueStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore(); const {
cycleIssue: cycleIssueStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -63,9 +68,9 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
text: "New issue", text: "New issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />, icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => { onClick: () => {
setTrackElement("CYCLE_EMPTY_STATE") setTrackElement("CYCLE_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true) commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE);
} },
}} }}
secondaryButton={ secondaryButton={
<Button <Button

View File

@ -6,9 +6,17 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
// assets // assets
import emptyIssue from "public/empty-state/issue.svg"; import emptyIssue from "public/empty-state/issue.svg";
import { EProjectStore } from "store/command-palette.store";
import { useRouter } from "next/router";
export const ProjectViewEmptyState: React.FC = observer(() => { export const ProjectViewEmptyState: React.FC = observer(() => {
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore(); const router = useRouter();
const { viewId } = router.query as { viewId: string };
const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
return ( return (
<div className="h-full w-full grid place-items-center"> <div className="h-full w-full grid place-items-center">
@ -21,8 +29,8 @@ export const ProjectViewEmptyState: React.FC = observer(() => {
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />, icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => { onClick: () => {
setTrackElement("VIEW_EMPTY_STATE"); setTrackElement("VIEW_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true) commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT_VIEW);
} },
}} }}
/> />
</div> </div>

View File

@ -6,9 +6,13 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
// assets // assets
import emptyIssue from "public/empty-state/issue.svg"; import emptyIssue from "public/empty-state/issue.svg";
import { EProjectStore } from "store/command-palette.store";
export const ProjectEmptyState: React.FC = observer(() => { export const ProjectEmptyState: React.FC = observer(() => {
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore(); const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
return ( return (
<div className="h-full w-full grid place-items-center"> <div className="h-full w-full grid place-items-center">
@ -21,8 +25,8 @@ export const ProjectEmptyState: React.FC = observer(() => {
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />, icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => { onClick: () => {
setTrackElement("PROJECT_EMPTY_STATE"); setTrackElement("PROJECT_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true) commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
} },
}} }}
/> />
</div> </div>

View File

@ -7,23 +7,24 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { AppliedFiltersList } from "components/issues"; import { AppliedFiltersList } from "components/issues";
// types // types
import { IIssueFilterOptions } from "types"; import { IIssueFilterOptions } from "types";
import { EFilterType } from "store/issues/types";
export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => { export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { const {
archivedIssueFilters: archivedIssueFiltersStore, projectArchivedIssuesFilter: { issueFilters, updateFilters },
projectLabel: { projectLabels }, projectLabel: { projectLabels },
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore, projectState: projectStateStore,
} = useMobxStore(); } = useMobxStore();
const userFilters = archivedIssueFiltersStore.userFilters; const userFilters = issueFilters?.filters;
// filters whose value not null or empty array // filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {}; const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters).forEach(([key, value]) => { Object.entries(userFilters ?? {}).forEach(([key, value]) => {
if (!value) return; if (!value) return;
if (Array.isArray(value) && value.length === 0) return; if (Array.isArray(value) && value.length === 0) return;
@ -36,22 +37,18 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
// remove all values of the key if value is null // remove all values of the key if value is null
if (!value) { if (!value) {
archivedIssueFiltersStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
filters: { [key]: null,
[key]: null,
},
}); });
return; return;
} }
// remove the passed value from the key // remove the passed value from the key
let newValues = archivedIssueFiltersStore.userFilters?.[key] ?? []; let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value); newValues = newValues.filter((val) => val !== value);
archivedIssueFiltersStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
filters: { [key]: newValues,
[key]: newValues,
},
}); });
}; };
@ -59,12 +56,12 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = null;
}); });
archivedIssueFiltersStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
filters: { ...newFilters }, ...newFilters,
}); });
}; };

View File

@ -0,0 +1,80 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList } from "components/issues";
// types
import { IIssueFilterOptions } from "types";
import { EFilterType } from "store/issues/types";
export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const {
projectDraftIssuesFilter: { issueFilters, updateFilters },
projectLabel: { projectLabels },
projectMember: { projectMembers },
projectState: projectStateStore,
} = useMobxStore();
const userFilters = issueFilters?.filters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
if (!value) return;
if (Array.isArray(value) && value.length === 0) return;
appliedFilters[key as keyof IIssueFilterOptions] = value;
});
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return;
// remove all values of the key if value is null
if (!value) {
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
[key]: null,
});
return;
}
// remove the passed value from the key
let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
[key]: newValues,
});
};
const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { ...newFilters });
};
// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;
return (
<div className="p-4">
<AppliedFiltersList
appliedFilters={appliedFilters}
handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter}
labels={projectLabels ?? []}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
/>
</div>
);
});

View File

@ -4,3 +4,4 @@ export * from "./module-root";
export * from "./project-view-root"; export * from "./project-view-root";
export * from "./project-root"; export * from "./project-root";
export * from "./archived-issue"; export * from "./archived-issue";
export * from "./profile-issues-root";

View File

@ -0,0 +1,72 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList } from "components/issues";
// types
import { IIssueFilterOptions } from "types";
import { EFilterType } from "store/issues/types";
export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query as {
workspaceSlug: string;
};
const {
workspace: { workspaceLabels },
workspaceProfileIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore();
const userFilters = issueFilters?.filters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
if (!value) return;
if (Array.isArray(value) && value.length === 0) return;
appliedFilters[key as keyof IIssueFilterOptions] = value;
});
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug) return;
if (!value) {
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: null });
return;
}
let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
updateFilters(workspaceSlug, EFilterType.FILTERS, {
[key]: newValues,
});
};
const handleClearAllFilters = () => {
if (!workspaceSlug) return;
const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
updateFilters(workspaceSlug, EFilterType.FILTERS, { ...newFilters });
};
// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;
return (
<div className="p-4">
<AppliedFiltersList
appliedFilters={appliedFilters}
handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter}
labels={workspaceLabels ?? []}
members={[]}
states={[]}
/>
</div>
);
});

View File

@ -57,10 +57,16 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
if (!workspaceSlug) return; if (!workspaceSlug) return;
//Todo fix sort order in the structure //Todo fix sort order in the structure
issueStore.updateIssue(workspaceSlug, issue.project, issue.id, { issueStore.updateIssue(
start_date: payload.start_date, workspaceSlug,
target_date: payload.target_date, issue.project,
}); issue.id,
{
start_date: payload.start_date,
target_date: payload.target_date,
},
viewId
);
}; };
const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER; const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER;

View File

@ -1,5 +1,4 @@
import { FC, useCallback, useState } from "react"; import { FC, useCallback, useState } from "react";
import { useRouter } from "next/router";
import { DragDropContext } from "@hello-pangea/dnd"; import { DragDropContext } from "@hello-pangea/dnd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
@ -9,7 +8,19 @@ import { Spinner } from "@plane/ui";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { ICycleIssuesStore, IModuleIssuesStore, IProjectIssuesStore, IViewIssuesStore } from "store/issues"; import {
ICycleIssuesFilterStore,
ICycleIssuesStore,
IModuleIssuesFilterStore,
IModuleIssuesStore,
IProfileIssuesFilterStore,
IProfileIssuesStore,
IProjectDraftIssuesStore,
IProjectIssuesFilterStore,
IProjectIssuesStore,
IViewIssuesFilterStore,
IViewIssuesStore,
} from "store/issues";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { IIssueKanBanViewStore } from "store/issue"; import { IIssueKanBanViewStore } from "store/issue";
// constants // constants
@ -17,9 +28,22 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
//components //components
import { KanBan } from "./default"; import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes"; import { KanBanSwimLanes } from "./swimlanes";
import { EProjectStore } from "store/command-palette.store";
export interface IBaseKanBanLayout { export interface IBaseKanBanLayout {
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore; issueStore:
| IProjectIssuesStore
| IModuleIssuesStore
| ICycleIssuesStore
| IViewIssuesStore
| IProjectDraftIssuesStore
| IProfileIssuesStore;
issuesFilterStore:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
kanbanViewStore: IIssueKanBanViewStore; kanbanViewStore: IIssueKanBanViewStore;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: { issueActions: {
@ -29,24 +53,33 @@ export interface IBaseKanBanLayout {
}; };
showLoader?: boolean; showLoader?: boolean;
viewId?: string; viewId?: string;
currentStore?: EProjectStore;
} }
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => { export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
const { issueStore, kanbanViewStore, QuickActions, issueActions, showLoader, viewId } = props; const {
issueStore,
issuesFilterStore,
kanbanViewStore,
QuickActions,
issueActions,
showLoader,
viewId,
currentStore,
} = props;
const { const {
project: { workspaceProjects }, project: { workspaceProjects },
projectLabel: { projectLabels }, projectLabel: { projectLabels },
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore, projectState: projectStateStore,
projectIssuesFilter: issueFilterStore,
} = useMobxStore(); } = useMobxStore();
const issues = issueStore?.getIssues || {}; const issues = issueStore?.getIssues || {};
const issueIds = issueStore?.getIssuesIds || []; const issueIds = issueStore?.getIssuesIds || [];
const displayFilters = issueFilterStore?.issueFilters?.displayFilters; const displayFilters = issuesFilterStore?.issueFilters?.displayFilters;
const displayProperties = issueFilterStore?.issueFilters?.displayProperties || null; const displayProperties = issuesFilterStore?.issueFilters?.displayProperties || null;
const sub_group_by: string | null = displayFilters?.sub_group_by || null; const sub_group_by: string | null = displayFilters?.sub_group_by || null;
@ -60,6 +93,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const [isDragStarted, setIsDragStarted] = useState<boolean>(false); const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {};
const onDragStart = () => { const onDragStart = () => {
setIsDragStarted(true); setIsDragStarted(true);
}; };
@ -103,7 +137,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
return ( return (
<> <>
{showLoader && issueStore?.loader === "mutation" && ( {showLoader && issueStore?.loader === "init-loader" && (
<div className="fixed top-16 right-2 z-30 bg-custom-background-80 shadow-custom-shadow-sm w-10 h-10 rounded flex justify-center items-center"> <div className="fixed top-16 right-2 z-30 bg-custom-background-80 shadow-custom-shadow-sm w-10 h-10 rounded flex justify-center items-center">
<Spinner className="w-5 h-5" /> <Spinner className="w-5 h-5" />
</div> </div>
@ -144,11 +178,14 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
labels={projectLabels} labels={projectLabels}
members={projectMembers?.map((m) => m.member) ?? null} members={projectMembers?.map((m) => m.member) ?? null}
projects={workspaceProjects} projects={workspaceProjects}
enableQuickIssueCreate enableQuickIssueCreate={enableQuickAdd}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true} showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={issueStore.quickAddIssue} quickAddCallback={issueStore?.quickAddIssue}
viewId={viewId} viewId={viewId}
disableIssueCreation={!enableIssueCreation}
isReadOnly={!enableInlineEditing}
currentStore={currentStore}
/> />
) : ( ) : (
<KanBanSwimLanes <KanBanSwimLanes
@ -185,6 +222,10 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
projects={workspaceProjects} projects={workspaceProjects}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true} showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={true}
enableQuickIssueCreate={enableQuickAdd}
isReadOnly={!enableInlineEditing}
currentStore={currentStore}
/> />
)} )}
</DragDropContext> </DragDropContext>

View File

@ -17,6 +17,7 @@ interface IssueBlockProps {
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void; handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode; quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null; displayProperties: IIssueDisplayProperties | null;
isReadOnly: boolean;
} }
export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => { export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
@ -30,6 +31,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties, displayProperties,
isReadOnly,
} = props; } = props;
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => { const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
@ -91,6 +93,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
handleIssues={updateIssue} handleIssues={updateIssue}
displayProperties={displayProperties} displayProperties={displayProperties}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
isReadOnly={isReadOnly}
/> />
</div> </div>
</div> </div>

View File

@ -14,6 +14,7 @@ interface IssueBlocksListProps {
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void; handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode; quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null; displayProperties: IIssueDisplayProperties | null;
isReadOnly: boolean;
} }
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => { export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
@ -27,6 +28,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties, displayProperties,
isReadOnly,
} = props; } = props;
return ( return (
@ -50,6 +52,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
columnId={columnId} columnId={columnId}
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
isDragDisabled={isDragDisabled} isDragDisabled={isDragDisabled}
isReadOnly={isReadOnly}
/> />
); );
})} })}

View File

@ -13,6 +13,7 @@ import { getValueFromObject } from "constants/issue";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types"; import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store";
export interface IGroupByKanBan { export interface IGroupByKanBan {
issues: IIssueResponse; issues: IIssueResponse;
@ -40,6 +41,9 @@ export interface IGroupByKanBan {
viewId?: string viewId?: string
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
isReadOnly: boolean;
} }
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => { const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
@ -63,6 +67,9 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
isDragStarted, isDragStarted,
quickAddCallback, quickAddCallback,
viewId, viewId,
disableIssueCreation,
isReadOnly,
currentStore,
} = props; } = props;
const verticalAlignPosition = (_list: any) => const verticalAlignPosition = (_list: any) =>
@ -86,6 +93,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
issues_count={issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0} issues_count={issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
</div> </div>
)} )}
@ -95,10 +104,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
verticalAlignPosition(_list) ? `w-[0px] overflow-hidden` : `w-full transition-all` verticalAlignPosition(_list) ? `w-[0px] overflow-hidden` : `w-full transition-all`
}`} }`}
> >
<Droppable <Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}
isDropDisabled={isDragDisabled}
>
{(provided: any, snapshot: any) => ( {(provided: any, snapshot: any) => (
<div <div
className={`w-full h-full relative transition-all ${ className={`w-full h-full relative transition-all ${
@ -118,6 +124,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties} displayProperties={displayProperties}
isReadOnly={isReadOnly}
/> />
) : ( ) : (
isDragDisabled && ( isDragDisabled && (
@ -149,7 +156,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
)} )}
</div> </div>
{isDragStarted && isDragDisabled && ( {/* {isDragStarted && isDragDisabled && (
<div className="invisible group-hover:visible transition-all text-sm absolute top-12 bottom-10 left-0 right-0 bg-custom-background-100/40 text-center"> <div className="invisible group-hover:visible transition-all text-sm absolute top-12 bottom-10 left-0 right-0 bg-custom-background-100/40 text-center">
<div className="rounded inline-flex mt-80 h-8 px-3 justify-center items-center bg-custom-background-80 text-custom-text-100 font-medium"> <div className="rounded inline-flex mt-80 h-8 px-3 justify-center items-center bg-custom-background-80 text-custom-text-100 font-medium">
{`This board is ordered by "${replaceUnderscoreIfSnakeCase( {`This board is ordered by "${replaceUnderscoreIfSnakeCase(
@ -157,7 +164,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
)}"`} )}"`}
</div> </div>
</div> </div>
)} )} */}
</div> </div>
))} ))}
</div> </div>
@ -192,6 +199,9 @@ export interface IKanBan {
viewId?: string viewId?: string
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
isReadOnly: boolean;
} }
export const KanBan: React.FC<IKanBan> = observer((props) => { export const KanBan: React.FC<IKanBan> = observer((props) => {
@ -218,6 +228,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted, isDragStarted,
quickAddCallback, quickAddCallback,
viewId, viewId,
disableIssueCreation,
isReadOnly,
currentStore,
} = props; } = props;
const { issueKanBanView: issueKanBanViewStore } = useMobxStore(); const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
@ -246,6 +259,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
@ -271,6 +287,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
@ -296,6 +315,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
@ -321,6 +343,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
@ -346,6 +371,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
@ -371,6 +399,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
@ -396,6 +427,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/> />
)} )}
</div> </div>

View File

@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card";
// ui // ui
import { Avatar } from "@plane/ui"; import { Avatar } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IAssigneesHeader { export interface IAssigneesHeader {
column_id: string; column_id: string;
@ -15,6 +16,8 @@ export interface IAssigneesHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="base" />; export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="base" />;
@ -29,6 +32,8 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const assignee = column_value ?? null; const assignee = column_value ?? null;
@ -56,6 +61,8 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{ assignees: [assignee?.id] }} issuePayload={{ assignees: [assignee?.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { Icon } from "./assignee"; import { Icon } from "./assignee";
import { EProjectStore } from "store/command-palette.store";
export interface ICreatedByHeader { export interface ICreatedByHeader {
column_id: string; column_id: string;
@ -14,6 +15,8 @@ export interface ICreatedByHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => { export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
@ -26,6 +29,8 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const createdBy = column_value ?? null; const createdBy = column_value ?? null;
@ -53,6 +58,8 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{ created_by: createdBy?.id }} issuePayload={{ created_by: createdBy?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -15,6 +15,7 @@ import useToast from "hooks/use-toast";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// types // types
import { IIssue, ISearchIssueResponse } from "types"; import { IIssue, ISearchIssueResponse } from "types";
import { EProjectStore } from "store/command-palette.store";
interface IHeaderGroupByCard { interface IHeaderGroupByCard {
sub_group_by: string | null; sub_group_by: string | null;
@ -26,13 +27,26 @@ interface IHeaderGroupByCard {
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
issuePayload: Partial<IIssue>; issuePayload: Partial<IIssue>;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
const moduleService = new ModuleService(); const moduleService = new ModuleService();
const issueService = new IssueService(); const issueService = new IssueService();
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => { export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
const { sub_group_by, column_id, icon, title, count, kanBanToggle, handleKanBanToggle, issuePayload } = props; const {
sub_group_by,
column_id,
icon,
title,
count,
kanBanToggle,
handleKanBanToggle,
issuePayload,
disableIssueCreation,
currentStore,
} = props;
const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id); const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id);
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
@ -84,7 +98,12 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
return ( return (
<> <>
<CreateUpdateIssueModal isOpen={isOpen} handleClose={() => setIsOpen(false)} prePopulateData={issuePayload} /> <CreateUpdateIssueModal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
prePopulateData={issuePayload}
currentStore={currentStore}
/>
{renderExistingIssueModal && ( {renderExistingIssueModal && (
<ExistingIssuesListModal <ExistingIssuesListModal
isOpen={openExistingIssueListModal} isOpen={openExistingIssueListModal}
@ -126,30 +145,31 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
</div> </div>
)} )}
{renderExistingIssueModal ? ( {!disableIssueCreation &&
<CustomMenu (renderExistingIssueModal ? (
width="auto" <CustomMenu
customButton={ width="auto"
<span className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"> customButton={
<Plus height={14} width={14} strokeWidth={2} /> <span className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
</span> <Plus height={14} width={14} strokeWidth={2} />
} </span>
> }
<CustomMenu.MenuItem onClick={() => setIsOpen(true)}> >
<span className="flex items-center justify-start gap-2">Create issue</span> <CustomMenu.MenuItem onClick={() => setIsOpen(true)}>
</CustomMenu.MenuItem> <span className="flex items-center justify-start gap-2">Create issue</span>
<CustomMenu.MenuItem onClick={() => setOpenExistingIssueListModal(true)}> </CustomMenu.MenuItem>
<span className="flex items-center justify-start gap-2">Add an existing issue</span> <CustomMenu.MenuItem onClick={() => setOpenExistingIssueListModal(true)}>
</CustomMenu.MenuItem> <span className="flex items-center justify-start gap-2">Add an existing issue</span>
</CustomMenu> </CustomMenu.MenuItem>
) : ( </CustomMenu>
<div ) : (
className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all" <div
onClick={() => setIsOpen(true)} className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
> onClick={() => setIsOpen(true)}
<Plus width={14} strokeWidth={2} /> >
</div> <Plus width={14} strokeWidth={2} />
)} </div>
))}
</div> </div>
</> </>
); );

View File

@ -8,6 +8,7 @@ import { LabelHeader } from "./label";
import { CreatedByHeader } from "./created_by"; import { CreatedByHeader } from "./created_by";
// mobx // mobx
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { EProjectStore } from "store/command-palette.store";
export interface IKanBanGroupByHeaderRoot { export interface IKanBanGroupByHeaderRoot {
column_id: string; column_id: string;
@ -17,10 +18,22 @@ export interface IKanBanGroupByHeaderRoot {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer( export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
({ column_id, column_value, sub_group_by, group_by, issues_count, kanBanToggle, handleKanBanToggle }) => ( ({
column_id,
column_value,
sub_group_by,
group_by,
issues_count,
kanBanToggle,
disableIssueCreation,
handleKanBanToggle,
currentStore,
}) => (
<> <>
{group_by && group_by === "project" && ( {group_by && group_by === "project" && (
<ProjectHeader <ProjectHeader
@ -32,6 +45,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -45,6 +60,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{group_by && group_by === "state_detail.group" && ( {group_by && group_by === "state_detail.group" && (
@ -57,6 +74,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{group_by && group_by === "priority" && ( {group_by && group_by === "priority" && (
@ -69,6 +88,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{group_by && group_by === "labels" && ( {group_by && group_by === "labels" && (
@ -81,6 +102,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{group_by && group_by === "assignees" && ( {group_by && group_by === "assignees" && (
@ -93,6 +116,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{group_by && group_by === "created_by" && ( {group_by && group_by === "created_by" && (
@ -105,6 +130,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite";
// components // components
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface ILabelHeader { export interface ILabelHeader {
column_id: string; column_id: string;
@ -13,6 +14,8 @@ export interface ILabelHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
const Icon = ({ color }: any) => ( const Icon = ({ color }: any) => (
@ -29,6 +32,8 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const label = column_value ?? null; const label = column_value ?? null;
@ -56,6 +61,8 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{ labels: [label?.id] }} issuePayload={{ labels: [label?.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -7,6 +7,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
// Icons // Icons
import { PriorityIcon } from "@plane/ui"; import { PriorityIcon } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IPriorityHeader { export interface IPriorityHeader {
column_id: string; column_id: string;
@ -17,6 +18,8 @@ export interface IPriorityHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => { export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
@ -29,6 +32,8 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const priority = column_value || null; const priority = column_value || null;
@ -56,6 +61,8 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{ priority: priority?.key }} issuePayload={{ priority: priority?.key }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card";
// emoji helper // emoji helper
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
import { EProjectStore } from "store/command-palette.store";
export interface IProjectHeader { export interface IProjectHeader {
column_id: string; column_id: string;
@ -15,6 +16,8 @@ export interface IProjectHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>; const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
@ -29,6 +32,8 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const project = column_value ?? null; const project = column_value ?? null;
@ -56,6 +61,8 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{ project: project?.id }} issuePayload={{ project: project?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { StateGroupIcon } from "@plane/ui"; import { StateGroupIcon } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IStateGroupHeader { export interface IStateGroupHeader {
column_id: string; column_id: string;
@ -14,6 +15,8 @@ export interface IStateGroupHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => ( export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
@ -32,6 +35,8 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const stateGroup = column_value || null; const stateGroup = column_value || null;
@ -59,6 +64,8 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{}} issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { Icon } from "./state-group"; import { Icon } from "./state-group";
import { EProjectStore } from "store/command-palette.store";
export interface IStateHeader { export interface IStateHeader {
column_id: string; column_id: string;
@ -14,6 +15,8 @@ export interface IStateHeader {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const StateHeader: FC<IStateHeader> = observer((props) => { export const StateHeader: FC<IStateHeader> = observer((props) => {
@ -26,6 +29,8 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
issues_count, issues_count,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props; } = props;
const state = column_value ?? null; const state = column_value ?? null;
@ -53,6 +58,8 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
issuePayload={{ state: state?.id }} issuePayload={{ state: state?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
))} ))}
</> </>

View File

@ -7,6 +7,7 @@ import { AssigneesHeader } from "./assignee";
import { PriorityHeader } from "./priority"; import { PriorityHeader } from "./priority";
import { LabelHeader } from "./label"; import { LabelHeader } from "./label";
import { CreatedByHeader } from "./created_by"; import { CreatedByHeader } from "./created_by";
import { EProjectStore } from "store/command-palette.store";
export interface IKanBanSubGroupByHeaderRoot { export interface IKanBanSubGroupByHeaderRoot {
column_id: string; column_id: string;
@ -16,10 +17,22 @@ export interface IKanBanSubGroupByHeaderRoot {
issues_count: number; issues_count: number;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => { export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => {
const { column_id, column_value, sub_group_by, group_by, issues_count, kanBanToggle, handleKanBanToggle } = props; const {
column_id,
column_value,
sub_group_by,
group_by,
issues_count,
kanBanToggle,
handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props;
return ( return (
<> <>
@ -33,6 +46,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{sub_group_by && sub_group_by === "state_detail.group" && ( {sub_group_by && sub_group_by === "state_detail.group" && (
@ -45,6 +60,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{sub_group_by && sub_group_by === "priority" && ( {sub_group_by && sub_group_by === "priority" && (
@ -57,6 +74,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{sub_group_by && sub_group_by === "labels" && ( {sub_group_by && sub_group_by === "labels" && (
@ -69,6 +88,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{sub_group_by && sub_group_by === "assignees" && ( {sub_group_by && sub_group_by === "assignees" && (
@ -81,6 +102,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
{sub_group_by && sub_group_by === "created_by" && ( {sub_group_by && sub_group_by === "created_by" && (
@ -93,6 +116,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count} issues_count={issues_count}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -19,10 +19,11 @@ export interface IKanBanProperties {
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void; handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
displayProperties: IIssueDisplayProperties | null; displayProperties: IIssueDisplayProperties | null;
showEmptyGroup: boolean; showEmptyGroup: boolean;
isReadOnly: boolean;
} }
export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) => { export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) => {
const { sub_group_id, columnId: group_id, issue, handleIssues, displayProperties, showEmptyGroup } = props; const { sub_group_id, columnId: group_id, issue, handleIssues, displayProperties, isReadOnly } = props;
const handleState = (state: IState) => { const handleState = (state: IState) => {
handleIssues( handleIssues(
@ -89,7 +90,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.state || null} value={issue?.state || null}
onChange={handleState} onChange={handleState}
disabled={false} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} )}
@ -99,7 +100,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<IssuePropertyPriority <IssuePropertyPriority
value={issue?.priority || null} value={issue?.priority || null}
onChange={handlePriority} onChange={handlePriority}
disabled={false} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} )}
@ -110,7 +111,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.labels || null} value={issue?.labels || null}
onChange={handleLabel} onChange={handleLabel}
disabled={false} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} )}
@ -120,7 +121,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<IssuePropertyDate <IssuePropertyDate
value={issue?.start_date || null} value={issue?.start_date || null}
onChange={(date: string) => handleStartDate(date)} onChange={(date: string) => handleStartDate(date)}
disabled={false} disabled={isReadOnly}
placeHolder="Start date" placeHolder="Start date"
/> />
)} )}
@ -130,7 +131,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<IssuePropertyDate <IssuePropertyDate
value={issue?.target_date || null} value={issue?.target_date || null}
onChange={(date: string) => handleTargetDate(date)} onChange={(date: string) => handleTargetDate(date)}
disabled={false} disabled={isReadOnly}
placeHolder="Target date" placeHolder="Target date"
/> />
)} )}
@ -142,7 +143,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
value={issue?.assignees || null} value={issue?.assignees || null}
hideDropdownArrow hideDropdownArrow
onChange={handleAssignee} onChange={handleAssignee}
disabled={false} disabled={isReadOnly}
multiple multiple
/> />
)} )}
@ -153,7 +154,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.estimate_point || null} value={issue?.estimate_point || null}
onChange={handleEstimate} onChange={handleEstimate}
disabled={false} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} )}

View File

@ -10,6 +10,7 @@ import { IIssue } from "types";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
// components // components
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface ICycleKanBanLayout {} export interface ICycleKanBanLayout {}
@ -18,7 +19,11 @@ export const CycleKanBanLayout: React.FC = observer(() => {
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
// store // store
const { cycleIssues: cycleIssueStore, cycleIssueKanBanView: cycleIssueKanBanViewStore } = useMobxStore(); const {
cycleIssues: cycleIssueStore,
cycleIssuesFilter: cycleIssueFilterStore,
cycleIssueKanBanView: cycleIssueKanBanViewStore,
} = useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
@ -39,10 +44,12 @@ export const CycleKanBanLayout: React.FC = observer(() => {
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
issueStore={cycleIssueStore} issueStore={cycleIssueStore}
issuesFilterStore={cycleIssueFilterStore}
kanbanViewStore={cycleIssueKanBanViewStore} kanbanViewStore={cycleIssueKanBanViewStore}
showLoader={true} showLoader={true}
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
viewId={cycleId} viewId={cycleId}
currentStore={EProjectStore.CYCLE}
/> />
); );
}); });

View File

@ -0,0 +1,48 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { ProjectIssueQuickActions } from "components/issues";
// types
import { IIssue } from "types";
// constants
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root";
export interface IKanBanLayout {}
export const DraftKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string };
const {
projectDraftIssues: issueStore,
projectDraftIssuesFilter: projectIssuesFilterStore,
issueKanBanView: issueKanBanViewStore,
} = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
},
};
return (
<BaseKanBanRoot
issueActions={issueActions}
issuesFilterStore={projectIssuesFilterStore}
issueStore={issueStore}
kanbanViewStore={issueKanBanViewStore}
showLoader={true}
QuickActions={ProjectIssueQuickActions}
/>
);
});

View File

@ -10,6 +10,7 @@ import { IIssue } from "types";
// constants // constants
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface IModuleKanBanLayout {} export interface IModuleKanBanLayout {}
@ -20,8 +21,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
// store // store
const { const {
moduleIssues: moduleIssueStore, moduleIssues: moduleIssueStore,
moduleIssuesFilter: moduleIssueFilterStore,
moduleIssueKanBanView: moduleIssueKanBanViewStore, moduleIssueKanBanView: moduleIssueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore(); } = useMobxStore();
// const handleIssues = useCallback( // const handleIssues = useCallback(
@ -58,17 +59,19 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
}, },
[EIssueActions.REMOVE]: async (issue: IIssue) => { [EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId || !issue.bridge_id) return; if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, issue.id, moduleId, issue.bridge_id); moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
}, },
}; };
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
issueStore={moduleIssueStore} issueStore={moduleIssueStore}
issuesFilterStore={moduleIssueFilterStore}
kanbanViewStore={moduleIssueKanBanViewStore} kanbanViewStore={moduleIssueKanBanViewStore}
showLoader={true} showLoader={true}
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
viewId={moduleId} viewId={moduleId}
currentStore={EProjectStore.MODULE}
/> />
); );
}); });

View File

@ -1,166 +1,48 @@
import { FC, useCallback, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { DragDropContext } from "@hello-pangea/dnd";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { KanBanSwimLanes } from "../swimlanes";
import { KanBan } from "../default";
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
import { Spinner } from "@plane/ui";
// constants
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
// constants
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface IProfileIssuesKanBanLayout {} export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string };
export const ProfileIssuesKanBanLayout: FC = observer(() => {
const { const {
workspace: workspaceStore, workspaceProfileIssues: profileIssuesStore,
project: projectStore, workspaceProfileIssuesFilter: profileIssueFiltersStore,
projectMember: { projectMembers },
projectState: projectStateStore,
profileIssues: profileIssuesStore,
profileIssueFilters: profileIssueFiltersStore,
issueKanBanView: issueKanBanViewStore, issueKanBanView: issueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore(); } = useMobxStore();
const router = useRouter(); const issueActions = {
const { workspaceSlug } = router.query; [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !userId) return;
const issues = profileIssuesStore?.getIssues; await profileIssuesStore.updateIssue(workspaceSlug, userId, issue.id, issue);
const sub_group_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.sub_group_by || null;
const group_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.group_by || null;
const order_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.order_by || null;
const userDisplayFilters = profileIssueFiltersStore?.userDisplayFilters || null;
const displayProperties = profileIssueFiltersStore?.userDisplayProperties || null;
const currentKanBanView: "swimlanes" | "default" = profileIssueFiltersStore?.userDisplayFilters?.sub_group_by
? "swimlanes"
: "default";
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
// const onDragStart = () => {
// setIsDragStarted(true);
// };
const onDragEnd = (result: any) => {
setIsDragStarted(false);
if (!result) return;
if (
result.destination &&
result.source &&
result.destination.droppableId === result.source.droppableId &&
result.destination.index === result.source.index
)
return;
currentKanBanView === "default"
? issueKanBanViewStore?.handleDragDrop(result.source, result.destination)
: issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
};
const handleIssues = useCallback(
(sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
if (!workspaceSlug) return;
if (action === EIssueActions.UPDATE) {
profileIssuesStore.updateIssueStructure(group_by, sub_group_by, issue);
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
}
if (action === EIssueActions.DELETE) profileIssuesStore.deleteIssue(group_by, sub_group_by, issue);
}, },
[profileIssuesStore, issueDetailStore, workspaceSlug] [EIssueActions.DELETE]: async (issue: IIssue) => {
); if (!workspaceSlug || !userId) return;
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
issueKanBanViewStore.handleKanBanToggle(toggle, value); },
}; };
const states = projectStateStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = workspaceStore.workspaceLabels || null;
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = projectStore?.workspaceProjects || null;
return ( return (
<> <BaseKanBanRoot
{profileIssuesStore.loader ? ( issueActions={issueActions}
<div className="w-full h-full flex justify-center items-center"> issuesFilterStore={profileIssueFiltersStore}
<Spinner /> issueStore={profileIssuesStore}
</div> kanbanViewStore={issueKanBanViewStore}
) : ( showLoader={true}
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}> QuickActions={ProjectIssueQuickActions}
<DragDropContext onDragEnd={onDragEnd}> currentStore={EProjectStore.PROFILE}
{currentKanBanView === "default" ? ( />
<KanBan
issues={{}}
issueIds={[]}
sub_group_by={sub_group_by}
group_by={group_by}
order_by={order_by}
handleIssues={handleIssues}
quickActions={(sub_group_by, group_by, issue) => (
<ProjectIssueQuickActions
issue={issue}
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)}
/>
)}
displayProperties={displayProperties}
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={projectMembers?.map((m) => m.member) ?? null}
projects={projects}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
isDragStarted={isDragStarted}
/>
) : (
<KanBanSwimLanes
issues={{}}
issueIds={[]}
sub_group_by={sub_group_by}
group_by={group_by}
order_by={order_by}
handleIssues={handleIssues}
quickActions={(sub_group_by, group_by, issue) => (
<ProjectIssueQuickActions
issue={issue}
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)}
/>
)}
displayProperties={displayProperties}
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
states={states}
stateGroups={stateGroups}
priorities={priorities}
labels={labels}
members={projectMembers?.map((m) => m.member) ?? null}
projects={projects}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
isDragStarted={isDragStarted}
/>
)}
</DragDropContext>
</div>
)}
</>
); );
}); });

View File

@ -9,6 +9,7 @@ import { IIssue } from "types";
// constants // constants
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface IKanBanLayout {} export interface IKanBanLayout {}
@ -18,30 +19,32 @@ export const KanBanLayout: React.FC = observer(() => {
const { const {
projectIssues: issueStore, projectIssues: issueStore,
projectIssuesFilter: issuesFilterStore,
issueKanBanView: issueKanBanViewStore, issueKanBanView: issueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await issueDetailStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await issueDetailStore.deleteIssue(workspaceSlug, issue.project, issue.id); await issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
}, },
}; };
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
issuesFilterStore={issuesFilterStore}
issueStore={issueStore} issueStore={issueStore}
kanbanViewStore={issueKanBanViewStore} kanbanViewStore={issueKanBanViewStore}
showLoader={true} showLoader={true}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
currentStore={EProjectStore.PROJECT}
/> />
); );
}); });

View File

@ -9,6 +9,7 @@ import { EIssueActions } from "../../types";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
// components // components
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface IViewKanBanLayout {} export interface IViewKanBanLayout {}
@ -18,30 +19,32 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
const { const {
viewIssues: projectViewIssuesStore, viewIssues: projectViewIssuesStore,
viewIssuesFilter: projectIssueViewFiltersStore,
issueKanBanView: projectViewIssueKanBanViewStore, issueKanBanView: projectViewIssueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await issueDetailStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); await projectViewIssuesStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await issueDetailStore.deleteIssue(workspaceSlug, issue.project, issue.id); await projectViewIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id);
}, },
}; };
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
issuesFilterStore={projectIssueViewFiltersStore}
issueStore={projectViewIssuesStore} issueStore={projectViewIssuesStore}
kanbanViewStore={projectViewIssueKanBanViewStore} kanbanViewStore={projectViewIssueKanBanViewStore}
showLoader={true} showLoader={true}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
currentStore={EProjectStore.PROJECT_VIEW}
/> />
); );
}); });

View File

@ -10,6 +10,7 @@ import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } f
// constants // constants
import { getValueFromObject } from "constants/issue"; import { getValueFromObject } from "constants/issue";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { EProjectStore } from "store/command-palette.store";
interface ISubGroupSwimlaneHeader { interface ISubGroupSwimlaneHeader {
issues: IIssueResponse; issues: IIssueResponse;
@ -20,9 +21,10 @@ interface ISubGroupSwimlaneHeader {
listKey: string; listKey: string;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
} }
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
issues,
issueIds, issueIds,
sub_group_by, sub_group_by,
group_by, group_by,
@ -30,6 +32,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
listKey, listKey,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
disableIssueCreation,
currentStore,
}) => { }) => {
const calculateIssueCount = (column_id: string) => { const calculateIssueCount = (column_id: string) => {
let issueCount = 0; let issueCount = 0;
@ -54,6 +58,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)} issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
</div> </div>
))} ))}
@ -78,6 +84,10 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
isDragStarted?: boolean; isDragStarted?: boolean;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
enableQuickIssueCreate: boolean;
isReadOnly: boolean;
} }
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => { const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
const { const {
@ -101,6 +111,9 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
members, members,
projects, projects,
isDragStarted, isDragStarted,
disableIssueCreation,
enableQuickIssueCreate,
isReadOnly,
} = props; } = props;
const calculateIssueCount = (column_id: string) => { const calculateIssueCount = (column_id: string) => {
@ -128,6 +141,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)} issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
/> />
</div> </div>
<div className="w-full border-b border-custom-border-400 border-dashed" /> <div className="w-full border-b border-custom-border-400 border-dashed" />
@ -153,8 +167,9 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
labels={labels} labels={labels}
members={members} members={members}
projects={projects} projects={projects}
enableQuickIssueCreate enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
isReadOnly={isReadOnly}
/> />
</div> </div>
)} )}
@ -183,6 +198,10 @@ export interface IKanBanSwimLanes {
members: IUserLite[] | null; members: IUserLite[] | null;
projects: IProject[] | null; projects: IProject[] | null;
isDragStarted?: boolean; isDragStarted?: boolean;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
enableQuickIssueCreate: boolean;
isReadOnly: boolean;
} }
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => { export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
@ -205,6 +224,10 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members, members,
projects, projects,
isDragStarted, isDragStarted,
disableIssueCreation,
enableQuickIssueCreate,
isReadOnly,
currentStore,
} = props; } = props;
return ( return (
@ -220,6 +243,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`id`} listKey={`id`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -233,6 +258,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`id`} listKey={`id`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -246,6 +273,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`key`} listKey={`key`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -259,6 +288,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`key`} listKey={`key`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
currentStore={currentStore}
/> />
)} )}
@ -272,6 +302,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`id`} listKey={`id`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -285,6 +317,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`id`} listKey={`id`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -298,6 +332,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
listKey={`id`} listKey={`id`}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</div> </div>
@ -324,6 +360,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -349,6 +388,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -374,6 +416,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -399,6 +444,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -424,6 +472,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -449,6 +500,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -474,6 +528,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
@ -499,6 +556,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
members={members} members={members}
projects={projects} projects={projects}
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={disableIssueCreation}
enableQuickIssueCreate={enableQuickIssueCreate}
isReadOnly={isReadOnly}
/> />
)} )}
</div> </div>

View File

@ -11,6 +11,10 @@ import {
ICycleIssuesStore, ICycleIssuesStore,
IModuleIssuesFilterStore, IModuleIssuesFilterStore,
IModuleIssuesStore, IModuleIssuesStore,
IProfileIssuesFilterStore,
IProfileIssuesStore,
IProjectArchivedIssuesStore,
IProjectDraftIssuesStore,
IProjectIssuesFilterStore, IProjectIssuesFilterStore,
IProjectIssuesStore, IProjectIssuesStore,
IViewIssuesFilterStore, IViewIssuesFilterStore,
@ -18,6 +22,7 @@ import {
} from "store/issues"; } from "store/issues";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { IIssueResponse } from "store/issues/types"; import { IIssueResponse } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store";
enum EIssueActions { enum EIssueActions {
UPDATE = "update", UPDATE = "update",
@ -30,8 +35,16 @@ interface IBaseListRoot {
| IProjectIssuesFilterStore | IProjectIssuesFilterStore
| IModuleIssuesFilterStore | IModuleIssuesFilterStore
| ICycleIssuesFilterStore | ICycleIssuesFilterStore
| IViewIssuesFilterStore; | IViewIssuesFilterStore
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore; | IProfileIssuesFilterStore;
issueStore:
| IProjectIssuesStore
| IModuleIssuesStore
| ICycleIssuesStore
| IViewIssuesStore
| IProjectArchivedIssuesStore
| IProjectDraftIssuesStore
| IProfileIssuesStore;
QuickActions: FC<IQuickActionProps>; QuickActions: FC<IQuickActionProps>;
issueActions: { issueActions: {
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => void; [EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => void;
@ -40,10 +53,11 @@ interface IBaseListRoot {
}; };
getProjects: (projectStore: IProjectStore) => IProject[] | null; getProjects: (projectStore: IProjectStore) => IProject[] | null;
viewId?: string; viewId?: string;
currentStore: EProjectStore;
} }
export const BaseListRoot = observer((props: IBaseListRoot) => { export const BaseListRoot = observer((props: IBaseListRoot) => {
const { issueFilterStore, issueStore, QuickActions, issueActions, getProjects, viewId } = props; const { issueFilterStore, issueStore, QuickActions, issueActions, getProjects, viewId, currentStore } = props;
const { const {
project: projectStore, project: projectStore,
@ -52,8 +66,10 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
projectLabel: { projectLabels }, projectLabel: { projectLabels },
} = useMobxStore(); } = useMobxStore();
const issueIds = issueStore.getIssuesIds || []; const issueIds = issueStore?.getIssuesIds || [];
const issues = issueStore.getIssues; const issues = issueStore?.getIssues;
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {};
const displayFilters = issueFilterStore?.issueFilters?.displayFilters; const displayFilters = issueFilterStore?.issueFilters?.displayFilters;
const group_by = displayFilters?.group_by || null; const group_by = displayFilters?.group_by || null;
@ -75,7 +91,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
return ( return (
<> <>
{issueStore.loader === "mutation" ? ( {issueStore?.loader === "init-loader" ? (
<div className="w-full h-full flex justify-center items-center"> <div className="w-full h-full flex justify-center items-center">
<Spinner /> <Spinner />
</div> </div>
@ -108,10 +124,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
projects={projects} projects={projects}
issueIds={issueIds} issueIds={issueIds}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
enableIssueQuickAdd={true}
isReadonly={false}
quickAddCallback={issueStore.quickAddIssue}
viewId={viewId} viewId={viewId}
quickAddCallback={issueStore?.quickAddIssue}
enableIssueQuickAdd={!!enableQuickAdd}
isReadonly={!enableInlineEditing}
disableIssueCreation={!enableIssueCreation}
currentStore={currentStore}
/> />
</div> </div>
)} )}

View File

@ -4,10 +4,11 @@ import { ListGroupByHeaderRoot } from "./headers/group-by-root";
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues"; import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
// types // types
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types"; import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
import { IIssueResponse, IGroupedIssues, TUnGroupedIssues } from "store/issues/types"; import { IIssueResponse, IGroupedIssues, TUnGroupedIssues, ViewFlags } from "store/issues/types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
// constants // constants
import { getValueFromObject } from "constants/issue"; import { getValueFromObject } from "constants/issue";
import { EProjectStore } from "store/command-palette.store";
export interface IGroupByList { export interface IGroupByList {
issueIds: IGroupedIssues | TUnGroupedIssues | any; issueIds: IGroupedIssues | TUnGroupedIssues | any;
@ -29,6 +30,8 @@ export interface IGroupByList {
data: IIssue, data: IIssue,
viewId?: string viewId?: string
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
viewId?: string; viewId?: string;
} }
@ -49,6 +52,8 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
isReadonly, isReadonly,
quickAddCallback, quickAddCallback,
viewId, viewId,
disableIssueCreation,
currentStore,
} = props; } = props;
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => { const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
@ -84,6 +89,8 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
? issueIds?.length || 0 ? issueIds?.length || 0
: issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0 : issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0
} }
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
</div> </div>
@ -138,6 +145,8 @@ export interface IList {
viewId?: string viewId?: string
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const List: React.FC<IList> = (props) => { export const List: React.FC<IList> = (props) => {
@ -153,13 +162,14 @@ export const List: React.FC<IList> = (props) => {
showEmptyGroup, showEmptyGroup,
enableIssueQuickAdd, enableIssueQuickAdd,
isReadonly, isReadonly,
disableIssueCreation,
states, states,
stateGroups, stateGroups,
priorities, priorities,
labels, labels,
members, members,
projects, projects,
currentStore,
} = props; } = props;
return ( return (
@ -181,6 +191,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -200,6 +212,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -219,6 +233,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -238,6 +254,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -257,6 +275,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -276,6 +296,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -295,6 +317,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
@ -314,6 +338,8 @@ export const List: React.FC<IList> = (props) => {
isReadonly={isReadonly} isReadonly={isReadonly}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</div> </div>

View File

@ -4,17 +4,20 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
// ui // ui
import { Avatar } from "@plane/ui"; import { Avatar } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IAssigneesHeader { export interface IAssigneesHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />; export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => { export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const assignee = column_value ?? null; const assignee = column_value ?? null;
@ -26,6 +29,8 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
title={assignee?.display_name || ""} title={assignee?.display_name || ""}
count={issues_count} count={issues_count}
issuePayload={{ assignees: [assignee?.member?.id] }} issuePayload={{ assignees: [assignee?.member?.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -3,15 +3,18 @@ import { observer } from "mobx-react-lite";
// components // components
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { Icon } from "./assignee"; import { Icon } from "./assignee";
import { EProjectStore } from "store/command-palette.store";
export interface ICreatedByHeader { export interface ICreatedByHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => { export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const createdBy = column_value ?? null; const createdBy = column_value ?? null;
@ -23,6 +26,8 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
title={createdBy?.display_name || ""} title={createdBy?.display_name || ""}
count={issues_count} count={issues_count}
issuePayload={{ created_by: createdBy?.member?.id }} issuePayload={{ created_by: createdBy?.member?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -1,15 +1,26 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface IEmptyHeader { export interface IEmptyHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => { export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_id, column_value, issues_count, disableIssueCreation, currentStore } = props;
return <HeaderGroupByCard title={column_value?.title || "All Issues"} count={issues_count} issuePayload={{}} />; return (
<HeaderGroupByCard
title={column_value?.title || "All Issues"}
count={issues_count}
issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
);
}); });

View File

@ -6,125 +6,124 @@ import { CircleDashed, Plus } from "lucide-react";
import { CreateUpdateIssueModal } from "components/issues/modal"; import { CreateUpdateIssueModal } from "components/issues/modal";
import { ExistingIssuesListModal } from "components/core"; import { ExistingIssuesListModal } from "components/core";
import { CustomMenu } from "@plane/ui"; import { CustomMenu } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
// mobx // mobx
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// types // types
import { IIssue, ISearchIssueResponse } from "types"; import { IIssue, ISearchIssueResponse } from "types";
import { EProjectStore } from "store/command-palette.store";
interface IHeaderGroupByCard { interface IHeaderGroupByCard {
icon?: React.ReactNode; icon?: React.ReactNode;
title: string; title: string;
count: number; count: number;
issuePayload: Partial<IIssue>; issuePayload: Partial<IIssue>;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: IHeaderGroupByCard) => { export const HeaderGroupByCard = observer(
const router = useRouter(); ({ icon, title, count, issuePayload, disableIssueCreation, currentStore }: IHeaderGroupByCard) => {
const { workspaceSlug, projectId, moduleId, cycleId } = router.query; const router = useRouter();
const { workspaceSlug, projectId, moduleId, cycleId } = router.query;
const { setToastAlert } = useToast(); const [isOpen, setIsOpen] = React.useState(false);
const [isOpen, setIsOpen] = React.useState(false); const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false); const renderExistingIssueModal = moduleId || cycleId;
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
const renderExistingIssueModal = moduleId || cycleId; const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true }; if (!workspaceSlug || !projectId) return;
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => { const payload = {
if (!workspaceSlug || !projectId) return; issues: data.map((i) => i.id),
};
const payload = { // await moduleService
issues: data.map((i) => i.id), // .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user)
// .catch(() =>
// setToastAlert({
// type: "error",
// title: "Error!",
// message: "Selected issues could not be added to the module. Please try again.",
// })
// );
}; };
// await moduleService const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
// .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user) if (!workspaceSlug || !projectId) return;
// .catch(() =>
// setToastAlert({
// type: "error",
// title: "Error!",
// message: "Selected issues could not be added to the module. Please try again.",
// })
// );
};
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => { const payload = {
if (!workspaceSlug || !projectId) return; issues: data.map((i) => i.id),
};
const payload = { // await issueService
issues: data.map((i) => i.id), // .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user)
// .catch(() => {
// setToastAlert({
// type: "error",
// title: "Error!",
// message: "Selected issues could not be added to the cycle. Please try again.",
// });
// });
}; };
// await issueService return (
// .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user) <>
// .catch(() => { <div className="flex-shrink-0 relative flex gap-2 py-1.5 flex-row items-center w-full">
// setToastAlert({ <div className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center">
// type: "error", {icon ? icon : <CircleDashed className="h-3.5 w-3.5" strokeWidth={2} />}
// title: "Error!",
// message: "Selected issues could not be added to the cycle. Please try again.",
// });
// });
};
return (
<>
<div className="flex-shrink-0 relative flex gap-2 py-1.5 flex-row items-center w-full">
<div className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center">
{icon ? icon : <CircleDashed className="h-3.5 w-3.5" strokeWidth={2} />}
</div>
<div className="flex items-center gap-1 flex-row w-full">
<div className="font-medium line-clamp-1 text-custom-text-100">{title}</div>
<div className="text-sm font-medium text-custom-text-300 pl-2">{count || 0}</div>
</div>
{renderExistingIssueModal ? (
<CustomMenu
width="auto"
customButton={
<span className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
<Plus className="h-3.5 w-3.5" strokeWidth={2} />
</span>
}
>
<CustomMenu.MenuItem onClick={() => setIsOpen(true)}>
<span className="flex items-center justify-start gap-2">Create issue</span>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => setOpenExistingIssueListModal(true)}>
<span className="flex items-center justify-start gap-2">Add an existing issue</span>
</CustomMenu.MenuItem>
</CustomMenu>
) : (
<div
className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
onClick={() => setIsOpen(true)}
>
<Plus width={14} strokeWidth={2} />
</div> </div>
)}
<CreateUpdateIssueModal <div className="flex items-center gap-1 flex-row w-full">
isOpen={isOpen} <div className="font-medium line-clamp-1 text-custom-text-100">{title}</div>
handleClose={() => setIsOpen(false)} <div className="text-sm font-medium text-custom-text-300 pl-2">{count || 0}</div>
handleSubmit={(data: Partial<IIssue>) => { </div>
console.log(data);
return Promise.resolve();
}}
prePopulateData={issuePayload}
/>
{renderExistingIssueModal && ( {!disableIssueCreation &&
<ExistingIssuesListModal (renderExistingIssueModal ? (
isOpen={openExistingIssueListModal} <CustomMenu
handleClose={() => setOpenExistingIssueListModal(false)} width="auto"
searchParams={ExistingIssuesListModalPayload} customButton={
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle} <span className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
<Plus className="h-3.5 w-3.5" strokeWidth={2} />
</span>
}
>
<CustomMenu.MenuItem onClick={() => setIsOpen(true)}>
<span className="flex items-center justify-start gap-2">Create issue</span>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => setOpenExistingIssueListModal(true)}>
<span className="flex items-center justify-start gap-2">Add an existing issue</span>
</CustomMenu.MenuItem>
</CustomMenu>
) : (
<div
className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
onClick={() => setIsOpen(true)}
>
<Plus width={14} strokeWidth={2} />
</div>
))}
<CreateUpdateIssueModal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
currentStore={currentStore}
prePopulateData={issuePayload}
/> />
)}
</div> {renderExistingIssueModal && (
</> <ExistingIssuesListModal
); isOpen={openExistingIssueListModal}
}); handleClose={() => setOpenExistingIssueListModal(false)}
searchParams={ExistingIssuesListModalPayload}
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
/>
)}
</div>
</>
);
}
);

View File

@ -9,43 +9,94 @@ import { LabelHeader } from "./label";
import { CreatedByHeader } from "./created-by"; import { CreatedByHeader } from "./created-by";
// mobx // mobx
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { EProjectStore } from "store/command-palette.store";
export interface IListGroupByHeaderRoot { export interface IListGroupByHeaderRoot {
column_id: string; column_id: string;
column_value: any; column_value: any;
group_by: string | null; group_by: string | null;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer((props) => { export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer((props) => {
const { column_id, column_value, group_by, issues_count } = props; const { column_id, column_value, group_by, issues_count, disableIssueCreation, currentStore } = props;
return ( return (
<> <>
{!group_by && group_by === null && ( {!group_by && group_by === null && (
<EmptyHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <EmptyHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "project" && ( {group_by && group_by === "project" && (
<ProjectHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <ProjectHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "state" && ( {group_by && group_by === "state" && (
<StateHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <StateHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "state_detail.group" && ( {group_by && group_by === "state_detail.group" && (
<StateGroupHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <StateGroupHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "priority" && ( {group_by && group_by === "priority" && (
<PriorityHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <PriorityHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "labels" && ( {group_by && group_by === "labels" && (
<LabelHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <LabelHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "assignees" && ( {group_by && group_by === "assignees" && (
<AssigneesHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <AssigneesHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
{group_by && group_by === "created_by" && ( {group_by && group_by === "created_by" && (
<CreatedByHeader column_id={column_id} column_value={column_value} issues_count={issues_count} /> <CreatedByHeader
column_id={column_id}
column_value={column_value}
issues_count={issues_count}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)} )}
</> </>
); );

View File

@ -2,11 +2,14 @@ import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface ILabelHeader { export interface ILabelHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
const Icon = ({ color }: any) => ( const Icon = ({ color }: any) => (
@ -14,7 +17,7 @@ const Icon = ({ color }: any) => (
); );
export const LabelHeader: FC<ILabelHeader> = observer((props) => { export const LabelHeader: FC<ILabelHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const label = column_value ?? null; const label = column_value ?? null;
@ -26,6 +29,8 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
title={column_value?.name || ""} title={column_value?.name || ""}
count={issues_count} count={issues_count}
issuePayload={{ labels: [label.id] }} issuePayload={{ labels: [label.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -3,11 +3,14 @@ import { observer } from "mobx-react-lite";
import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react";
// components // components
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface IPriorityHeader { export interface IPriorityHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
const Icon = ({ priority }: any) => ( const Icon = ({ priority }: any) => (
@ -37,7 +40,7 @@ const Icon = ({ priority }: any) => (
); );
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => { export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_id, column_value, issues_count, disableIssueCreation, currentStore } = props;
const priority = column_value ?? null; const priority = column_value ?? null;
@ -49,6 +52,8 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
title={priority?.title || ""} title={priority?.title || ""}
count={issues_count} count={issues_count}
issuePayload={{ priority: priority?.key }} issuePayload={{ priority: priority?.key }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -4,17 +4,20 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
// emoji helper // emoji helper
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
import { EProjectStore } from "store/command-palette.store";
export interface IProjectHeader { export interface IProjectHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>; const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
export const ProjectHeader: FC<IProjectHeader> = observer((props) => { export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const project = column_value ?? null; const project = column_value ?? null;
@ -26,6 +29,8 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
title={project?.name || ""} title={project?.name || ""}
count={issues_count} count={issues_count}
issuePayload={{ project: project?.id ?? "" }} issuePayload={{ project: project?.id ?? "" }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -6,11 +6,14 @@ import { HeaderGroupByCard } from "./group-by-card";
import { StateGroupIcon } from "@plane/ui"; import { StateGroupIcon } from "@plane/ui";
// helpers // helpers
import { capitalizeFirstLetter } from "helpers/string.helper"; import { capitalizeFirstLetter } from "helpers/string.helper";
import { EProjectStore } from "store/command-palette.store";
export interface IStateGroupHeader { export interface IStateGroupHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => ( export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
@ -20,7 +23,7 @@ export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) =>
); );
export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => { export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const stateGroup = column_value ?? null; const stateGroup = column_value ?? null;
@ -32,6 +35,8 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
title={capitalizeFirstLetter(stateGroup?.key) || ""} title={capitalizeFirstLetter(stateGroup?.key) || ""}
count={issues_count} count={issues_count}
issuePayload={{}} issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -3,15 +3,18 @@ import { observer } from "mobx-react-lite";
// components // components
import { HeaderGroupByCard } from "./group-by-card"; import { HeaderGroupByCard } from "./group-by-card";
import { Icon } from "./state-group"; import { Icon } from "./state-group";
import { EProjectStore } from "store/command-palette.store";
export interface IStateHeader { export interface IStateHeader {
column_id: string; column_id: string;
column_value: any; column_value: any;
issues_count: number; issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
} }
export const StateHeader: FC<IStateHeader> = observer((props) => { export const StateHeader: FC<IStateHeader> = observer((props) => {
const { column_id, column_value, issues_count } = props; const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const state = column_value ?? null; const state = column_value ?? null;
@ -23,6 +26,8 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
title={state?.name || ""} title={state?.name || ""}
count={issues_count} count={issues_count}
issuePayload={{ state: state?.id }} issuePayload={{ state: state?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/> />
)} )}
</> </>

View File

@ -11,18 +11,20 @@ import { IIssue } from "types";
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
import { IProjectStore } from "store/project"; import { IProjectStore } from "store/project";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { EProjectStore } from "store/command-palette.store";
export const ArchivedIssueListLayout: FC = observer(() => { export const ArchivedIssueListLayout: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { archivedIssues: archivedIssueStore, archivedIssueFilters: archivedIssueFiltersStore } = useMobxStore(); const { projectArchivedIssues: archivedIssueStore, projectArchivedIssuesFilter: archivedIssueFiltersStore } =
useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => { [EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
archivedIssueStore.deleteArchivedIssue(group_by, null, issue); archivedIssueStore.removeIssue(workspaceSlug, projectId, issue.id);
}, },
}; };
@ -31,15 +33,14 @@ export const ArchivedIssueListLayout: FC = observer(() => {
return projectStore?.projects[workspaceSlug.toString()] || null; return projectStore?.projects[workspaceSlug.toString()] || null;
}; };
return null; return (
<BaseListRoot
// return ( issueFilterStore={archivedIssueFiltersStore}
// <BaseListRoot issueStore={archivedIssueStore}
// issueFilterStore={archivedIssueFiltersStore} QuickActions={ArchivedIssueQuickActions}
// issueStore={archivedIssueStore} issueActions={issueActions}
// QuickActions={ArchivedIssueQuickActions} getProjects={getProjects}
// issueActions={issueActions} currentStore={EProjectStore.PROJECT}
// getProjects={getProjects} />
// /> );
// );
}); });

View File

@ -11,6 +11,7 @@ import { IIssue } from "types";
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
import { IProjectStore } from "store/project"; import { IProjectStore } from "store/project";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { EProjectStore } from "store/command-palette.store";
export interface ICycleListLayout {} export interface ICycleListLayout {}
@ -47,6 +48,7 @@ export const CycleListLayout: React.FC = observer(() => {
issueActions={issueActions} issueActions={issueActions}
getProjects={getProjects} getProjects={getProjects}
viewId={cycleId} viewId={cycleId}
currentStore={EProjectStore.CYCLE}
/> />
); );
}); });

View File

@ -0,0 +1,48 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { ProjectIssueQuickActions } from "components/issues";
// types
import { IIssue } from "types";
import { EIssueActions } from "../../types";
// constants
import { BaseListRoot } from "../base-list-root";
import { IProjectStore } from "store/project";
import { EProjectStore } from "store/command-palette.store";
export const DraftIssueListLayout: FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
if (!workspaceSlug || !projectId) return null;
// store
const { projectDraftIssuesFilter: projectIssuesFilterStore, projectDraftIssues: projectIssuesStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectIssuesStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
},
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !projectId) return;
projectIssuesStore.removeIssue(workspaceSlug, projectId, issue.id);
},
};
const getProjects = (projectStore: IProjectStore) => projectStore.workspaceProjects;
return (
<BaseListRoot
issueFilterStore={projectIssuesFilterStore}
issueStore={projectIssuesStore}
QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
getProjects={getProjects}
currentStore={EProjectStore.PROJECT}
/>
);
});

View File

@ -11,6 +11,7 @@ import { EIssueActions } from "../../types";
// constants // constants
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
import { IProjectStore } from "store/project"; import { IProjectStore } from "store/project";
import { EProjectStore } from "store/command-palette.store";
export interface IModuleListLayout {} export interface IModuleListLayout {}
@ -48,6 +49,7 @@ export const ModuleListLayout: React.FC = observer(() => {
issueActions={issueActions} issueActions={issueActions}
getProjects={getProjects} getProjects={getProjects}
viewId={moduleId} viewId={moduleId}
currentStore={EProjectStore.MODULE}
/> />
); );
}); });

View File

@ -1,50 +1,49 @@
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { ProjectIssueQuickActions } from "components/issues"; import { ProjectIssueQuickActions } from "components/issues";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { IProjectStore } from "store/project"; // constants
//components
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
import { IProjectStore } from "store/project";
import { EProjectStore } from "store/command-palette.store";
export const ProfileIssuesListLayout: FC = observer(() => { export const ProfileIssuesListLayout: FC = observer(() => {
const {
profileIssueFilters: profileIssueFiltersStore,
profileIssues: profileIssuesStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string };
// store
const { workspaceProfileIssuesFilter: profileIssueFiltersStore, workspaceProfileIssues: profileIssuesStore } =
useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => { [EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug || !userId) return;
profileIssuesStore.updateIssueStructure(group_by, null, issue); await profileIssuesStore.updateIssue(workspaceSlug, userId, issue.id, issue);
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => { [EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
profileIssuesStore.deleteIssue(group_by, null, issue); if (!workspaceSlug || !userId) return;
await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
}, },
}; };
const getProjects = (projectStore: IProjectStore) => projectStore?.workspaceProjects || null; const getProjects = (projectStore: IProjectStore) => projectStore.workspaceProjects;
return null; return (
<BaseListRoot
// return ( issueFilterStore={profileIssueFiltersStore}
// <BaseListRoot issueStore={profileIssuesStore}
// issueFilterStore={profileIssueFiltersStore} QuickActions={ProjectIssueQuickActions}
// issueStore={profileIssuesStore} issueActions={issueActions}
// QuickActions={ProjectIssueQuickActions} getProjects={getProjects}
// issueActions={issueActions} currentStore={EProjectStore.PROFILE}
// getProjects={getProjects} />
// /> );
// );
}); });

View File

@ -11,6 +11,7 @@ import { EIssueActions } from "../../types";
// constants // constants
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
import { IProjectStore } from "store/project"; import { IProjectStore } from "store/project";
import { EProjectStore } from "store/command-palette.store";
export const ListLayout: FC = observer(() => { export const ListLayout: FC = observer(() => {
const router = useRouter(); const router = useRouter();
@ -41,6 +42,7 @@ export const ListLayout: FC = observer(() => {
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}
getProjects={getProjects} getProjects={getProjects}
currentStore={EProjectStore.PROJECT}
/> />
); );
}); });

View File

@ -12,6 +12,7 @@ import { IIssue } from "types";
// components // components
import { BaseListRoot } from "../base-list-root"; import { BaseListRoot } from "../base-list-root";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { EProjectStore } from "store/command-palette.store";
export interface IViewListLayout {} export interface IViewListLayout {}
@ -44,6 +45,7 @@ export const ProjectViewListLayout: React.FC = observer(() => {
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}
getProjects={getProjects} getProjects={getProjects}
currentStore={EProjectStore.PROJECT_VIEW}
/> />
); );
}); });

View File

@ -43,7 +43,9 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
<Popover.Button <Popover.Button
ref={dropdownBtn} ref={dropdownBtn}
className={`px-2.5 py-1 h-5 flex items-center rounded border-[0.5px] border-custom-border-300 duration-300 outline-none w-full ${ className={`px-2.5 py-1 h-5 flex items-center rounded border-[0.5px] border-custom-border-300 duration-300 outline-none w-full ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80" disabled
? "cursor-not-allowed text-custom-text-200 pointer-events-none"
: "cursor-pointer hover:bg-custom-background-80"
}`} }`}
> >
<div className="overflow-hidden flex justify-center items-center gap-2"> <div className="overflow-hidden flex justify-center items-center gap-2">

View File

@ -59,6 +59,8 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
if (workspaceSlug && projectId) fetchProjectLabels(workspaceSlug, projectId).then(() => setIsLoading(false)); if (workspaceSlug && projectId) fetchProjectLabels(workspaceSlug, projectId).then(() => setIsLoading(false));
}; };
if (!value) return null;
const options = (projectLabels ? projectLabels : []).map((label) => ({ const options = (projectLabels ? projectLabels : []).map((label) => ({
value: label.id, value: label.id,
query: label.name, query: label.name,

View File

@ -11,6 +11,7 @@ import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EProjectStore } from "store/command-palette.store";
export const CycleIssueQuickActions: React.FC<IQuickActionProps> = (props) => { export const CycleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
const { issue, handleDelete, handleUpdate, handleRemoveFromView } = props; const { issue, handleDelete, handleUpdate, handleRemoveFromView } = props;
@ -55,6 +56,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
}} }}
currentStore={EProjectStore.CYCLE}
/> />
<CustomMenu placement="bottom-start" ellipsis> <CustomMenu placement="bottom-start" ellipsis>
<CustomMenu.MenuItem <CustomMenu.MenuItem

View File

@ -11,6 +11,7 @@ import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EProjectStore } from "store/command-palette.store";
export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = (props) => { export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
const { issue, handleDelete, handleUpdate, handleRemoveFromView } = props; const { issue, handleDelete, handleUpdate, handleRemoveFromView } = props;
@ -55,6 +56,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
}} }}
currentStore={EProjectStore.MODULE}
/> />
<CustomMenu placement="bottom-start" ellipsis> <CustomMenu placement="bottom-start" ellipsis>
<CustomMenu.MenuItem <CustomMenu.MenuItem

View File

@ -11,6 +11,7 @@ import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EProjectStore } from "store/command-palette.store";
export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) => { export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
const { issue, handleDelete, handleUpdate } = props; const { issue, handleDelete, handleUpdate } = props;
@ -55,6 +56,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) =>
onSubmit={async (data) => { onSubmit={async (data) => {
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data }); if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
}} }}
currentStore={EProjectStore.PROJECT}
/> />
<CustomMenu placement="bottom-start" ellipsis> <CustomMenu placement="bottom-start" ellipsis>
<CustomMenu.MenuItem <CustomMenu.MenuItem

View File

@ -9,14 +9,17 @@ import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot } from "compon
export const ArchivedIssueLayoutRoot: React.FC = observer(() => { export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { archivedIssueFilters: archivedIssueFiltersStore, archivedIssues: archivedIssueStore } = useMobxStore(); const {
projectArchivedIssues: { getIssues, fetchIssues },
projectArchivedIssuesFilter: { fetchFilters },
} = useMobxStore();
useSWR(workspaceSlug && projectId ? `ARCHIVED_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => { useSWR(workspaceSlug && projectId ? `ARCHIVED_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
if (workspaceSlug && projectId) { if (workspaceSlug && projectId) {
await archivedIssueFiltersStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString()); await fetchFilters(workspaceSlug, projectId);
await archivedIssueStore.fetchIssues(workspaceSlug.toString(), projectId.toString()); await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
} }
}); });

View File

@ -0,0 +1,51 @@
import React from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { DraftIssueAppliedFiltersRoot } from "../filters/applied-filters/roots/draft-issue";
import { DraftIssueListLayout } from "../list/roots/draft-issue-root";
import { Spinner } from "@plane/ui";
import { DraftKanBanLayout } from "../kanban/roots/draft-issue-root";
export const DraftIssueLayoutRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const {
projectDraftIssuesFilter: { issueFilters, fetchFilters },
projectDraftIssues: { loader, getIssues, fetchIssues },
} = useMobxStore();
useSWR(workspaceSlug && projectId ? `DRAFT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
if (workspaceSlug && projectId) {
await fetchFilters(workspaceSlug, projectId);
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
}
});
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
return (
<div className="relative w-full h-full flex flex-col overflow-hidden">
<DraftIssueAppliedFiltersRoot />
{loader === "init-loader" ? (
<div className="w-full h-full flex justify-center items-center">
<Spinner />
</div>
) : (
<>
<div className="w-full h-full relative overflow-auto">
{activeLayout === "list" ? (
<DraftIssueListLayout />
) : activeLayout === "kanban" ? (
<DraftKanBanLayout />
) : null}
</div>
</>
)}
</div>
);
});

View File

@ -102,17 +102,15 @@ export const GlobalViewLayoutRoot: React.FC<Props> = observer((props) => {
<GlobalViewEmptyState /> <GlobalViewEmptyState />
) : ( ) : (
<div className="h-full w-full overflow-auto"> <div className="h-full w-full overflow-auto">
<SpreadsheetView {/* <SpreadsheetView
displayProperties={workspaceFilterStore.workspaceDisplayProperties} displayProperties={workspaceFilterStore.workspaceDisplayProperties}
displayFilters={workspaceFilterStore.workspaceDisplayFilters} displayFilters={workspaceFilterStore.workspaceDisplayFilters}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate} handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues} issues={issues}
members={workspaceMembers?.map((m) => m.member)} members={workspaceMembers?.map((m) => m.member)}
labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined} labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined}
handleIssueAction={() => {}}
handleUpdateIssue={handleUpdateIssue}
disableUserActions={false} disableUserActions={false}
/> /> */}
</div> </div>
)} )}
</div> </div>

View File

@ -26,12 +26,20 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
projectIssuesFilter: { issueFilters, fetchFilters }, projectIssuesFilter: { issueFilters, fetchFilters },
} = useMobxStore(); } = useMobxStore();
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { useSWR(
if (workspaceSlug && projectId) { workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null,
await fetchFilters(workspaceSlug, projectId); async () => {
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); if (workspaceSlug && projectId) {
await fetchFilters(workspaceSlug, projectId);
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
}
},
{
onErrorRetry: (error) => {
if (error.status === 404) return;
},
} }
}); );
const activeLayout = issueFilters?.displayFilters?.layout; const activeLayout = issueFilters?.displayFilters?.layout;

View File

@ -7,12 +7,11 @@ import useSWR from "swr";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { import {
ModuleKanBanLayout,
ModuleListLayout,
ProjectViewAppliedFiltersRoot, ProjectViewAppliedFiltersRoot,
ProjectViewCalendarLayout, ProjectViewCalendarLayout,
ProjectViewEmptyState,
ProjectViewGanttLayout, ProjectViewGanttLayout,
ProjectViewKanBanLayout,
ProjectViewListLayout,
ProjectViewSpreadsheetLayout, ProjectViewSpreadsheetLayout,
} from "components/issues"; } from "components/issues";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
@ -33,7 +32,7 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
useSWR(workspaceSlug && projectId && viewId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { useSWR(workspaceSlug && projectId && viewId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => {
if (workspaceSlug && projectId && viewId) { if (workspaceSlug && projectId && viewId) {
await fetchFilters(workspaceSlug, projectId, viewId); await fetchFilters(workspaceSlug, projectId, viewId);
// await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
} }
}); });
@ -49,12 +48,11 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
</div> </div>
) : ( ) : (
<> <>
{/* {(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 && <ProjectViewEmptyState />} */}
<div className="w-full h-full relative overflow-auto"> <div className="w-full h-full relative overflow-auto">
{activeLayout === "list" ? ( {activeLayout === "list" ? (
<ModuleListLayout /> <ProjectViewListLayout />
) : activeLayout === "kanban" ? ( ) : activeLayout === "kanban" ? (
<ModuleKanBanLayout /> <ProjectViewKanBanLayout />
) : activeLayout === "calendar" ? ( ) : activeLayout === "calendar" ? (
<ProjectViewCalendarLayout /> <ProjectViewCalendarLayout />
) : activeLayout === "gantt_chart" ? ( ) : activeLayout === "gantt_chart" ? (

View File

@ -1,6 +1,6 @@
import { IIssueUnGroupedStructure } from "store/issue"; import { IIssueUnGroupedStructure } from "store/issue";
import { SpreadsheetView } from "./spreadsheet-view"; import { SpreadsheetView } from "./spreadsheet-view";
import { useCallback } from "react"; import { FC, useCallback } from "react";
import { IIssue, IIssueDisplayFilterOptions } from "types"; import { IIssue, IIssueDisplayFilterOptions } from "types";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
@ -16,6 +16,8 @@ import {
} from "store/issues"; } from "store/issues";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { EFilterType, TUnGroupedIssues } from "store/issues/types"; import { EFilterType, TUnGroupedIssues } from "store/issues/types";
import { EIssueActions } from "../types";
import { IQuickActionProps } from "../list/list-view-types";
interface IBaseSpreadsheetRoot { interface IBaseSpreadsheetRoot {
issueFiltersStore: issueFiltersStore:
@ -25,16 +27,21 @@ interface IBaseSpreadsheetRoot {
| IProjectIssuesFilterStore; | IProjectIssuesFilterStore;
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore; issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
viewId?: string; viewId?: string;
QuickActions: FC<IQuickActionProps>;
issueActions: {
[EIssueActions.DELETE]: (issue: IIssue) => void;
[EIssueActions.UPDATE]?: (issue: IIssue) => void;
[EIssueActions.REMOVE]?: (issue: IIssue) => void;
};
} }
export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
const { issueFiltersStore, issueStore, viewId } = props; const { issueFiltersStore, issueStore, viewId, QuickActions, issueActions } = props;
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { const {
issueDetail: issueDetailStore,
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore, projectState: projectStateStore,
projectLabel: { projectLabels }, projectLabel: { projectLabels },
@ -46,19 +53,16 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
const issuesResponse = issueStore.getIssues; const issuesResponse = issueStore.getIssues;
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues; const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
const issues = issueIds?.map((id) => issuesResponse?.[id]); const issues = issueIds?.filter((id) => id && issuesResponse?.[id]).map((id) => issuesResponse?.[id]);
const handleIssueAction = async (issue: IIssue, action: "copy" | "delete" | "edit") => { const handleIssues = useCallback(
if (!workspaceSlug || !projectId || !user) return; async (issue: IIssue, action: EIssueActions) => {
if (issueActions[action]) {
if (action === "delete") { issueActions[action]!(issue);
issueDetailStore.deleteIssue(workspaceSlug.toString(), projectId.toString(), issue.id); }
// issueStore.removeIssueFromStructure(null, null, issue); },
} else if (action === "edit") { [issueStore]
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, issue); );
// issueStore.updateIssueStructure(null, null, issue);
}
};
const handleDisplayFiltersUpdate = useCallback( const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
@ -77,33 +81,28 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
[issueFiltersStore, projectId, workspaceSlug] [issueFiltersStore, projectId, workspaceSlug]
); );
const handleUpdateIssue = useCallback(
(issue: IIssue, data: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !user) return;
const payload = {
...issue,
...data,
};
// TODO: add update logic from the new store
// issueStore.updateIssueStructure(null, null, payload);
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, data);
},
[issueDetailStore, projectId, user, workspaceSlug]
);
return ( return (
<SpreadsheetView <SpreadsheetView
displayProperties={issueFiltersStore.issueFilters?.displayProperties ?? {}} displayProperties={issueFiltersStore.issueFilters?.displayProperties ?? {}}
displayFilters={issueFiltersStore.issueFilters?.displayFilters ?? {}} displayFilters={issueFiltersStore.issueFilters?.displayFilters ?? {}}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate} handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues as IIssueUnGroupedStructure} issues={issues as IIssueUnGroupedStructure}
quickActions={(issue) => (
<QuickActions
issue={issue}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
handleUpdate={
issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined
}
handleRemoveFromView={
issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined
}
/>
)}
members={projectMembers?.map((m) => m.member)} members={projectMembers?.map((m) => m.member)}
labels={projectLabels || undefined} labels={projectLabels || undefined}
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined} states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
handleIssueAction={handleIssueAction} handleIssues={handleIssues}
handleUpdateIssue={handleUpdateIssue}
disableUserActions={false} disableUserActions={false}
quickAddCallback={issueStore.quickAddIssue} quickAddCallback={issueStore.quickAddIssue}
viewId={viewId} viewId={viewId}

View File

@ -18,12 +18,12 @@ type Props = {
export const SpreadsheetAssigneeColumn: React.FC<Props> = ({ issue, members, onChange, expandedIssues, disabled }) => { export const SpreadsheetAssigneeColumn: React.FC<Props> = ({ issue, members, onChange, expandedIssues, disabled }) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>
<IssuePropertyAssignee <IssuePropertyAssignee
projectId={issue.project_detail.id ?? null} projectId={issue.project_detail?.id ?? null}
value={issue.assignees} value={issue.assignees}
onChange={(data) => onChange({ assignees: data })} onChange={(data) => onChange({ assignees: data })}
className="h-full w-full" className="h-full w-full"

View File

@ -14,7 +14,7 @@ export const SpreadsheetAttachmentColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -17,7 +17,7 @@ type Props = {
export const SpreadsheetDueDateColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => { export const SpreadsheetDueDateColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -17,12 +17,12 @@ export const SpreadsheetEstimateColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>
<IssuePropertyEstimates <IssuePropertyEstimates
projectId={issue.project_detail.id ?? null} projectId={issue.project_detail?.id ?? null}
value={issue.estimate_point} value={issue.estimate_point}
onChange={(data) => onChange({ estimate_point: data })} onChange={(data) => onChange({ estimate_point: data })}
className="h-full w-full" className="h-full w-full"

View File

@ -1,7 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Popover2 } from "@blueprintjs/popover2"; import { ChevronRight } from "lucide-react";
import { MoreHorizontal, Pencil, Trash2, ChevronRight, Link } from "lucide-react";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
@ -16,8 +15,7 @@ type Props = {
expanded: boolean; expanded: boolean;
handleToggleExpand: (issueId: string) => void; handleToggleExpand: (issueId: string) => void;
properties: IIssueDisplayProperties; properties: IIssueDisplayProperties;
handleEditIssue: (issue: IIssue) => void; quickActions: (issue: IIssue) => React.ReactNode;
handleDeleteIssue: (issue: IIssue) => void;
setIssuePeekOverView: React.Dispatch< setIssuePeekOverView: React.Dispatch<
React.SetStateAction<{ React.SetStateAction<{
workspaceSlug: string; workspaceSlug: string;
@ -35,8 +33,7 @@ export const IssueColumn: React.FC<Props> = ({
handleToggleExpand, handleToggleExpand,
setIssuePeekOverView, setIssuePeekOverView,
properties, properties,
handleEditIssue, quickActions,
handleDeleteIssue,
disableUserActions, disableUserActions,
nestingLevel, nestingLevel,
}) => { }) => {
@ -75,7 +72,7 @@ export const IssueColumn: React.FC<Props> = ({
return ( return (
<> <>
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-custom-border-100"> <div className="group flex items-center w-[28rem] text-sm h-11 top-0 bg-custom-background-100 truncate border-b border-custom-border-100">
{properties.key && ( {properties.key && (
<div <div
className="flex gap-1.5 px-4 pr-0 py-2.5 items-center min-w-min" className="flex gap-1.5 px-4 pr-0 py-2.5 items-center min-w-min"
@ -87,61 +84,7 @@ export const IssueColumn: React.FC<Props> = ({
</span> </span>
{!disableUserActions && ( {!disableUserActions && (
<div className="absolute top-0 left-2.5 opacity-0 group-hover:opacity-100"> <div className="absolute top-0 left-2.5 opacity-0 group-hover:opacity-100">{quickActions(issue)}</div>
<Popover2
isOpen={isOpen}
canEscapeKeyClose
onInteraction={(nextOpenState) => setIsOpen(nextOpenState)}
content={
<div className="flex flex-col whitespace-nowrap rounded-md border border-custom-border-100 p-1 text-xs shadow-lg focus:outline-none min-w-full bg-custom-background-100 space-y-0.5">
<button
type="button"
className="hover:text-custom-text-200 w-full select-none gap-2 rounded p-1 text-left text-custom-text-200 hover:bg-custom-background-80"
onClick={() => {
handleCopyText();
setIsOpen(false);
}}
>
<div className="flex items-center gap-2">
<Link className="h-3 w-3" />
<span>Copy link</span>
</div>
</button>
<button
type="button"
className="hover:text-custom-text-200 w-full select-none gap-2 rounded p-1 text-left text-custom-text-200 hover:bg-custom-background-80"
onClick={() => {
handleEditIssue(issue);
setIsOpen(false);
}}
>
<div className="flex items-center gap-2">
<Pencil className="h-3 w-3" />
<span>Edit issue</span>
</div>
</button>
<button
type="button"
className="w-full select-none gap-2 rounded p-1 text-left text-red-500 hover:bg-custom-background-80"
onClick={() => {
handleDeleteIssue(issue);
setIsOpen(false);
}}
>
<div className="flex items-center gap-2">
<Trash2 className="h-3 w-3" />
<span>Delete issue</span>
</div>
</button>
</div>
}
placement="bottom-start"
>
<MoreHorizontal className="h-5 w-5 text-custom-text-200" />
</Popover2>
</div>
)} )}
</div> </div>

View File

@ -6,13 +6,14 @@ import { IssueColumn } from "components/issues";
import useSubIssue from "hooks/use-sub-issue"; import useSubIssue from "hooks/use-sub-issue";
// types // types
import { IIssue, IIssueDisplayProperties } from "types"; import { IIssue, IIssueDisplayProperties } from "types";
import { EIssueActions } from "components/issues/issue-layouts/types";
type Props = { type Props = {
issue: IIssue; issue: IIssue;
expandedIssues: string[]; expandedIssues: string[];
setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>; setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>;
properties: IIssueDisplayProperties; properties: IIssueDisplayProperties;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; quickActions: (issue: IIssue) => React.ReactNode;
setIssuePeekOverView: React.Dispatch< setIssuePeekOverView: React.Dispatch<
React.SetStateAction<{ React.SetStateAction<{
workspaceSlug: string; workspaceSlug: string;
@ -30,7 +31,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
setExpandedIssues, setExpandedIssues,
setIssuePeekOverView, setIssuePeekOverView,
properties, properties,
handleIssueAction, quickActions,
disableUserActions, disableUserActions,
nestingLevel = 0, nestingLevel = 0,
}) => { }) => {
@ -48,7 +49,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>
@ -57,11 +58,10 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
expanded={isExpanded} expanded={isExpanded}
handleToggleExpand={handleToggleExpand} handleToggleExpand={handleToggleExpand}
properties={properties} properties={properties}
handleEditIssue={() => handleIssueAction(issue, "edit")}
handleDeleteIssue={() => handleIssueAction(issue, "delete")}
setIssuePeekOverView={setIssuePeekOverView} setIssuePeekOverView={setIssuePeekOverView}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
nestingLevel={nestingLevel} nestingLevel={nestingLevel}
quickActions={quickActions}
/> />
{isExpanded && {isExpanded &&
@ -75,7 +75,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
expandedIssues={expandedIssues} expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues} setExpandedIssues={setExpandedIssues}
properties={properties} properties={properties}
handleIssueAction={handleIssueAction} quickActions={quickActions}
setIssuePeekOverView={setIssuePeekOverView} setIssuePeekOverView={setIssuePeekOverView}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
nestingLevel={nestingLevel + 1} nestingLevel={nestingLevel + 1}

View File

@ -20,12 +20,12 @@ export const SpreadsheetLabelColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>
<IssuePropertyLabels <IssuePropertyLabels
projectId={issue.project_detail.id ?? null} projectId={issue.project_detail?.id ?? null}
value={issue.labels} value={issue.labels}
onChange={(data) => onChange({ labels: data })} onChange={(data) => onChange({ labels: data })}
className="h-full w-full" className="h-full w-full"

View File

@ -14,7 +14,7 @@ export const SpreadsheetLinkColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -17,7 +17,7 @@ type Props = {
export const SpreadsheetPriorityColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => { export const SpreadsheetPriorityColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -17,7 +17,7 @@ type Props = {
export const SpreadsheetStartDateColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => { export const SpreadsheetStartDateColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -20,12 +20,12 @@ export const SpreadsheetStateColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>
<IssuePropertyState <IssuePropertyState
projectId={issue.project_detail.id ?? null} projectId={issue.project_detail?.id ?? null}
value={issue.state_detail} value={issue.state_detail}
onChange={(data) => onChange({ state: data.id, state_detail: data })} onChange={(data) => onChange({ state: data.id, state_detail: data })}
className="h-full w-full" className="h-full w-full"

View File

@ -14,7 +14,7 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -17,7 +17,7 @@ export const SpreadsheetUpdatedOnColumn: React.FC<Props> = (props) => {
const isExpanded = expandedIssues.indexOf(issue.id) > -1; const isExpanded = expandedIssues.indexOf(issue.id) > -1;
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return ( return (
<> <>

View File

@ -5,14 +5,39 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { EIssueActions } from "../../types";
import { IIssue } from "types";
import { CycleIssueQuickActions } from "../../quick-action-dropdowns";
export const CycleSpreadsheetLayout: React.FC = observer(() => { export const CycleSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { cycleId } = router.query as { cycleId: string }; const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore(); const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
},
};
return ( return (
<BaseSpreadsheetRoot issueStore={cycleIssueStore} issueFiltersStore={cycleIssueFilterStore} viewId={cycleId} /> <BaseSpreadsheetRoot
issueStore={cycleIssueStore}
issueFiltersStore={cycleIssueFilterStore}
viewId={cycleId}
issueActions={issueActions}
QuickActions={CycleIssueQuickActions}
/>
); );
}); });

View File

@ -6,13 +6,39 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { EIssueActions } from "../../types";
import { IIssue } from "types";
import { ModuleIssueQuickActions } from "../../quick-action-dropdowns";
export const ModuleSpreadsheetLayout: React.FC = observer(() => { export const ModuleSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { moduleId } = router.query as { moduleId: string }; const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string };
const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore(); const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
},
};
return ( return (
<BaseSpreadsheetRoot issueStore={moduleIssueStore} issueFiltersStore={moduleIssueFilterStore} viewId={moduleId} /> <BaseSpreadsheetRoot
issueStore={moduleIssueStore}
issueFiltersStore={moduleIssueFilterStore}
viewId={moduleId}
issueActions={issueActions}
QuickActions={ModuleIssueQuickActions}
/>
); );
}); });

View File

@ -4,8 +4,36 @@ import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
import { EIssueActions } from "../../types";
import { IIssue } from "types";
import { useRouter } from "next/router";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
export const ProjectSpreadsheetLayout: React.FC = observer(() => { export const ProjectSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string };
const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore(); const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore();
return <BaseSpreadsheetRoot issueStore={projectIssuesStore} issueFiltersStore={projectIssueFiltersStore} />;
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectIssuesStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id);
},
};
return (
<BaseSpreadsheetRoot
issueStore={projectIssuesStore}
issueFiltersStore={projectIssueFiltersStore}
issueActions={issueActions}
QuickActions={ProjectIssueQuickActions}
/>
);
}); });

View File

@ -4,8 +4,36 @@ import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
import { EIssueActions } from "../../types";
import { IIssue } from "types";
import { useRouter } from "next/router";
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
export const ProjectViewSpreadsheetLayout: React.FC = observer(() => { export const ProjectViewSpreadsheetLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string };
const { viewIssues: projectViewIssuesStore, viewIssuesFilter: projectViewIssueFiltersStore } = useMobxStore(); const { viewIssues: projectViewIssuesStore, viewIssuesFilter: projectViewIssueFiltersStore } = useMobxStore();
return <BaseSpreadsheetRoot issueStore={projectViewIssuesStore} issueFiltersStore={projectViewIssueFiltersStore} />;
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectViewIssuesStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectViewIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id);
},
};
return (
<BaseSpreadsheetRoot
issueStore={projectViewIssuesStore}
issueFiltersStore={projectViewIssueFiltersStore}
issueActions={issueActions}
QuickActions={ProjectIssueQuickActions}
/>
);
}); });

View File

@ -7,6 +7,7 @@ import { IssuePeekOverview } from "components/issues/issue-peek-overview";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
// types // types
import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState, IUserLite } from "types"; import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState, IUserLite } from "types";
import { EIssueActions } from "../types";
type Props = { type Props = {
displayProperties: IIssueDisplayProperties; displayProperties: IIssueDisplayProperties;
@ -16,8 +17,8 @@ type Props = {
members?: IUserLite[] | undefined; members?: IUserLite[] | undefined;
labels?: IIssueLabel[] | undefined; labels?: IIssueLabel[] | undefined;
states?: IState[] | undefined; states?: IState[] | undefined;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; quickActions: (issue: IIssue) => React.ReactNode;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
openIssuesListModal?: (() => void) | null; openIssuesListModal?: (() => void) | null;
quickAddCallback?: ( quickAddCallback?: (
workspaceSlug: string, workspaceSlug: string,
@ -39,8 +40,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
members, members,
labels, labels,
states, states,
handleIssueAction, quickActions,
handleUpdateIssue, handleIssues,
quickAddCallback, quickAddCallback,
viewId, viewId,
disableUserActions, disableUserActions,
@ -80,6 +81,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
}; };
}, []); }, []);
console.log("spreadsheet issues", issues);
return ( return (
<div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200"> <div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200">
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">
@ -103,18 +106,20 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
<span className="flex items-center justify-center px-4 py-2.5 h-full w-full flex-grow">Issue</span> <span className="flex items-center justify-center px-4 py-2.5 h-full w-full flex-grow">Issue</span>
</div> </div>
{issues.map((issue, index) => ( {issues.map((issue, index) =>
<SpreadsheetIssuesColumn issue ? (
key={`${issue.id}_${index}`} <SpreadsheetIssuesColumn
issue={issue} key={`${issue?.id}_${index}`}
expandedIssues={expandedIssues} issue={issue}
setExpandedIssues={setExpandedIssues} expandedIssues={expandedIssues}
properties={displayProperties} setExpandedIssues={setExpandedIssues}
handleIssueAction={handleIssueAction} properties={displayProperties}
disableUserActions={disableUserActions} quickActions={quickActions}
setIssuePeekOverView={setIssuePeekOverView} disableUserActions={disableUserActions}
/> setIssuePeekOverView={setIssuePeekOverView}
))} />
) : null
)}
</div> </div>
</div> </div>
@ -124,7 +129,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
expandedIssues={expandedIssues} expandedIssues={expandedIssues}
handleDisplayFilterUpdate={handleDisplayFilterUpdate} handleDisplayFilterUpdate={handleDisplayFilterUpdate}
handleUpdateIssue={handleUpdateIssue} handleUpdateIssue={(issue, data) => handleIssues({ ...issue, ...data }, EIssueActions.UPDATE)}
issues={issues} issues={issues}
members={members} members={members}
labels={labels} labels={labels}
@ -185,7 +190,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
workspaceSlug={issuePeekOverview?.workspaceSlug} workspaceSlug={issuePeekOverview?.workspaceSlug}
projectId={issuePeekOverview?.projectId} projectId={issuePeekOverview?.projectId}
issueId={issuePeekOverview?.issueId} issueId={issuePeekOverview?.issueId}
handleIssue={(issueToUpdate: any) => handleUpdateIssue(issueToUpdate as IIssue, issueToUpdate)} handleIssue={(issueToUpdate: any) => handleIssues(issueToUpdate, EIssueActions.UPDATE)}
/> />
)} )}
</div> </div>

View File

@ -16,6 +16,7 @@ import { IssueForm, ConfirmIssueDiscard } from "components/issues";
import type { IIssue } from "types"; import type { IIssue } from "types";
// fetch-keys // fetch-keys
import { USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys"; import { USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys";
import { EProjectStore } from "store/command-palette.store";
export interface IssuesModalProps { export interface IssuesModalProps {
data?: IIssue | null; data?: IIssue | null;
@ -40,6 +41,7 @@ export interface IssuesModalProps {
)[]; )[];
onSubmit?: (data: Partial<IIssue>) => Promise<void>; onSubmit?: (data: Partial<IIssue>) => Promise<void>;
handleSubmit?: (data: Partial<IIssue>) => Promise<void>; handleSubmit?: (data: Partial<IIssue>) => Promise<void>;
currentStore?: EProjectStore;
} }
const issueDraftService = new IssueDraftService(); const issueDraftService = new IssueDraftService();
@ -53,6 +55,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
fieldsToShow = ["all"], fieldsToShow = ["all"],
onSubmit, onSubmit,
handleSubmit, handleSubmit,
currentStore = EProjectStore.PROJECT,
} = props; } = props;
// states // states
@ -63,20 +66,56 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue>>({}); const [prePopulateData, setPreloadedData] = useState<Partial<IIssue>>({});
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string | undefined;
cycleId: string | undefined;
moduleId: string | undefined;
};
const { const {
project: projectStore, project: projectStore,
issue: issueStore, projectIssues: projectIssueStore,
issueDetail: issueDetailStore, viewIssues: projectViewIssueStore,
cycleIssue: cycleIssueStore, workspaceProfileIssues: profileIssueStore,
moduleIssue: moduleIssueStore, cycleIssues: cycleIssueStore,
moduleIssues: moduleIssueStore,
user: userStore, user: userStore,
trackEvent: { postHogEventTracker } trackEvent: { postHogEventTracker },
} = useMobxStore(); } = useMobxStore();
const user = userStore.currentUser; const user = userStore.currentUser;
const issueStores = {
[EProjectStore.PROJECT]: {
store: projectIssueStore,
dataIdToUpdate: activeProject,
viewId: undefined,
},
[EProjectStore.PROJECT_VIEW]: {
store: projectViewIssueStore,
dataIdToUpdate: activeProject,
viewId: undefined,
},
[EProjectStore.PROFILE]: {
store: profileIssueStore,
dataIdToUpdate: user?.id || undefined,
viewId: undefined,
},
[EProjectStore.CYCLE]: {
store: cycleIssueStore,
dataIdToUpdate: activeProject,
viewId: cycleId,
},
[EProjectStore.MODULE]: {
store: moduleIssueStore,
dataIdToUpdate: activeProject,
viewId: moduleId,
},
};
const { store: currentIssueStore, viewId, dataIdToUpdate } = issueStores[currentStore];
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined; const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
const { setValue: setValueInLocalStorage, clearValue: clearLocalStorageValue } = useLocalStorage<any>( const { setValue: setValueInLocalStorage, clearValue: clearLocalStorageValue } = useLocalStorage<any>(
@ -176,60 +215,57 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
// in the url. This has the least priority. // in the url. This has the least priority.
if (projects && projects.length > 0 && !activeProject) if (projects && projects.length > 0 && !activeProject)
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null); setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
}, [activeProject, data, projectId, projects, isOpen]); }, [data, projectId, projects, isOpen]);
const addIssueToCycle = async (issueId: string, cycleId: string) => { const addIssueToCycle = async (issue: IIssue, cycleId: string) => {
if (!workspaceSlug || !activeProject) return; if (!workspaceSlug || !activeProject) return;
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, [issueId]); cycleIssueStore.addIssueToCycle(workspaceSlug, activeProject, cycleId, issue);
}; };
const addIssueToModule = async (issueId: string, moduleId: string) => { const addIssueToModule = async (issue: IIssue, moduleId: string) => {
if (!workspaceSlug || !activeProject) return; if (!workspaceSlug || !activeProject) return;
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, [issueId]); moduleIssueStore.addIssueToModule(workspaceSlug, activeProject, moduleId, issue);
}; };
const createIssue = async (payload: Partial<IIssue>) => { const createIssue = async (payload: Partial<IIssue>) => {
if (!workspaceSlug || !activeProject) return; if (!workspaceSlug || !dataIdToUpdate) return;
await issueDetailStore await currentIssueStore
.createIssue(workspaceSlug.toString(), activeProject, payload) .createIssue(workspaceSlug, dataIdToUpdate, payload, viewId)
.then(async (res) => { .then(async (res) => {
if (!res) throw new Error();
if (handleSubmit) { if (handleSubmit) {
await handleSubmit(res); await handleSubmit(res);
} else { } else {
issueStore.fetchIssues(workspaceSlug.toString(), activeProject); currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation");
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle); if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res, payload.cycle);
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module); if (payload.module && payload.module !== "") await addIssueToModule(res, payload.module);
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",
message: "Issue created successfully.", message: "Issue created successfully.",
}); });
postHogEventTracker( postHogEventTracker("ISSUE_CREATE", {
"ISSUE_CREATE", ...res,
{ state: "SUCCESS",
...res, });
state: "SUCCESS"
}
);
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent)); if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
} }
}).catch(() => { })
.catch(() => {
setToastAlert({ setToastAlert({
type: "error", type: "error",
title: "Error!", title: "Error!",
message: "Issue could not be created. Please try again.", message: "Issue could not be created. Please try again.",
}); });
postHogEventTracker( postHogEventTracker("ISSUE_CREATE", {
"ISSUE_CREATE", state: "FAILED",
{ });
state: "FAILED"
}
);
}); });
if (!createMore) onFormSubmitClose(); if (!createMore) onFormSubmitClose();
@ -269,10 +305,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
}; };
const updateIssue = async (payload: Partial<IIssue>) => { const updateIssue = async (payload: Partial<IIssue>) => {
if (!workspaceSlug || !activeProject || !data) return; if (!workspaceSlug || !dataIdToUpdate || !data) return;
await issueDetailStore await currentIssueStore
.updateIssue(workspaceSlug.toString(), activeProject, data.id, payload) .updateIssue(workspaceSlug, dataIdToUpdate, data.id, payload, viewId)
.then((res) => { .then((res) => {
if (!createMore) onFormSubmitClose(); if (!createMore) onFormSubmitClose();
@ -281,13 +317,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
title: "Success!", title: "Success!",
message: "Issue updated successfully.", message: "Issue updated successfully.",
}); });
postHogEventTracker( postHogEventTracker("ISSUE_UPDATE", {
"ISSUE_UPDATE", ...res,
{ state: "SUCCESS",
...res, });
state: "SUCCESS"
}
);
}) })
.catch(() => { .catch(() => {
setToastAlert({ setToastAlert({
@ -295,17 +328,14 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
title: "Error!", title: "Error!",
message: "Issue could not be updated. Please try again.", message: "Issue could not be updated. Please try again.",
}); });
postHogEventTracker( postHogEventTracker("ISSUE_UPDATE", {
"ISSUE_UPDATE", state: "FAILED",
{ });
state: "FAILED"
}
);
}); });
}; };
const handleFormSubmit = async (formData: Partial<IIssue>) => { const handleFormSubmit = async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !activeProject) return; if (!workspaceSlug || !dataIdToUpdate || !currentStore) return;
const payload: Partial<IIssue> = { const payload: Partial<IIssue> = {
...formData, ...formData,

View File

@ -1,4 +1,6 @@
import { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// components // components
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "components/issues";
// hooks // hooks
@ -6,40 +8,68 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
import { EFilterType } from "store/issues/types";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
export const ProfileIssuesFilter = observer(() => { export const ProfileIssuesFilter = observer(() => {
const { workspace: workspaceStore, profileIssueFilters: profileIssueFiltersStore }: RootStore = useMobxStore(); const router = useRouter();
const { workspaceSlug } = router.query as {
const handleLayoutChange = (_layout: string) => { workspaceSlug: string;
const payload = {
layout: _layout,
group_by: profileIssueFiltersStore.userDisplayFilters.group_by
? profileIssueFiltersStore.userDisplayFilters.group_by
: "state_detail.group",
};
profileIssueFiltersStore.handleIssueFilters("userDisplayFilters", payload);
}; };
const handleFilters = (key: any, value: any) => { const {
let updatesFilters: any = profileIssueFiltersStore?.userFilters; workspace: workspaceStore,
updatesFilters = updatesFilters[key] || []; workspaceProfileIssuesFilter: { issueFilters, updateFilters },
if (updatesFilters && updatesFilters.length > 0 && updatesFilters.includes(value)) }: RootStore = useMobxStore();
updatesFilters = updatesFilters.filter((item: any) => item !== value);
else updatesFilters.push(value);
profileIssueFiltersStore.handleIssueFilters("userFilters", { [key]: updatesFilters });
};
const handleDisplayFilters = (value: any) => profileIssueFiltersStore.handleIssueFilters("userDisplayFilters", value);
const handleDisplayProperties = (value: any) =>
profileIssueFiltersStore.handleIssueFilters("userDisplayProperties", value);
const states = undefined; const states = undefined;
const labels = workspaceStore.workspaceLabels || undefined; const labels = workspaceStore.workspaceLabels || undefined;
const members = undefined; const members = undefined;
const activeLayout = profileIssueFiltersStore?.userDisplayFilters?.layout; const activeLayout = issueFilters?.displayFilters?.layout;
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, { layout: layout });
},
[workspaceSlug, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues });
},
[workspaceSlug, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[workspaceSlug, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_PROPERTIES, property);
},
[workspaceSlug, updateFilters]
);
return ( return (
<div className="relative flex items-center justify-end gap-2"> <div className="relative flex items-center justify-end gap-2">
@ -51,11 +81,11 @@ export const ProfileIssuesFilter = observer(() => {
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end">
<FilterSelection <FilterSelection
filters={profileIssueFiltersStore.userFilters}
handleFiltersUpdate={handleFilters}
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
} }
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
states={states} states={states}
labels={labels} labels={labels}
members={members} members={members}
@ -64,13 +94,13 @@ export const ProfileIssuesFilter = observer(() => {
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection <DisplayFiltersSelection
displayFilters={profileIssueFiltersStore.userDisplayFilters}
displayProperties={profileIssueFiltersStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFilters}
handleDisplayPropertiesUpdate={handleDisplayProperties}
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
} }
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/> />
</FiltersDropdown> </FiltersDropdown>
</div> </div>

View File

@ -0,0 +1,63 @@
import React, { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// layouts
import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components
import { UserProfileHeader } from "components/headers";
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
import { ProfileIssuesAppliedFiltersRoot } from "components/issues";
import { Spinner } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
interface IProfileIssuesPage {
type: "assigned" | "subscribed" | "created";
}
export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
const router = useRouter();
const { workspaceSlug, userId } = router.query as {
workspaceSlug: string;
userId: string;
};
const {
workspaceProfileIssues: { loader, getIssues, fetchIssues },
workspaceProfileIssuesFilter: { issueFilters, fetchFilters },
}: RootStore = useMobxStore();
useSWR(workspaceSlug && userId ? `CURRENT_WORKSPACE_PROFILE_ISSUES_${workspaceSlug}_${userId}` : null, async () => {
if (workspaceSlug && userId) {
await fetchFilters(workspaceSlug);
await fetchIssues(workspaceSlug, userId, getIssues ? "mutation" : "init-loader", props.type);
}
});
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
return (
<>
{loader === "init-loader" ? (
<div className="flex justify-center items-center w-full h-full">
<Spinner />
</div>
) : (
<>
<ProfileIssuesAppliedFiltersRoot />
<div className="w-full h-full relative overflow-auto -z-1">
{activeLayout === "list" ? (
<ProfileIssuesListLayout />
) : activeLayout === "kanban" ? (
<ProfileIssuesKanBanLayout />
) : null}
</div>
</>
)}
</>
);
});

View File

@ -9,12 +9,17 @@ import { CreateUpdateDraftIssueModal } from "components/issues";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { EProjectStore } from "store/command-palette.store";
export const WorkspaceSidebarQuickAction = observer(() => { export const WorkspaceSidebarQuickAction = observer(() => {
// states // states
const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false); const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false);
const { theme: themeStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore(); const {
theme: themeStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const { storedValue, clearValue } = useLocalStorage<any>("draftedIssue", JSON.stringify({})); const { storedValue, clearValue } = useLocalStorage<any>("draftedIssue", JSON.stringify({}));
@ -34,24 +39,26 @@ export const WorkspaceSidebarQuickAction = observer(() => {
/> />
<div <div
className={`flex items-center justify-between w-full cursor-pointer px-4 mt-4 ${isSidebarCollapsed ? "flex-col gap-1" : "gap-2" className={`flex items-center justify-between w-full cursor-pointer px-4 mt-4 ${
}`} isSidebarCollapsed ? "flex-col gap-1" : "gap-2"
}`}
> >
<div <div
className={`relative flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${isSidebarCollapsed className={`relative flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${
? "px-2 hover:bg-custom-sidebar-background-80" isSidebarCollapsed
: "px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200" ? "px-2 hover:bg-custom-sidebar-background-80"
}`} : "px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
}`}
> >
<button <button
type="button" type="button"
className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none ${isSidebarCollapsed ? "justify-center" : "" className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none ${
}`} isSidebarCollapsed ? "justify-center" : ""
}`}
onClick={() => { onClick={() => {
setTrackElement("APP_SIDEBAR_QUICK_ACTIONS"); setTrackElement("APP_SIDEBAR_QUICK_ACTIONS");
commandPaletteStore.toggleCreateIssueModal(true); commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
} }}
}
> >
<PenSquare className="h-4 w-4 text-custom-sidebar-text-300" /> <PenSquare className="h-4 w-4 text-custom-sidebar-text-300" />
{!isSidebarCollapsed && <span className="text-sm font-medium">New Issue</span>} {!isSidebarCollapsed && <span className="text-sm font-medium">New Issue</span>}
@ -63,8 +70,9 @@ export const WorkspaceSidebarQuickAction = observer(() => {
<button <button
type="button" type="button"
className={`flex items-center justify-center rounded flex-shrink-0 py-1.5 ml-1.5 ${isSidebarCollapsed ? "hidden" : "block" className={`flex items-center justify-center rounded flex-shrink-0 py-1.5 ml-1.5 ${
}`} isSidebarCollapsed ? "hidden" : "block"
}`}
> >
<ChevronDown <ChevronDown
size={16} size={16}
@ -73,8 +81,9 @@ export const WorkspaceSidebarQuickAction = observer(() => {
</button> </button>
<div <div
className={`fixed h-10 pt-2 w-[203px] left-4 opacity-0 group-hover:opacity-100 mt-0 pointer-events-none group-hover:pointer-events-auto ${isSidebarCollapsed ? "top-[5.5rem]" : "top-24" className={`fixed h-10 pt-2 w-[203px] left-4 opacity-0 group-hover:opacity-100 mt-0 pointer-events-none group-hover:pointer-events-auto ${
}`} isSidebarCollapsed ? "top-[5.5rem]" : "top-24"
}`}
> >
<div className="w-full h-full"> <div className="w-full h-full">
<button <button
@ -91,10 +100,11 @@ export const WorkspaceSidebarQuickAction = observer(() => {
</div> </div>
<button <button
className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none ${isSidebarCollapsed className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none ${
? "hover:bg-custom-sidebar-background-80" isSidebarCollapsed
: "shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200" ? "hover:bg-custom-sidebar-background-80"
}`} : "shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
}`}
onClick={() => commandPaletteStore.toggleCommandPaletteModal(true)} onClick={() => commandPaletteStore.toggleCommandPaletteModal(true)}
> >
<Search className="h-4 w-4 text-custom-sidebar-text-300" /> <Search className="h-4 w-4 text-custom-sidebar-text-300" />

View File

@ -1,67 +1,15 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout"; import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components // components
import { UserProfileHeader } from "components/headers"; import { UserProfileHeader } from "components/headers";
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
import { Spinner } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => { const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => <ProfileIssuesPage type="assigned" />);
const {
workspace: workspaceStore,
project: projectStore,
profileIssueFilters: profileIssueFiltersStore,
profileIssues: profileIssuesStore,
}: RootStore = useMobxStore();
const router = useRouter();
const { workspaceSlug, userId } = router.query as {
workspaceSlug: string;
userId: string;
};
const { isLoading } = useSWR(`PROFILE_ISSUES_${workspaceSlug}_${userId}`, async () => {
if (workspaceSlug && userId) {
// workspace labels
workspaceStore.setWorkspaceSlug(workspaceSlug);
await workspaceStore.fetchWorkspaceLabels(workspaceSlug);
await projectStore.fetchProjects(workspaceSlug);
//profile issues
await profileIssuesStore.fetchIssues(workspaceSlug, userId, "assigned");
}
});
const activeLayout = profileIssueFiltersStore.userDisplayFilters.layout;
return (
<>
{isLoading ? (
<div className="flex justify-center items-center w-full h-full">
<Spinner />
</div>
) : (
<div className="w-full h-full relative overflow-auto -z-1">
{activeLayout === "list" ? (
<ProfileIssuesListLayout />
) : activeLayout === "kanban" ? (
<ProfileIssuesKanBanLayout />
) : null}
</div>
)}
</>
);
});
ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) { ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return ( return (

View File

@ -1,63 +1,16 @@
import { ReactElement } from "react"; import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// store // store
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout"; import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components // components
import { UserProfileHeader } from "components/headers"; import { UserProfileHeader } from "components/headers";
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
import { Spinner } from "@plane/ui";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileCreatedIssuesPage: NextPageWithLayout = () => { const ProfileCreatedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="created" />;
const {
workspace: workspaceStore,
project: projectStore,
profileIssueFilters: profileIssueFiltersStore,
profileIssues: profileIssuesStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, userId } = router.query;
const { isLoading } = useSWR(`PROFILE_ISSUES_CREATED_${workspaceSlug}_${userId}`, async () => {
if (workspaceSlug && userId) {
// workspace labels
workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
await workspaceStore.fetchWorkspaceLabels(workspaceSlug.toString());
await projectStore.fetchProjects(workspaceSlug.toString());
//profile issues
await profileIssuesStore.fetchIssues(workspaceSlug.toString(), userId.toString(), "created");
}
});
const activeLayout = profileIssueFiltersStore.userDisplayFilters.layout;
return (
<>
{isLoading ? (
<div className="flex justify-center items-center w-full h-full">
<Spinner />
</div>
) : (
<div className="w-full h-full relative overflow-auto -z-1">
{activeLayout === "list" ? (
<ProfileIssuesListLayout />
) : activeLayout === "kanban" ? (
<ProfileIssuesKanBanLayout />
) : null}
</div>
)}
</>
);
};
ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) { ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return ( return (

View File

@ -1,63 +1,16 @@
import { ReactElement } from "react"; import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// store // store
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout"; import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components // components
import { UserProfileHeader } from "components/headers"; import { UserProfileHeader } from "components/headers";
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
import { Spinner } from "@plane/ui";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileSubscribedIssuesPage: NextPageWithLayout = () => { const ProfileSubscribedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="subscribed" />;
const {
workspace: workspaceStore,
project: projectStore,
profileIssueFilters: profileIssueFiltersStore,
profileIssues: profileIssuesStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, userId } = router.query;
const { isLoading } = useSWR(`PROFILE_ISSUES_SUBSCRIBED_${workspaceSlug}_${userId}`, async () => {
if (workspaceSlug && userId) {
// workspace labels
workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
await workspaceStore.fetchWorkspaceLabels(workspaceSlug.toString());
await projectStore.fetchProjects(workspaceSlug.toString());
//profile issues
await profileIssuesStore.fetchIssues(workspaceSlug.toString(), userId.toString(), "subscribed");
}
});
const activeLayout = profileIssueFiltersStore.userDisplayFilters.layout;
return (
<>
{isLoading ? (
<div className="flex justify-center items-center w-full h-full">
<Spinner />
</div>
) : (
<div className="w-full h-full relative overflow-auto -z-1">
{activeLayout === "list" ? (
<ProfileIssuesListLayout />
) : activeLayout === "kanban" ? (
<ProfileIssuesKanBanLayout />
) : null}
</div>
)}
</>
);
};
ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) { ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return ( return (

View File

@ -10,6 +10,7 @@ import { ProjectDraftIssueHeader } from "components/headers";
import { X, PenSquare } from "lucide-react"; import { X, PenSquare } from "lucide-react";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { DraftIssueLayoutRoot } from "components/issues/issue-layouts/roots/draft-issue-layout-root";
const ProjectDraftIssuesPage: NextPageWithLayout = () => { const ProjectDraftIssuesPage: NextPageWithLayout = () => {
const router = useRouter(); const router = useRouter();
@ -21,14 +22,14 @@ const ProjectDraftIssuesPage: NextPageWithLayout = () => {
<button <button
type="button" type="button"
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/`)} onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/`)}
className="flex items-center gap-1.5 rounded border border-custom-border-200 px-3 py-1.5 text-xs" className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs"
> >
<PenSquare className="h-3 w-3 text-custom-text-300" /> <PenSquare className="h-4 w-4" />
<span>Draft Issues</span> <span>Draft Issues</span>
<X className="h-3 w-3" /> <X className="h-3 w-3" />
</button> </button>
</div> </div>
<DraftIssueLayoutRoot />
</div> </div>
); );
}; };

View File

@ -18,8 +18,8 @@ export class IssueArchiveService extends APIService {
} }
async getV3ArchivedIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<any> { async getV3ArchivedIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<any> {
return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/`, { return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, {
params: queries, params: { ...queries, archived: true },
}) })
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {

View File

@ -17,13 +17,13 @@ export class IssueDraftService extends APIService {
}); });
} }
async getV3DraftIssues(workspaceSlug: string, projectId: string, params?: any): Promise<any> { async getV3DraftIssues(workspaceSlug: string, projectId: string, query?: any): Promise<any> {
return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, { return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issues/?draft=true`, {
params, params: { ...query },
}) })
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response?.data;
}); });
} }

Some files were not shown because too many files have changed in this diff Show More