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 GitHub
parent eb78fd6088
commit 2bf7e63625
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 3187 additions and 912 deletions

View File

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

View File

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

View File

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

View File

@ -1,20 +1,74 @@
import { FC } from "react";
import { FC, useCallback } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
// ui
import { Breadcrumbs, LayersIcon } from "@plane/ui";
// 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(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const {
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 (
<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">
@ -44,6 +98,37 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
/>
</Breadcrumbs>
</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>
);

View File

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

View File

@ -72,7 +72,7 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
<p className="text-sm text-custom-text-200">
Are you sure you want to delete issue{" "}
<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>
{""}? All of the data related to the issue will be permanently removed. This action cannot be
undone.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,9 +6,17 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { EmptyState } from "components/common";
// assets
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(() => {
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
const router = useRouter();
const { viewId } = router.query as { viewId: string };
const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
return (
<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} />,
onClick: () => {
setTrackElement("VIEW_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true)
}
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT_VIEW);
},
}}
/>
</div>

View File

@ -6,9 +6,13 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
import { EProjectStore } from "store/command-palette.store";
export const ProjectEmptyState: React.FC = observer(() => {
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
return (
<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} />,
onClick: () => {
setTrackElement("PROJECT_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true)
}
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
},
}}
/>
</div>

View File

@ -7,23 +7,24 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { AppliedFiltersList } from "components/issues";
// types
import { IIssueFilterOptions } from "types";
import { EFilterType } from "store/issues/types";
export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const {
archivedIssueFilters: archivedIssueFiltersStore,
projectArchivedIssuesFilter: { issueFilters, updateFilters },
projectLabel: { projectLabels },
projectMember: { projectMembers },
projectState: projectStateStore,
} = useMobxStore();
const userFilters = archivedIssueFiltersStore.userFilters;
const userFilters = issueFilters?.filters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters).forEach(([key, value]) => {
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
if (!value) 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
if (!value) {
archivedIssueFiltersStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: null,
},
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
[key]: null,
});
return;
}
// remove the passed value from the key
let newValues = archivedIssueFiltersStore.userFilters?.[key] ?? [];
let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
archivedIssueFiltersStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: newValues,
},
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
[key]: newValues,
});
};
@ -59,12 +56,12 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters).forEach((key) => {
Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
archivedIssueFiltersStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: { ...newFilters },
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
...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-root";
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;
//Todo fix sort order in the structure
issueStore.updateIssue(workspaceSlug, issue.project, issue.id, {
start_date: payload.start_date,
target_date: payload.target_date,
});
issueStore.updateIssue(
workspaceSlug,
issue.project,
issue.id,
{
start_date: payload.start_date,
target_date: payload.target_date,
},
viewId
);
};
const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER;

View File

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

View File

@ -17,6 +17,7 @@ interface IssueBlockProps {
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;
displayProperties: IIssueDisplayProperties | null;
isReadOnly: boolean;
}
export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
@ -30,6 +31,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
handleIssues,
quickActions,
displayProperties,
isReadOnly,
} = props;
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}
displayProperties={displayProperties}
showEmptyGroup={showEmptyGroup}
isReadOnly={isReadOnly}
/>
</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;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
isReadOnly: boolean;
}
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
@ -27,6 +28,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
handleIssues,
quickActions,
displayProperties,
isReadOnly,
} = props;
return (
@ -50,6 +52,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
columnId={columnId}
sub_group_id={sub_group_id}
isDragDisabled={isDragDisabled}
isReadOnly={isReadOnly}
/>
);
})}

View File

@ -13,6 +13,7 @@ import { getValueFromObject } from "constants/issue";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { EIssueActions } from "../types";
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store";
export interface IGroupByKanBan {
issues: IIssueResponse;
@ -40,6 +41,9 @@ export interface IGroupByKanBan {
viewId?: string
) => Promise<IIssue | undefined>;
viewId?: string;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
isReadOnly: boolean;
}
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
@ -63,6 +67,9 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
isDragStarted,
quickAddCallback,
viewId,
disableIssueCreation,
isReadOnly,
currentStore,
} = props;
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}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
</div>
)}
@ -95,10 +104,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
verticalAlignPosition(_list) ? `w-[0px] overflow-hidden` : `w-full transition-all`
}`}
>
<Droppable
droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}
isDropDisabled={isDragDisabled}
>
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
{(provided: any, snapshot: any) => (
<div
className={`w-full h-full relative transition-all ${
@ -118,6 +124,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
handleIssues={handleIssues}
quickActions={quickActions}
displayProperties={displayProperties}
isReadOnly={isReadOnly}
/>
) : (
isDragDisabled && (
@ -149,7 +156,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
)}
</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="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(
@ -157,7 +164,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
)}"`}
</div>
</div>
)}
)} */}
</div>
))}
</div>
@ -192,6 +199,9 @@ export interface IKanBan {
viewId?: string
) => Promise<IIssue | undefined>;
viewId?: string;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
isReadOnly: boolean;
}
export const KanBan: React.FC<IKanBan> = observer((props) => {
@ -218,6 +228,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted,
quickAddCallback,
viewId,
disableIssueCreation,
isReadOnly,
currentStore,
} = props;
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
@ -246,6 +259,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
@ -271,6 +287,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
@ -296,6 +315,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
@ -321,6 +343,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
@ -346,6 +371,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
@ -371,6 +399,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
@ -396,6 +427,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
isDragStarted={isDragStarted}
quickAddCallback={quickAddCallback}
viewId={viewId}
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
/>
)}
</div>

View File

@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card";
// ui
import { Avatar } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IAssigneesHeader {
column_id: string;
@ -15,6 +16,8 @@ export interface IAssigneesHeader {
issues_count: number;
kanBanToggle: any;
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
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,
kanBanToggle,
handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props;
const assignee = column_value ?? null;
@ -56,6 +61,8 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
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 { HeaderSubGroupByCard } from "./sub-group-by-card";
import { Icon } from "./assignee";
import { EProjectStore } from "store/command-palette.store";
export interface ICreatedByHeader {
column_id: string;
@ -14,6 +15,8 @@ export interface ICreatedByHeader {
issues_count: number;
kanBanToggle: any;
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
@ -26,6 +29,8 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
issues_count,
kanBanToggle,
handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props;
const createdBy = column_value ?? null;
@ -53,6 +58,8 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
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";
// types
import { IIssue, ISearchIssueResponse } from "types";
import { EProjectStore } from "store/command-palette.store";
interface IHeaderGroupByCard {
sub_group_by: string | null;
@ -26,13 +27,26 @@ interface IHeaderGroupByCard {
kanBanToggle: any;
handleKanBanToggle: any;
issuePayload: Partial<IIssue>;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
const moduleService = new ModuleService();
const issueService = new IssueService();
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 [isOpen, setIsOpen] = React.useState(false);
@ -84,7 +98,12 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
return (
<>
<CreateUpdateIssueModal isOpen={isOpen} handleClose={() => setIsOpen(false)} prePopulateData={issuePayload} />
<CreateUpdateIssueModal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
prePopulateData={issuePayload}
currentStore={currentStore}
/>
{renderExistingIssueModal && (
<ExistingIssuesListModal
isOpen={openExistingIssueListModal}
@ -126,30 +145,31 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
</div>
)}
{renderExistingIssueModal ? (
<CustomMenu
width="auto"
customButton={
<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">
<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>
<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-[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>
)}
{!disableIssueCreation &&
(renderExistingIssueModal ? (
<CustomMenu
width="auto"
customButton={
<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">
<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>
<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-[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>
))}
</div>
</>
);

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
// Icons
import { PriorityIcon } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IPriorityHeader {
column_id: string;
@ -17,6 +18,8 @@ export interface IPriorityHeader {
issues_count: number;
kanBanToggle: any;
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
@ -29,6 +32,8 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
issues_count,
kanBanToggle,
handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props;
const priority = column_value || null;
@ -56,6 +61,8 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
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";
// emoji helper
import { renderEmoji } from "helpers/emoji.helper";
import { EProjectStore } from "store/command-palette.store";
export interface IProjectHeader {
column_id: string;
@ -15,6 +16,8 @@ export interface IProjectHeader {
issues_count: number;
kanBanToggle: any;
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
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,
kanBanToggle,
handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props;
const project = column_value ?? null;
@ -56,6 +61,8 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
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 { HeaderSubGroupByCard } from "./sub-group-by-card";
import { StateGroupIcon } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
export interface IStateGroupHeader {
column_id: string;
@ -14,6 +15,8 @@ export interface IStateGroupHeader {
issues_count: number;
kanBanToggle: any;
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
@ -32,6 +35,8 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
issues_count,
kanBanToggle,
handleKanBanToggle,
disableIssueCreation,
currentStore,
} = props;
const stateGroup = column_value || null;
@ -59,6 +64,8 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
))}
</>

View File

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

View File

@ -7,6 +7,7 @@ import { AssigneesHeader } from "./assignee";
import { PriorityHeader } from "./priority";
import { LabelHeader } from "./label";
import { CreatedByHeader } from "./created_by";
import { EProjectStore } from "store/command-palette.store";
export interface IKanBanSubGroupByHeaderRoot {
column_id: string;
@ -16,10 +17,22 @@ export interface IKanBanSubGroupByHeaderRoot {
issues_count: number;
kanBanToggle: any;
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
}
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 (
<>
@ -33,6 +46,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)}
{sub_group_by && sub_group_by === "state_detail.group" && (
@ -45,6 +60,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)}
{sub_group_by && sub_group_by === "priority" && (
@ -57,6 +74,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)}
{sub_group_by && sub_group_by === "labels" && (
@ -69,6 +88,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)}
{sub_group_by && sub_group_by === "assignees" && (
@ -81,6 +102,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)}
{sub_group_by && sub_group_by === "created_by" && (
@ -93,6 +116,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
issues_count={issues_count}
kanBanToggle={kanBanToggle}
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;
displayProperties: IIssueDisplayProperties | null;
showEmptyGroup: boolean;
isReadOnly: boolean;
}
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) => {
handleIssues(
@ -89,7 +90,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
projectId={issue?.project_detail?.id || null}
value={issue?.state || null}
onChange={handleState}
disabled={false}
disabled={isReadOnly}
hideDropdownArrow
/>
)}
@ -99,7 +100,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<IssuePropertyPriority
value={issue?.priority || null}
onChange={handlePriority}
disabled={false}
disabled={isReadOnly}
hideDropdownArrow
/>
)}
@ -110,7 +111,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
projectId={issue?.project_detail?.id || null}
value={issue?.labels || null}
onChange={handleLabel}
disabled={false}
disabled={isReadOnly}
hideDropdownArrow
/>
)}
@ -120,7 +121,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<IssuePropertyDate
value={issue?.start_date || null}
onChange={(date: string) => handleStartDate(date)}
disabled={false}
disabled={isReadOnly}
placeHolder="Start date"
/>
)}
@ -130,7 +131,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<IssuePropertyDate
value={issue?.target_date || null}
onChange={(date: string) => handleTargetDate(date)}
disabled={false}
disabled={isReadOnly}
placeHolder="Target date"
/>
)}
@ -142,7 +143,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
value={issue?.assignees || null}
hideDropdownArrow
onChange={handleAssignee}
disabled={false}
disabled={isReadOnly}
multiple
/>
)}
@ -153,7 +154,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
projectId={issue?.project_detail?.id || null}
value={issue?.estimate_point || null}
onChange={handleEstimate}
disabled={false}
disabled={isReadOnly}
hideDropdownArrow
/>
)}

View File

@ -10,6 +10,7 @@ import { IIssue } from "types";
import { EIssueActions } from "../../types";
// components
import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface ICycleKanBanLayout {}
@ -18,7 +19,11 @@ export const CycleKanBanLayout: React.FC = observer(() => {
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
// store
const { cycleIssues: cycleIssueStore, cycleIssueKanBanView: cycleIssueKanBanViewStore } = useMobxStore();
const {
cycleIssues: cycleIssueStore,
cycleIssuesFilter: cycleIssueFilterStore,
cycleIssueKanBanView: cycleIssueKanBanViewStore,
} = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
@ -39,10 +44,12 @@ export const CycleKanBanLayout: React.FC = observer(() => {
<BaseKanBanRoot
issueActions={issueActions}
issueStore={cycleIssueStore}
issuesFilterStore={cycleIssueFilterStore}
kanbanViewStore={cycleIssueKanBanViewStore}
showLoader={true}
QuickActions={CycleIssueQuickActions}
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
import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store";
export interface IModuleKanBanLayout {}
@ -20,8 +21,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
// store
const {
moduleIssues: moduleIssueStore,
moduleIssuesFilter: moduleIssueFilterStore,
moduleIssueKanBanView: moduleIssueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore();
// const handleIssues = useCallback(
@ -58,17 +59,19 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
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 (
<BaseKanBanRoot
issueActions={issueActions}
issueStore={moduleIssueStore}
issuesFilterStore={moduleIssueFilterStore}
kanbanViewStore={moduleIssueKanBanViewStore}
showLoader={true}
QuickActions={ModuleIssueQuickActions}
viewId={moduleId}
currentStore={EProjectStore.MODULE}
/>
);
});

View File

@ -1,166 +1,48 @@
import { FC, useCallback, useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { DragDropContext } from "@hello-pangea/dnd";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { KanBanSwimLanes } from "../swimlanes";
import { KanBan } from "../default";
import { ProjectIssueQuickActions } from "components/issues";
import { Spinner } from "@plane/ui";
// constants
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
// types
import { IIssue } from "types";
// constants
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 {
workspace: workspaceStore,
project: projectStore,
projectMember: { projectMembers },
projectState: projectStateStore,
profileIssues: profileIssuesStore,
profileIssueFilters: profileIssueFiltersStore,
workspaceProfileIssues: profileIssuesStore,
workspaceProfileIssuesFilter: profileIssueFiltersStore,
issueKanBanView: issueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug } = router.query;
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !userId) return;
const issues = profileIssuesStore?.getIssues;
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);
await profileIssuesStore.updateIssue(workspaceSlug, userId, issue.id, issue);
},
[profileIssuesStore, issueDetailStore, workspaceSlug]
);
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !userId) return;
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
issueKanBanViewStore.handleKanBanToggle(toggle, value);
await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
},
};
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 (
<>
{profileIssuesStore.loader ? (
<div className="w-full h-full flex justify-center items-center">
<Spinner />
</div>
) : (
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragEnd={onDragEnd}>
{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>
)}
</>
<BaseKanBanRoot
issueActions={issueActions}
issuesFilterStore={profileIssueFiltersStore}
issueStore={profileIssuesStore}
kanbanViewStore={issueKanBanViewStore}
showLoader={true}
QuickActions={ProjectIssueQuickActions}
currentStore={EProjectStore.PROFILE}
/>
);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,26 @@
import { observer } from "mobx-react-lite";
// components
import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface IEmptyHeader {
column_id: string;
column_value: any;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
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 { ExistingIssuesListModal } from "components/core";
import { CustomMenu } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
// mobx
import { observer } from "mobx-react-lite";
// types
import { IIssue, ISearchIssueResponse } from "types";
import { EProjectStore } from "store/command-palette.store";
interface IHeaderGroupByCard {
icon?: React.ReactNode;
title: string;
count: number;
issuePayload: Partial<IIssue>;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: IHeaderGroupByCard) => {
const router = useRouter();
const { workspaceSlug, projectId, moduleId, cycleId } = router.query;
export const HeaderGroupByCard = observer(
({ icon, title, count, issuePayload, disableIssueCreation, currentStore }: IHeaderGroupByCard) => {
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 ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId) return;
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId) return;
const payload = {
issues: data.map((i) => i.id),
};
const payload = {
issues: data.map((i) => i.id),
// await moduleService
// .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
// .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.",
// })
// );
};
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId) return;
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId) return;
const payload = {
issues: data.map((i) => i.id),
};
const payload = {
issues: data.map((i) => i.id),
// await issueService
// .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
// .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.",
// });
// });
};
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} />
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>
)}
<CreateUpdateIssueModal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
handleSubmit={(data: Partial<IIssue>) => {
console.log(data);
return Promise.resolve();
}}
prePopulateData={issuePayload}
/>
<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 && (
<ExistingIssuesListModal
isOpen={openExistingIssueListModal}
handleClose={() => setOpenExistingIssueListModal(false)}
searchParams={ExistingIssuesListModalPayload}
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
{!disableIssueCreation &&
(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>
))}
<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";
// mobx
import { observer } from "mobx-react-lite";
import { EProjectStore } from "store/command-palette.store";
export interface IListGroupByHeaderRoot {
column_id: string;
column_value: any;
group_by: string | null;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
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 (
<>
{!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" && (
<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" && (
<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" && (
<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" && (
<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" && (
<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" && (
<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" && (
<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";
// components
import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface ILabelHeader {
column_id: string;
column_value: any;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
const Icon = ({ color }: any) => (
@ -14,7 +17,7 @@ const Icon = ({ color }: any) => (
);
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;
@ -26,6 +29,8 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
title={column_value?.name || ""}
count={issues_count}
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";
// components
import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
export interface IPriorityHeader {
column_id: string;
column_value: any;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
const Icon = ({ priority }: any) => (
@ -37,7 +40,7 @@ const Icon = ({ priority }: any) => (
);
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;
@ -49,6 +52,8 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
title={priority?.title || ""}
count={issues_count}
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";
// emoji helper
import { renderEmoji } from "helpers/emoji.helper";
import { EProjectStore } from "store/command-palette.store";
export interface IProjectHeader {
column_id: string;
column_value: any;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
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;
@ -26,6 +29,8 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
title={project?.name || ""}
count={issues_count}
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";
// helpers
import { capitalizeFirstLetter } from "helpers/string.helper";
import { EProjectStore } from "store/command-palette.store";
export interface IStateGroupHeader {
column_id: string;
column_value: any;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
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) => {
const { column_id, column_value, issues_count } = props;
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const stateGroup = column_value ?? null;
@ -32,6 +35,8 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
title={capitalizeFirstLetter(stateGroup?.key) || ""}
count={issues_count}
issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
/>
)}
</>

View File

@ -3,15 +3,18 @@ import { observer } from "mobx-react-lite";
// components
import { HeaderGroupByCard } from "./group-by-card";
import { Icon } from "./state-group";
import { EProjectStore } from "store/command-palette.store";
export interface IStateHeader {
column_id: string;
column_value: any;
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
}
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;
@ -23,6 +26,8 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
title={state?.name || ""}
count={issues_count}
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 { IProjectStore } from "store/project";
import { EIssueActions } from "../../types";
import { EProjectStore } from "store/command-palette.store";
export const ArchivedIssueListLayout: FC = observer(() => {
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 = {
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
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 null;
// return (
// <BaseListRoot
// issueFilterStore={archivedIssueFiltersStore}
// issueStore={archivedIssueStore}
// QuickActions={ArchivedIssueQuickActions}
// issueActions={issueActions}
// getProjects={getProjects}
// />
// );
return (
<BaseListRoot
issueFilterStore={archivedIssueFiltersStore}
issueStore={archivedIssueStore}
QuickActions={ArchivedIssueQuickActions}
issueActions={issueActions}
getProjects={getProjects}
currentStore={EProjectStore.PROJECT}
/>
);
});

View File

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

View File

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

View File

@ -11,6 +11,7 @@ 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 ListLayout: FC = observer(() => {
const router = useRouter();
@ -41,6 +42,7 @@ export const ListLayout: FC = observer(() => {
QuickActions={ProjectIssueQuickActions}
issueActions={issueActions}
getProjects={getProjects}
currentStore={EProjectStore.PROJECT}
/>
);
});

View File

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

View File

@ -43,7 +43,9 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
<Popover.Button
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 ${
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">

View File

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

View File

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

View File

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

View File

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

View File

@ -9,14 +9,17 @@ import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot } from "compon
export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
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 () => {
if (workspaceSlug && projectId) {
await archivedIssueFiltersStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
await archivedIssueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
await fetchFilters(workspaceSlug, projectId);
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 />
) : (
<div className="h-full w-full overflow-auto">
<SpreadsheetView
{/* <SpreadsheetView
displayProperties={workspaceFilterStore.workspaceDisplayProperties}
displayFilters={workspaceFilterStore.workspaceDisplayFilters}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues}
members={workspaceMembers?.map((m) => m.member)}
labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined}
handleIssueAction={() => {}}
handleUpdateIssue={handleUpdateIssue}
disableUserActions={false}
/>
/> */}
</div>
)}
</div>

View File

@ -26,12 +26,20 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
projectIssuesFilter: { issueFilters, fetchFilters },
} = useMobxStore();
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => {
if (workspaceSlug && projectId) {
await fetchFilters(workspaceSlug, projectId);
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
useSWR(
workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null,
async () => {
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;

View File

@ -7,12 +7,11 @@ import useSWR from "swr";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import {
ModuleKanBanLayout,
ModuleListLayout,
ProjectViewAppliedFiltersRoot,
ProjectViewCalendarLayout,
ProjectViewEmptyState,
ProjectViewGanttLayout,
ProjectViewKanBanLayout,
ProjectViewListLayout,
ProjectViewSpreadsheetLayout,
} from "components/issues";
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 () => {
if (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>
) : (
<>
{/* {(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 && <ProjectViewEmptyState />} */}
<div className="w-full h-full relative overflow-auto">
{activeLayout === "list" ? (
<ModuleListLayout />
<ProjectViewListLayout />
) : activeLayout === "kanban" ? (
<ModuleKanBanLayout />
<ProjectViewKanBanLayout />
) : activeLayout === "calendar" ? (
<ProjectViewCalendarLayout />
) : activeLayout === "gantt_chart" ? (

View File

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

View File

@ -18,12 +18,12 @@ type Props = {
export const SpreadsheetAssigneeColumn: React.FC<Props> = ({ issue, members, onChange, expandedIssues, disabled }) => {
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 (
<>
<IssuePropertyAssignee
projectId={issue.project_detail.id ?? null}
projectId={issue.project_detail?.id ?? null}
value={issue.assignees}
onChange={(data) => onChange({ assignees: data })}
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 { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded);
const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return (
<>

View File

@ -17,7 +17,7 @@ type Props = {
export const SpreadsheetDueDateColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => {
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 (
<>

View File

@ -17,12 +17,12 @@ export const SpreadsheetEstimateColumn: React.FC<Props> = (props) => {
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 (
<>
<IssuePropertyEstimates
projectId={issue.project_detail.id ?? null}
projectId={issue.project_detail?.id ?? null}
value={issue.estimate_point}
onChange={(data) => onChange({ estimate_point: data })}
className="h-full w-full"

View File

@ -1,7 +1,6 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { Popover2 } from "@blueprintjs/popover2";
import { MoreHorizontal, Pencil, Trash2, ChevronRight, Link } from "lucide-react";
import { ChevronRight } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
// components
@ -16,8 +15,7 @@ type Props = {
expanded: boolean;
handleToggleExpand: (issueId: string) => void;
properties: IIssueDisplayProperties;
handleEditIssue: (issue: IIssue) => void;
handleDeleteIssue: (issue: IIssue) => void;
quickActions: (issue: IIssue) => React.ReactNode;
setIssuePeekOverView: React.Dispatch<
React.SetStateAction<{
workspaceSlug: string;
@ -35,8 +33,7 @@ export const IssueColumn: React.FC<Props> = ({
handleToggleExpand,
setIssuePeekOverView,
properties,
handleEditIssue,
handleDeleteIssue,
quickActions,
disableUserActions,
nestingLevel,
}) => {
@ -75,7 +72,7 @@ export const IssueColumn: React.FC<Props> = ({
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 && (
<div
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>
{!disableUserActions && (
<div className="absolute top-0 left-2.5 opacity-0 group-hover:opacity-100">
<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 className="absolute top-0 left-2.5 opacity-0 group-hover:opacity-100">{quickActions(issue)}</div>
)}
</div>

View File

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

View File

@ -20,12 +20,12 @@ export const SpreadsheetLabelColumn: React.FC<Props> = (props) => {
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 (
<>
<IssuePropertyLabels
projectId={issue.project_detail.id ?? null}
projectId={issue.project_detail?.id ?? null}
value={issue.labels}
onChange={(data) => onChange({ labels: data })}
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 { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded);
const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return (
<>

View File

@ -17,7 +17,7 @@ type Props = {
export const SpreadsheetPriorityColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => {
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 (
<>

View File

@ -17,7 +17,7 @@ type Props = {
export const SpreadsheetStartDateColumn: React.FC<Props> = ({ issue, onChange, expandedIssues, disabled }) => {
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 (
<>

View File

@ -20,12 +20,12 @@ export const SpreadsheetStateColumn: React.FC<Props> = (props) => {
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 (
<>
<IssuePropertyState
projectId={issue.project_detail.id ?? null}
projectId={issue.project_detail?.id ?? null}
value={issue.state_detail}
onChange={(data) => onChange({ state: data.id, state_detail: data })}
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 { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded);
const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded);
return (
<>

View File

@ -17,7 +17,7 @@ export const SpreadsheetUpdatedOnColumn: React.FC<Props> = (props) => {
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 (
<>

View File

@ -5,14 +5,39 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
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(() => {
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 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 (
<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
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
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(() => {
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 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 (
<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
import { useMobxStore } from "lib/mobx/store-provider";
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(() => {
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string };
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";
// components
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(() => {
const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string };
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";
// types
import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState, IUserLite } from "types";
import { EIssueActions } from "../types";
type Props = {
displayProperties: IIssueDisplayProperties;
@ -16,8 +17,8 @@ type Props = {
members?: IUserLite[] | undefined;
labels?: IIssueLabel[] | undefined;
states?: IState[] | undefined;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
quickActions: (issue: IIssue) => React.ReactNode;
handleIssues: (issue: IIssue, action: EIssueActions) => void;
openIssuesListModal?: (() => void) | null;
quickAddCallback?: (
workspaceSlug: string,
@ -39,8 +40,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
members,
labels,
states,
handleIssueAction,
handleUpdateIssue,
quickActions,
handleIssues,
quickAddCallback,
viewId,
disableUserActions,
@ -80,6 +81,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
};
}, []);
console.log("spreadsheet issues", issues);
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="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>
</div>
{issues.map((issue, index) => (
<SpreadsheetIssuesColumn
key={`${issue.id}_${index}`}
issue={issue}
expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues}
properties={displayProperties}
handleIssueAction={handleIssueAction}
disableUserActions={disableUserActions}
setIssuePeekOverView={setIssuePeekOverView}
/>
))}
{issues.map((issue, index) =>
issue ? (
<SpreadsheetIssuesColumn
key={`${issue?.id}_${index}`}
issue={issue}
expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues}
properties={displayProperties}
quickActions={quickActions}
disableUserActions={disableUserActions}
setIssuePeekOverView={setIssuePeekOverView}
/>
) : null
)}
</div>
</div>
@ -124,7 +129,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
disableUserActions={disableUserActions}
expandedIssues={expandedIssues}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
handleUpdateIssue={handleUpdateIssue}
handleUpdateIssue={(issue, data) => handleIssues({ ...issue, ...data }, EIssueActions.UPDATE)}
issues={issues}
members={members}
labels={labels}
@ -185,7 +190,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
workspaceSlug={issuePeekOverview?.workspaceSlug}
projectId={issuePeekOverview?.projectId}
issueId={issuePeekOverview?.issueId}
handleIssue={(issueToUpdate: any) => handleUpdateIssue(issueToUpdate as IIssue, issueToUpdate)}
handleIssue={(issueToUpdate: any) => handleIssues(issueToUpdate, EIssueActions.UPDATE)}
/>
)}
</div>

View File

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

View File

@ -1,4 +1,6 @@
import { useCallback } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// components
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "components/issues";
// hooks
@ -6,40 +8,68 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// constants
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(() => {
const { workspace: workspaceStore, profileIssueFilters: profileIssueFiltersStore }: RootStore = useMobxStore();
const handleLayoutChange = (_layout: string) => {
const payload = {
layout: _layout,
group_by: profileIssueFiltersStore.userDisplayFilters.group_by
? profileIssueFiltersStore.userDisplayFilters.group_by
: "state_detail.group",
};
profileIssueFiltersStore.handleIssueFilters("userDisplayFilters", payload);
const router = useRouter();
const { workspaceSlug } = router.query as {
workspaceSlug: string;
};
const handleFilters = (key: any, value: any) => {
let updatesFilters: any = profileIssueFiltersStore?.userFilters;
updatesFilters = updatesFilters[key] || [];
if (updatesFilters && updatesFilters.length > 0 && updatesFilters.includes(value))
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 {
workspace: workspaceStore,
workspaceProfileIssuesFilter: { issueFilters, updateFilters },
}: RootStore = useMobxStore();
const states = undefined;
const labels = workspaceStore.workspaceLabels || 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 (
<div className="relative flex items-center justify-end gap-2">
@ -51,11 +81,11 @@ export const ProfileIssuesFilter = observer(() => {
<FiltersDropdown title="Filters" placement="bottom-end">
<FilterSelection
filters={profileIssueFiltersStore.userFilters}
handleFiltersUpdate={handleFilters}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
}
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
states={states}
labels={labels}
members={members}
@ -64,13 +94,13 @@ export const ProfileIssuesFilter = observer(() => {
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
displayFilters={profileIssueFiltersStore.userDisplayFilters}
displayProperties={profileIssueFiltersStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFilters}
handleDisplayPropertiesUpdate={handleDisplayProperties}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
</FiltersDropdown>
</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
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
import { EProjectStore } from "store/command-palette.store";
export const WorkspaceSidebarQuickAction = observer(() => {
// states
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({}));
@ -34,24 +39,26 @@ export const WorkspaceSidebarQuickAction = observer(() => {
/>
<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
className={`relative flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${isSidebarCollapsed
? "px-2 hover:bg-custom-sidebar-background-80"
: "px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
}`}
className={`relative flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${
isSidebarCollapsed
? "px-2 hover:bg-custom-sidebar-background-80"
: "px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
}`}
>
<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={() => {
setTrackElement("APP_SIDEBAR_QUICK_ACTIONS");
commandPaletteStore.toggleCreateIssueModal(true);
}
}
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
}}
>
<PenSquare className="h-4 w-4 text-custom-sidebar-text-300" />
{!isSidebarCollapsed && <span className="text-sm font-medium">New Issue</span>}
@ -63,8 +70,9 @@ export const WorkspaceSidebarQuickAction = observer(() => {
<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
size={16}
@ -73,8 +81,9 @@ export const WorkspaceSidebarQuickAction = observer(() => {
</button>
<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">
<button
@ -91,10 +100,11 @@ export const WorkspaceSidebarQuickAction = observer(() => {
</div>
<button
className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none ${isSidebarCollapsed
? "hover:bg-custom-sidebar-background-80"
: "shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
}`}
className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none ${
isSidebarCollapsed
? "hover:bg-custom-sidebar-background-80"
: "shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
}`}
onClick={() => commandPaletteStore.toggleCommandPaletteModal(true)}
>
<Search className="h-4 w-4 text-custom-sidebar-text-300" />

View File

@ -1,67 +1,15 @@
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 { Spinner } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// types
import { NextPageWithLayout } from "types/app";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => {
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>
)}
</>
);
});
const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => <ProfileIssuesPage type="assigned" />);
ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,63 +1,16 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// 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 { Spinner } from "@plane/ui";
// types
import { NextPageWithLayout } from "types/app";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileCreatedIssuesPage: NextPageWithLayout = () => {
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>
)}
</>
);
};
const ProfileCreatedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="created" />;
ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,63 +1,16 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// 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 { Spinner } from "@plane/ui";
// types
import { NextPageWithLayout } from "types/app";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileSubscribedIssuesPage: NextPageWithLayout = () => {
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>
)}
</>
);
};
const ProfileSubscribedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="subscribed" />;
ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -10,6 +10,7 @@ import { ProjectDraftIssueHeader } from "components/headers";
import { X, PenSquare } from "lucide-react";
// types
import { NextPageWithLayout } from "types/app";
import { DraftIssueLayoutRoot } from "components/issues/issue-layouts/roots/draft-issue-layout-root";
const ProjectDraftIssuesPage: NextPageWithLayout = () => {
const router = useRouter();
@ -21,14 +22,14 @@ const ProjectDraftIssuesPage: NextPageWithLayout = () => {
<button
type="button"
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>
<X className="h-3 w-3" />
</button>
</div>
<DraftIssueLayoutRoot />
</div>
);
};

View File

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

View File

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