mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: implemented new store and issue layouts for issues and updated new data structure for issues (#2843)
* fix: Implemented new workflow in the issue store and updated the quick add workflow in list layout * fix: initial load and mutaion of issues in list layout * dev: implemented the new project issues store with grouped, subGrouped and unGrouped issue computed functions * dev: default display properties data made as a function * conflict: merge conflict resolved * dev: implemented quick add logic in kanban * chore: implemented quick add logic in calendar and spreadsheet layout * fix: spreadsheet layout quick add fix * dev: optimised the issues workflow and handled the issues order_by filter * dev: project issue CRUD operations in new issue store architecture * dev: issues filtering in calendar layout * fix: build error * dev/issue_filters_store * chore: updated filters computed structure * conflict: merge conflicts resolved in project issues * dev: implemented gantt chart for project issues using the new mobx store * dev: initialized cycle and module issue filters store * dev: issue store and list layout store updates * dev: quick add and update, delete issue in the list * refactor list root changes * dev: store new structure * refactor spreadsheet and gnatt project roots * fix errors for base gantt and spreadsheet roots * connect Calendar project view * minor house keeping * connect Kanban View to th enew store * generalise base calendar issue actions * dev: store project issues and issue filters * dev: store project issues and filters * dev: updated undefined with displayFilters in project issue store * Add Quick add to all the layouts * connect module views to store * dev: Rendering list issues in project issues * dev: removed console log * dev: module filters store * fix errors and connect modules list and quick add for list * dev: module issue store * dev: modle filter store issue fixed and updates cycle issue filters * minor house keeping changes * dev: cycle issues and cycle filters * connecty cycles to teh store * dev: project view issues and issue filtrs * connect project views * dev: updated applied filters in layouts * dev: replaced project id with view id in project views * dev: in cycle and module store made cycledId and moduleId as optional * fix minor issues and build errots * dev: project draft and archived issues store and filters --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
parent
db75eced0a
commit
d6abb87a3a
@ -17,7 +17,7 @@ export const Spinner: React.FC<ISpinner> = ({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
className={`mr-2 animate-spin fill-blue-600 text-custom-text-200 ${className}`}
|
className={`animate-spin fill-blue-600 text-custom-text-200 ${className}`}
|
||||||
viewBox="0 0 100 101"
|
viewBox="0 0 100 101"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -12,6 +12,7 @@ import { GanttInlineCreateIssueForm, IssueGanttSidebarBlock } from "components/i
|
|||||||
import { findTotalDaysInRange } from "helpers/date-time.helper";
|
import { findTotalDaysInRange } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
|
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -19,11 +20,18 @@ type Props = {
|
|||||||
blocks: IGanttBlock[] | null;
|
blocks: IGanttBlock[] | null;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { title, blockUpdateHandler, blocks, enableReorder, enableQuickIssueCreate } = props;
|
const { title, blockUpdateHandler, blocks, enableReorder, enableQuickIssueCreate, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { cycleId } = router.query;
|
const { cycleId } = router.query;
|
||||||
@ -152,7 +160,9 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
{droppableProvided.placeholder}
|
{droppableProvided.placeholder}
|
||||||
</>
|
</>
|
||||||
{enableQuickIssueCreate && <GanttInlineCreateIssueForm />}
|
{enableQuickIssueCreate && (
|
||||||
|
<GanttInlineCreateIssueForm quickAddCallback={quickAddCallback} viewId={viewId} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</StrictModeDroppable>
|
</StrictModeDroppable>
|
||||||
|
@ -19,25 +19,32 @@ import { renderEmoji } from "helpers/emoji.helper";
|
|||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const CycleIssuesHeader: React.FC = observer(() => {
|
export const CycleIssuesHeader: React.FC = observer(() => {
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
cycleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
cycle: cycleStore,
|
cycle: cycleStore,
|
||||||
cycleIssueFilter: cycleIssueFilterStore,
|
cycleIssueFilters: cycleIssueFiltersStore,
|
||||||
|
projectIssuesFilter: projectIssueFiltersStore,
|
||||||
project: { currentProjectDetails },
|
project: { currentProjectDetails },
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
projectMember: { projectMembers },
|
||||||
|
projectLabel: { projectLabels },
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
commandPalette: commandPaletteStore,
|
commandPalette: commandPaletteStore,
|
||||||
|
|
||||||
|
cycleIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = projectIssueFiltersStore.issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
|
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
|
||||||
|
|
||||||
@ -49,58 +56,44 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, { layout: layout }, cycleId);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
layout,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, cycleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
const newValues = cycleIssueFilterStore.cycleFilters?.[key] ?? [];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((val) => {
|
value.forEach((val) => {
|
||||||
if (!newValues.includes(val)) newValues.push(val);
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (cycleIssueFilterStore.cycleFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleIssueFilterStore.updateCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), {
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { [key]: newValues }, cycleId);
|
||||||
[key]: newValues,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[cycleId, cycleIssueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
const handleDisplayFilters = useCallback(
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter, cycleId);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, cycleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayPropertiesUpdate = useCallback(
|
const handleDisplayProperties = useCallback(
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_PROPERTIES, property, cycleId);
|
||||||
issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property);
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, cycleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cyclesList = cycleStore.projectCycles;
|
const cyclesList = cycleStore.projectCycles;
|
||||||
@ -173,25 +166,25 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
/>
|
/>
|
||||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={cycleIssueFilterStore.cycleFilters}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels ?? undefined}
|
labels={projectLabels ?? undefined}
|
||||||
members={projectMembers?.map((m) => m.member)}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
|
states={projectStateStore.states?.[projectId ?? ""] ?? undefined}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title="Display" placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
|
||||||
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
|
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||||
|
@ -19,24 +19,30 @@ import { renderEmoji } from "helpers/emoji.helper";
|
|||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const ModuleIssuesHeader: React.FC = observer(() => {
|
export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
moduleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
module: moduleStore,
|
module: moduleStore,
|
||||||
moduleFilter: moduleFilterStore,
|
projectIssuesFilter: projectIssueFiltersStore,
|
||||||
project: { currentProjectDetails },
|
project: projectStore,
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
projectMember: { projectMembers },
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
commandPalette: commandPaletteStore,
|
commandPalette: commandPaletteStore,
|
||||||
|
projectLabel: { projectLabels },
|
||||||
|
moduleIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
|
||||||
|
const { currentProjectDetails } = projectStore;
|
||||||
|
|
||||||
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
|
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
|
||||||
|
|
||||||
@ -45,61 +51,49 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
setValue(`${!isSidebarCollapsed}`);
|
setValue(`${!isSidebarCollapsed}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
layout,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
const newValues = moduleFilterStore.moduleFilters?.[key] ?? [];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((val) => {
|
value.forEach((val) => {
|
||||||
if (!newValues.includes(val)) newValues.push(val);
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (moduleFilterStore.moduleFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleFilterStore.updateModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), {
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { [key]: newValues }, moduleId);
|
||||||
[key]: newValues,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[moduleId, moduleFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
const handleDisplayFilters = useCallback(
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayPropertiesUpdate = useCallback(
|
const handleDisplayProperties = useCallback(
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_PROPERTIES, property, moduleId);
|
||||||
issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property);
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const modulesList = projectId ? moduleStore.modules[projectId.toString()] : undefined;
|
const modulesList = projectId ? moduleStore.modules[projectId.toString()] : undefined;
|
||||||
@ -172,25 +166,25 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
/>
|
/>
|
||||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={moduleFilterStore.moduleFilters}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels ?? undefined}
|
labels={projectLabels ?? undefined}
|
||||||
members={projectMembers?.map((m) => m.member)}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
|
states={projectStateStore.states?.[projectId ?? ""] ?? undefined}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title="Display" placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
|
||||||
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
|
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||||
|
@ -16,85 +16,72 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
// helper
|
// helper
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const ProjectIssuesHeader: React.FC = observer(() => {
|
export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
project: { currentProjectDetails },
|
project: { currentProjectDetails },
|
||||||
projectLabel: { projectLabels },
|
projectLabel: { projectLabels },
|
||||||
projectMember: { projectMembers },
|
projectMember: { projectMembers },
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
inbox: inboxStore,
|
inbox: inboxStore,
|
||||||
commandPalette: commandPaletteStore,
|
commandPalette: commandPaletteStore,
|
||||||
|
// issue filters
|
||||||
|
projectIssuesFilter: { issueFilters, updateFilters },
|
||||||
|
projectIssues: {},
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
|
||||||
(layout: TIssueLayouts) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
layout,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
const newValues = issueFilterStore.userFilters?.[key] ?? [];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((val) => {
|
value.forEach((val) => {
|
||||||
if (!newValues.includes(val)) newValues.push(val);
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (issueFilterStore.userFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { [key]: newValues });
|
||||||
filters: {
|
|
||||||
[key]: newValues,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, issueFilters, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
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>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayPropertiesUpdate = useCallback(
|
const handleDisplayProperties = useCallback(
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_PROPERTIES, property);
|
||||||
issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property);
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const inboxDetails = projectId ? inboxStore.inboxesList?.[projectId.toString()]?.[0] : undefined;
|
const inboxDetails = projectId ? inboxStore.inboxesList?.[projectId]?.[0] : undefined;
|
||||||
|
|
||||||
const deployUrl = process.env.NEXT_PUBLIC_DEPLOY_URL;
|
const deployUrl = process.env.NEXT_PUBLIC_DEPLOY_URL;
|
||||||
|
|
||||||
@ -173,29 +160,29 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||||||
/>
|
/>
|
||||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={issueFilterStore.userFilters}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels ?? undefined}
|
labels={projectLabels ?? undefined}
|
||||||
members={projectMembers?.map((m) => m.member)}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
|
states={projectStateStore.states?.[projectId ?? ""] ?? undefined}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title="Display" placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
|
||||||
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
|
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
{projectId && inboxStore.isInboxEnabled && inboxDetails && (
|
{projectId && inboxStore.isInboxEnabled && inboxDetails && (
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxStore.getInboxId(projectId.toString())}`}>
|
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxStore.getInboxId(projectId)}`}>
|
||||||
<a>
|
<a>
|
||||||
<Button variant="neutral-primary" size="sm" className="relative">
|
<Button variant="neutral-primary" size="sm" className="relative">
|
||||||
Inbox
|
Inbox
|
||||||
|
@ -14,10 +14,15 @@ import { renderEmoji } from "helpers/emoji.helper";
|
|||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, viewId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
viewId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
issueFilter: issueFilterStore,
|
||||||
@ -27,67 +32,54 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
projectMember: { projectMembers },
|
projectMember: { projectMembers },
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
projectViews: projectViewsStore,
|
projectViews: projectViewsStore,
|
||||||
|
viewIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, { layout: layout }, viewId);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
layout,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, viewId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !viewId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
const newValues = storedFilters?.[key] ?? [];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((val) => {
|
value.forEach((val) => {
|
||||||
if (!newValues.includes(val)) newValues.push(val);
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (storedFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { [key]: newValues }, viewId);
|
||||||
[key]: newValues,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[projectViewFiltersStore, storedFilters, viewId, workspaceSlug]
|
[workspaceSlug, projectId, viewId, issueFilters, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
const handleDisplayFilters = useCallback(
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter, viewId);
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, viewId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDisplayPropertiesUpdate = useCallback(
|
const handleDisplayProperties = useCallback(
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.DISPLAY_PROPERTIES, property, viewId);
|
||||||
issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property);
|
|
||||||
},
|
},
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
[workspaceSlug, projectId, viewId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined;
|
const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined;
|
||||||
@ -157,25 +149,25 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
/>
|
/>
|
||||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={storedFilters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels ?? undefined}
|
labels={projectLabels ?? undefined}
|
||||||
members={projectMembers?.map((m) => m.member)}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
|
states={projectStateStore.states?.[projectId ?? ""] ?? undefined}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title="Display" placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
|
||||||
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
|
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { useEffect, useState, Fragment } from "react";
|
import { useEffect, useState, Fragment } from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
// mobx store
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
@ -17,14 +13,9 @@ type Props = {
|
|||||||
onSubmit?: () => Promise<void>;
|
onSubmit?: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteIssueModal: React.FC<Props> = observer((props) => {
|
export const DeleteIssueModal: React.FC<Props> = (props) => {
|
||||||
const { data, isOpen, handleClose, onSubmit } = props;
|
const { data, isOpen, handleClose, onSubmit } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug } = router.query;
|
|
||||||
|
|
||||||
const { issueDetail: issueDetailStore } = useMobxStore();
|
|
||||||
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -37,12 +28,7 @@ export const DeleteIssueModal: React.FC<Props> = observer((props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleIssueDelete = async () => {
|
const handleIssueDelete = async () => {
|
||||||
if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
await issueDetailStore.deleteIssue(workspaceSlug.toString(), data.project, data.id);
|
|
||||||
|
|
||||||
if (onSubmit) await onSubmit().finally(() => setIsDeleteLoading(false));
|
if (onSubmit) await onSubmit().finally(() => setIsDeleteLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,4 +100,4 @@ export const DeleteIssueModal: React.FC<Props> = observer((props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import { FC, useCallback } from "react";
|
||||||
|
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 } from "components/issues";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
import { ICycleIssuesStore, IModuleIssuesStore, IProjectIssuesStore, IViewIssuesStore } from "store/issues";
|
||||||
|
import { IIssueCalendarViewStore, IssueStore } 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;
|
||||||
|
calendarViewStore: IIssueCalendarViewStore;
|
||||||
|
QuickActions: FC<IQuickActionProps>;
|
||||||
|
issueActions: {
|
||||||
|
[EIssueActions.DELETE]: (issue: IIssue) => void;
|
||||||
|
[EIssueActions.UPDATE]?: (issue: IIssue) => void;
|
||||||
|
[EIssueActions.REMOVE]?: (issue: IIssue) => void;
|
||||||
|
};
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
|
const { issueStore, calendarViewStore, QuickActions, issueActions, viewId } = props;
|
||||||
|
const { projectIssuesFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
const displayFilters = issueFilterStore.issueFilters?.displayFilters;
|
||||||
|
|
||||||
|
const issues = issueStore.getIssues;
|
||||||
|
const groupedIssueIds = (issueStore.getIssuesIds ?? {}) as IGroupedIssues;
|
||||||
|
|
||||||
|
const onDragEnd = (result: DropResult) => {
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
// return if not dropped on the correct place
|
||||||
|
if (!result.destination) return;
|
||||||
|
|
||||||
|
// return if dropped on the same date
|
||||||
|
if (result.destination.droppableId === result.source.droppableId) return;
|
||||||
|
|
||||||
|
calendarViewStore?.handleDragDrop(result.source, result.destination);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIssues = useCallback(
|
||||||
|
(date: string, issue: IIssue, action: EIssueActions) => {
|
||||||
|
if (issueActions[action]) {
|
||||||
|
issueActions[action]!(issue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[issueStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
<CalendarChart
|
||||||
|
issues={issues}
|
||||||
|
groupedIssueIds={groupedIssueIds}
|
||||||
|
layout={displayFilters?.calendar?.layout}
|
||||||
|
showWeekends={displayFilters?.calendar?.show_weekends ?? false}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(issue) => (
|
||||||
|
<QuickActions
|
||||||
|
issue={issue}
|
||||||
|
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.DELETE)}
|
||||||
|
handleUpdate={
|
||||||
|
issueActions[EIssueActions.UPDATE]
|
||||||
|
? async (data) => handleIssues(issue.target_date ?? "", data, EIssueActions.UPDATE)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
handleRemoveFromView={
|
||||||
|
issueActions[EIssueActions.REMOVE]
|
||||||
|
? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.UPDATE)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
quickAddCallback={issueStore.quickAddIssue}
|
||||||
|
viewId={viewId}
|
||||||
|
/>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -8,19 +8,28 @@ import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components
|
|||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { ICalendarWeek } from "./types";
|
import { ICalendarWeek } from "./types";
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { IGroupedIssues, IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issues: IIssueGroupedStructure | null;
|
issues: IIssueResponse | undefined;
|
||||||
|
groupedIssueIds: IGroupedIssues;
|
||||||
layout: "month" | "week" | undefined;
|
layout: "month" | "week" | undefined;
|
||||||
showWeekends: boolean;
|
showWeekends: boolean;
|
||||||
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
|
handleIssues: (date: string, issue: IIssue, action: EIssueActions) => void;
|
||||||
quickActions: (issue: IIssue) => React.ReactNode;
|
quickActions: (issue: IIssue) => React.ReactNode;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarChart: React.FC<Props> = observer((props) => {
|
export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
const { issues, layout, showWeekends, handleIssues, quickActions } = props;
|
const { issues, groupedIssueIds, layout, showWeekends, handleIssues, quickActions, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
const { calendar: calendarStore } = useMobxStore();
|
const { calendar: calendarStore } = useMobxStore();
|
||||||
|
|
||||||
@ -49,9 +58,12 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||||||
key={weekIndex}
|
key={weekIndex}
|
||||||
week={week}
|
week={week}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
groupedIssueIds={groupedIssueIds}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -60,9 +72,12 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||||||
<CalendarWeekDays
|
<CalendarWeekDays
|
||||||
week={calendarStore.allDaysOfActiveWeek}
|
week={calendarStore.allDaysOfActiveWeek}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
groupedIssueIds={groupedIssueIds}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,31 +4,48 @@ import { Droppable } from "@hello-pangea/dnd";
|
|||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { CalendarIssueBlocks, ICalendarDate, CalendarInlineCreateIssueForm } from "components/issues";
|
import { CalendarIssueBlocks, ICalendarDate, CalendarQuickAddIssueForm } from "components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderDateFormat } from "helpers/date-time.helper";
|
import { renderDateFormat } from "helpers/date-time.helper";
|
||||||
// types
|
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
|
||||||
// constants
|
// constants
|
||||||
import { MONTHS_LIST } from "constants/calendar";
|
import { MONTHS_LIST } from "constants/calendar";
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { IGroupedIssues, IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
date: ICalendarDate;
|
date: ICalendarDate;
|
||||||
issues: IIssueGroupedStructure | null;
|
issues: IIssueResponse | undefined;
|
||||||
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
|
groupedIssueIds: IGroupedIssues;
|
||||||
|
handleIssues: (date: string, issue: IIssue, action: EIssueActions) => void;
|
||||||
quickActions: (issue: IIssue) => React.ReactNode;
|
quickActions: (issue: IIssue) => React.ReactNode;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||||
const { date, issues, handleIssues, quickActions, enableQuickIssueCreate } = props;
|
const {
|
||||||
|
date,
|
||||||
|
issues,
|
||||||
|
groupedIssueIds,
|
||||||
|
handleIssues,
|
||||||
|
quickActions,
|
||||||
|
enableQuickIssueCreate,
|
||||||
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const { issueFilter: issueFilterStore } = useMobxStore();
|
const { issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month";
|
const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month";
|
||||||
|
|
||||||
const issuesList = issues ? (issues as IIssueGroupedStructure)[renderDateFormat(date.date)] : null;
|
const issueIdList = groupedIssueIds ? groupedIssueIds[renderDateFormat(date.date)] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -64,14 +81,22 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
>
|
>
|
||||||
<CalendarIssueBlocks issues={issuesList} handleIssues={handleIssues} quickActions={quickActions} />
|
<CalendarIssueBlocks
|
||||||
|
issues={issues}
|
||||||
|
issueIdList={issueIdList}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={quickActions}
|
||||||
|
/>
|
||||||
{enableQuickIssueCreate && (
|
{enableQuickIssueCreate && (
|
||||||
<div className="py-1 px-2">
|
<div className="py-1 px-2">
|
||||||
<CalendarInlineCreateIssueForm
|
<CalendarQuickAddIssueForm
|
||||||
|
formKey="target_date"
|
||||||
groupId={renderDateFormat(date.date)}
|
groupId={renderDateFormat(date.date)}
|
||||||
prePopulatedData={{
|
prePopulatedData={{
|
||||||
target_date: renderDateFormat(date.date),
|
target_date: renderDateFormat(date.date),
|
||||||
}}
|
}}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -7,4 +7,4 @@ export * from "./header";
|
|||||||
export * from "./issue-blocks";
|
export * from "./issue-blocks";
|
||||||
export * from "./week-days";
|
export * from "./week-days";
|
||||||
export * from "./week-header";
|
export * from "./week-header";
|
||||||
export * from "./inline-create-issue-form";
|
export * from "./quick-add-issue-form";
|
||||||
|
@ -5,66 +5,74 @@ import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
|||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issues: IIssue[] | null;
|
issues: IIssueResponse | undefined;
|
||||||
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
|
issueIdList: string[] | null;
|
||||||
|
handleIssues: (date: string, issue: IIssue, action: EIssueActions) => void;
|
||||||
quickActions: (issue: IIssue) => React.ReactNode;
|
quickActions: (issue: IIssue) => React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||||
const { issues, handleIssues, quickActions } = props;
|
const { issues, issueIdList, handleIssues, quickActions } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{issues?.map((issue, index) => (
|
{issueIdList?.map((issueId, index) => {
|
||||||
<Draggable key={issue.id} draggableId={issue.id} index={index}>
|
if (!issues?.[issueId]) return null;
|
||||||
{(provided, snapshot) => (
|
|
||||||
<div
|
|
||||||
className="p-1 px-2 relative"
|
|
||||||
{...provided.draggableProps}
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
ref={provided.innerRef}
|
|
||||||
>
|
|
||||||
{issue?.tempId !== undefined && (
|
|
||||||
<div className="absolute top-0 left-0 w-full h-full animate-pulse bg-custom-background-100/20 z-[99999]" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
const issue = issues?.[issueId];
|
||||||
|
return (
|
||||||
|
<Draggable key={issue.id} draggableId={issue.id} index={index}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
className={`group/calendar-block h-8 w-full shadow-custom-shadow-2xs rounded py-1.5 px-1 flex items-center gap-1.5 border-[0.5px] border-custom-border-100 ${
|
className="p-1 px-2 relative"
|
||||||
snapshot.isDragging
|
{...provided.draggableProps}
|
||||||
? "shadow-custom-shadow-rg bg-custom-background-90"
|
{...provided.dragHandleProps}
|
||||||
: "bg-custom-background-100 hover:bg-custom-background-90"
|
ref={provided.innerRef}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<span
|
{issue?.tempId !== undefined && (
|
||||||
className="h-full w-0.5 rounded flex-shrink-0"
|
<div className="absolute top-0 left-0 w-full h-full animate-pulse bg-custom-background-100/20 z-[99999]" />
|
||||||
style={{
|
)}
|
||||||
backgroundColor: issue.state_detail.color,
|
|
||||||
}}
|
<div
|
||||||
/>
|
className={`group/calendar-block h-8 w-full shadow-custom-shadow-2xs rounded py-1.5 px-1 flex items-center gap-1.5 border-[0.5px] border-custom-border-100 ${
|
||||||
<div className="text-xs text-custom-text-300 flex-shrink-0">
|
snapshot.isDragging
|
||||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
? "shadow-custom-shadow-rg bg-custom-background-90"
|
||||||
</div>
|
: "bg-custom-background-100 hover:bg-custom-background-90"
|
||||||
<IssuePeekOverview
|
}`}
|
||||||
workspaceSlug={issue?.workspace_detail?.slug}
|
|
||||||
projectId={issue?.project_detail?.id}
|
|
||||||
issueId={issue?.id}
|
|
||||||
// TODO: add the logic here
|
|
||||||
handleIssue={(issueToUpdate) => {
|
|
||||||
handleIssues(issue.target_date ?? "", { ...issue, ...issueToUpdate }, "update");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
<span
|
||||||
<div className="text-xs truncate">{issue.name}</div>
|
className="h-full w-0.5 rounded flex-shrink-0"
|
||||||
</Tooltip>
|
style={{
|
||||||
</IssuePeekOverview>
|
backgroundColor: issue.state_detail.color,
|
||||||
<div className="hidden group-hover/calendar-block:block">{quickActions(issue)}</div>
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-xs text-custom-text-300 flex-shrink-0">
|
||||||
|
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||||
|
</div>
|
||||||
|
<IssuePeekOverview
|
||||||
|
workspaceSlug={issue?.workspace_detail?.slug}
|
||||||
|
projectId={issue?.project_detail?.id}
|
||||||
|
issueId={issue?.id}
|
||||||
|
// TODO: add the logic here
|
||||||
|
handleIssue={(issueToUpdate) => {
|
||||||
|
handleIssues(issue.target_date ?? "", { ...issue, ...issueToUpdate }, EIssueActions.UPDATE);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||||
|
<div className="text-xs truncate">{issue.name}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</IssuePeekOverview>
|
||||||
|
<div className="hidden group-hover/calendar-block:block">{quickActions(issue)}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</Draggable>
|
||||||
</Draggable>
|
);
|
||||||
))}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -7,19 +7,26 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useKeypress from "hooks/use-keypress";
|
import useKeypress from "hooks/use-keypress";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// helpers
|
// helpers
|
||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue, IProject } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
formKey: keyof IIssue;
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
subGroupId?: string | null;
|
||||||
prePopulatedData?: Partial<IIssue>;
|
prePopulatedData?: Partial<IIssue>;
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IIssue> = {
|
const defaultValues: Partial<IIssue> = {
|
||||||
@ -49,15 +56,14 @@ const Inputs = (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||||
const { prePopulatedData, groupId } = props;
|
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
// store
|
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||||
const { workspace: workspaceStore, quickAddIssue: quickAddStore } = useMobxStore();
|
|
||||||
|
|
||||||
// ref
|
// ref
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -67,7 +73,10 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) =
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
// derived values
|
||||||
|
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||||
|
const projectDetail: IProject | null =
|
||||||
|
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
@ -84,9 +93,6 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) =
|
|||||||
useKeypress("Escape", handleClose);
|
useKeypress("Escape", handleClose);
|
||||||
useOutsideClickDetector(ref, handleClose);
|
useOutsideClickDetector(ref, handleClose);
|
||||||
|
|
||||||
// derived values
|
|
||||||
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) reset({ ...defaultValues });
|
if (!isOpen) reset({ ...defaultValues });
|
||||||
}, [isOpen, reset]);
|
}, [isOpen, reset]);
|
||||||
@ -106,42 +112,36 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) =
|
|||||||
}, [errors, setToastAlert]);
|
}, [errors, setToastAlert]);
|
||||||
|
|
||||||
const onSubmitHandler = async (formData: IIssue) => {
|
const onSubmitHandler = async (formData: IIssue) => {
|
||||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
|
||||||
|
|
||||||
// resetting the form so that user can add another issue quickly
|
reset({ ...defaultValues });
|
||||||
reset({ ...defaultValues, ...(prePopulatedData ?? {}) });
|
|
||||||
|
|
||||||
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
const payload = createIssuePayload(workspaceDetail, projectDetail, {
|
||||||
...(prePopulatedData ?? {}),
|
...(prePopulatedData ?? {}),
|
||||||
...formData,
|
...formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
quickAddStore.createIssue(
|
quickAddCallback &&
|
||||||
workspaceSlug.toString(),
|
(await quickAddCallback(
|
||||||
projectId.toString(),
|
workspaceSlug,
|
||||||
{
|
projectId,
|
||||||
group_id: groupId ?? null,
|
{
|
||||||
sub_group_id: null,
|
...payload,
|
||||||
},
|
},
|
||||||
payload
|
viewId
|
||||||
);
|
));
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Object.keys(err || {}).forEach((key) => {
|
console.error(err);
|
||||||
const error = err?.[key];
|
setToastAlert({
|
||||||
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
setToastAlert({
|
message: err?.message || "Some error occurred. Please try again.",
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: errorTitle || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -159,7 +159,7 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = observer((props) =
|
|||||||
onSubmit={handleSubmit(onSubmitHandler)}
|
onSubmit={handleSubmit(onSubmitHandler)}
|
||||||
className="flex w-full px-2 border-[0.5px] border-custom-border-200 rounded z-50 items-center gap-x-2 bg-custom-background-100 shadow-custom-shadow-2xs transition-opacity"
|
className="flex w-full px-2 border-[0.5px] border-custom-border-200 rounded z-50 items-center gap-x-2 bg-custom-background-100 shadow-custom-shadow-2xs transition-opacity"
|
||||||
>
|
>
|
||||||
<Inputs register={register} setFocus={setFocus} projectDetails={projectDetails} />
|
<Inputs formKey={formKey} register={register} setFocus={setFocus} projectDetails={projectDetail} />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
@ -1,80 +1,43 @@
|
|||||||
import { useCallback } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DragDropContext, DropResult } from "@hello-pangea/dnd";
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { CalendarChart, CycleIssueQuickActions } from "components/issues";
|
import { CycleIssueQuickActions } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||||
|
|
||||||
export const CycleCalendarLayout: React.FC = observer(() => {
|
export const CycleCalendarLayout: React.FC = observer(() => {
|
||||||
const {
|
const { cycleIssues: cycleIssueStore, cycleIssueCalendarView: cycleIssueCalendarViewStore } = useMobxStore();
|
||||||
cycleIssue: cycleIssueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
cycleIssueCalendarView: cycleIssueCalendarViewStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, cycleId } = router.query;
|
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const issueActions = {
|
||||||
if (!result) return;
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
|
|
||||||
// return if not dropped on the correct place
|
|
||||||
if (!result.destination) return;
|
|
||||||
|
|
||||||
// return if dropped on the same date
|
|
||||||
if (result.destination.droppableId === result.source.droppableId) return;
|
|
||||||
|
|
||||||
cycleIssueCalendarViewStore?.handleDragDrop(result.source, result.destination);
|
|
||||||
};
|
|
||||||
|
|
||||||
const issues = cycleIssueStore.getIssues;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(date: string, issue: IIssue, action: "update" | "delete" | "remove") => {
|
|
||||||
if (!workspaceSlug || !cycleId) return;
|
if (!workspaceSlug || !cycleId) return;
|
||||||
|
|
||||||
if (action === "update") {
|
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
|
||||||
cycleIssueStore.updateIssueStructure(date, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
}
|
|
||||||
if (action === "delete") cycleIssueStore.deleteIssue(date, null, issue);
|
|
||||||
if (action === "remove" && issue.bridge_id) {
|
|
||||||
cycleIssueStore.deleteIssue(date, null, issue);
|
|
||||||
cycleIssueStore.removeIssueFromCycle(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
issue.project,
|
|
||||||
cycleId.toString(),
|
|
||||||
issue.bridge_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[cycleIssueStore, issueDetailStore, cycleId, workspaceSlug]
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug || !cycleId) return;
|
||||||
|
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
|
||||||
|
},
|
||||||
|
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||||
|
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
|
||||||
|
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
<BaseCalendarRoot
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
issueStore={cycleIssueStore}
|
||||||
<CalendarChart
|
calendarViewStore={cycleIssueCalendarViewStore}
|
||||||
issues={issues as IIssueGroupedStructure | null}
|
QuickActions={CycleIssueQuickActions}
|
||||||
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
|
issueActions={issueActions}
|
||||||
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
|
viewId={cycleId}
|
||||||
handleIssues={handleIssues}
|
/>
|
||||||
quickActions={(issue) => (
|
|
||||||
<CycleIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(issue.target_date ?? "", data, "update")}
|
|
||||||
handleRemoveFromCycle={async () => handleIssues(issue.target_date ?? "", issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,82 +1,42 @@
|
|||||||
import { useCallback } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DragDropContext, DropResult } from "@hello-pangea/dnd";
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { CalendarChart, ModuleIssueQuickActions } from "components/issues";
|
import { ModuleIssueQuickActions } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||||
|
|
||||||
export const ModuleCalendarLayout: React.FC = observer(() => {
|
export const ModuleCalendarLayout: React.FC = observer(() => {
|
||||||
const {
|
const { moduleIssues: moduleIssueStore, moduleIssueCalendarView: moduleIssueCalendarViewStore } = useMobxStore();
|
||||||
moduleIssue: moduleIssueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
moduleIssueCalendarView: moduleIssueCalendarViewStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, moduleId } = router.query;
|
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string };
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const issueActions = {
|
||||||
if (!result) return;
|
[EIssueActions.UPDATE]: (issue: IIssue) => {
|
||||||
|
if (!workspaceSlug || !moduleId) return;
|
||||||
// return if not dropped on the correct place
|
moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
|
||||||
if (!result.destination) return;
|
},
|
||||||
|
[EIssueActions.DELETE]: (issue: IIssue) => {
|
||||||
// return if dropped on the same date
|
if (!workspaceSlug || !moduleId) return;
|
||||||
if (result.destination.droppableId === result.source.droppableId) return;
|
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
|
||||||
|
},
|
||||||
moduleIssueCalendarViewStore?.handleDragDrop(result.source, result.destination);
|
[EIssueActions.REMOVE]: (issue: IIssue) => {
|
||||||
|
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
|
||||||
|
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const issues = moduleIssueStore.getIssues;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(date: string, issue: IIssue, action: "update" | "delete" | "remove") => {
|
|
||||||
if (!workspaceSlug || !moduleId) return;
|
|
||||||
|
|
||||||
if (action === "update") {
|
|
||||||
moduleIssueStore.updateIssueStructure(date, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
} else {
|
|
||||||
moduleIssueStore.deleteIssue(date, null, issue);
|
|
||||||
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id);
|
|
||||||
}
|
|
||||||
if (action === "remove" && issue.bridge_id) {
|
|
||||||
moduleIssueStore.deleteIssue(date, null, issue);
|
|
||||||
moduleIssueStore.removeIssueFromModule(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
issue.project,
|
|
||||||
moduleId.toString(),
|
|
||||||
issue.bridge_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
<BaseCalendarRoot
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
issueStore={moduleIssueStore}
|
||||||
<CalendarChart
|
calendarViewStore={moduleIssueCalendarViewStore}
|
||||||
issues={issues as IIssueGroupedStructure | null}
|
QuickActions={ModuleIssueQuickActions}
|
||||||
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
|
issueActions={issueActions}
|
||||||
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
|
viewId={moduleId}
|
||||||
handleIssues={handleIssues}
|
/>
|
||||||
quickActions={(issue) => (
|
|
||||||
<ModuleIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(issue.target_date ?? "", data, "update")}
|
|
||||||
handleRemoveFromModule={async () => handleIssues(issue.target_date ?? "", issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,72 +1,42 @@
|
|||||||
import { useCallback } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DragDropContext, DropResult } from "@hello-pangea/dnd";
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { CalendarChart, ProjectIssueQuickActions } from "components/issues";
|
import { ProjectIssueQuickActions } from "components/issues";
|
||||||
// types
|
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
import { EIssueActions } from "../../types";
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
export const CalendarLayout: React.FC = observer(() => {
|
export const CalendarLayout: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query as { workspaceSlug: string };
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issue: issueStore,
|
projectIssues: issueStore,
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
issueCalendarView: issueCalendarViewStore,
|
issueCalendarView: issueCalendarViewStore,
|
||||||
|
issueDetail: issueDetailStore,
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const issueActions = {
|
||||||
const { workspaceSlug } = router.query;
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
// return if not dropped on the correct place
|
|
||||||
if (!result.destination) return;
|
|
||||||
|
|
||||||
// return if dropped on the same date
|
|
||||||
if (result.destination.droppableId === result.source.droppableId) return;
|
|
||||||
|
|
||||||
issueCalendarViewStore?.handleDragDrop(result.source, result.destination);
|
|
||||||
};
|
|
||||||
|
|
||||||
const issues = issueStore.getIssues;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(date: string, issue: IIssue, action: "update" | "delete") => {
|
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
if (action === "update") {
|
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||||
issueStore.updateIssueStructure(date, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
} else {
|
|
||||||
issueStore.deleteIssue(date, null, issue);
|
|
||||||
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[issueStore, issueDetailStore, workspaceSlug]
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
<BaseCalendarRoot
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
issueStore={issueStore}
|
||||||
<CalendarChart
|
calendarViewStore={issueCalendarViewStore}
|
||||||
issues={issues as IIssueGroupedStructure | null}
|
QuickActions={ProjectIssueQuickActions}
|
||||||
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
|
issueActions={issueActions}
|
||||||
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
|
/>
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(issue) => (
|
|
||||||
<ProjectIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(issue.target_date ?? "", data, "update")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -7,66 +7,39 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
// components
|
// components
|
||||||
import { CalendarChart, ProjectIssueQuickActions } from "components/issues";
|
import { CalendarChart, ProjectIssueQuickActions } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||||
|
|
||||||
export const ProjectViewCalendarLayout: React.FC = observer(() => {
|
export const ProjectViewCalendarLayout: React.FC = observer(() => {
|
||||||
const {
|
const {
|
||||||
projectViewIssues: projectViewIssuesStore,
|
viewIssues: projectViewIssuesStore,
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueDetail: issueDetailStore,
|
issueDetail: issueDetailStore,
|
||||||
projectViewIssueCalendarView: projectViewIssueCalendarViewStore,
|
projectViewIssueCalendarView: projectViewIssueCalendarViewStore,
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query as { workspaceSlug: string };
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const issueActions = {
|
||||||
if (!result) return;
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
|
|
||||||
// return if not dropped on the correct place
|
|
||||||
if (!result.destination) return;
|
|
||||||
|
|
||||||
// return if dropped on the same date
|
|
||||||
if (result.destination.droppableId === result.source.droppableId) return;
|
|
||||||
|
|
||||||
projectViewIssueCalendarViewStore?.handleDragDrop(result.source, result.destination);
|
|
||||||
};
|
|
||||||
|
|
||||||
const issues = projectViewIssuesStore.getIssues;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(date: string, issue: IIssue, action: "update" | "delete") => {
|
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
if (action === "update") {
|
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||||
projectViewIssuesStore.updateIssueStructure(date, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
} else {
|
|
||||||
projectViewIssuesStore.deleteIssue(date, null, issue);
|
|
||||||
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[projectViewIssuesStore, issueDetailStore, workspaceSlug]
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
<BaseCalendarRoot
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
issueStore={projectViewIssuesStore}
|
||||||
<CalendarChart
|
calendarViewStore={projectViewIssueCalendarViewStore}
|
||||||
issues={issues as IIssueGroupedStructure | null}
|
QuickActions={ProjectIssueQuickActions}
|
||||||
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
|
issueActions={issueActions}
|
||||||
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
|
/>
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(issue) => (
|
|
||||||
<ProjectIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(issue.target_date ?? "", issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(issue.target_date ?? "", data, "update")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -8,19 +8,37 @@ import { CalendarDayTile } from "components/issues";
|
|||||||
import { renderDateFormat } from "helpers/date-time.helper";
|
import { renderDateFormat } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { ICalendarDate, ICalendarWeek } from "./types";
|
import { ICalendarDate, ICalendarWeek } from "./types";
|
||||||
import { IIssueGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { IGroupedIssues, IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issues: IIssueGroupedStructure | null;
|
issues: IIssueResponse | undefined;
|
||||||
|
groupedIssueIds: IGroupedIssues;
|
||||||
week: ICalendarWeek | undefined;
|
week: ICalendarWeek | undefined;
|
||||||
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
|
handleIssues: (date: string, issue: IIssue, action: EIssueActions) => void;
|
||||||
quickActions: (issue: IIssue) => React.ReactNode;
|
quickActions: (issue: IIssue) => React.ReactNode;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||||
const { issues, week, handleIssues, quickActions, enableQuickIssueCreate } = props;
|
const {
|
||||||
|
issues,
|
||||||
|
groupedIssueIds,
|
||||||
|
week,
|
||||||
|
handleIssues,
|
||||||
|
quickActions,
|
||||||
|
enableQuickIssueCreate,
|
||||||
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const { issueFilter: issueFilterStore } = useMobxStore();
|
const { issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
@ -43,9 +61,12 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
|||||||
key={renderDateFormat(date.date)}
|
key={renderDateFormat(date.date)}
|
||||||
date={date}
|
date={date}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
groupedIssueIds={groupedIssueIds}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -6,61 +6,69 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { AppliedFiltersList } from "components/issues";
|
import { AppliedFiltersList } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "types";
|
import { IIssueFilterOptions } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
cycleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectLabel: { projectLabels },
|
projectLabel: { projectLabels },
|
||||||
projectMember: { projectMembers },
|
|
||||||
cycleIssueFilter: cycleIssueFilterStore,
|
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
|
projectMember: { projectMembers },
|
||||||
|
cycleIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const userFilters = cycleIssueFilterStore.cycleFilters;
|
const userFilters = issueFilters?.filters;
|
||||||
|
|
||||||
// filters whose value not null or empty array
|
// filters whose value not null or empty array
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
const appliedFilters: IIssueFilterOptions = {};
|
||||||
Object.entries(userFilters).forEach(([key, value]) => {
|
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
|
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
// remove all values of the key if value is null
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
cycleIssueFilterStore.updateCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), {
|
updateFilters(
|
||||||
[key]: null,
|
workspaceSlug,
|
||||||
});
|
projectId,
|
||||||
|
EFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
[key]: null,
|
||||||
|
},
|
||||||
|
cycleId
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the passed value from the key
|
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
let newValues = cycleIssueFilterStore.cycleFilters?.[key] ?? [];
|
|
||||||
newValues = newValues.filter((val) => val !== value);
|
newValues = newValues.filter((val) => val !== value);
|
||||||
|
|
||||||
cycleIssueFilterStore.updateCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), {
|
updateFilters(
|
||||||
[key]: newValues,
|
workspaceSlug,
|
||||||
});
|
projectId,
|
||||||
|
EFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
[key]: newValues,
|
||||||
|
},
|
||||||
|
cycleId
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearAllFilters = () => {
|
const handleClearAllFilters = () => {
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const newFilters: IIssueFilterOptions = {};
|
const newFilters: IIssueFilterOptions = {};
|
||||||
Object.keys(userFilters).forEach((key) => {
|
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
});
|
});
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, cycleId);
|
||||||
cycleIssueFilterStore.updateCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId?.toString(), {
|
|
||||||
...newFilters,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// return if no filters are applied
|
// return if no filters are applied
|
||||||
@ -74,7 +82,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
handleRemoveFilter={handleRemoveFilter}
|
handleRemoveFilter={handleRemoveFilter}
|
||||||
labels={projectLabels ?? []}
|
labels={projectLabels ?? []}
|
||||||
members={projectMembers?.map((m) => m.member)}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
states={projectStateStore.states?.[cycleId ?? ""]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -7,61 +7,69 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { AppliedFiltersList } from "components/issues";
|
import { AppliedFiltersList } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "types";
|
import { IIssueFilterOptions } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
moduleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectLabel: { projectLabels },
|
projectLabel: { projectLabels },
|
||||||
moduleFilter: moduleFilterStore,
|
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
projectMember: { projectMembers },
|
projectMember: { projectMembers },
|
||||||
|
moduleIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const userFilters = moduleFilterStore.moduleFilters;
|
const userFilters = issueFilters?.filters;
|
||||||
|
|
||||||
// filters whose value not null or empty array
|
// filters whose value not null or empty array
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
const appliedFilters: IIssueFilterOptions = {};
|
||||||
Object.entries(userFilters).forEach(([key, value]) => {
|
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
|
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
// remove all values of the key if value is null
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
moduleFilterStore.updateModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), {
|
updateFilters(
|
||||||
[key]: null,
|
workspaceSlug,
|
||||||
});
|
projectId,
|
||||||
|
EFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
[key]: null,
|
||||||
|
},
|
||||||
|
moduleId
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the passed value from the key
|
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
let newValues = moduleFilterStore.moduleFilters?.[key] ?? [];
|
|
||||||
newValues = newValues.filter((val) => val !== value);
|
newValues = newValues.filter((val) => val !== value);
|
||||||
|
|
||||||
moduleFilterStore.updateModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), {
|
updateFilters(
|
||||||
[key]: newValues,
|
workspaceSlug,
|
||||||
});
|
projectId,
|
||||||
|
EFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
[key]: newValues,
|
||||||
|
},
|
||||||
|
moduleId
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearAllFilters = () => {
|
const handleClearAllFilters = () => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const newFilters: IIssueFilterOptions = {};
|
const newFilters: IIssueFilterOptions = {};
|
||||||
Object.keys(userFilters).forEach((key) => {
|
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
});
|
});
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, moduleId);
|
||||||
moduleFilterStore.updateModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId?.toString(), {
|
|
||||||
...newFilters,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// return if no filters are applied
|
// return if no filters are applied
|
||||||
@ -75,7 +83,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
handleRemoveFilter={handleRemoveFilter}
|
handleRemoveFilter={handleRemoveFilter}
|
||||||
labels={projectLabels ?? []}
|
labels={projectLabels ?? []}
|
||||||
members={projectMembers?.map((m) => m.member)}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
states={projectStateStore.states?.[moduleId ?? ""]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -7,65 +7,53 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { AppliedFiltersList } from "components/issues";
|
import { AppliedFiltersList } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "types";
|
import { IIssueFilterOptions } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
projectLabel: { projectLabels },
|
projectLabel: { projectLabels },
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
projectMember: { projectMembers },
|
projectMember: { projectMembers },
|
||||||
|
projectIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const userFilters = issueFilterStore.userFilters;
|
const userFilters = issueFilters?.filters;
|
||||||
|
|
||||||
// filters whose value not null or empty array
|
// filters whose value not null or empty array
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
const appliedFilters: IIssueFilterOptions = {};
|
||||||
Object.entries(userFilters).forEach(([key, value]) => {
|
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
|
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
// remove all values of the key if value is null
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||||
filters: {
|
[key]: null,
|
||||||
[key]: null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the passed value from the key
|
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
let newValues = issueFilterStore.userFilters?.[key] ?? [];
|
|
||||||
newValues = newValues.filter((val) => val !== value);
|
newValues = newValues.filter((val) => val !== value);
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||||
filters: {
|
[key]: newValues,
|
||||||
[key]: newValues,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearAllFilters = () => {
|
const handleClearAllFilters = () => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const newFilters: IIssueFilterOptions = {};
|
const newFilters: IIssueFilterOptions = {};
|
||||||
Object.keys(userFilters).forEach((key) => {
|
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
});
|
});
|
||||||
|
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { ...newFilters });
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
filters: { ...newFilters },
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// return if no filters are applied
|
// return if no filters are applied
|
||||||
|
@ -12,65 +12,78 @@ import { Button } from "@plane/ui";
|
|||||||
import { areFiltersDifferent } from "helpers/filter.helper";
|
import { areFiltersDifferent } from "helpers/filter.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "types";
|
import { IIssueFilterOptions } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, viewId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
viewId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectLabel: { projectLabels },
|
projectLabel: { projectLabels },
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
projectState: projectStateStore,
|
||||||
|
projectMember: { projectMembers },
|
||||||
projectViews: projectViewsStore,
|
projectViews: projectViewsStore,
|
||||||
projectViewFilters: projectViewFiltersStore,
|
projectViewFilters: projectViewFiltersStore,
|
||||||
|
viewIssuesFilter: { issueFilters, updateFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
|
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
|
||||||
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
||||||
|
|
||||||
|
const userFilters = issueFilters?.filters;
|
||||||
// filters whose value not null or empty array
|
// filters whose value not null or empty array
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
const appliedFilters: IIssueFilterOptions = {};
|
||||||
Object.entries(storedFilters ?? {}).forEach(([key, value]) => {
|
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
|
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||||
if (!viewId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
// remove all values of the key if value is null
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
|
updateFilters(
|
||||||
[key]: null,
|
workspaceSlug,
|
||||||
});
|
projectId,
|
||||||
|
EFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
[key]: null,
|
||||||
|
},
|
||||||
|
viewId
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the passed value from the key
|
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
let newValues = storedFilters?.[key] ?? [];
|
|
||||||
newValues = newValues.filter((val) => val !== value);
|
newValues = newValues.filter((val) => val !== value);
|
||||||
|
|
||||||
projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
|
updateFilters(
|
||||||
[key]: newValues,
|
workspaceSlug,
|
||||||
});
|
projectId,
|
||||||
|
EFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
[key]: newValues,
|
||||||
|
},
|
||||||
|
viewId
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearAllFilters = () => {
|
const handleClearAllFilters = () => {
|
||||||
if (!workspaceSlug || !projectId || !viewId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const newFilters: IIssueFilterOptions = {};
|
const newFilters: IIssueFilterOptions = {};
|
||||||
Object.keys(storedFilters ?? {}).forEach((key) => {
|
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
});
|
});
|
||||||
|
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, viewId);
|
||||||
projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
|
|
||||||
...newFilters,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// return if no filters are applied
|
||||||
|
if (Object.keys(appliedFilters).length === 0) return null;
|
||||||
|
|
||||||
const handleUpdateView = () => {
|
const handleUpdateView = () => {
|
||||||
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
|
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
|
||||||
|
|
||||||
@ -82,17 +95,6 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// update stored filters when view details are fetched
|
|
||||||
useEffect(() => {
|
|
||||||
if (!viewId || !viewDetails) return;
|
|
||||||
|
|
||||||
if (!projectViewFiltersStore.storedFilters[viewId.toString()])
|
|
||||||
projectViewFiltersStore.updateStoredFilters(viewId.toString(), viewDetails?.query_data ?? {});
|
|
||||||
}, [projectViewFiltersStore, viewDetails, viewId]);
|
|
||||||
|
|
||||||
// return if no filters are applied
|
|
||||||
if (Object.keys(appliedFilters).length === 0) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-4 p-4">
|
<div className="flex items-center justify-between gap-4 p-4">
|
||||||
<AppliedFiltersList
|
<AppliedFiltersList
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// hooks
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
|
// components
|
||||||
|
import { IssueGanttBlock } from "components/issues";
|
||||||
|
import {
|
||||||
|
GanttChartRoot,
|
||||||
|
IBlockUpdateData,
|
||||||
|
renderIssueBlocksStructure,
|
||||||
|
IssueGanttSidebar,
|
||||||
|
} from "components/gantt-chart";
|
||||||
|
// types
|
||||||
|
import { IIssueUnGroupedStructure } from "store/issue";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
import {
|
||||||
|
ICycleIssuesFilterStore,
|
||||||
|
ICycleIssuesStore,
|
||||||
|
IModuleIssuesFilterStore,
|
||||||
|
IModuleIssuesStore,
|
||||||
|
IProjectIssuesFilterStore,
|
||||||
|
IProjectIssuesStore,
|
||||||
|
IViewIssuesFilterStore,
|
||||||
|
IViewIssuesStore,
|
||||||
|
} from "store/issues";
|
||||||
|
import { EUserWorkspaceRoles } from "layouts/settings-layout/workspace/sidebar";
|
||||||
|
import { TUnGroupedIssues } from "store/issues/types";
|
||||||
|
|
||||||
|
interface IBaseGanttRoot {
|
||||||
|
issueFiltersStore:
|
||||||
|
| IProjectIssuesFilterStore
|
||||||
|
| IModuleIssuesFilterStore
|
||||||
|
| ICycleIssuesFilterStore
|
||||||
|
| IViewIssuesFilterStore;
|
||||||
|
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
|
||||||
|
const { issueFiltersStore, issueStore, viewId } = props;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
|
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
|
||||||
|
|
||||||
|
const issuesResponse = issueStore.getIssues;
|
||||||
|
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
|
||||||
|
|
||||||
|
const issues = issueIds.map((id) => issuesResponse?.[id]);
|
||||||
|
|
||||||
|
const updateIssue = (issue: IIssue, payload: IBlockUpdateData) => {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-full">
|
||||||
|
<GanttChartRoot
|
||||||
|
border={false}
|
||||||
|
title="Issues"
|
||||||
|
loaderTitle="Issues"
|
||||||
|
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
||||||
|
blockUpdateHandler={updateIssue}
|
||||||
|
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
|
||||||
|
sidebarToRender={(props) => (
|
||||||
|
<IssueGanttSidebar
|
||||||
|
{...props}
|
||||||
|
quickAddCallback={issueStore.quickAddIssue}
|
||||||
|
viewId={viewId}
|
||||||
|
enableQuickIssueCreate
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
enableBlockLeftResize={isAllowed}
|
||||||
|
enableBlockRightResize={isAllowed}
|
||||||
|
enableBlockMove={isAllowed}
|
||||||
|
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -1,57 +1,15 @@
|
|||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
// components
|
// components
|
||||||
import { IssueGanttBlock } from "components/issues";
|
import { BaseGanttRoot } from "./base-gantt-root";
|
||||||
import {
|
import { useRouter } from "next/router";
|
||||||
GanttChartRoot,
|
|
||||||
IBlockUpdateData,
|
|
||||||
renderIssueBlocksStructure,
|
|
||||||
IssueGanttSidebar,
|
|
||||||
} from "components/gantt-chart";
|
|
||||||
// types
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
export const CycleGanttLayout: React.FC = observer(() => {
|
export const CycleGanttLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, cycleId } = router.query;
|
const { cycleId } = router.query as { cycleId: string };
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
return <BaseGanttRoot issueFiltersStore={cycleIssueFilterStore} issueStore={cycleIssueStore} viewId={cycleId} />;
|
||||||
|
|
||||||
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
|
||||||
|
|
||||||
const issues = cycleIssueStore.getIssues;
|
|
||||||
|
|
||||||
const updateIssue = (block: any, payload: IBlockUpdateData) => {
|
|
||||||
if (!workspaceSlug || !cycleId) return;
|
|
||||||
|
|
||||||
cycleIssueStore.updateGanttIssueStructure(workspaceSlug.toString(), cycleId.toString(), block, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<GanttChartRoot
|
|
||||||
border={false}
|
|
||||||
title="Issues"
|
|
||||||
loaderTitle="Issues"
|
|
||||||
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
|
||||||
blockUpdateHandler={updateIssue}
|
|
||||||
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
|
|
||||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} />}
|
|
||||||
enableBlockLeftResize={isAllowed}
|
|
||||||
enableBlockRightResize={isAllowed}
|
|
||||||
enableBlockMove={isAllowed}
|
|
||||||
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export * from "./blocks";
|
export * from "./blocks";
|
||||||
export * from "./cycle-root";
|
export * from "./cycle-root";
|
||||||
|
export * from "./quick-add-issue-form";
|
||||||
export * from "./module-root";
|
export * from "./module-root";
|
||||||
|
export * from "./project-root";
|
||||||
export * from "./project-view-root";
|
export * from "./project-view-root";
|
||||||
export * from "./root";
|
|
||||||
export * from "./inline-create-issue-form";
|
|
||||||
|
@ -1,57 +1,15 @@
|
|||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
// components
|
// components
|
||||||
import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues";
|
import { BaseGanttRoot } from "./base-gantt-root";
|
||||||
import {
|
import { useRouter } from "next/router";
|
||||||
GanttChartRoot,
|
|
||||||
IBlockUpdateData,
|
|
||||||
renderIssueBlocksStructure,
|
|
||||||
IssueGanttSidebar,
|
|
||||||
} from "components/gantt-chart";
|
|
||||||
// types
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
export const ModuleGanttLayout: React.FC = observer(() => {
|
export const ModuleGanttLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, moduleId } = router.query;
|
const { moduleId } = router.query as { moduleId: string };
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
return <BaseGanttRoot issueFiltersStore={moduleIssueFilterStore} issueStore={moduleIssueStore} viewId={moduleId} />;
|
||||||
|
|
||||||
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
|
||||||
|
|
||||||
const issues = moduleIssueStore.getIssues;
|
|
||||||
|
|
||||||
const updateIssue = (block: any, payload: IBlockUpdateData) => {
|
|
||||||
if (!workspaceSlug || !moduleId) return;
|
|
||||||
|
|
||||||
moduleIssueStore.updateGanttIssueStructure(workspaceSlug.toString(), moduleId.toString(), block, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<GanttChartRoot
|
|
||||||
border={false}
|
|
||||||
title="Issues"
|
|
||||||
loaderTitle="Issues"
|
|
||||||
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
|
||||||
blockUpdateHandler={updateIssue}
|
|
||||||
sidebarToRender={(data) => <IssueGanttSidebar {...data} />}
|
|
||||||
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
|
|
||||||
enableBlockLeftResize={isAllowed}
|
|
||||||
enableBlockRightResize={isAllowed}
|
|
||||||
enableBlockMove={isAllowed}
|
|
||||||
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
12
web/components/issues/issue-layouts/gantt/project-root.tsx
Normal file
12
web/components/issues/issue-layouts/gantt/project-root.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// hooks
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// components
|
||||||
|
import { BaseGanttRoot } from "./base-gantt-root";
|
||||||
|
|
||||||
|
export const GanttLayout: React.FC = observer(() => {
|
||||||
|
const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore();
|
||||||
|
|
||||||
|
return <BaseGanttRoot issueFiltersStore={projectIssueFiltersStore} issueStore={projectIssuesStore} />;
|
||||||
|
});
|
@ -1,59 +1,11 @@
|
|||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
// components
|
// components
|
||||||
import { IssueGanttBlock } from "components/issues";
|
import { BaseGanttRoot } from "./base-gantt-root";
|
||||||
import {
|
|
||||||
GanttChartRoot,
|
|
||||||
IBlockUpdateData,
|
|
||||||
renderIssueBlocksStructure,
|
|
||||||
ProjectViewGanttSidebar,
|
|
||||||
} from "components/gantt-chart";
|
|
||||||
// types
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
export const ProjectViewGanttLayout: React.FC = observer(() => {
|
export const ProjectViewGanttLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const { viewIssues: projectIssueViewStore, viewIssuesFilter: projectIssueViewFiltersStore } = useMobxStore();
|
||||||
const { workspaceSlug, viewId } = router.query;
|
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
return <BaseGanttRoot issueFiltersStore={projectIssueViewFiltersStore} issueStore={projectIssueViewStore} />;
|
||||||
|
|
||||||
const { projectViewIssues: projectViewIssuesStore, issueFilter: issueFilterStore } = useMobxStore();
|
|
||||||
|
|
||||||
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
|
||||||
|
|
||||||
const issues = projectViewIssuesStore.getIssues;
|
|
||||||
|
|
||||||
const updateIssue = (block: any, payload: IBlockUpdateData) => {
|
|
||||||
if (!workspaceSlug || !viewId) return;
|
|
||||||
|
|
||||||
projectViewIssuesStore.updateGanttIssueStructure(workspaceSlug.toString(), viewId.toString(), block, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<GanttChartRoot
|
|
||||||
border={false}
|
|
||||||
title="Issues"
|
|
||||||
loaderTitle="Issues"
|
|
||||||
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
|
||||||
blockUpdateHandler={updateIssue}
|
|
||||||
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
|
|
||||||
sidebarToRender={(props) => <ProjectViewGanttSidebar {...props} />}
|
|
||||||
enableBlockLeftResize={isAllowed}
|
|
||||||
enableBlockRightResize={isAllowed}
|
|
||||||
enableBlockMove={isAllowed}
|
|
||||||
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,13 @@ import { createIssuePayload } from "helpers/issue.helper";
|
|||||||
type Props = {
|
type Props = {
|
||||||
prePopulatedData?: Partial<IIssue>;
|
prePopulatedData?: Partial<IIssue>;
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
onSuccess?: (data: IIssue) => Promise<void> | void;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IIssue> = {
|
const defaultValues: Partial<IIssue> = {
|
||||||
@ -47,14 +54,14 @@ const Inputs = (props: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||||
const { prePopulatedData } = props;
|
const { prePopulatedData, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
// store
|
// store
|
||||||
const { workspace: workspaceStore, quickAddIssue: quickAddStore } = useMobxStore();
|
const { workspace: workspaceStore } = useMobxStore();
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
@ -114,15 +121,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
quickAddStore.createIssue(
|
quickAddCallback && quickAddCallback(workspaceSlug, projectId, payload, viewId);
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
{
|
|
||||||
group_id: null,
|
|
||||||
sub_group_id: null,
|
|
||||||
},
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
@ -1,58 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
// hooks
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
// components
|
|
||||||
import { IssueGanttBlock } from "components/issues";
|
|
||||||
import {
|
|
||||||
GanttChartRoot,
|
|
||||||
IBlockUpdateData,
|
|
||||||
renderIssueBlocksStructure,
|
|
||||||
IssueGanttSidebar,
|
|
||||||
} from "components/gantt-chart";
|
|
||||||
// types
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
export const GanttLayout: React.FC = observer(() => {
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug } = router.query;
|
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
|
||||||
|
|
||||||
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
|
|
||||||
|
|
||||||
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
|
||||||
|
|
||||||
const issues = issueStore.getIssues;
|
|
||||||
|
|
||||||
const updateIssue = (block: IIssue, payload: IBlockUpdateData) => {
|
|
||||||
if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
issueStore.updateGanttIssueStructure(workspaceSlug.toString(), block, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<GanttChartRoot
|
|
||||||
border={false}
|
|
||||||
title="Issues"
|
|
||||||
loaderTitle="Issues"
|
|
||||||
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
|
||||||
blockUpdateHandler={updateIssue}
|
|
||||||
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
|
|
||||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} enableQuickIssueCreate />}
|
|
||||||
enableBlockLeftResize={isAllowed}
|
|
||||||
enableBlockRightResize={isAllowed}
|
|
||||||
enableBlockMove={isAllowed}
|
|
||||||
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
194
web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
Normal file
194
web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
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
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// ui
|
||||||
|
import { Spinner } from "@plane/ui";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { ICycleIssuesStore, IModuleIssuesStore, IProjectIssuesStore, IViewIssuesStore } from "store/issues";
|
||||||
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
import { IIssueKanBanViewStore } from "store/issue";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||||
|
//components
|
||||||
|
import { KanBan } from "./default";
|
||||||
|
import { KanBanSwimLanes } from "./swimlanes";
|
||||||
|
|
||||||
|
export interface IBaseKanBanLayout {
|
||||||
|
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
|
||||||
|
kanbanViewStore: IIssueKanBanViewStore;
|
||||||
|
QuickActions: FC<IQuickActionProps>;
|
||||||
|
issueActions: {
|
||||||
|
[EIssueActions.DELETE]: (issue: IIssue) => void;
|
||||||
|
[EIssueActions.UPDATE]?: (issue: IIssue) => void;
|
||||||
|
[EIssueActions.REMOVE]?: (issue: IIssue) => void;
|
||||||
|
};
|
||||||
|
showLoader?: boolean;
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
|
||||||
|
const { issueStore, kanbanViewStore, QuickActions, issueActions, showLoader, viewId } = 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 sub_group_by: string | null = displayFilters?.sub_group_by || null;
|
||||||
|
|
||||||
|
const group_by: string | null = displayFilters?.group_by || null;
|
||||||
|
|
||||||
|
const order_by: string | null = displayFilters?.order_by || null;
|
||||||
|
|
||||||
|
const userDisplayFilters = displayFilters || null;
|
||||||
|
|
||||||
|
const currentKanBanView: "swimlanes" | "default" = 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.source.droppableId &&
|
||||||
|
result.destination.droppableId &&
|
||||||
|
result.destination.droppableId === result.source.droppableId &&
|
||||||
|
result.destination.index === result.source.index
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentKanBanView === "default"
|
||||||
|
? kanbanViewStore?.handleDragDrop(result.source, result.destination)
|
||||||
|
: kanbanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIssues = useCallback(
|
||||||
|
async (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
|
||||||
|
if (issueActions[action]) {
|
||||||
|
issueActions[action]!(issue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[issueStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||||
|
kanbanViewStore.handleKanBanToggle(toggle, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const states = projectStateStore?.projectStates || null;
|
||||||
|
const priorities = ISSUE_PRIORITIES || null;
|
||||||
|
const stateGroups = ISSUE_STATE_GROUPS || null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showLoader && issueStore?.loader === "mutation" && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||||
|
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
||||||
|
{currentKanBanView === "default" ? (
|
||||||
|
<KanBan
|
||||||
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
|
sub_group_by={sub_group_by}
|
||||||
|
group_by={group_by}
|
||||||
|
order_by={order_by}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(sub_group_by, group_by, issue) => (
|
||||||
|
<QuickActions
|
||||||
|
issue={issue}
|
||||||
|
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||||
|
handleUpdate={
|
||||||
|
issueActions[EIssueActions.UPDATE]
|
||||||
|
? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
handleRemoveFromView={
|
||||||
|
issueActions[EIssueActions.REMOVE]
|
||||||
|
? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
displayProperties={displayProperties}
|
||||||
|
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||||
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={projectLabels}
|
||||||
|
members={projectMembers?.map((m) => m.member) ?? null}
|
||||||
|
projects={workspaceProjects}
|
||||||
|
enableQuickIssueCreate
|
||||||
|
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||||
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={issueStore.quickAddIssue}
|
||||||
|
viewId={viewId}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<KanBanSwimLanes
|
||||||
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
|
sub_group_by={sub_group_by}
|
||||||
|
group_by={group_by}
|
||||||
|
order_by={order_by}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(sub_group_by, group_by, issue) => (
|
||||||
|
<QuickActions
|
||||||
|
issue={issue}
|
||||||
|
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||||
|
handleUpdate={
|
||||||
|
issueActions[EIssueActions.UPDATE]
|
||||||
|
? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
handleRemoveFromView={
|
||||||
|
issueActions[EIssueActions.REMOVE]
|
||||||
|
? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
displayProperties={displayProperties}
|
||||||
|
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||||
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={projectLabels}
|
||||||
|
members={projectMembers?.map((m) => m.member) ?? null}
|
||||||
|
projects={workspaceProjects}
|
||||||
|
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||||
|
isDragStarted={isDragStarted}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -5,6 +5,7 @@ import { Tooltip } from "@plane/ui";
|
|||||||
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayProperties, IIssue } from "types";
|
import { IIssueDisplayProperties, IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
|
||||||
interface IssueBlockProps {
|
interface IssueBlockProps {
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
@ -13,14 +14,9 @@ interface IssueBlockProps {
|
|||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
handleIssues: (
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||||
sub_group_by: string | null,
|
|
||||||
group_by: string | null,
|
|
||||||
issue: IIssue,
|
|
||||||
action: "update" | "delete"
|
|
||||||
) => void;
|
|
||||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||||
@ -37,7 +33,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
|
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
|
||||||
if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, "update");
|
if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, EIssueActions.UPDATE);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,7 +75,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
|||||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||||
!columnId && columnId === "null" ? null : columnId,
|
!columnId && columnId === "null" ? null : columnId,
|
||||||
{ ...issue, ...issueToUpdate },
|
{ ...issue, ...issueToUpdate },
|
||||||
"update"
|
EIssueActions.UPDATE
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
// components
|
// components
|
||||||
import { KanbanIssueBlock } from "components/issues";
|
import { KanbanIssueBlock } from "components/issues";
|
||||||
import { IIssueDisplayProperties, IIssue } from "types";
|
import { IIssueDisplayProperties, IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
interface IssueBlocksListProps {
|
interface IssueBlocksListProps {
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
columnId: string;
|
columnId: string;
|
||||||
issues: IIssue[];
|
issues: IIssueResponse;
|
||||||
|
issueIds: string[];
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
handleIssues: (
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||||
sub_group_by: string | null,
|
|
||||||
group_by: string | null,
|
|
||||||
issue: IIssue,
|
|
||||||
action: "update" | "delete"
|
|
||||||
) => void;
|
|
||||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
|
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
|
||||||
@ -23,6 +21,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
|
|||||||
sub_group_id,
|
sub_group_id,
|
||||||
columnId,
|
columnId,
|
||||||
issues,
|
issues,
|
||||||
|
issueIds,
|
||||||
showEmptyGroup,
|
showEmptyGroup,
|
||||||
isDragDisabled,
|
isDragDisabled,
|
||||||
handleIssues,
|
handleIssues,
|
||||||
@ -32,22 +31,28 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{issues && issues.length > 0 ? (
|
{issueIds && issueIds.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{issues.map((issue, index) => (
|
{issueIds.map((issueId, index) => {
|
||||||
<KanbanIssueBlock
|
if (!issues[issueId]) return null;
|
||||||
key={`kanban-issue-block-${issue.id}`}
|
|
||||||
index={index}
|
const issue = issues[issueId];
|
||||||
issue={issue}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
return (
|
||||||
handleIssues={handleIssues}
|
<KanbanIssueBlock
|
||||||
quickActions={quickActions}
|
key={`kanban-issue-block-${issue.id}`}
|
||||||
displayProperties={displayProperties}
|
index={index}
|
||||||
columnId={columnId}
|
issue={issue}
|
||||||
sub_group_id={sub_group_id}
|
showEmptyGroup={showEmptyGroup}
|
||||||
isDragDisabled={isDragDisabled}
|
handleIssues={handleIssues}
|
||||||
/>
|
quickActions={quickActions}
|
||||||
))}
|
displayProperties={displayProperties}
|
||||||
|
columnId={columnId}
|
||||||
|
sub_group_id={sub_group_id}
|
||||||
|
isDragDisabled={isDragDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
!isDragDisabled && (
|
!isDragDisabled && (
|
||||||
|
@ -5,40 +5,47 @@ import { Droppable } from "@hello-pangea/dnd";
|
|||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||||
import { KanbanIssueBlocksList, BoardInlineCreateIssueForm } from "components/issues";
|
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayProperties, IIssue } from "types";
|
import { IIssueDisplayProperties, IIssue, IState } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { getValueFromObject } from "constants/issue";
|
import { getValueFromObject } from "constants/issue";
|
||||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||||
|
|
||||||
export interface IGroupByKanBan {
|
export interface IGroupByKanBan {
|
||||||
issues: any;
|
issues: IIssueResponse;
|
||||||
|
issueIds: any;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
order_by: string | null;
|
order_by: string | null;
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
list: any;
|
list: any;
|
||||||
listKey: string;
|
listKey: string;
|
||||||
|
states: IState[] | null;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
handleIssues: (
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||||
sub_group_by: string | null,
|
|
||||||
group_by: string | null,
|
|
||||||
issue: IIssue,
|
|
||||||
action: "update" | "delete"
|
|
||||||
) => void;
|
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
kanBanToggle: any;
|
kanBanToggle: any;
|
||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
isDragStarted?: boolean;
|
isDragStarted?: boolean;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues,
|
issues,
|
||||||
|
issueIds,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
order_by,
|
order_by,
|
||||||
@ -54,6 +61,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
isDragStarted,
|
isDragStarted,
|
||||||
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const verticalAlignPosition = (_list: any) =>
|
const verticalAlignPosition = (_list: any) =>
|
||||||
@ -74,7 +83,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
column_value={_list}
|
column_value={_list}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
issues_count={issues?.[getValueFromObject(_list, listKey) as string]?.length || 0}
|
issues_count={issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0}
|
||||||
kanBanToggle={kanBanToggle}
|
kanBanToggle={kanBanToggle}
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
/>
|
/>
|
||||||
@ -102,7 +111,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
<KanbanIssueBlocksList
|
<KanbanIssueBlocksList
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
columnId={getValueFromObject(_list, listKey) as string}
|
columnId={getValueFromObject(_list, listKey) as string}
|
||||||
issues={issues[getValueFromObject(_list, listKey) as string]}
|
issues={issues}
|
||||||
|
issueIds={issueIds?.[getValueFromObject(_list, listKey) as string] || []}
|
||||||
isDragDisabled={isDragDisabled}
|
isDragDisabled={isDragDisabled}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -125,13 +135,16 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
|
|
||||||
<div className="flex-shrink-0 w-full bg-custom-background-90 py-1 sticky bottom-0 z-[0]">
|
<div className="flex-shrink-0 w-full bg-custom-background-90 py-1 sticky bottom-0 z-[0]">
|
||||||
{enableQuickIssueCreate && (
|
{enableQuickIssueCreate && (
|
||||||
<BoardInlineCreateIssueForm
|
<KanBanQuickAddIssueForm
|
||||||
|
formKey="name"
|
||||||
groupId={getValueFromObject(_list, listKey) as string}
|
groupId={getValueFromObject(_list, listKey) as string}
|
||||||
subGroupId={sub_group_id}
|
subGroupId={sub_group_id}
|
||||||
prePopulatedData={{
|
prePopulatedData={{
|
||||||
...(group_by && { [group_by]: getValueFromObject(_list, listKey) }),
|
...(group_by && { [group_by]: getValueFromObject(_list, listKey) }),
|
||||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||||
}}
|
}}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -152,19 +165,15 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export interface IKanBan {
|
export interface IKanBan {
|
||||||
issues: any;
|
issues: IIssueResponse;
|
||||||
|
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
order_by: string | null;
|
order_by: string | null;
|
||||||
sub_group_id?: string;
|
sub_group_id?: string;
|
||||||
handleIssues: (
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||||
sub_group_by: string | null,
|
|
||||||
group_by: string | null,
|
|
||||||
issue: IIssue,
|
|
||||||
action: "update" | "delete"
|
|
||||||
) => void;
|
|
||||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
kanBanToggle: any;
|
kanBanToggle: any;
|
||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
@ -176,11 +185,19 @@ export interface IKanBan {
|
|||||||
projects: any;
|
projects: any;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
isDragStarted?: boolean;
|
isDragStarted?: boolean;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues,
|
issues,
|
||||||
|
issueIds,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
order_by,
|
order_by,
|
||||||
@ -199,6 +216,8 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
projects,
|
projects,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
isDragStarted,
|
isDragStarted,
|
||||||
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
||||||
@ -208,12 +227,14 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
{group_by && group_by === "project" && (
|
{group_by && group_by === "project" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={projects}
|
list={projects}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -223,18 +244,22 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "state" && (
|
{group_by && group_by === "state" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={states}
|
list={states}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -244,18 +269,22 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "state_detail.group" && (
|
{group_by && group_by === "state_detail.group" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={stateGroups}
|
list={stateGroups}
|
||||||
listKey={`key`}
|
listKey={`key`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -265,18 +294,22 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "priority" && (
|
{group_by && group_by === "priority" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={priorities}
|
list={priorities}
|
||||||
listKey={`key`}
|
listKey={`key`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -286,18 +319,22 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "labels" && (
|
{group_by && group_by === "labels" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -307,18 +344,22 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "assignees" && (
|
{group_by && group_by === "assignees" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -328,18 +369,22 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "created_by" && (
|
{group_by && group_by === "created_by" && (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
list={members}
|
list={members}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
@ -349,6 +394,8 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export * from "./block";
|
export * from "./block";
|
||||||
export * from "./roots";
|
export * from "./roots";
|
||||||
export * from "./blocks-list";
|
export * from "./blocks-list";
|
||||||
export * from "./inline-create-issue-form";
|
export * from "./quick-add-issue-form";
|
||||||
|
@ -17,7 +17,7 @@ export interface IKanBanProperties {
|
|||||||
columnId: string;
|
columnId: string;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
{displayProperties && displayProperties?.state && (
|
{displayProperties && displayProperties?.state && (
|
||||||
<IssuePropertyState
|
<IssuePropertyState
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.state_detail || null}
|
value={issue?.state || null}
|
||||||
onChange={handleState}
|
onChange={handleState}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
@ -105,7 +105,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* label */}
|
{/* label */}
|
||||||
{displayProperties && displayProperties?.labels && (showEmptyGroup || issue?.labels.length > 0) && (
|
{displayProperties && displayProperties?.labels && (
|
||||||
<IssuePropertyLabels
|
<IssuePropertyLabels
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.labels || null}
|
value={issue?.labels || null}
|
||||||
@ -116,7 +116,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* start date */}
|
{/* start date */}
|
||||||
{displayProperties && displayProperties?.start_date && (showEmptyGroup || issue?.start_date) && (
|
{displayProperties && displayProperties?.start_date && (
|
||||||
<IssuePropertyDate
|
<IssuePropertyDate
|
||||||
value={issue?.start_date || null}
|
value={issue?.start_date || null}
|
||||||
onChange={(date: string) => handleStartDate(date)}
|
onChange={(date: string) => handleStartDate(date)}
|
||||||
@ -126,7 +126,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* target/due date */}
|
{/* target/due date */}
|
||||||
{displayProperties && displayProperties?.due_date && (showEmptyGroup || issue?.target_date) && (
|
{displayProperties && displayProperties?.due_date && (
|
||||||
<IssuePropertyDate
|
<IssuePropertyDate
|
||||||
value={issue?.target_date || null}
|
value={issue?.target_date || null}
|
||||||
onChange={(date: string) => handleTargetDate(date)}
|
onChange={(date: string) => handleTargetDate(date)}
|
||||||
@ -135,6 +135,18 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* assignee */}
|
||||||
|
{displayProperties && displayProperties?.assignee && (
|
||||||
|
<IssuePropertyAssignee
|
||||||
|
projectId={issue?.project_detail?.id || null}
|
||||||
|
value={issue?.assignees || null}
|
||||||
|
hideDropdownArrow
|
||||||
|
onChange={handleAssignee}
|
||||||
|
disabled={false}
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* estimates */}
|
{/* estimates */}
|
||||||
{displayProperties && displayProperties?.estimate && (
|
{displayProperties && displayProperties?.estimate && (
|
||||||
<IssuePropertyEstimates
|
<IssuePropertyEstimates
|
||||||
@ -176,18 +188,6 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* assignee */}
|
|
||||||
{displayProperties && displayProperties?.assignee && (showEmptyGroup || issue?.assignees.length > 0) && (
|
|
||||||
<IssuePropertyAssignee
|
|
||||||
projectId={issue?.project_detail?.id || null}
|
|
||||||
value={issue?.assignees || null}
|
|
||||||
hideDropdownArrow
|
|
||||||
onChange={handleAssignee}
|
|
||||||
disabled={false}
|
|
||||||
multiple
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -8,23 +8,11 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useKeypress from "hooks/use-keypress";
|
import useKeypress from "hooks/use-keypress";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// helpers
|
// helpers
|
||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue, IProject } from "types";
|
||||||
|
|
||||||
type Props = {
|
|
||||||
groupId?: string;
|
|
||||||
subGroupId?: string;
|
|
||||||
prePopulatedData?: Partial<IIssue>;
|
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultValues: Partial<IIssue> = {
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const Inputs = (props: any) => {
|
const Inputs = (props: any) => {
|
||||||
const { register, setFocus, projectDetails } = props;
|
const { register, setFocus, projectDetails } = props;
|
||||||
@ -48,106 +36,117 @@ const Inputs = (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BoardInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
interface IKanBanQuickAddIssueForm {
|
||||||
const { prePopulatedData, groupId, subGroupId } = props;
|
formKey: keyof IIssue;
|
||||||
|
groupId?: string;
|
||||||
|
subGroupId?: string | null;
|
||||||
|
prePopulatedData?: Partial<IIssue>;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValues: Partial<IIssue> = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = observer((props) => {
|
||||||
|
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
// store
|
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||||
const { workspace: workspaceStore, quickAddIssue: quickAddStore } = useMobxStore();
|
|
||||||
|
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||||
|
const projectDetail: IProject | null =
|
||||||
|
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||||
|
|
||||||
// ref
|
|
||||||
const ref = useRef<HTMLFormElement>(null);
|
const ref = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
// states
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const handleClose = () => setIsOpen(false);
|
||||||
|
|
||||||
|
useKeypress("Escape", handleClose);
|
||||||
|
useOutsideClickDetector(ref, handleClose);
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
|
||||||
setFocus,
|
setFocus,
|
||||||
|
register,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<IIssue>({ defaultValues });
|
} = useForm<IIssue>({ defaultValues });
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useKeypress("Escape", handleClose);
|
|
||||||
useOutsideClickDetector(ref, handleClose);
|
|
||||||
|
|
||||||
// derived values
|
|
||||||
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) reset({ ...defaultValues });
|
if (!isOpen) reset({ ...defaultValues });
|
||||||
}, [isOpen, reset]);
|
}, [isOpen, reset]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!errors) return;
|
|
||||||
|
|
||||||
Object.keys(errors).forEach((key) => {
|
|
||||||
const error = errors[key as keyof IIssue];
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: error?.message?.toString() || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [errors, setToastAlert]);
|
|
||||||
|
|
||||||
const onSubmitHandler = async (formData: IIssue) => {
|
const onSubmitHandler = async (formData: IIssue) => {
|
||||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
|
||||||
|
|
||||||
// resetting the form so that user can add another issue quickly
|
reset({ ...defaultValues });
|
||||||
reset({ ...defaultValues, ...(prePopulatedData ?? {}) });
|
|
||||||
|
|
||||||
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
const payload = createIssuePayload(workspaceDetail, projectDetail, {
|
||||||
...(prePopulatedData ?? {}),
|
...(prePopulatedData ?? {}),
|
||||||
...formData,
|
...formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
quickAddStore.createIssue(
|
quickAddCallback &&
|
||||||
workspaceSlug.toString(),
|
(await quickAddCallback(
|
||||||
projectId.toString(),
|
workspaceSlug,
|
||||||
{
|
projectId,
|
||||||
group_id: groupId ?? null,
|
{
|
||||||
sub_group_id: subGroupId ?? null,
|
...payload,
|
||||||
},
|
},
|
||||||
payload
|
viewId
|
||||||
);
|
));
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Object.keys(err || {}).forEach((key) => {
|
console.error(err);
|
||||||
const error = err?.[key];
|
setToastAlert({
|
||||||
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
setToastAlert({
|
message: err?.message || "Some error occurred. Please try again.",
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: errorTitle || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{isOpen && (
|
{isOpen ? (
|
||||||
|
<div className="shadow-custom-shadow-sm">
|
||||||
|
<form
|
||||||
|
ref={ref}
|
||||||
|
onSubmit={handleSubmit(onSubmitHandler)}
|
||||||
|
className="flex items-center gap-x-3 border-[0.5px] w-full border-t-0 border-custom-border-100 px-3 bg-custom-background-100"
|
||||||
|
>
|
||||||
|
<Inputs formKey={formKey} register={register} setFocus={setFocus} projectDetail={projectDetail} />
|
||||||
|
</form>
|
||||||
|
<div className="text-xs italic text-custom-text-200 px-3 py-2">{`Press 'Enter' to add another issue`}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="w-full flex items-center text-custom-primary-100 p-3 py-3 cursor-pointer gap-2"
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||||
|
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* {isOpen && (
|
||||||
<form
|
<form
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onSubmit={handleSubmit(onSubmitHandler)}
|
onSubmit={handleSubmit(onSubmitHandler)}
|
||||||
@ -172,7 +171,7 @@ export const BoardInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||||
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
@ -1,179 +1,48 @@
|
|||||||
import React, { useCallback, useState } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DragDropContext } from "@hello-pangea/dnd";
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// ui
|
||||||
import { KanBanSwimLanes } from "../swimlanes";
|
|
||||||
import { KanBan } from "../default";
|
|
||||||
import { CycleIssueQuickActions } from "components/issues";
|
import { CycleIssueQuickActions } from "components/issues";
|
||||||
import { Spinner } from "@plane/ui";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
import { EIssueActions } from "../../types";
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
// components
|
||||||
|
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||||
|
|
||||||
export interface ICycleKanBanLayout {}
|
export interface ICycleKanBanLayout {}
|
||||||
|
|
||||||
export const CycleKanBanLayout: React.FC = observer(() => {
|
export const CycleKanBanLayout: React.FC = observer(() => {
|
||||||
// router
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, cycleId } = router.query;
|
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
|
||||||
|
|
||||||
// store
|
// store
|
||||||
const {
|
const { cycleIssues: cycleIssueStore, cycleIssueKanBanView: cycleIssueKanBanViewStore } = useMobxStore();
|
||||||
project: projectStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
cycleIssue: cycleIssueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
cycleIssueKanBanView: cycleIssueKanBanViewStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const issues = cycleIssueStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
|
||||||
|
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
|
||||||
|
|
||||||
const order_by: string | null = issueFilterStore?.userDisplayFilters?.order_by || null;
|
|
||||||
|
|
||||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
|
||||||
|
|
||||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.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"
|
|
||||||
? cycleIssueKanBanViewStore?.handleDragDrop(result.source, result.destination)
|
|
||||||
: cycleIssueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(sub_group_by: string | null, group_by: string | null, issue: IIssue, action: "update" | "delete" | "remove") => {
|
|
||||||
if (!workspaceSlug || !cycleId) return;
|
if (!workspaceSlug || !cycleId) return;
|
||||||
|
|
||||||
if (action === "update") {
|
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
|
||||||
cycleIssueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
},
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
}
|
if (!workspaceSlug || !cycleId) return;
|
||||||
if (action === "delete") cycleIssueStore.deleteIssue(group_by, sub_group_by, issue);
|
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
|
||||||
if (action === "remove" && issue.bridge_id) {
|
},
|
||||||
cycleIssueStore.deleteIssue(group_by, sub_group_by, issue);
|
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||||
cycleIssueStore.removeIssueFromCycle(
|
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
|
||||||
workspaceSlug.toString(),
|
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
|
||||||
issue.project,
|
|
||||||
cycleId.toString(),
|
|
||||||
issue.bridge_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[cycleIssueStore, issueDetailStore, cycleId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
|
||||||
cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
|
||||||
// const estimates =
|
|
||||||
// currentProjectDetails?.estimate !== null
|
|
||||||
// ? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
|
|
||||||
// : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BaseKanBanRoot
|
||||||
{cycleIssueStore.loader ? (
|
issueActions={issueActions}
|
||||||
<div className="w-full h-full flex justify-center items-center">
|
issueStore={cycleIssueStore}
|
||||||
<Spinner />
|
kanbanViewStore={cycleIssueKanBanViewStore}
|
||||||
</div>
|
showLoader={true}
|
||||||
) : (
|
QuickActions={CycleIssueQuickActions}
|
||||||
<div className={`relative min-w-full min-h-full h-max bg-custom-background-90 px-3 horizontal-scroll-enable`}>
|
viewId={cycleId}
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
/>
|
||||||
{currentKanBanView === "default" ? (
|
|
||||||
<KanBan
|
|
||||||
issues={issues}
|
|
||||||
sub_group_by={sub_group_by}
|
|
||||||
group_by={group_by}
|
|
||||||
order_by={order_by}
|
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(sub_group_by, group_by, issue) => (
|
|
||||||
<CycleIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
|
||||||
handleRemoveFromCycle={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
|
||||||
isDragStarted={isDragStarted}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<KanBanSwimLanes
|
|
||||||
issues={issues}
|
|
||||||
sub_group_by={sub_group_by}
|
|
||||||
group_by={group_by}
|
|
||||||
order_by={order_by}
|
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(sub_group_by, group_by, issue) => (
|
|
||||||
<CycleIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
|
||||||
handleRemoveFromCycle={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
|
||||||
isDragStarted={isDragStarted}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,176 +1,74 @@
|
|||||||
import React, { useCallback, useState } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DragDropContext } from "@hello-pangea/dnd";
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { KanBanSwimLanes } from "../swimlanes";
|
|
||||||
import { KanBan } from "../default";
|
|
||||||
import { ModuleIssueQuickActions } from "components/issues";
|
import { ModuleIssueQuickActions } from "components/issues";
|
||||||
import { Spinner } from "@plane/ui";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { EIssueActions } from "../../types";
|
||||||
|
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||||
|
|
||||||
export interface IModuleKanBanLayout {}
|
export interface IModuleKanBanLayout {}
|
||||||
|
|
||||||
export const ModuleKanBanLayout: React.FC = observer(() => {
|
export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, moduleId } = router.query;
|
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string };
|
||||||
|
|
||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
project: { workspaceProjects },
|
moduleIssues: moduleIssueStore,
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
moduleIssue: moduleIssueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
moduleIssueKanBanView: moduleIssueKanBanViewStore,
|
moduleIssueKanBanView: moduleIssueKanBanViewStore,
|
||||||
issueDetail: issueDetailStore,
|
issueDetail: issueDetailStore,
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const issues = moduleIssueStore?.getIssues;
|
// const handleIssues = useCallback(
|
||||||
|
// (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
|
||||||
|
// if (!workspaceSlug || !moduleId) return;
|
||||||
|
|
||||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
// if (action === "update") {
|
||||||
|
// moduleIssueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||||
|
// issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||||
|
// }
|
||||||
|
// if (action === "delete") moduleIssueStore.deleteIssue(group_by, sub_group_by, issue);
|
||||||
|
// if (action === "remove" && issue.bridge_id) {
|
||||||
|
// moduleIssueStore.deleteIssue(group_by, null, issue);
|
||||||
|
// moduleIssueStore.removeIssueFromModule(
|
||||||
|
// workspaceSlug.toString(),
|
||||||
|
// issue.project,
|
||||||
|
// moduleId.toString(),
|
||||||
|
// issue.bridge_id
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// [moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
|
||||||
|
// );
|
||||||
|
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
const order_by: string | null = issueFilterStore?.userDisplayFilters?.order_by || null;
|
|
||||||
|
|
||||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
|
||||||
|
|
||||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.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"
|
|
||||||
? moduleIssueKanBanViewStore?.handleDragDrop(result.source, result.destination)
|
|
||||||
: moduleIssueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(sub_group_by: string | null, group_by: string | null, issue: IIssue, action: "update" | "delete" | "remove") => {
|
|
||||||
if (!workspaceSlug || !moduleId) return;
|
if (!workspaceSlug || !moduleId) return;
|
||||||
|
|
||||||
if (action === "update") {
|
moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId);
|
||||||
moduleIssueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
},
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
}
|
if (!workspaceSlug || !moduleId) return;
|
||||||
if (action === "delete") moduleIssueStore.deleteIssue(group_by, sub_group_by, issue);
|
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
|
||||||
if (action === "remove" && issue.bridge_id) {
|
},
|
||||||
moduleIssueStore.deleteIssue(group_by, null, issue);
|
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||||
moduleIssueStore.removeIssueFromModule(
|
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
|
||||||
workspaceSlug.toString(),
|
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, issue.id, moduleId, issue.bridge_id);
|
||||||
issue.project,
|
|
||||||
moduleId.toString(),
|
|
||||||
issue.bridge_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
|
||||||
moduleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
// const estimates =
|
|
||||||
// currentProjectDetails?.estimate !== null
|
|
||||||
// ? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
|
|
||||||
// : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BaseKanBanRoot
|
||||||
{moduleIssueStore.loader ? (
|
issueActions={issueActions}
|
||||||
<div className="w-full h-full flex justify-center items-center">
|
issueStore={moduleIssueStore}
|
||||||
<Spinner />
|
kanbanViewStore={moduleIssueKanBanViewStore}
|
||||||
</div>
|
showLoader={true}
|
||||||
) : (
|
QuickActions={ModuleIssueQuickActions}
|
||||||
<div className={`relative min-w-full min-h-full h-max bg-custom-background-90 px-3 horizontal-scroll-enable`}>
|
viewId={moduleId}
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
/>
|
||||||
{currentKanBanView === "default" ? (
|
|
||||||
<KanBan
|
|
||||||
issues={issues}
|
|
||||||
sub_group_by={sub_group_by}
|
|
||||||
group_by={group_by}
|
|
||||||
order_by={order_by}
|
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(sub_group_by, group_by, issue) => (
|
|
||||||
<ModuleIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
|
||||||
handleRemoveFromModule={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
|
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={workspaceProjects}
|
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
|
||||||
isDragStarted={isDragStarted}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<KanBanSwimLanes
|
|
||||||
issues={issues}
|
|
||||||
sub_group_by={sub_group_by}
|
|
||||||
group_by={group_by}
|
|
||||||
order_by={order_by}
|
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(sub_group_by, group_by, issue) => (
|
|
||||||
<ModuleIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
|
||||||
handleRemoveFromModule={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
|
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={workspaceProjects}
|
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
|
||||||
isDragStarted={isDragStarted}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,7 @@ import { Spinner } from "@plane/ui";
|
|||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
|
||||||
export interface IProfileIssuesKanBanLayout {}
|
export interface IProfileIssuesKanBanLayout {}
|
||||||
|
|
||||||
@ -71,14 +72,14 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
const handleIssues = useCallback(
|
||||||
(sub_group_by: string | null, group_by: string | null, issue: IIssue, action: "update" | "delete") => {
|
(sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
if (action === "update") {
|
if (action === EIssueActions.UPDATE) {
|
||||||
profileIssuesStore.updateIssueStructure(group_by, sub_group_by, issue);
|
profileIssuesStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||||
}
|
}
|
||||||
if (action === "delete") profileIssuesStore.deleteIssue(group_by, sub_group_by, issue);
|
if (action === EIssueActions.DELETE) profileIssuesStore.deleteIssue(group_by, sub_group_by, issue);
|
||||||
},
|
},
|
||||||
[profileIssuesStore, issueDetailStore, workspaceSlug]
|
[profileIssuesStore, issueDetailStore, workspaceSlug]
|
||||||
);
|
);
|
||||||
@ -104,7 +105,8 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
|||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
{currentKanBanView === "default" ? (
|
{currentKanBanView === "default" ? (
|
||||||
<KanBan
|
<KanBan
|
||||||
issues={issues}
|
issues={{}}
|
||||||
|
issueIds={[]}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -112,8 +114,8 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
|||||||
quickActions={(sub_group_by, group_by, issue) => (
|
quickActions={(sub_group_by, group_by, issue) => (
|
||||||
<ProjectIssueQuickActions
|
<ProjectIssueQuickActions
|
||||||
issue={issue}
|
issue={issue}
|
||||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
@ -130,7 +132,8 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<KanBanSwimLanes
|
<KanBanSwimLanes
|
||||||
issues={issues}
|
issues={{}}
|
||||||
|
issueIds={[]}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -138,8 +141,8 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
|||||||
quickActions={(sub_group_by, group_by, issue) => (
|
quickActions={(sub_group_by, group_by, issue) => (
|
||||||
<ProjectIssueQuickActions
|
<ProjectIssueQuickActions
|
||||||
issue={issue}
|
issue={issue}
|
||||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
import { useCallback, useState } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { DragDropContext } from "@hello-pangea/dnd";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { KanBanSwimLanes } from "../swimlanes";
|
|
||||||
import { KanBan } from "../default";
|
|
||||||
import { ProjectIssueQuickActions } from "components/issues";
|
import { ProjectIssueQuickActions } from "components/issues";
|
||||||
import { Spinner } from "@plane/ui";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { EIssueActions } from "../../types";
|
||||||
|
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||||
|
|
||||||
export interface IKanBanLayout {}
|
export interface IKanBanLayout {}
|
||||||
|
|
||||||
@ -21,149 +17,31 @@ export const KanBanLayout: React.FC = observer(() => {
|
|||||||
const { workspaceSlug } = router.query as { workspaceSlug: string };
|
const { workspaceSlug } = router.query as { workspaceSlug: string };
|
||||||
|
|
||||||
const {
|
const {
|
||||||
project: { workspaceProjects },
|
projectIssues: issueStore,
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
issue: issueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueKanBanView: issueKanBanViewStore,
|
issueKanBanView: issueKanBanViewStore,
|
||||||
issueDetail: issueDetailStore,
|
issueDetail: issueDetailStore,
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const issues = issueStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
|
||||||
|
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
|
||||||
|
|
||||||
const order_by: string | null = issueFilterStore?.userDisplayFilters?.order_by || null;
|
|
||||||
|
|
||||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
|
||||||
|
|
||||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.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.source.droppableId &&
|
|
||||||
result.destination.droppableId &&
|
|
||||||
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: "update" | "delete") => {
|
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
if (action === "update") {
|
await issueDetailStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
|
||||||
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
}
|
|
||||||
if (action === "delete") issueStore.deleteIssue(group_by, sub_group_by, issue);
|
|
||||||
},
|
},
|
||||||
[issueStore, issueDetailStore, workspaceSlug]
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
await issueDetailStore.deleteIssue(workspaceSlug, issue.project, issue.id);
|
||||||
issueKanBanViewStore.handleKanBanToggle(toggle, value);
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
// const estimates =
|
|
||||||
// currentProjectDetails?.estimate !== null
|
|
||||||
// ? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
|
|
||||||
// : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BaseKanBanRoot
|
||||||
{issueStore.loader ? (
|
issueActions={issueActions}
|
||||||
<div className="w-full h-full flex justify-center items-center">
|
issueStore={issueStore}
|
||||||
<Spinner />
|
kanbanViewStore={issueKanBanViewStore}
|
||||||
</div>
|
showLoader={true}
|
||||||
) : (
|
QuickActions={ProjectIssueQuickActions}
|
||||||
<div className="relative min-w-full min-h-full h-max bg-custom-background-90 px-3 horizontal-scroll-enable">
|
/>
|
||||||
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
|
||||||
{currentKanBanView === "default" ? (
|
|
||||||
<KanBan
|
|
||||||
issues={issues}
|
|
||||||
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, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={workspaceProjects}
|
|
||||||
enableQuickIssueCreate
|
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
|
||||||
isDragStarted={isDragStarted}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<KanBanSwimLanes
|
|
||||||
issues={issues}
|
|
||||||
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, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={workspaceProjects}
|
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
|
||||||
isDragStarted={isDragStarted}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,107 +1,47 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { DragDropContext } from "@hello-pangea/dnd";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
import { useRouter } from "next/router";
|
||||||
import { KanBanSwimLanes } from "../swimlanes";
|
|
||||||
import { KanBan } from "../default";
|
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import { RootStore } from "store/root";
|
// constant
|
||||||
// constants
|
import { IIssue } from "types";
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { EIssueActions } from "../../types";
|
||||||
|
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
|
||||||
|
// components
|
||||||
|
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||||
|
|
||||||
export interface IViewKanBanLayout {}
|
export interface IViewKanBanLayout {}
|
||||||
|
|
||||||
export const ProjectViewKanBanLayout: React.FC = observer(() => {
|
export const ProjectViewKanBanLayout: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query as { workspaceSlug: string };
|
||||||
|
|
||||||
const {
|
const {
|
||||||
project: projectStore,
|
viewIssues: projectViewIssuesStore,
|
||||||
projectMember: { projectMembers },
|
issueKanBanView: projectViewIssueKanBanViewStore,
|
||||||
projectState: projectStateStore,
|
issueDetail: issueDetailStore,
|
||||||
issue: issueStore,
|
} = useMobxStore();
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueKanBanView: issueKanBanViewStore,
|
|
||||||
}: RootStore = useMobxStore();
|
|
||||||
|
|
||||||
const issues = issueStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||||
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
await issueDetailStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
|
||||||
|
},
|
||||||
|
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||||
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
await issueDetailStore.deleteIssue(workspaceSlug, issue.project, issue.id);
|
||||||
|
},
|
||||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by
|
|
||||||
? "swimlanes"
|
|
||||||
: "default";
|
|
||||||
|
|
||||||
const onDragEnd = (result: any) => {
|
|
||||||
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 updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => {
|
return (
|
||||||
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
<BaseKanBanRoot
|
||||||
};
|
issueActions={issueActions}
|
||||||
|
issueStore={projectViewIssuesStore}
|
||||||
const states = projectStateStore?.projectStates || null;
|
kanbanViewStore={projectViewIssueKanBanViewStore}
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
showLoader={true}
|
||||||
// const labels = projectStore?.projectLabels || null;
|
QuickActions={ProjectIssueQuickActions}
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
/>
|
||||||
const projects = projectStateStore?.projectStates || null;
|
);
|
||||||
const estimates = null;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <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={issues}
|
|
||||||
// sub_group_by={sub_group_by}
|
|
||||||
// group_by={group_by}
|
|
||||||
// handleIssues={updateIssue}
|
|
||||||
// display_properties={display_properties}
|
|
||||||
// kanBanToggle={() => {}}
|
|
||||||
// handleKanBanToggle={() => {}}
|
|
||||||
// states={states}
|
|
||||||
// stateGroups={stateGroups}
|
|
||||||
// priorities={priorities}
|
|
||||||
// labels={labels}
|
|
||||||
// members={members}
|
|
||||||
// projects={projects}
|
|
||||||
// estimates={estimates}
|
|
||||||
// />
|
|
||||||
// ) : (
|
|
||||||
// <KanBanSwimLanes
|
|
||||||
// issues={issues}
|
|
||||||
// sub_group_by={sub_group_by}
|
|
||||||
// group_by={group_by}
|
|
||||||
// handleIssues={updateIssue}
|
|
||||||
// display_properties={display_properties}
|
|
||||||
// kanBanToggle={() => {}}
|
|
||||||
// handleKanBanToggle={() => {}}
|
|
||||||
// states={states}
|
|
||||||
// stateGroups={stateGroups}
|
|
||||||
// priorities={priorities}
|
|
||||||
// labels={labels}
|
|
||||||
// members={members}
|
|
||||||
// projects={projects}
|
|
||||||
// estimates={estimates}
|
|
||||||
// />
|
|
||||||
// )}
|
|
||||||
// </DragDropContext>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
});
|
});
|
||||||
|
@ -6,11 +6,14 @@ import { KanBanSubGroupByHeaderRoot } from "./headers/sub-group-by-root";
|
|||||||
import { KanBan } from "./default";
|
import { KanBan } from "./default";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||||
|
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||||
// constants
|
// constants
|
||||||
import { getValueFromObject } from "constants/issue";
|
import { getValueFromObject } from "constants/issue";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
|
||||||
interface ISubGroupSwimlaneHeader {
|
interface ISubGroupSwimlaneHeader {
|
||||||
issues: any;
|
issues: IIssueResponse;
|
||||||
|
issueIds: any;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
list: any;
|
list: any;
|
||||||
@ -20,6 +23,7 @@ interface ISubGroupSwimlaneHeader {
|
|||||||
}
|
}
|
||||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||||
issues,
|
issues,
|
||||||
|
issueIds,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
list,
|
list,
|
||||||
@ -29,9 +33,9 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const calculateIssueCount = (column_id: string) => {
|
const calculateIssueCount = (column_id: string) => {
|
||||||
let issueCount = 0;
|
let issueCount = 0;
|
||||||
issues &&
|
issueIds &&
|
||||||
Object.keys(issues)?.forEach((_issueKey: any) => {
|
Object.keys(issueIds)?.forEach((_issueKey: any) => {
|
||||||
issueCount += issues?.[_issueKey]?.[column_id]?.length || 0;
|
issueCount += issueIds?.[_issueKey]?.[column_id]?.length || 0;
|
||||||
});
|
});
|
||||||
return issueCount;
|
return issueCount;
|
||||||
};
|
};
|
||||||
@ -58,6 +62,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||||
|
issues: IIssueResponse;
|
||||||
|
issueIds: any;
|
||||||
order_by: string | null;
|
order_by: string | null;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
states: IState[] | null;
|
states: IState[] | null;
|
||||||
@ -66,15 +72,9 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
|||||||
labels: IIssueLabel[] | null;
|
labels: IIssueLabel[] | null;
|
||||||
members: IUserLite[] | null;
|
members: IUserLite[] | null;
|
||||||
projects: IProject[] | null;
|
projects: IProject[] | null;
|
||||||
issues: any;
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||||
handleIssues: (
|
|
||||||
sub_group_by: string | null,
|
|
||||||
group_by: string | null,
|
|
||||||
issue: IIssue,
|
|
||||||
action: "update" | "delete"
|
|
||||||
) => void;
|
|
||||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
kanBanToggle: any;
|
kanBanToggle: any;
|
||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
isDragStarted?: boolean;
|
isDragStarted?: boolean;
|
||||||
@ -82,6 +82,7 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
|||||||
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues,
|
issues,
|
||||||
|
issueIds,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
order_by,
|
order_by,
|
||||||
@ -104,9 +105,9 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
|
|
||||||
const calculateIssueCount = (column_id: string) => {
|
const calculateIssueCount = (column_id: string) => {
|
||||||
let issueCount = 0;
|
let issueCount = 0;
|
||||||
issues?.[column_id] &&
|
issueIds?.[column_id] &&
|
||||||
Object.keys(issues?.[column_id])?.forEach((_list: any) => {
|
Object.keys(issueIds?.[column_id])?.forEach((_list: any) => {
|
||||||
issueCount += issues?.[column_id]?.[_list]?.length || 0;
|
issueCount += issueIds?.[column_id]?.[_list]?.length || 0;
|
||||||
});
|
});
|
||||||
return issueCount;
|
return issueCount;
|
||||||
};
|
};
|
||||||
@ -134,7 +135,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
|
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<KanBan
|
<KanBan
|
||||||
issues={issues?.[getValueFromObject(_list, listKey) as string]}
|
issues={issues}
|
||||||
|
issueIds={issueIds?.[getValueFromObject(_list, listKey) as string]}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -163,18 +165,14 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export interface IKanBanSwimLanes {
|
export interface IKanBanSwimLanes {
|
||||||
issues: any;
|
issues: IIssueResponse;
|
||||||
|
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
order_by: string | null;
|
order_by: string | null;
|
||||||
handleIssues: (
|
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||||
sub_group_by: string | null,
|
|
||||||
group_by: string | null,
|
|
||||||
issue: IIssue,
|
|
||||||
action: "update" | "delete"
|
|
||||||
) => void;
|
|
||||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | null;
|
||||||
kanBanToggle: any;
|
kanBanToggle: any;
|
||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
@ -190,6 +188,7 @@ export interface IKanBanSwimLanes {
|
|||||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues,
|
issues,
|
||||||
|
issueIds,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
order_by,
|
order_by,
|
||||||
@ -214,6 +213,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "project" && (
|
{group_by && group_by === "project" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={projects}
|
list={projects}
|
||||||
@ -226,6 +226,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "state" && (
|
{group_by && group_by === "state" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={states}
|
list={states}
|
||||||
@ -238,6 +239,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "state_detail.group" && (
|
{group_by && group_by === "state_detail.group" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={stateGroups}
|
list={stateGroups}
|
||||||
@ -250,6 +252,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "priority" && (
|
{group_by && group_by === "priority" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={priorities}
|
list={priorities}
|
||||||
@ -262,6 +265,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "labels" && (
|
{group_by && group_by === "labels" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||||
@ -274,6 +278,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "assignees" && (
|
{group_by && group_by === "assignees" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||||
@ -286,6 +291,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{group_by && group_by === "created_by" && (
|
{group_by && group_by === "created_by" && (
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={members}
|
list={members}
|
||||||
@ -299,6 +305,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "project" && (
|
{sub_group_by && sub_group_by === "project" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -323,6 +330,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "state" && (
|
{sub_group_by && sub_group_by === "state" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -347,6 +355,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "state" && (
|
{sub_group_by && sub_group_by === "state" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -371,6 +380,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -395,6 +405,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "priority" && (
|
{sub_group_by && sub_group_by === "priority" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -419,6 +430,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "labels" && (
|
{sub_group_by && sub_group_by === "labels" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -443,6 +455,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "assignees" && (
|
{sub_group_by && sub_group_by === "assignees" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
@ -467,6 +480,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
{sub_group_by && sub_group_by === "created_by" && (
|
{sub_group_by && sub_group_by === "created_by" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
issueIds={issueIds}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
order_by={order_by}
|
order_by={order_by}
|
||||||
|
120
web/components/issues/issue-layouts/list/base-list-root.tsx
Normal file
120
web/components/issues/issue-layouts/list/base-list-root.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { List } from "./default";
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
|
||||||
|
import { FC } from "react";
|
||||||
|
import { IIssue, IProject } from "types";
|
||||||
|
import { IProjectStore } from "store/project";
|
||||||
|
import { Spinner } from "@plane/ui";
|
||||||
|
import { IQuickActionProps } from "./list-view-types";
|
||||||
|
import {
|
||||||
|
ICycleIssuesFilterStore,
|
||||||
|
ICycleIssuesStore,
|
||||||
|
IModuleIssuesFilterStore,
|
||||||
|
IModuleIssuesStore,
|
||||||
|
IProjectIssuesFilterStore,
|
||||||
|
IProjectIssuesStore,
|
||||||
|
IViewIssuesFilterStore,
|
||||||
|
IViewIssuesStore,
|
||||||
|
} from "store/issues";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
|
enum EIssueActions {
|
||||||
|
UPDATE = "update",
|
||||||
|
DELETE = "delete",
|
||||||
|
REMOVE = "remove",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBaseListRoot {
|
||||||
|
issueFilterStore:
|
||||||
|
| IProjectIssuesFilterStore
|
||||||
|
| IModuleIssuesFilterStore
|
||||||
|
| ICycleIssuesFilterStore
|
||||||
|
| IViewIssuesFilterStore;
|
||||||
|
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
|
||||||
|
QuickActions: FC<IQuickActionProps>;
|
||||||
|
issueActions: {
|
||||||
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => void;
|
||||||
|
[EIssueActions.UPDATE]?: (group_by: string | null, issue: IIssue) => void;
|
||||||
|
[EIssueActions.REMOVE]?: (group_by: string | null, issue: IIssue) => void;
|
||||||
|
};
|
||||||
|
getProjects: (projectStore: IProjectStore) => IProject[] | null;
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||||
|
const { issueFilterStore, issueStore, QuickActions, issueActions, getProjects, viewId } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
project: projectStore,
|
||||||
|
projectMember: { projectMembers },
|
||||||
|
projectState: projectStateStore,
|
||||||
|
projectLabel: { projectLabels },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
|
const issueIds = issueStore.getIssuesIds || [];
|
||||||
|
const issues = issueStore.getIssues;
|
||||||
|
|
||||||
|
const displayFilters = issueFilterStore?.issueFilters?.displayFilters;
|
||||||
|
const group_by = displayFilters?.group_by || null;
|
||||||
|
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
|
||||||
|
|
||||||
|
const displayProperties = issueFilterStore?.issueFilters?.displayProperties;
|
||||||
|
|
||||||
|
const states = projectStateStore?.projectStates;
|
||||||
|
const priorities = ISSUE_PRIORITIES;
|
||||||
|
const labels = projectLabels;
|
||||||
|
const stateGroups = ISSUE_STATE_GROUPS;
|
||||||
|
const projects = getProjects(projectStore);
|
||||||
|
const members = projectMembers?.map((m) => m.member) ?? null;
|
||||||
|
const handleIssues = async (issue: IIssue, action: EIssueActions) => {
|
||||||
|
if (issueActions[action]) {
|
||||||
|
issueActions[action]!(group_by, issue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{issueStore.loader === "mutation" ? (
|
||||||
|
<div className="w-full h-full flex justify-center items-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={`relative w-full h-full bg-custom-background-90`}>
|
||||||
|
<List
|
||||||
|
issues={issues as unknown as IIssueResponse}
|
||||||
|
group_by={group_by}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(group_by, 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
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
displayProperties={displayProperties}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={labels}
|
||||||
|
members={members}
|
||||||
|
projects={projects}
|
||||||
|
issueIds={issueIds}
|
||||||
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={true}
|
||||||
|
isReadonly={false}
|
||||||
|
quickAddCallback={issueStore.quickAddIssue}
|
||||||
|
viewId={viewId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -1,26 +1,27 @@
|
|||||||
// components
|
// components
|
||||||
import { KanBanProperties } from "./properties";
|
import { ListProperties } from "./properties";
|
||||||
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
||||||
// ui
|
// ui
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Spinner, Tooltip } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IIssueDisplayProperties } from "types";
|
import { IIssue, IIssueDisplayProperties } from "types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
|
||||||
interface IssueBlockProps {
|
interface IssueBlockProps {
|
||||||
columnId: string;
|
columnId: string;
|
||||||
|
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
isReadonly?: boolean;
|
isReadonly?: boolean;
|
||||||
showEmptyGroup?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||||
const { columnId, issue, handleIssues, quickActions, displayProperties, showEmptyGroup, isReadonly } = props;
|
const { columnId, issue, handleIssues, quickActions, displayProperties, isReadonly } = props;
|
||||||
|
|
||||||
const updateIssue = (group_by: string | null, issueToUpdate: IIssue) => {
|
const updateIssue = (group_by: string | null, issueToUpdate: IIssue) => {
|
||||||
handleIssues(group_by, issueToUpdate, "update");
|
handleIssues(issueToUpdate, EIssueActions.UPDATE);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,16 +32,18 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
|||||||
{issue?.project_detail?.identifier}-{issue.sequence_id}
|
{issue?.project_detail?.identifier}-{issue.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{issue?.tempId !== undefined && (
|
{issue?.tempId !== undefined && (
|
||||||
<div className="absolute top-0 left-0 w-full h-full animate-pulse bg-custom-background-100/20 z-[99999]" />
|
<div className="absolute top-0 left-0 w-full h-full animate-pulse bg-custom-background-100/20 z-[99999]" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<IssuePeekOverview
|
<IssuePeekOverview
|
||||||
workspaceSlug={issue?.workspace_detail?.slug}
|
workspaceSlug={issue?.workspace_detail?.slug}
|
||||||
projectId={issue?.project_detail?.id}
|
projectId={issue?.project_detail?.id}
|
||||||
issueId={issue?.id}
|
issueId={issue?.id}
|
||||||
isArchived={issue?.archived_at !== null}
|
isArchived={issue?.archived_at !== null}
|
||||||
handleIssue={(issueToUpdate) => {
|
handleIssue={(issueToUpdate) => {
|
||||||
handleIssues(!columnId && columnId === "null" ? null : columnId, issueToUpdate as IIssue, "update");
|
handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||||
@ -49,15 +52,22 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
|||||||
</IssuePeekOverview>
|
</IssuePeekOverview>
|
||||||
|
|
||||||
<div className="ml-auto flex-shrink-0 flex items-center gap-2">
|
<div className="ml-auto flex-shrink-0 flex items-center gap-2">
|
||||||
<KanBanProperties
|
{!issue?.tempId ? (
|
||||||
columnId={columnId}
|
<>
|
||||||
issue={issue}
|
<ListProperties
|
||||||
isReadonly={isReadonly}
|
columnId={columnId}
|
||||||
handleIssues={updateIssue}
|
issue={issue}
|
||||||
displayProperties={displayProperties}
|
isReadonly={isReadonly}
|
||||||
showEmptyGroup={showEmptyGroup}
|
handleIssues={updateIssue}
|
||||||
/>
|
displayProperties={displayProperties}
|
||||||
{quickActions(!columnId && columnId === "null" ? null : columnId, issue)}
|
/>
|
||||||
|
{quickActions(!columnId && columnId === "null" ? null : columnId, issue)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="w-4 h-4">
|
||||||
|
<Spinner className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -3,33 +3,34 @@ import { FC } from "react";
|
|||||||
import { IssueBlock } from "components/issues";
|
import { IssueBlock } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IIssueDisplayProperties } from "types";
|
import { IIssue, IIssueDisplayProperties } from "types";
|
||||||
|
import { IIssueResponse, IGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
columnId: string;
|
columnId: string;
|
||||||
issues: IIssue[];
|
issueIds: IGroupedIssues | TUnGroupedIssues | any;
|
||||||
|
issues: IIssueResponse;
|
||||||
isReadonly?: boolean;
|
isReadonly?: boolean;
|
||||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
showEmptyGroup?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueBlocksList: FC<Props> = (props) => {
|
export const IssueBlocksList: FC<Props> = (props) => {
|
||||||
const { columnId, issues, handleIssues, quickActions, displayProperties, showEmptyGroup, isReadonly } = props;
|
const { columnId, issueIds, issues, handleIssues, quickActions, displayProperties, isReadonly } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full relative divide-y-[0.5px] divide-custom-border-200">
|
<div className="w-full h-full relative divide-y-[0.5px] divide-custom-border-200">
|
||||||
{issues && issues.length > 0 ? (
|
{issueIds && issueIds.length > 0 ? (
|
||||||
issues.map((issue) => (
|
issueIds.map((issueId: string) => (
|
||||||
<IssueBlock
|
<IssueBlock
|
||||||
key={issue.id}
|
key={issues[issueId].id}
|
||||||
columnId={columnId}
|
columnId={columnId}
|
||||||
issue={issue}
|
issue={issues[issueId]}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
isReadonly={isReadonly}
|
isReadonly={isReadonly}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
showEmptyGroup={showEmptyGroup}
|
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,243 +1,321 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
// components
|
// components
|
||||||
import { ListGroupByHeaderRoot } from "./headers/group-by-root";
|
import { ListGroupByHeaderRoot } from "./headers/group-by-root";
|
||||||
import { IssueBlocksList, ListInlineCreateIssueForm } from "components/issues";
|
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IEstimatePoint, IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||||
|
import { IIssueResponse, IGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||||
|
import { EIssueActions } from "../types";
|
||||||
// constants
|
// constants
|
||||||
import { getValueFromObject } from "constants/issue";
|
import { getValueFromObject } from "constants/issue";
|
||||||
|
|
||||||
export interface IGroupByList {
|
export interface IGroupByList {
|
||||||
|
issueIds: IGroupedIssues | TUnGroupedIssues | any;
|
||||||
issues: any;
|
issues: any;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
list: any;
|
list: any;
|
||||||
isReadonly?: boolean;
|
|
||||||
listKey: string;
|
listKey: string;
|
||||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
states: IState[] | null;
|
||||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
|
||||||
displayProperties: IIssueDisplayProperties;
|
|
||||||
is_list?: boolean;
|
is_list?: boolean;
|
||||||
enableQuickIssueCreate?: boolean;
|
handleIssues: (issue: IIssue, action: EIssueActions) => Promise<void>;
|
||||||
|
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
|
enableIssueQuickAdd: boolean;
|
||||||
showEmptyGroup?: boolean;
|
showEmptyGroup?: boolean;
|
||||||
|
isReadonly: boolean;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupByList: React.FC<IGroupByList> = observer((props) => {
|
const GroupByList: React.FC<IGroupByList> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
issueIds,
|
||||||
issues,
|
issues,
|
||||||
group_by,
|
group_by,
|
||||||
list,
|
list,
|
||||||
isReadonly,
|
|
||||||
listKey,
|
listKey,
|
||||||
|
is_list = false,
|
||||||
|
states,
|
||||||
handleIssues,
|
handleIssues,
|
||||||
quickActions,
|
quickActions,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
is_list = false,
|
enableIssueQuickAdd,
|
||||||
enableQuickIssueCreate,
|
|
||||||
showEmptyGroup,
|
showEmptyGroup,
|
||||||
|
isReadonly,
|
||||||
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
|
||||||
|
const defaultState = states?.find((state) => state.default);
|
||||||
|
if (groupByKey === null) return { state: defaultState?.id };
|
||||||
|
else {
|
||||||
|
if (groupByKey === "state") return { state: groupByKey === "state" ? value : defaultState?.id };
|
||||||
|
else return { state: defaultState?.id, [groupByKey]: value };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateEmptyIssueGroups = (issues: IIssue[]) => {
|
||||||
|
const issuesCount = issues?.length || 0;
|
||||||
|
if (!showEmptyGroup && issuesCount <= 0) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
{list &&
|
{list &&
|
||||||
list.length > 0 &&
|
list.length > 0 &&
|
||||||
list.map((_list: any) => (
|
list.map(
|
||||||
<div key={getValueFromObject(_list, listKey) as string} className={`flex-shrink-0 flex flex-col`}>
|
(_list: any) =>
|
||||||
<div className="flex-shrink-0 w-full py-1 sticky top-0 z-[2] px-3 bg-custom-background-90">
|
validateEmptyIssueGroups(is_list ? issueIds : issueIds?.[getValueFromObject(_list, listKey) as string]) && (
|
||||||
<ListGroupByHeaderRoot
|
<div key={getValueFromObject(_list, listKey) as string} className={`flex-shrink-0 flex flex-col`}>
|
||||||
column_id={getValueFromObject(_list, listKey) as string}
|
<div className="flex-shrink-0 w-full py-1 sticky top-0 z-[2] px-3 bg-custom-background-90 border-b border-custom-border-200">
|
||||||
column_value={_list}
|
<ListGroupByHeaderRoot
|
||||||
group_by={group_by}
|
column_id={getValueFromObject(_list, listKey) as string}
|
||||||
issues_count={
|
column_value={_list}
|
||||||
is_list ? issues?.length || 0 : issues?.[getValueFromObject(_list, listKey) as string]?.length || 0
|
group_by={group_by}
|
||||||
}
|
issues_count={
|
||||||
/>
|
is_list
|
||||||
</div>
|
? issueIds?.length || 0
|
||||||
{issues && (
|
: issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0
|
||||||
<IssueBlocksList
|
}
|
||||||
columnId={getValueFromObject(_list, listKey) as string}
|
/>
|
||||||
issues={is_list ? issues : issues[getValueFromObject(_list, listKey) as string]}
|
</div>
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={quickActions}
|
{issues && (
|
||||||
displayProperties={displayProperties}
|
<IssueBlocksList
|
||||||
isReadonly={isReadonly}
|
columnId={getValueFromObject(_list, listKey) as string}
|
||||||
showEmptyGroup={showEmptyGroup}
|
issueIds={is_list ? issueIds || 0 : issueIds?.[getValueFromObject(_list, listKey) as string] || 0}
|
||||||
/>
|
issues={issues}
|
||||||
)}
|
handleIssues={handleIssues}
|
||||||
{enableQuickIssueCreate && (
|
quickActions={quickActions}
|
||||||
<ListInlineCreateIssueForm
|
displayProperties={displayProperties}
|
||||||
groupId={getValueFromObject(_list, listKey) as string}
|
isReadonly={isReadonly}
|
||||||
prePopulatedData={{
|
/>
|
||||||
[group_by!]: getValueFromObject(_list, listKey),
|
)}
|
||||||
}}
|
|
||||||
/>
|
{enableIssueQuickAdd && (
|
||||||
)}
|
<div className="flex-shrink-0 w-full sticky bottom-0 z-[1]">
|
||||||
</div>
|
<ListQuickAddIssueForm
|
||||||
))}
|
prePopulatedData={prePopulateQuickAddData(group_by, getValueFromObject(_list, listKey))}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
// TODO: update all the types
|
|
||||||
export interface IList {
|
export interface IList {
|
||||||
issues: any;
|
issueIds: IGroupedIssues | TUnGroupedIssues | any;
|
||||||
|
issues: IIssueResponse | undefined;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
isReadonly?: boolean;
|
handleIssues: (issue: IIssue, action: EIssueActions) => Promise<void>;
|
||||||
handleDragDrop?: (result: any) => void | undefined;
|
|
||||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
|
||||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
|
showEmptyGroup: boolean;
|
||||||
|
enableIssueQuickAdd: boolean;
|
||||||
|
isReadonly: boolean;
|
||||||
states: IState[] | null;
|
states: IState[] | null;
|
||||||
labels: IIssueLabel[] | null;
|
labels: IIssueLabel[] | null;
|
||||||
members: IUserLite[] | null;
|
members: IUserLite[] | null;
|
||||||
projects: IProject[] | null;
|
projects: IProject[] | null;
|
||||||
stateGroups: any;
|
stateGroups: any;
|
||||||
priorities: any;
|
priorities: any;
|
||||||
enableQuickIssueCreate?: boolean;
|
quickAddCallback?: (
|
||||||
estimates: IEstimatePoint[] | null;
|
workspaceSlug: string,
|
||||||
showEmptyGroup?: boolean;
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const List: React.FC<IList> = observer((props) => {
|
export const List: React.FC<IList> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
issueIds,
|
||||||
issues,
|
issues,
|
||||||
group_by,
|
group_by,
|
||||||
isReadonly,
|
|
||||||
handleIssues,
|
handleIssues,
|
||||||
quickActions,
|
quickActions,
|
||||||
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
|
showEmptyGroup,
|
||||||
|
enableIssueQuickAdd,
|
||||||
|
isReadonly,
|
||||||
|
|
||||||
states,
|
states,
|
||||||
|
stateGroups,
|
||||||
|
priorities,
|
||||||
labels,
|
labels,
|
||||||
members,
|
members,
|
||||||
projects,
|
projects,
|
||||||
stateGroups,
|
|
||||||
priorities,
|
|
||||||
showEmptyGroup,
|
|
||||||
enableQuickIssueCreate,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
{group_by === null && (
|
{group_by === null && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as TUnGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={[{ id: "null", title: "All Issues" }]}
|
list={[{ id: `null`, title: `All Issues` }]}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
is_list={true}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
is_list
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "project" && projects && (
|
{group_by && group_by === "project" && projects && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={projects}
|
list={projects}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "state" && states && (
|
{group_by && group_by === "state" && states && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={states}
|
list={states}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "state_detail.group" && stateGroups && (
|
{group_by && group_by === "state_detail.group" && stateGroups && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={stateGroups}
|
list={stateGroups}
|
||||||
listKey={`key`}
|
listKey={`key`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "priority" && priorities && (
|
{group_by && group_by === "priority" && priorities && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={priorities}
|
list={priorities}
|
||||||
listKey={`key`}
|
listKey={`key`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "labels" && labels && (
|
{group_by && group_by === "labels" && labels && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={[...labels, { id: "None", name: "None" }]}
|
list={[...labels, { id: "None", name: "None" }]}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "assignees" && members && (
|
{group_by && group_by === "assignees" && members && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={[...members, { id: "None", display_name: "None" }]}
|
list={[...members, { id: "None", display_name: "None" }]}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group_by && group_by === "created_by" && members && (
|
{group_by && group_by === "created_by" && members && (
|
||||||
<GroupByList
|
<GroupByList
|
||||||
|
issueIds={issueIds as IGroupedIssues}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
list={members}
|
list={members}
|
||||||
listKey={`id`}
|
listKey={`id`}
|
||||||
|
states={states}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
enableIssueQuickAdd={enableIssueQuickAdd}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
quickAddCallback={quickAddCallback}
|
||||||
|
viewId={viewId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// services
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
import { IssueService } from "services/issue";
|
|
||||||
// lucide icons
|
// lucide icons
|
||||||
import { CircleDashed, Plus } from "lucide-react";
|
import { CircleDashed, Plus } from "lucide-react";
|
||||||
// components
|
// components
|
||||||
@ -23,18 +20,15 @@ interface IHeaderGroupByCard {
|
|||||||
issuePayload: Partial<IIssue>;
|
issuePayload: Partial<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleService = new ModuleService();
|
|
||||||
const issueService = new IssueService();
|
|
||||||
|
|
||||||
export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: IHeaderGroupByCard) => {
|
export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: IHeaderGroupByCard) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId, cycleId } = router.query;
|
const { workspaceSlug, projectId, moduleId, cycleId } = router.query;
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
|
||||||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const verticalAlignPosition = false;
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
||||||
|
|
||||||
const renderExistingIssueModal = moduleId || cycleId;
|
const renderExistingIssueModal = moduleId || cycleId;
|
||||||
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
|
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
|
||||||
@ -46,15 +40,15 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }:
|
|||||||
issues: data.map((i) => i.id),
|
issues: data.map((i) => i.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
await moduleService
|
// await moduleService
|
||||||
.addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload)
|
// .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user)
|
||||||
.catch(() =>
|
// .catch(() =>
|
||||||
setToastAlert({
|
// setToastAlert({
|
||||||
type: "error",
|
// type: "error",
|
||||||
title: "Error!",
|
// title: "Error!",
|
||||||
message: "Selected issues could not be added to the module. Please try again.",
|
// message: "Selected issues could not be added to the module. Please try again.",
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
|
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
|
||||||
@ -64,46 +58,27 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }:
|
|||||||
issues: data.map((i) => i.id),
|
issues: data.map((i) => i.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
await issueService
|
// await issueService
|
||||||
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload)
|
// .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user)
|
||||||
.catch(() => {
|
// .catch(() => {
|
||||||
setToastAlert({
|
// setToastAlert({
|
||||||
type: "error",
|
// type: "error",
|
||||||
title: "Error!",
|
// title: "Error!",
|
||||||
message: "Selected issues could not be added to the cycle. Please try again.",
|
// message: "Selected issues could not be added to the cycle. Please try again.",
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdateIssueModal isOpen={isOpen} handleClose={() => setIsOpen(false)} prePopulateData={issuePayload} />
|
<div className="flex-shrink-0 relative flex gap-2 py-1.5 flex-row items-center w-full">
|
||||||
{renderExistingIssueModal && (
|
|
||||||
<ExistingIssuesListModal
|
|
||||||
isOpen={openExistingIssueListModal}
|
|
||||||
handleClose={() => setOpenExistingIssueListModal(false)}
|
|
||||||
searchParams={ExistingIssuesListModalPayload}
|
|
||||||
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`flex-shrink-0 relative flex gap-2 py-1.5 ${
|
|
||||||
verticalAlignPosition ? `flex-col items-center w-11` : `flex-row items-center w-full`
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center">
|
<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} />}
|
{icon ? icon : <CircleDashed className="h-3.5 w-3.5" strokeWidth={2} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`flex items-center gap-1 ${verticalAlignPosition ? `flex-col` : `flex-row w-full`}`}>
|
<div className="flex items-center gap-1 flex-row w-full">
|
||||||
<div
|
<div className="font-medium line-clamp-1 text-custom-text-100">{title}</div>
|
||||||
className={`font-medium line-clamp-1 text-custom-text-100 ${verticalAlignPosition ? `vertical-lr` : ``}`}
|
<div className="text-sm font-medium text-custom-text-300 pl-2">{count || 0}</div>
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
<div className={`text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}>
|
|
||||||
{count || 0}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renderExistingIssueModal ? (
|
{renderExistingIssueModal ? (
|
||||||
@ -130,6 +105,25 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }:
|
|||||||
<Plus width={14} strokeWidth={2} />
|
<Plus width={14} strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<CreateUpdateIssueModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
handleClose={() => setIsOpen(false)}
|
||||||
|
handleSubmit={(data: Partial<IIssue>) => {
|
||||||
|
console.log(data);
|
||||||
|
return Promise.resolve();
|
||||||
|
}}
|
||||||
|
prePopulateData={issuePayload}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{renderExistingIssueModal && (
|
||||||
|
<ExistingIssuesListModal
|
||||||
|
isOpen={openExistingIssueListModal}
|
||||||
|
handleClose={() => setOpenExistingIssueListModal(false)}
|
||||||
|
searchParams={ExistingIssuesListModalPayload}
|
||||||
|
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -2,4 +2,4 @@ export * from "./roots";
|
|||||||
export * from "./block";
|
export * from "./block";
|
||||||
export * from "./roots";
|
export * from "./roots";
|
||||||
export * from "./blocks-list";
|
export * from "./blocks-list";
|
||||||
export * from "./inline-create-issue-form";
|
export * from "./quick-add-issue-form";
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
// hooks
|
|
||||||
import useToast from "hooks/use-toast";
|
|
||||||
import useKeypress from "hooks/use-keypress";
|
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|
||||||
// store
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// helpers
|
|
||||||
import { createIssuePayload } from "helpers/issue.helper";
|
|
||||||
// types
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
groupId?: string;
|
|
||||||
prePopulatedData?: Partial<IIssue>;
|
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultValues: Partial<IIssue> = {
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const Inputs = (props: any) => {
|
|
||||||
const { register, setFocus, projectDetails } = props;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFocus("name");
|
|
||||||
}, [setFocus]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h4 className="text-xs font-medium text-custom-text-400">{projectDetails?.identifier ?? "..."}</h4>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
placeholder="Issue Title"
|
|
||||||
{...register("name", {
|
|
||||||
required: "Issue title is required.",
|
|
||||||
})}
|
|
||||||
className="w-full px-2 py-3 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ListInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|
||||||
const { prePopulatedData, groupId } = props;
|
|
||||||
|
|
||||||
// router
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
|
|
||||||
// store
|
|
||||||
const { workspace: workspaceStore, quickAddIssue: quickAddStore } = useMobxStore();
|
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
|
||||||
|
|
||||||
const {
|
|
||||||
reset,
|
|
||||||
handleSubmit,
|
|
||||||
setFocus,
|
|
||||||
register,
|
|
||||||
formState: { errors, isSubmitting },
|
|
||||||
} = useForm<IIssue>({ defaultValues });
|
|
||||||
|
|
||||||
// ref
|
|
||||||
const ref = useRef<HTMLFormElement>(null);
|
|
||||||
|
|
||||||
// states
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleClose = () => setIsOpen(false);
|
|
||||||
|
|
||||||
// hooks
|
|
||||||
useKeypress("Escape", handleClose);
|
|
||||||
useOutsideClickDetector(ref, handleClose);
|
|
||||||
const { setToastAlert } = useToast();
|
|
||||||
|
|
||||||
// derived values
|
|
||||||
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) reset({ ...defaultValues });
|
|
||||||
}, [isOpen, reset]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!errors) return;
|
|
||||||
|
|
||||||
Object.keys(errors).forEach((key) => {
|
|
||||||
const error = errors[key as keyof IIssue];
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: error?.message?.toString() || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [errors, setToastAlert]);
|
|
||||||
|
|
||||||
const onSubmitHandler = async (formData: IIssue) => {
|
|
||||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
// resetting the form so that user can add another issue quickly
|
|
||||||
reset({ ...defaultValues });
|
|
||||||
|
|
||||||
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
|
||||||
...(prePopulatedData ?? {}),
|
|
||||||
...formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
quickAddStore.createIssue(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
{
|
|
||||||
group_id: groupId ?? null,
|
|
||||||
sub_group_id: null,
|
|
||||||
},
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Issue created successfully.",
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
Object.keys(err || {}).forEach((key) => {
|
|
||||||
const error = err?.[key];
|
|
||||||
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: errorTitle || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-custom-background-100">
|
|
||||||
{isOpen && (
|
|
||||||
<form
|
|
||||||
ref={ref}
|
|
||||||
onSubmit={handleSubmit(onSubmitHandler)}
|
|
||||||
className="absolute flex items-center gap-x-3 border-[0.5px] w-full border-t-0 border-custom-border-100 px-3 bg-custom-background-100 shadow-custom-shadow-sm z-10"
|
|
||||||
>
|
|
||||||
<Inputs register={register} setFocus={setFocus} projectDetails={projectDetails} />
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<p className="text-xs ml-3 my-3 mt-14 italic text-custom-text-200">
|
|
||||||
Press {"'"}Enter{"'"} to add another issue
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isOpen && (
|
|
||||||
<div className="w-full border-t-[0.5px] border-custom-border-200">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex items-center gap-x-[6px] text-custom-primary-100 p-3"
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
>
|
|
||||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
|
||||||
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
6
web/components/issues/issue-layouts/list/list-view-types.d.ts
vendored
Normal file
6
web/components/issues/issue-layouts/list/list-view-types.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface IQuickActionProps {
|
||||||
|
issue: IIssue;
|
||||||
|
handleDelete: () => Promise<void>;
|
||||||
|
handleUpdate?: (data: IIssue) => Promise<void>;
|
||||||
|
handleRemoveFromView?: () => Promise<void>;
|
||||||
|
}
|
@ -13,17 +13,16 @@ import { Tooltip } from "@plane/ui";
|
|||||||
// types
|
// types
|
||||||
import { IIssue, IIssueDisplayProperties, IState, TIssuePriorities } from "types";
|
import { IIssue, IIssueDisplayProperties, IState, TIssuePriorities } from "types";
|
||||||
|
|
||||||
export interface IKanBanProperties {
|
export interface IListProperties {
|
||||||
columnId: string;
|
columnId: string;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
handleIssues: (group_by: string | null, issue: IIssue) => void;
|
handleIssues: (group_by: string | null, issue: IIssue) => void;
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
isReadonly?: boolean;
|
isReadonly?: boolean;
|
||||||
showEmptyGroup?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
export const ListProperties: FC<IListProperties> = observer((props) => {
|
||||||
const { columnId: group_id, issue, handleIssues, displayProperties, isReadonly, showEmptyGroup } = props;
|
const { columnId: group_id, issue, handleIssues, displayProperties, isReadonly } = props;
|
||||||
|
|
||||||
const handleState = (state: IState) => {
|
const handleState = (state: IState) => {
|
||||||
handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, state: state.id });
|
handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, state: state.id });
|
||||||
@ -60,7 +59,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
|||||||
{displayProperties && displayProperties?.state && (
|
{displayProperties && displayProperties?.state && (
|
||||||
<IssuePropertyState
|
<IssuePropertyState
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.state_detail || null}
|
value={issue?.state || null}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
onChange={handleState}
|
onChange={handleState}
|
||||||
disabled={isReadonly}
|
disabled={isReadonly}
|
||||||
@ -78,7 +77,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* label */}
|
{/* label */}
|
||||||
{displayProperties && displayProperties?.labels && (showEmptyGroup || issue?.labels.length > 0) && (
|
{displayProperties && displayProperties?.labels && (
|
||||||
<IssuePropertyLabels
|
<IssuePropertyLabels
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.labels || null}
|
value={issue?.labels || null}
|
||||||
@ -89,7 +88,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* assignee */}
|
{/* assignee */}
|
||||||
{displayProperties && displayProperties?.assignee && (showEmptyGroup || issue?.assignees?.length > 0) && (
|
{displayProperties && displayProperties?.assignee && (
|
||||||
<IssuePropertyAssignee
|
<IssuePropertyAssignee
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.assignees || null}
|
value={issue?.assignees || null}
|
||||||
@ -101,7 +100,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* start date */}
|
{/* start date */}
|
||||||
{displayProperties && displayProperties?.start_date && (showEmptyGroup || issue?.start_date) && (
|
{displayProperties && displayProperties?.start_date && (
|
||||||
<IssuePropertyDate
|
<IssuePropertyDate
|
||||||
value={issue?.start_date || null}
|
value={issue?.start_date || null}
|
||||||
onChange={(date: string) => handleStartDate(date)}
|
onChange={(date: string) => handleStartDate(date)}
|
||||||
@ -111,7 +110,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* target/due date */}
|
{/* target/due date */}
|
||||||
{displayProperties && displayProperties?.due_date && (showEmptyGroup || issue?.target_date) && (
|
{displayProperties && displayProperties?.due_date && (
|
||||||
<IssuePropertyDate
|
<IssuePropertyDate
|
||||||
value={issue?.target_date || null}
|
value={issue?.target_date || null}
|
||||||
onChange={(date: string) => handleTargetDate(date)}
|
onChange={(date: string) => handleTargetDate(date)}
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
import { FC, useEffect, useState, useRef } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// hooks
|
||||||
|
import useToast from "hooks/use-toast";
|
||||||
|
import useKeypress from "hooks/use-keypress";
|
||||||
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
|
// store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// constants
|
||||||
|
import { IIssue, IProject } from "types";
|
||||||
|
// types
|
||||||
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
|
|
||||||
|
interface IInputProps {
|
||||||
|
formKey: string;
|
||||||
|
register: any;
|
||||||
|
setFocus: any;
|
||||||
|
projectDetail: IProject | null;
|
||||||
|
}
|
||||||
|
const Inputs: FC<IInputProps> = (props) => {
|
||||||
|
const { formKey, register, setFocus, projectDetail } = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFocus(formKey);
|
||||||
|
}, [formKey, setFocus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 w-full">
|
||||||
|
<div className="text-xs font-medium text-custom-text-400">{projectDetail?.identifier ?? "..."}</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="Issue Title"
|
||||||
|
{...register(formKey, {
|
||||||
|
required: "Issue title is required.",
|
||||||
|
})}
|
||||||
|
className="w-full px-2 py-3 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IListQuickAddIssueForm {
|
||||||
|
prePopulatedData?: Partial<IIssue>;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValues: Partial<IIssue> = {
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props) => {
|
||||||
|
const { prePopulatedData, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
|
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||||
|
|
||||||
|
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||||
|
const projectDetail: IProject | null =
|
||||||
|
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||||
|
|
||||||
|
const ref = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const handleClose = () => setIsOpen(false);
|
||||||
|
|
||||||
|
useKeypress("Escape", handleClose);
|
||||||
|
useOutsideClickDetector(ref, handleClose);
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
const {
|
||||||
|
reset,
|
||||||
|
handleSubmit,
|
||||||
|
setFocus,
|
||||||
|
register,
|
||||||
|
formState: { errors, isSubmitting },
|
||||||
|
} = useForm<IIssue>({ defaultValues });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) reset({ ...defaultValues });
|
||||||
|
}, [isOpen, reset]);
|
||||||
|
|
||||||
|
const onSubmitHandler = async (formData: IIssue) => {
|
||||||
|
if (isSubmitting || !workspaceDetail || !projectDetail) return;
|
||||||
|
|
||||||
|
reset({ ...defaultValues });
|
||||||
|
|
||||||
|
const payload = createIssuePayload(workspaceDetail, projectDetail, {
|
||||||
|
...(prePopulatedData ?? {}),
|
||||||
|
...formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
quickAddCallback && (await quickAddCallback(workspaceSlug, projectId, { ...payload }, viewId));
|
||||||
|
setToastAlert({
|
||||||
|
type: "success",
|
||||||
|
title: "Success!",
|
||||||
|
message: "Issue created successfully.",
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: err?.message || "Some error occurred. Please try again.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`bg-custom-background-100 border-t border-b border-custom-border-200 ${
|
||||||
|
errors && errors?.name && errors?.name?.message ? `border-red-500 bg-red-500/10` : ``
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isOpen ? (
|
||||||
|
<div className="shadow-custom-shadow-sm">
|
||||||
|
<form
|
||||||
|
ref={ref}
|
||||||
|
onSubmit={handleSubmit(onSubmitHandler)}
|
||||||
|
className="flex items-center gap-x-3 border-[0.5px] w-full border-t-0 border-custom-border-100 px-3 bg-custom-background-100"
|
||||||
|
>
|
||||||
|
<Inputs formKey={"name"} register={register} setFocus={setFocus} projectDetail={projectDetail} />
|
||||||
|
</form>
|
||||||
|
<div className="text-xs italic text-custom-text-200 px-3 py-2">{`Press 'Enter' to add another issue`}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="w-full flex items-center text-custom-primary-100 p-3 py-3 cursor-pointer gap-2"
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||||
|
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -4,73 +4,42 @@ import { observer } from "mobx-react-lite";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { List } from "../default";
|
|
||||||
import { ArchivedIssueQuickActions } from "components/issues";
|
import { ArchivedIssueQuickActions } from "components/issues";
|
||||||
// helpers
|
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
import { IProjectStore } from "store/project";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
|
||||||
export const ArchivedIssueListLayout: FC = observer(() => {
|
export const ArchivedIssueListLayout: FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const {
|
const { archivedIssues: archivedIssueStore, archivedIssueFilters: archivedIssueFiltersStore } = useMobxStore();
|
||||||
project: projectStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
projectEstimates: { projectEstimates },
|
|
||||||
archivedIssues: archivedIssueStore,
|
|
||||||
archivedIssueFilters: archivedIssueFiltersStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
// derived values
|
const issueActions = {
|
||||||
const issues = archivedIssueStore.getIssues;
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const displayProperties = archivedIssueFiltersStore?.userDisplayProperties || null;
|
if (!workspaceSlug || !projectId) return;
|
||||||
const group_by: string | null = archivedIssueFiltersStore?.userDisplayFilters?.group_by || null;
|
|
||||||
const showEmptyGroup = archivedIssueFiltersStore?.userDisplayFilters?.show_empty_groups || false;
|
|
||||||
|
|
||||||
const handleIssues = (group_by: string | null, issue: IIssue, action: "delete" | "update") => {
|
archivedIssueStore.deleteArchivedIssue(group_by, null, issue);
|
||||||
if (!workspaceSlug || !projectId) return;
|
},
|
||||||
|
|
||||||
if (action === "delete") {
|
|
||||||
archivedIssueStore.deleteArchivedIssue(group_by === "null" ? null : group_by, null, issue);
|
|
||||||
archivedIssueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
|
const getProjects = (projectStore: IProjectStore) => {
|
||||||
|
if (!workspaceSlug) return null;
|
||||||
|
return projectStore?.projects[workspaceSlug.toString()] || null;
|
||||||
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
return null;
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
|
||||||
const estimates =
|
|
||||||
projectDetails?.estimate !== null ? projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null : null;
|
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div className="relative w-full h-full bg-custom-background-90">
|
// <BaseListRoot
|
||||||
<List
|
// issueFilterStore={archivedIssueFiltersStore}
|
||||||
issues={issues}
|
// issueStore={archivedIssueStore}
|
||||||
group_by={group_by}
|
// QuickActions={ArchivedIssueQuickActions}
|
||||||
isReadonly
|
// issueActions={issueActions}
|
||||||
handleIssues={handleIssues}
|
// getProjects={getProjects}
|
||||||
quickActions={(group_by, issue) => (
|
// />
|
||||||
<ArchivedIssueQuickActions issue={issue} handleDelete={async () => handleIssues(group_by, issue, "delete")} />
|
// );
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
|
||||||
showEmptyGroup={showEmptyGroup}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -1,96 +1,52 @@
|
|||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { List } from "../default";
|
|
||||||
import { CycleIssueQuickActions } from "components/issues";
|
import { CycleIssueQuickActions } from "components/issues";
|
||||||
// helpers
|
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
import { IProjectStore } from "store/project";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
|
||||||
export interface ICycleListLayout {}
|
export interface ICycleListLayout {}
|
||||||
|
|
||||||
export const CycleListLayout: React.FC = observer(() => {
|
export const CycleListLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, cycleId } = router.query;
|
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string };
|
||||||
// store
|
// store
|
||||||
const {
|
const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
|
||||||
project: projectStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
projectEstimates: { projectEstimates },
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
cycleIssue: cycleIssueStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
const { currentProjectDetails } = projectStore;
|
|
||||||
|
|
||||||
const issues = cycleIssueStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
|
||||||
|
|
||||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(group_by: string | null, issue: IIssue, action: "update" | "delete" | "remove") => {
|
|
||||||
if (!workspaceSlug || !cycleId) return;
|
if (!workspaceSlug || !cycleId) return;
|
||||||
|
cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId);
|
||||||
if (action === "update") {
|
|
||||||
cycleIssueStore.updateIssueStructure(group_by, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
}
|
|
||||||
if (action === "delete") cycleIssueStore.deleteIssue(group_by, null, issue);
|
|
||||||
if (action === "remove" && issue.bridge_id) {
|
|
||||||
cycleIssueStore.deleteIssue(group_by, null, issue);
|
|
||||||
cycleIssueStore.removeIssueFromCycle(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
issue.project,
|
|
||||||
cycleId.toString(),
|
|
||||||
issue.bridge_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[cycleIssueStore, issueDetailStore, cycleId, workspaceSlug]
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug || !cycleId) return;
|
||||||
|
cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId);
|
||||||
const states = projectStateStore?.projectStates || null;
|
},
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
[EIssueActions.REMOVE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
|
||||||
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
|
||||||
const estimates =
|
},
|
||||||
currentProjectDetails?.estimate !== null
|
};
|
||||||
? projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
|
const getProjects = (projectStore: IProjectStore) => {
|
||||||
: null;
|
if (!workspaceSlug) return null;
|
||||||
|
return projectStore?.projects[workspaceSlug] || null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative w-full h-full bg-custom-background-90`}>
|
<BaseListRoot
|
||||||
<List
|
issueFilterStore={cycleIssueFilterStore}
|
||||||
issues={issues}
|
issueStore={cycleIssueStore}
|
||||||
group_by={group_by}
|
QuickActions={CycleIssueQuickActions}
|
||||||
handleIssues={handleIssues}
|
issueActions={issueActions}
|
||||||
quickActions={(group_by, issue) => (
|
getProjects={getProjects}
|
||||||
<CycleIssueQuickActions
|
viewId={cycleId}
|
||||||
issue={issue}
|
/>
|
||||||
handleDelete={async () => handleIssues(group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
|
||||||
handleRemoveFromCycle={async () => handleIssues(group_by, issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,96 +1,53 @@
|
|||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { List } from "../default";
|
|
||||||
import { ModuleIssueQuickActions } from "components/issues";
|
import { ModuleIssueQuickActions } from "components/issues";
|
||||||
// helpers
|
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
import { IProjectStore } from "store/project";
|
||||||
|
|
||||||
export interface IModuleListLayout {}
|
export interface IModuleListLayout {}
|
||||||
|
|
||||||
export const ModuleListLayout: React.FC = observer(() => {
|
export const ModuleListLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, moduleId } = router.query;
|
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string };
|
||||||
|
|
||||||
const {
|
const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
|
||||||
project: projectStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
projectEstimates: { projectEstimates },
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
moduleIssue: moduleIssueStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
const { currentProjectDetails } = projectStore;
|
|
||||||
|
|
||||||
const issues = moduleIssueStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
|
||||||
|
|
||||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(group_by: string | null, issue: IIssue, action: "update" | "delete" | "remove") => {
|
|
||||||
if (!workspaceSlug || !moduleId) return;
|
if (!workspaceSlug || !moduleId) return;
|
||||||
|
moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
|
||||||
if (action === "update") {
|
|
||||||
moduleIssueStore.updateIssueStructure(group_by, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
}
|
|
||||||
if (action === "delete") moduleIssueStore.deleteIssue(group_by, null, issue);
|
|
||||||
if (action === "remove" && issue.bridge_id) {
|
|
||||||
moduleIssueStore.deleteIssue(group_by, null, issue);
|
|
||||||
moduleIssueStore.removeIssueFromModule(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
issue.project,
|
|
||||||
moduleId.toString(),
|
|
||||||
issue.bridge_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug || !moduleId) return;
|
||||||
|
moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
|
||||||
|
},
|
||||||
|
[EIssueActions.REMOVE]: (group_by: string | null, issue: IIssue) => {
|
||||||
|
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
|
||||||
|
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
const getProjects = (projectStore: IProjectStore) => {
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
if (!workspaceSlug) return null;
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
return projectStore?.projects[workspaceSlug] || null;
|
||||||
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
};
|
||||||
const estimates =
|
|
||||||
currentProjectDetails?.estimate !== null
|
|
||||||
? projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full bg-custom-background-90">
|
<BaseListRoot
|
||||||
<List
|
issueFilterStore={moduleIssueFilterStore}
|
||||||
issues={issues}
|
issueStore={moduleIssueStore}
|
||||||
group_by={group_by}
|
QuickActions={ModuleIssueQuickActions}
|
||||||
handleIssues={handleIssues}
|
issueActions={issueActions}
|
||||||
quickActions={(group_by, issue) => (
|
getProjects={getProjects}
|
||||||
<ModuleIssueQuickActions
|
viewId={moduleId}
|
||||||
issue={issue}
|
/>
|
||||||
handleDelete={async () => handleIssues(group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
|
||||||
handleRemoveFromModule={async () => handleIssues(group_by, issue, "remove")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
import { FC, useCallback } from "react";
|
import { FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { List } from "../default";
|
|
||||||
import { ProjectIssueQuickActions } from "components/issues";
|
import { ProjectIssueQuickActions } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
import { EIssueActions } from "../../types";
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { IProjectStore } from "store/project";
|
||||||
|
//components
|
||||||
export interface IProfileIssuesListLayout {}
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
|
||||||
export const ProfileIssuesListLayout: FC = observer(() => {
|
export const ProfileIssuesListLayout: FC = observer(() => {
|
||||||
const {
|
const {
|
||||||
workspace: workspaceStore,
|
|
||||||
projectState: projectStateStore,
|
|
||||||
project: projectStore,
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
profileIssueFilters: profileIssueFiltersStore,
|
profileIssueFilters: profileIssueFiltersStore,
|
||||||
profileIssues: profileIssuesStore,
|
profileIssues: profileIssuesStore,
|
||||||
issueDetail: issueDetailStore,
|
issueDetail: issueDetailStore,
|
||||||
@ -27,53 +22,29 @@ export const ProfileIssuesListLayout: FC = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const issues = profileIssuesStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const group_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.group_by || null;
|
|
||||||
|
|
||||||
const displayProperties = profileIssueFiltersStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(group_by: string | null, issue: IIssue, action: "update" | "delete") => {
|
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
if (action === "update") {
|
profileIssuesStore.updateIssueStructure(group_by, null, issue);
|
||||||
profileIssuesStore.updateIssueStructure(group_by, null, issue);
|
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
}
|
|
||||||
if (action === "delete") profileIssuesStore.deleteIssue(group_by, null, issue);
|
|
||||||
},
|
},
|
||||||
[profileIssuesStore, issueDetailStore, workspaceSlug]
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
|
||||||
);
|
profileIssuesStore.deleteIssue(group_by, null, issue);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
const getProjects = (projectStore: IProjectStore) => projectStore?.workspaceProjects || null;
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
const labels = workspaceStore.workspaceLabels || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
const projects = projectStore?.workspaceProjects || null;
|
|
||||||
|
|
||||||
return (
|
return null;
|
||||||
<div className={`relative w-full h-full bg-custom-background-90`}>
|
|
||||||
<List
|
// return (
|
||||||
issues={issues}
|
// <BaseListRoot
|
||||||
group_by={group_by}
|
// issueFilterStore={profileIssueFiltersStore}
|
||||||
handleIssues={handleIssues}
|
// issueStore={profileIssuesStore}
|
||||||
quickActions={(group_by, issue) => (
|
// QuickActions={ProjectIssueQuickActions}
|
||||||
<ProjectIssueQuickActions
|
// issueActions={issueActions}
|
||||||
issue={issue}
|
// getProjects={getProjects}
|
||||||
handleDelete={async () => handleIssues(group_by, issue, "delete")}
|
// />
|
||||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
// );
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={labels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
estimates={null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -1,95 +1,46 @@
|
|||||||
import { FC, useCallback } from "react";
|
import { FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { List } from "../default";
|
|
||||||
import { ProjectIssueQuickActions } from "components/issues";
|
import { ProjectIssueQuickActions } from "components/issues";
|
||||||
import { Spinner } from "@plane/ui";
|
|
||||||
// helpers
|
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
import { IProjectStore } from "store/project";
|
||||||
|
|
||||||
export const ListLayout: FC = observer(() => {
|
export const ListLayout: FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
|
if (!workspaceSlug || !projectId) return null;
|
||||||
|
|
||||||
// store
|
// store
|
||||||
const {
|
const { projectIssuesFilter: projectIssuesFilterStore, projectIssues: projectIssuesStore } = useMobxStore();
|
||||||
project: projectStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
projectEstimates: { projectEstimates },
|
|
||||||
issue: issueStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
const { currentProjectDetails } = projectStore;
|
|
||||||
|
|
||||||
const issues = issueStore?.getIssues;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
if (!workspaceSlug || !projectId) return;
|
||||||
const group_by: string | null = userDisplayFilters?.group_by || null;
|
projectIssuesStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
|
||||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
|
||||||
(group_by: string | null, issue: IIssue, action: "update" | "delete") => {
|
|
||||||
if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
if (action === "update") {
|
|
||||||
issueStore.updateIssueStructure(group_by, null, issue);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
|
||||||
}
|
|
||||||
if (action === "delete") issueStore.deleteIssue(group_by, null, issue);
|
|
||||||
},
|
},
|
||||||
[issueStore, issueDetailStore, workspaceSlug]
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
|
||||||
);
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
projectIssuesStore.removeIssue(workspaceSlug, projectId, issue.id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
const getProjects = (projectStore: IProjectStore) => projectStore.workspaceProjects;
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
|
||||||
const estimates =
|
|
||||||
currentProjectDetails?.estimate !== null
|
|
||||||
? projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BaseListRoot
|
||||||
{issueStore.loader ? (
|
issueFilterStore={projectIssuesFilterStore}
|
||||||
<div className="w-full h-full flex justify-center items-center">
|
issueStore={projectIssuesStore}
|
||||||
<Spinner />
|
QuickActions={ProjectIssueQuickActions}
|
||||||
</div>
|
issueActions={issueActions}
|
||||||
) : (
|
getProjects={getProjects}
|
||||||
<div className="relative w-full h-full bg-custom-background-90">
|
/>
|
||||||
<List
|
|
||||||
issues={issues}
|
|
||||||
group_by={group_by}
|
|
||||||
handleIssues={handleIssues}
|
|
||||||
quickActions={(group_by, issue) => (
|
|
||||||
<ProjectIssueQuickActions
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => handleIssues(group_by, issue, "delete")}
|
|
||||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
displayProperties={displayProperties}
|
|
||||||
states={states}
|
|
||||||
stateGroups={stateGroups}
|
|
||||||
priorities={priorities}
|
|
||||||
labels={projectLabels}
|
|
||||||
members={projectMembers?.map((m) => m.member) ?? null}
|
|
||||||
projects={projects}
|
|
||||||
enableQuickIssueCreate
|
|
||||||
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
|
||||||
showEmptyGroup={userDisplayFilters.show_empty_groups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,57 +1,49 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
|
||||||
import { List } from "../default";
|
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
import { useRouter } from "next/router";
|
||||||
|
import { EIssueActions } from "../../types";
|
||||||
|
import { IProjectStore } from "store/project";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
// components
|
||||||
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
|
||||||
|
|
||||||
export interface IViewListLayout {}
|
export interface IViewListLayout {}
|
||||||
|
|
||||||
export const ProjectViewListLayout: React.FC = observer(() => {
|
export const ProjectViewListLayout: React.FC = observer(() => {
|
||||||
const {
|
const { viewIssues: projectViewIssueStore, viewIssuesFilter: projectViewIssueFilterStore }: RootStore =
|
||||||
project: projectStore,
|
useMobxStore();
|
||||||
issue: issueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
projectState: projectStateStore,
|
|
||||||
}: RootStore = useMobxStore();
|
|
||||||
|
|
||||||
const issues = issueStore?.getIssues;
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
if (!workspaceSlug || !projectId) return null;
|
||||||
|
|
||||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
const issueActions = {
|
||||||
|
[EIssueActions.UPDATE]: (group_by: string | null, issue: IIssue) => {
|
||||||
const updateIssue = (group_by: string | null, issue: any) => {
|
if (!workspaceSlug || !projectId) return;
|
||||||
issueStore.updateIssueStructure(group_by, null, issue);
|
projectViewIssueStore.updateIssue(workspaceSlug, projectId, issue.id, issue);
|
||||||
|
},
|
||||||
|
[EIssueActions.DELETE]: (group_by: string | null, issue: IIssue) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
projectViewIssueStore.removeIssue(workspaceSlug, projectId, issue.id);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const states = projectStateStore?.projectStates || null;
|
const getProjects = (projectStore: IProjectStore) => projectStore.workspaceProjects;
|
||||||
const priorities = ISSUE_PRIORITIES || null;
|
|
||||||
// const labels = projectStore?.projectLabels || null;
|
|
||||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
|
||||||
const projects = projectStateStore?.projectStates || null;
|
|
||||||
const estimates = null;
|
|
||||||
|
|
||||||
return null;
|
return (
|
||||||
|
<BaseListRoot
|
||||||
// return (
|
issueFilterStore={projectViewIssueFilterStore}
|
||||||
// <div className={`relative w-full h-full bg-custom-background-90`}>
|
issueStore={projectViewIssueStore}
|
||||||
// <List
|
QuickActions={ProjectIssueQuickActions}
|
||||||
// issues={issues}
|
issueActions={issueActions}
|
||||||
// group_by={group_by}
|
getProjects={getProjects}
|
||||||
// handleIssues={updateIssue}
|
/>
|
||||||
// display_properties={display_properties}
|
);
|
||||||
// states={states}
|
|
||||||
// stateGroups={stateGroups}
|
|
||||||
// priorities={priorities}
|
|
||||||
// labels={labels}
|
|
||||||
// members={members}
|
|
||||||
// projects={projects}
|
|
||||||
// estimates={estimates}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@ import { RootStore } from "store/root";
|
|||||||
export interface IIssuePropertyState {
|
export interface IIssuePropertyState {
|
||||||
view?: "profile" | "workspace" | "project";
|
view?: "profile" | "workspace" | "project";
|
||||||
projectId: string | null;
|
projectId: string | null;
|
||||||
value: IState;
|
value: any | string | null;
|
||||||
onChange: (state: IState) => void;
|
onChange: (state: IState) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hideDropdownArrow?: boolean;
|
hideDropdownArrow?: boolean;
|
||||||
@ -62,6 +62,9 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
projectStateStore.fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
|
projectStateStore.fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectedOption: IState | undefined =
|
||||||
|
(projectStates && value && projectStates?.find((state) => state.id === value)) || undefined;
|
||||||
|
|
||||||
const dropdownOptions = projectStates?.map((state) => ({
|
const dropdownOptions = projectStates?.map((state) => ({
|
||||||
value: state.id,
|
value: state.id,
|
||||||
query: state.name,
|
query: state.name,
|
||||||
@ -91,10 +94,10 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
: dropdownOptions?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
|
: dropdownOptions?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
|
||||||
|
|
||||||
const label = (
|
const label = (
|
||||||
<Tooltip tooltipHeading="State" tooltipContent={value?.name ?? ""} position="top">
|
<Tooltip tooltipHeading="State" tooltipContent={selectedOption?.name ?? ""} position="top">
|
||||||
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
||||||
{value && <StateGroupIcon stateGroup={value.group} color={value.color} />}
|
{selectedOption && <StateGroupIcon stateGroup={selectedOption?.group as any} color={selectedOption?.color} />}
|
||||||
<span className="truncate line-clamp-1 inline-block w-auto max-w-[100px]">{value?.name ?? "State"}</span>
|
<span className="truncate line-clamp-1 inline-block">{selectedOption?.name ?? "State"}</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@ -104,8 +107,8 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
{workspaceSlug && projectId && (
|
{workspaceSlug && projectId && (
|
||||||
<Combobox
|
<Combobox
|
||||||
as="div"
|
as="div"
|
||||||
className={`text-left w-auto max-w-full ${className}`}
|
className={`flex-shrink-0 text-left w-auto max-w-full ${className}`}
|
||||||
value={value.id}
|
value={selectedOption?.id}
|
||||||
onChange={(data: string) => {
|
onChange={(data: string) => {
|
||||||
const selectedState = projectStates?.find((state) => state.id === data);
|
const selectedState = projectStates?.find((state) => state.id === data);
|
||||||
if (selectedState) onChange(selectedState);
|
if (selectedState) onChange(selectedState);
|
||||||
|
@ -9,14 +9,9 @@ import { DeleteArchivedIssueModal } from "components/issues";
|
|||||||
// helpers
|
// helpers
|
||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
||||||
type Props = {
|
export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
|
||||||
issue: IIssue;
|
|
||||||
handleDelete: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ArchivedIssueQuickActions: React.FC<Props> = (props) => {
|
|
||||||
const { issue, handleDelete } = props;
|
const { issue, handleDelete } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -10,16 +10,10 @@ import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
|||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
||||||
type Props = {
|
export const CycleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
|
||||||
issue: IIssue;
|
const { issue, handleDelete, handleUpdate, handleRemoveFromView } = props;
|
||||||
handleDelete: () => Promise<void>;
|
|
||||||
handleUpdate: (data: IIssue) => Promise<void>;
|
|
||||||
handleRemoveFromCycle: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CycleIssueQuickActions: React.FC<Props> = (props) => {
|
|
||||||
const { issue, handleDelete, handleUpdate, handleRemoveFromCycle } = props;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -59,7 +53,7 @@ export const CycleIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
|
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
|
||||||
data={issueToEdit}
|
data={issueToEdit}
|
||||||
onSubmit={async (data) => {
|
onSubmit={async (data) => {
|
||||||
if (issueToEdit) handleUpdate({ ...issueToEdit, ...data });
|
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CustomMenu placement="bottom-start" ellipsis>
|
<CustomMenu placement="bottom-start" ellipsis>
|
||||||
@ -92,7 +86,7 @@ export const CycleIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleRemoveFromCycle();
|
handleRemoveFromView && handleRemoveFromView();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -10,16 +10,10 @@ import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
|||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
||||||
type Props = {
|
export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
|
||||||
issue: IIssue;
|
const { issue, handleDelete, handleUpdate, handleRemoveFromView } = props;
|
||||||
handleDelete: () => Promise<void>;
|
|
||||||
handleUpdate: (data: IIssue) => Promise<void>;
|
|
||||||
handleRemoveFromModule: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ModuleIssueQuickActions: React.FC<Props> = (props) => {
|
|
||||||
const { issue, handleDelete, handleUpdate, handleRemoveFromModule } = props;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -59,7 +53,7 @@ export const ModuleIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
|
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
|
||||||
data={issueToEdit}
|
data={issueToEdit}
|
||||||
onSubmit={async (data) => {
|
onSubmit={async (data) => {
|
||||||
if (issueToEdit) handleUpdate({ ...issueToEdit, ...data });
|
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CustomMenu placement="bottom-start" ellipsis>
|
<CustomMenu placement="bottom-start" ellipsis>
|
||||||
@ -92,7 +86,7 @@ export const ModuleIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleRemoveFromModule();
|
handleRemoveFromView && handleRemoveFromView();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -10,14 +10,9 @@ import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
|||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
|
|
||||||
type Props = {
|
export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
|
||||||
issue: IIssue;
|
|
||||||
handleDelete: () => Promise<void>;
|
|
||||||
handleUpdate: (data: IIssue) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProjectIssueQuickActions: React.FC<Props> = (props) => {
|
|
||||||
const { issue, handleDelete, handleUpdate } = props;
|
const { issue, handleDelete, handleUpdate } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -58,7 +53,7 @@ export const ProjectIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
|
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
|
||||||
data={issueToEdit}
|
data={issueToEdit}
|
||||||
onSubmit={async (data) => {
|
onSubmit={async (data) => {
|
||||||
if (issueToEdit) handleUpdate({ ...issueToEdit, ...data });
|
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CustomMenu placement="bottom-start" ellipsis>
|
<CustomMenu placement="bottom-start" ellipsis>
|
||||||
|
@ -24,28 +24,29 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||||||
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
|
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
cycleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
cycle: cycleStore,
|
cycle: cycleStore,
|
||||||
cycleIssue: cycleIssueStore,
|
cycleIssues: { loader, getIssues, fetchIssues },
|
||||||
cycleIssueFilter: cycleIssueFilterStore,
|
cycleIssuesFilter: { issueFilters, fetchFilters },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
useSWR(workspaceSlug && projectId && cycleId ? `CYCLE_FILTERS_AND_ISSUES_${cycleId.toString()}` : null, async () => {
|
useSWR(
|
||||||
if (workspaceSlug && projectId && cycleId) {
|
workspaceSlug && projectId && cycleId ? `CYCLE_ISSUES_V3_${workspaceSlug}_${projectId}_${cycleId}` : null,
|
||||||
// fetching the project display filters and display properties
|
async () => {
|
||||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
if (workspaceSlug && projectId && cycleId) {
|
||||||
// fetching the cycle filters
|
await fetchFilters(workspaceSlug, projectId, cycleId);
|
||||||
await cycleIssueFilterStore.fetchCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader", cycleId);
|
||||||
|
}
|
||||||
// fetching the cycle issues
|
|
||||||
await cycleIssueStore.fetchIssues(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const cycleDetails = cycleId ? cycleStore.cycle_details[cycleId.toString()] : undefined;
|
const cycleDetails = cycleId ? cycleStore.cycle_details[cycleId.toString()] : undefined;
|
||||||
const cycleStatus =
|
const cycleStatus =
|
||||||
@ -53,41 +54,35 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||||||
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date)
|
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date)
|
||||||
: "draft";
|
: "draft";
|
||||||
|
|
||||||
const issueCount = cycleIssueStore.getIssuesCount;
|
|
||||||
|
|
||||||
if (!cycleIssueStore.getIssues)
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full grid place-items-center">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
|
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
|
||||||
|
|
||||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||||
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
|
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
|
||||||
<CycleAppliedFiltersRoot />
|
<CycleAppliedFiltersRoot />
|
||||||
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
|
|
||||||
<CycleEmptyState
|
{loader === "init-loader" ? (
|
||||||
workspaceSlug={workspaceSlug?.toString()}
|
<div className="w-full h-full flex justify-center items-center">
|
||||||
projectId={projectId?.toString()}
|
<Spinner />
|
||||||
cycleId={cycleId?.toString()}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="w-full h-full overflow-auto">
|
|
||||||
{activeLayout === "list" ? (
|
|
||||||
<CycleListLayout />
|
|
||||||
) : activeLayout === "kanban" ? (
|
|
||||||
<CycleKanBanLayout />
|
|
||||||
) : activeLayout === "calendar" ? (
|
|
||||||
<CycleCalendarLayout />
|
|
||||||
) : activeLayout === "gantt_chart" ? (
|
|
||||||
<CycleGanttLayout />
|
|
||||||
) : activeLayout === "spreadsheet" ? (
|
|
||||||
<CycleSpreadsheetLayout />
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* <CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> */}
|
||||||
|
<div className="h-full w-full overflow-auto">
|
||||||
|
{activeLayout === "list" ? (
|
||||||
|
<CycleListLayout />
|
||||||
|
) : activeLayout === "kanban" ? (
|
||||||
|
<CycleKanBanLayout />
|
||||||
|
) : activeLayout === "calendar" ? (
|
||||||
|
<CycleCalendarLayout />
|
||||||
|
) : activeLayout === "gantt_chart" ? (
|
||||||
|
<CycleGanttLayout />
|
||||||
|
) : activeLayout === "spreadsheet" ? (
|
||||||
|
<CycleSpreadsheetLayout />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -27,60 +27,47 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
moduleIssues: { loader, getIssues, fetchIssues },
|
||||||
moduleIssue: moduleIssueStore,
|
moduleIssuesFilter: { issueFilters, fetchFilters },
|
||||||
moduleFilter: moduleIssueFilterStore,
|
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
workspaceSlug && projectId && moduleId ? `MODULE_FILTERS_AND_ISSUES_${moduleId.toString()}` : null,
|
workspaceSlug && projectId && moduleId ? `MODULE_ISSUES_V3_${workspaceSlug}_${projectId}_${moduleId}` : null,
|
||||||
async () => {
|
async () => {
|
||||||
if (workspaceSlug && projectId && moduleId) {
|
if (workspaceSlug && projectId && moduleId) {
|
||||||
// fetching the project display filters and display properties
|
await fetchFilters(workspaceSlug, projectId, moduleId);
|
||||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId);
|
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader", moduleId);
|
||||||
// fetching the module filters
|
|
||||||
await moduleIssueFilterStore.fetchModuleFilters(workspaceSlug, projectId, moduleId);
|
|
||||||
|
|
||||||
// fetching the module issues
|
|
||||||
await moduleIssueStore.fetchIssues(workspaceSlug, projectId, moduleId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||||
|
|
||||||
const issueCount = moduleIssueStore.getIssuesCount;
|
|
||||||
|
|
||||||
if (!moduleIssueStore.getIssues)
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full grid place-items-center">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||||
<ModuleAppliedFiltersRoot />
|
<ModuleAppliedFiltersRoot />
|
||||||
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
|
|
||||||
<ModuleEmptyState
|
{loader === "init-loader" ? (
|
||||||
workspaceSlug={workspaceSlug?.toString()}
|
<div className="w-full h-full flex justify-center items-center">
|
||||||
projectId={projectId?.toString()}
|
<Spinner />
|
||||||
moduleId={moduleId?.toString()}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="h-full w-full overflow-auto">
|
|
||||||
{activeLayout === "list" ? (
|
|
||||||
<ModuleListLayout />
|
|
||||||
) : activeLayout === "kanban" ? (
|
|
||||||
<ModuleKanBanLayout />
|
|
||||||
) : activeLayout === "calendar" ? (
|
|
||||||
<ModuleCalendarLayout />
|
|
||||||
) : activeLayout === "gantt_chart" ? (
|
|
||||||
<ModuleGanttLayout />
|
|
||||||
) : activeLayout === "spreadsheet" ? (
|
|
||||||
<ModuleSpreadsheetLayout />
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> */}
|
||||||
|
<div className="h-full w-full overflow-auto">
|
||||||
|
{activeLayout === "list" ? (
|
||||||
|
<ModuleListLayout />
|
||||||
|
) : activeLayout === "kanban" ? (
|
||||||
|
<ModuleKanBanLayout />
|
||||||
|
) : activeLayout === "calendar" ? (
|
||||||
|
<ModuleCalendarLayout />
|
||||||
|
) : activeLayout === "gantt_chart" ? (
|
||||||
|
<ModuleGanttLayout />
|
||||||
|
) : activeLayout === "spreadsheet" ? (
|
||||||
|
<ModuleSpreadsheetLayout />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -17,48 +17,49 @@ import {
|
|||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
|
|
||||||
export const ProjectLayoutRoot: React.FC = observer(() => {
|
export const ProjectLayoutRoot: React.FC = observer(() => {
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
|
const {
|
||||||
|
projectIssues: { loader, getIssues, fetchIssues },
|
||||||
|
projectIssuesFilter: { issueFilters, fetchFilters },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
useSWR(workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
|
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => {
|
||||||
if (workspaceSlug && projectId) {
|
if (workspaceSlug && projectId) {
|
||||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
await fetchFilters(workspaceSlug, projectId);
|
||||||
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const issueCount = issueStore.getIssuesCount;
|
|
||||||
|
|
||||||
if (!issueStore.getIssues)
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full grid place-items-center">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||||
<ProjectAppliedFiltersRoot />
|
<ProjectAppliedFiltersRoot />
|
||||||
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
|
|
||||||
<ProjectEmptyState />
|
{loader === "init-loader" ? (
|
||||||
) : (
|
<div className="w-full h-full flex justify-center items-center">
|
||||||
<div className="w-full h-full overflow-auto">
|
<Spinner />
|
||||||
{activeLayout === "list" ? (
|
|
||||||
<ListLayout />
|
|
||||||
) : activeLayout === "kanban" ? (
|
|
||||||
<KanBanLayout />
|
|
||||||
) : activeLayout === "calendar" ? (
|
|
||||||
<CalendarLayout />
|
|
||||||
) : activeLayout === "gantt_chart" ? (
|
|
||||||
<GanttLayout />
|
|
||||||
) : activeLayout === "spreadsheet" ? (
|
|
||||||
<ProjectSpreadsheetLayout />
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* {(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 && <ProjectEmptyState />} */}
|
||||||
|
<div className="w-full h-full relative overflow-auto">
|
||||||
|
{activeLayout === "list" ? (
|
||||||
|
<ListLayout />
|
||||||
|
) : activeLayout === "kanban" ? (
|
||||||
|
<KanBanLayout />
|
||||||
|
) : activeLayout === "calendar" ? (
|
||||||
|
<CalendarLayout />
|
||||||
|
) : activeLayout === "gantt_chart" ? (
|
||||||
|
<GanttLayout />
|
||||||
|
) : activeLayout === "spreadsheet" ? (
|
||||||
|
<ProjectSpreadsheetLayout />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -19,65 +19,51 @@ import { Spinner } from "@plane/ui";
|
|||||||
|
|
||||||
export const ProjectViewLayoutRoot: React.FC = observer(() => {
|
export const ProjectViewLayoutRoot: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, viewId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query as {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
viewId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueFilter: issueFilterStore,
|
viewIssues: { loader, getIssues, fetchIssues },
|
||||||
projectViews: projectViewsStore,
|
viewIssuesFilter: { issueFilters, fetchFilters },
|
||||||
projectViewIssues: projectViewIssuesStore,
|
|
||||||
projectViewFilters: projectViewFiltersStore,
|
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
useSWR(
|
useSWR(workspaceSlug && projectId && viewId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => {
|
||||||
workspaceSlug && projectId && viewId ? `PROJECT_VIEW_FILTERS_AND_ISSUES_${viewId.toString()}` : null,
|
if (workspaceSlug && projectId && viewId) {
|
||||||
async () => {
|
await fetchFilters(workspaceSlug, projectId, viewId);
|
||||||
if (workspaceSlug && projectId && viewId) {
|
// await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
|
||||||
// fetching the project display filters and display properties
|
|
||||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
|
||||||
|
|
||||||
// fetching the view details
|
|
||||||
await projectViewsStore.fetchViewDetails(workspaceSlug.toString(), projectId.toString(), viewId.toString());
|
|
||||||
// fetching the view issues
|
|
||||||
await projectViewIssuesStore.fetchViewIssues(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
viewId.toString(),
|
|
||||||
projectViewFiltersStore.storedFilters[viewId.toString()] ?? {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const issueCount = projectViewIssuesStore.getIssuesCount;
|
|
||||||
|
|
||||||
if (!projectViewIssuesStore.getIssues)
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full grid place-items-center">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full flex flex-col overflow-hidden">
|
<div className="relative h-full w-full flex flex-col overflow-hidden">
|
||||||
<ProjectViewAppliedFiltersRoot />
|
<ProjectViewAppliedFiltersRoot />
|
||||||
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
|
|
||||||
<ProjectViewEmptyState />
|
{loader === "init-loader" ? (
|
||||||
) : (
|
<div className="w-full h-full flex justify-center items-center">
|
||||||
<div className="h-full w-full overflow-y-auto">
|
<Spinner />
|
||||||
{activeLayout === "list" ? (
|
|
||||||
<ModuleListLayout />
|
|
||||||
) : activeLayout === "kanban" ? (
|
|
||||||
<ModuleKanBanLayout />
|
|
||||||
) : activeLayout === "calendar" ? (
|
|
||||||
<ProjectViewCalendarLayout />
|
|
||||||
) : activeLayout === "gantt_chart" ? (
|
|
||||||
<ProjectViewGanttLayout />
|
|
||||||
) : activeLayout === "spreadsheet" ? (
|
|
||||||
<ProjectViewSpreadsheetLayout />
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* {(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 && <ProjectViewEmptyState />} */}
|
||||||
|
<div className="w-full h-full relative overflow-auto">
|
||||||
|
{activeLayout === "list" ? (
|
||||||
|
<ModuleListLayout />
|
||||||
|
) : activeLayout === "kanban" ? (
|
||||||
|
<ModuleKanBanLayout />
|
||||||
|
) : activeLayout === "calendar" ? (
|
||||||
|
<ProjectViewCalendarLayout />
|
||||||
|
) : activeLayout === "gantt_chart" ? (
|
||||||
|
<ProjectViewGanttLayout />
|
||||||
|
) : activeLayout === "spreadsheet" ? (
|
||||||
|
<ProjectViewSpreadsheetLayout />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
import { IIssueUnGroupedStructure } from "store/issue";
|
||||||
|
import { SpreadsheetView } from "./spreadsheet-view";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { IIssue, IIssueDisplayFilterOptions } from "types";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
import {
|
||||||
|
ICycleIssuesFilterStore,
|
||||||
|
ICycleIssuesStore,
|
||||||
|
IModuleIssuesFilterStore,
|
||||||
|
IModuleIssuesStore,
|
||||||
|
IProjectIssuesFilterStore,
|
||||||
|
IProjectIssuesStore,
|
||||||
|
IViewIssuesFilterStore,
|
||||||
|
IViewIssuesStore,
|
||||||
|
} from "store/issues";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { EFilterType, TUnGroupedIssues } from "store/issues/types";
|
||||||
|
|
||||||
|
interface IBaseSpreadsheetRoot {
|
||||||
|
issueFiltersStore:
|
||||||
|
| IViewIssuesFilterStore
|
||||||
|
| ICycleIssuesFilterStore
|
||||||
|
| IModuleIssuesFilterStore
|
||||||
|
| IProjectIssuesFilterStore;
|
||||||
|
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
|
||||||
|
viewId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||||
|
const { issueFiltersStore, issueStore, viewId } = props;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
|
const {
|
||||||
|
issueDetail: issueDetailStore,
|
||||||
|
projectMember: { projectMembers },
|
||||||
|
projectState: projectStateStore,
|
||||||
|
projectLabel: { projectLabels },
|
||||||
|
user: userStore,
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
|
const user = userStore.currentUser;
|
||||||
|
|
||||||
|
const issuesResponse = issueStore.getIssues;
|
||||||
|
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
|
||||||
|
|
||||||
|
const issues = issueIds?.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 handleDisplayFiltersUpdate = useCallback(
|
||||||
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
issueFiltersStore.updateFilters(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
EFilterType.DISPLAY_FILTERS,
|
||||||
|
{
|
||||||
|
...updatedDisplayFilter,
|
||||||
|
},
|
||||||
|
viewId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[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}
|
||||||
|
members={projectMembers?.map((m) => m.member)}
|
||||||
|
labels={projectLabels || undefined}
|
||||||
|
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
||||||
|
handleIssueAction={handleIssueAction}
|
||||||
|
handleUpdateIssue={handleUpdateIssue}
|
||||||
|
disableUserActions={false}
|
||||||
|
quickAddCallback={issueStore.quickAddIssue}
|
||||||
|
viewId={viewId}
|
||||||
|
enableQuickCreateIssue
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
@ -15,7 +15,7 @@ type Props = {
|
|||||||
export const SpreadsheetCreatedOnColumn: React.FC<Props> = ({ issue, expandedIssues }) => {
|
export const SpreadsheetCreatedOnColumn: React.FC<Props> = ({ issue, expandedIssues }) => {
|
||||||
const isExpanded = expandedIssues.indexOf(issue.id) > -1;
|
const isExpanded = expandedIssues.indexOf(issue.id) > -1;
|
||||||
|
|
||||||
const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded);
|
const { subIssues, isLoading } = useSubIssue(issue.project, issue.id, isExpanded);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -2,4 +2,4 @@ export * from "./columns";
|
|||||||
export * from "./roots";
|
export * from "./roots";
|
||||||
export * from "./spreadsheet-column";
|
export * from "./spreadsheet-column";
|
||||||
export * from "./spreadsheet-view";
|
export * from "./spreadsheet-view";
|
||||||
export * from "./inline-create-issue-form";
|
export * from "./quick-add-issue-form";
|
||||||
|
@ -6,19 +6,26 @@ import { PlusIcon } from "lucide-react";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useKeypress from "hooks/use-keypress";
|
import useKeypress from "hooks/use-keypress";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// helpers
|
// helpers
|
||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue, IProject } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
formKey: keyof IIssue;
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
subGroupId?: string | null;
|
||||||
prePopulatedData?: Partial<IIssue>;
|
prePopulatedData?: Partial<IIssue>;
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IIssue> = {
|
const defaultValues: Partial<IIssue> = {
|
||||||
@ -48,17 +55,15 @@ const Inputs = (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SpreadsheetInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||||
const { prePopulatedData, groupId } = props;
|
const { formKey, groupId, subGroupId = null, prePopulatedData, quickAddCallback, viewId } = props;
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||||
|
|
||||||
// store
|
// store
|
||||||
const { workspace: workspaceStore, quickAddIssue: quickAddStore } = useMobxStore();
|
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
@ -82,7 +87,9 @@ export const SpreadsheetInlineCreateIssueForm: React.FC<Props> = observer((props
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
|
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||||
|
const projectDetail: IProject | null =
|
||||||
|
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFocus("name");
|
setFocus("name");
|
||||||
@ -106,43 +113,70 @@ export const SpreadsheetInlineCreateIssueForm: React.FC<Props> = observer((props
|
|||||||
});
|
});
|
||||||
}, [errors, setToastAlert]);
|
}, [errors, setToastAlert]);
|
||||||
|
|
||||||
const onSubmitHandler = async (formData: IIssue) => {
|
// const onSubmitHandler = async (formData: IIssue) => {
|
||||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
// if (isSubmitting || !workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
// // resetting the form so that user can add another issue quickly
|
||||||
|
// reset({ ...defaultValues });
|
||||||
|
|
||||||
|
// const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
||||||
|
// ...(prePopulatedData ?? {}),
|
||||||
|
// ...formData,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// quickAddStore.createIssue(
|
||||||
|
// workspaceSlug.toString(),
|
||||||
|
// projectId.toString(),
|
||||||
|
// {
|
||||||
|
// group_id: groupId ?? null,
|
||||||
|
// sub_group_id: null,
|
||||||
|
// },
|
||||||
|
// payload
|
||||||
|
// );
|
||||||
|
|
||||||
|
// setToastAlert({
|
||||||
|
// type: "success",
|
||||||
|
// title: "Success!",
|
||||||
|
// message: "Issue created successfully.",
|
||||||
|
// });
|
||||||
|
// } catch (err: any) {
|
||||||
|
// Object.keys(err || {}).forEach((key) => {
|
||||||
|
// const error = err?.[key];
|
||||||
|
// const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
||||||
|
|
||||||
|
// setToastAlert({
|
||||||
|
// type: "error",
|
||||||
|
// title: "Error!",
|
||||||
|
// message: errorTitle || "Some error occurred. Please try again.",
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const onSubmitHandler = async (formData: IIssue) => {
|
||||||
|
if (isSubmitting || !workspaceDetail || !projectDetail) return;
|
||||||
|
|
||||||
// resetting the form so that user can add another issue quickly
|
|
||||||
reset({ ...defaultValues });
|
reset({ ...defaultValues });
|
||||||
|
|
||||||
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
const payload = createIssuePayload(workspaceDetail, projectDetail, {
|
||||||
...(prePopulatedData ?? {}),
|
...(prePopulatedData ?? {}),
|
||||||
...formData,
|
...formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
quickAddStore.createIssue(
|
quickAddCallback && (await quickAddCallback(workspaceSlug, projectId, { ...payload } as IIssue, viewId));
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
{
|
|
||||||
group_id: groupId ?? null,
|
|
||||||
sub_group_id: null,
|
|
||||||
},
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Object.keys(err || {}).forEach((key) => {
|
console.error(err);
|
||||||
const error = err?.[key];
|
setToastAlert({
|
||||||
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
setToastAlert({
|
message: err?.message || "Some error occurred. Please try again.",
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: errorTitle || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -156,7 +190,7 @@ export const SpreadsheetInlineCreateIssueForm: React.FC<Props> = observer((props
|
|||||||
onSubmit={handleSubmit(onSubmitHandler)}
|
onSubmit={handleSubmit(onSubmitHandler)}
|
||||||
className="flex border-[0.5px] border-t-0 border-custom-border-100 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
|
className="flex border-[0.5px] border-t-0 border-custom-border-100 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
|
||||||
>
|
>
|
||||||
<Inputs register={register} setFocus={setFocus} projectDetails={projectDetails} />
|
<Inputs formKey={formKey} register={register} setFocus={setFocus} projectDetails={projectDetail} />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
@ -1,70 +1,18 @@
|
|||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { SpreadsheetView } from "components/issues";
|
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
|
||||||
// types
|
import { useRouter } from "next/router";
|
||||||
import { IIssue, IIssueDisplayFilterOptions } from "types";
|
|
||||||
// constants
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
|
|
||||||
export const CycleSpreadsheetLayout: React.FC = observer(() => {
|
export const CycleSpreadsheetLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { cycleId } = router.query as { cycleId: string };
|
||||||
|
|
||||||
const {
|
const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
cycleIssue: cycleIssueStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const issues = cycleIssueStore.getIssues;
|
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUpdateIssue = useCallback(
|
|
||||||
(issue: IIssue, data: Partial<IIssue>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...issue,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
cycleIssueStore.updateIssueStructure(null, null, payload);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, data);
|
|
||||||
},
|
|
||||||
[issueDetailStore, cycleIssueStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SpreadsheetView
|
<BaseSpreadsheetRoot issueStore={cycleIssueStore} issueFiltersStore={cycleIssueFilterStore} viewId={cycleId} />
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
|
||||||
issues={issues as IIssueUnGroupedStructure}
|
|
||||||
members={projectMembers?.map((m) => m.member)}
|
|
||||||
labels={projectLabels || undefined}
|
|
||||||
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
|
||||||
handleIssueAction={() => {}}
|
|
||||||
handleUpdateIssue={handleUpdateIssue}
|
|
||||||
disableUserActions={false}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,71 +1,18 @@
|
|||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { SpreadsheetView } from "components/issues";
|
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
|
||||||
// types
|
import { useRouter } from "next/router";
|
||||||
import { IIssue, IIssueDisplayFilterOptions } from "types";
|
|
||||||
// constants
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
|
|
||||||
export const ModuleSpreadsheetLayout: React.FC = observer(() => {
|
export const ModuleSpreadsheetLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { moduleId } = router.query as { moduleId: string };
|
||||||
|
|
||||||
const {
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
moduleIssue: moduleIssueStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const issues = moduleIssueStore.getIssues;
|
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUpdateIssue = useCallback(
|
|
||||||
(issue: IIssue, data: Partial<IIssue>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...issue,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
moduleIssueStore.updateIssueStructure(null, null, payload);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, data);
|
|
||||||
},
|
|
||||||
[issueDetailStore, moduleIssueStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
|
||||||
return (
|
return (
|
||||||
<SpreadsheetView
|
<BaseSpreadsheetRoot issueStore={moduleIssueStore} issueFiltersStore={moduleIssueFilterStore} viewId={moduleId} />
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
|
||||||
issues={issues as IIssueUnGroupedStructure}
|
|
||||||
members={projectMembers?.map((m) => m.member)}
|
|
||||||
labels={projectLabels ?? undefined}
|
|
||||||
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
|
||||||
handleIssueAction={() => {}}
|
|
||||||
handleUpdateIssue={handleUpdateIssue}
|
|
||||||
disableUserActions={false}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,86 +1,11 @@
|
|||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
|
||||||
import { SpreadsheetView } from "components/issues";
|
|
||||||
// types
|
|
||||||
import { IIssue, IIssueDisplayFilterOptions } from "types";
|
|
||||||
// constants
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
|
|
||||||
export const ProjectSpreadsheetLayout: React.FC = observer(() => {
|
export const ProjectSpreadsheetLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
return <BaseSpreadsheetRoot issueStore={projectIssuesStore} issueFiltersStore={projectIssueFiltersStore} />;
|
||||||
|
|
||||||
const {
|
|
||||||
issue: issueStore,
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
user: userStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const user = userStore.currentUser;
|
|
||||||
const issues = issueStore.getIssues;
|
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
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 handleUpdateIssue = useCallback(
|
|
||||||
(issue: IIssue, data: Partial<IIssue>) => {
|
|
||||||
if (!workspaceSlug || !projectId || !user) return;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...issue,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
issueStore.updateIssueStructure(null, null, payload);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, data);
|
|
||||||
},
|
|
||||||
[issueStore, issueDetailStore, projectId, user, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SpreadsheetView
|
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
|
||||||
issues={issues as IIssueUnGroupedStructure}
|
|
||||||
members={projectMembers?.map((m) => m.member)}
|
|
||||||
labels={projectLabels || undefined}
|
|
||||||
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
|
||||||
handleIssueAction={handleIssueAction}
|
|
||||||
handleUpdateIssue={handleUpdateIssue}
|
|
||||||
disableUserActions={false}
|
|
||||||
enableQuickCreateIssue
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -1,70 +1,11 @@
|
|||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { SpreadsheetView } from "components/issues";
|
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
|
||||||
// types
|
|
||||||
import { IIssue, IIssueDisplayFilterOptions } from "types";
|
|
||||||
// constants
|
|
||||||
import { IIssueUnGroupedStructure } from "store/issue";
|
|
||||||
|
|
||||||
export const ProjectViewSpreadsheetLayout: React.FC = observer(() => {
|
export const ProjectViewSpreadsheetLayout: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const { viewIssues: projectViewIssuesStore, viewIssuesFilter: projectViewIssueFiltersStore } = useMobxStore();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
return <BaseSpreadsheetRoot issueStore={projectViewIssuesStore} issueFiltersStore={projectViewIssueFiltersStore} />;
|
||||||
|
|
||||||
const {
|
|
||||||
issueFilter: issueFilterStore,
|
|
||||||
projectViewIssues: projectViewIssueStore,
|
|
||||||
issueDetail: issueDetailStore,
|
|
||||||
projectLabel: { projectLabels },
|
|
||||||
projectMember: { projectMembers },
|
|
||||||
projectState: projectStateStore,
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const issues = projectViewIssueStore.getIssues;
|
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
|
||||||
display_filters: {
|
|
||||||
...updatedDisplayFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[issueFilterStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUpdateIssue = useCallback(
|
|
||||||
(issue: IIssue, data: Partial<IIssue>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...issue,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
projectViewIssueStore.updateIssueStructure(null, null, payload);
|
|
||||||
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, data);
|
|
||||||
},
|
|
||||||
[issueDetailStore, projectViewIssueStore, projectId, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SpreadsheetView
|
|
||||||
displayProperties={issueFilterStore.userDisplayProperties}
|
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
|
||||||
issues={issues as IIssueUnGroupedStructure}
|
|
||||||
members={projectMembers?.map((m) => m.member)}
|
|
||||||
labels={projectLabels || undefined}
|
|
||||||
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
|
||||||
handleIssueAction={() => {}}
|
|
||||||
handleUpdateIssue={handleUpdateIssue}
|
|
||||||
disableUserActions={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
// components
|
||||||
import { SpreadsheetColumnsList, SpreadsheetIssuesColumn, SpreadsheetInlineCreateIssueForm } from "components/issues";
|
import { SpreadsheetColumnsList, SpreadsheetIssuesColumn, SpreadsheetQuickAddIssueForm } from "components/issues";
|
||||||
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
@ -19,6 +19,13 @@ type Props = {
|
|||||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
||||||
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
|
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
|
||||||
openIssuesListModal?: (() => void) | null;
|
openIssuesListModal?: (() => void) | null;
|
||||||
|
quickAddCallback?: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
viewId?: string
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
viewId?: string;
|
||||||
disableUserActions: boolean;
|
disableUserActions: boolean;
|
||||||
enableQuickCreateIssue?: boolean;
|
enableQuickCreateIssue?: boolean;
|
||||||
};
|
};
|
||||||
@ -34,7 +41,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
states,
|
states,
|
||||||
handleIssueAction,
|
handleIssueAction,
|
||||||
handleUpdateIssue,
|
handleUpdateIssue,
|
||||||
openIssuesListModal,
|
quickAddCallback,
|
||||||
|
viewId,
|
||||||
disableUserActions,
|
disableUserActions,
|
||||||
enableQuickCreateIssue,
|
enableQuickCreateIssue,
|
||||||
} = props;
|
} = props;
|
||||||
@ -132,7 +140,9 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
<div className="border-t border-custom-border-100">
|
<div className="border-t border-custom-border-100">
|
||||||
<div className="mb-3 z-50 sticky bottom-0 left-0">
|
<div className="mb-3 z-50 sticky bottom-0 left-0">
|
||||||
{enableQuickCreateIssue && <SpreadsheetInlineCreateIssueForm />}
|
{enableQuickCreateIssue && (
|
||||||
|
<SpreadsheetQuickAddIssueForm formKey="name" quickAddCallback={quickAddCallback} viewId={viewId} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* {!disableUserActions &&
|
{/* {!disableUserActions &&
|
||||||
|
5
web/components/issues/issue-layouts/types.ts
Normal file
5
web/components/issues/issue-layouts/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum EIssueActions {
|
||||||
|
UPDATE = "update",
|
||||||
|
DELETE = "delete",
|
||||||
|
REMOVE = "remove",
|
||||||
|
}
|
@ -109,7 +109,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
|
|
||||||
const handleDeleteIssue = async () => {
|
const handleDeleteIssue = async () => {
|
||||||
if (isArchived) await archivedIssuesStore.deleteArchivedIssue(workspaceSlug, projectId, issue!);
|
if (isArchived) await archivedIssuesStore.deleteArchivedIssue(workspaceSlug, projectId, issue!);
|
||||||
else await issueStore.deleteIssue(workspaceSlug, projectId, issue!);
|
else await issueStore.removeIssueFromStructure(workspaceSlug, projectId, issue!);
|
||||||
const { query } = router;
|
const { query } = router;
|
||||||
if (query.peekIssueId) {
|
if (query.peekIssueId) {
|
||||||
issueDetailStore.setPeekId(null);
|
issueDetailStore.setPeekId(null);
|
||||||
|
@ -39,12 +39,21 @@ export interface IssuesModalProps {
|
|||||||
| "cycle"
|
| "cycle"
|
||||||
)[];
|
)[];
|
||||||
onSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
onSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
||||||
|
handleSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const issueDraftService = new IssueDraftService();
|
const issueDraftService = new IssueDraftService();
|
||||||
|
|
||||||
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
||||||
const { data, handleClose, isOpen, prePopulateData: prePopulateDataProps, fieldsToShow = ["all"], onSubmit } = props;
|
const {
|
||||||
|
data,
|
||||||
|
handleClose,
|
||||||
|
isOpen,
|
||||||
|
prePopulateData: prePopulateDataProps,
|
||||||
|
fieldsToShow = ["all"],
|
||||||
|
onSubmit,
|
||||||
|
handleSubmit,
|
||||||
|
} = props;
|
||||||
|
|
||||||
// states
|
// states
|
||||||
const [createMore, setCreateMore] = useState(false);
|
const [createMore, setCreateMore] = useState(false);
|
||||||
@ -186,18 +195,22 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
await issueDetailStore
|
await issueDetailStore
|
||||||
.createIssue(workspaceSlug.toString(), activeProject, payload)
|
.createIssue(workspaceSlug.toString(), activeProject, payload)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
issueStore.fetchIssues(workspaceSlug.toString(), activeProject);
|
if (handleSubmit) {
|
||||||
|
await handleSubmit(res);
|
||||||
|
} else {
|
||||||
|
issueStore.fetchIssues(workspaceSlug.toString(), activeProject);
|
||||||
|
|
||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
|
@ -18,6 +18,7 @@ export const WorkspaceDashboardView = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
|
|
||||||
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||||
|
|
||||||
const user = userStore.currentUser;
|
const user = userStore.currentUser;
|
||||||
|
@ -41,7 +41,7 @@ const PosthogWrapper: FC<IPosthogWrapper> = (props) => {
|
|||||||
api_host: posthogHost || "https://app.posthog.com",
|
api_host: posthogHost || "https://app.posthog.com",
|
||||||
// Enable debug mode in development
|
// Enable debug mode in development
|
||||||
loaded: (posthog) => {
|
loaded: (posthog) => {
|
||||||
if (process.env.NODE_ENV === "development") posthog.debug();
|
// if (process.env.NODE_ENV === "development") posthog.debug();
|
||||||
},
|
},
|
||||||
autocapture: false,
|
autocapture: false,
|
||||||
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
|
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lucide-react": "^0.274.0",
|
"lucide-react": "^0.274.0",
|
||||||
"mobx": "^6.10.0",
|
"mobx": "^6.10.0",
|
||||||
|
@ -4,6 +4,7 @@ import { APIService } from "services/api.service";
|
|||||||
import type { CycleDateCheckData, ICycle, IIssue } from "types";
|
import type { CycleDateCheckData, ICycle, IIssue } from "types";
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
|
import { IIssueResponse } from "store/issues/types";
|
||||||
|
|
||||||
export class CycleService extends APIService {
|
export class CycleService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -50,6 +51,21 @@ export class CycleService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getV3CycleIssues(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
queries?: any
|
||||||
|
): Promise<IIssueResponse> {
|
||||||
|
return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, {
|
||||||
|
params: queries,
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getCycleIssuesWithParams(
|
async getCycleIssuesWithParams(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// services
|
// services
|
||||||
import { APIService } from "services/api.service";
|
import { APIService } from "services/api.service";
|
||||||
// type
|
// type
|
||||||
import type { IIssue, IIssueActivity, ISubIssueResponse, IIssueDisplayProperties } from "types";
|
import type { IUser, IIssue, IIssueActivity, ISubIssueResponse, IIssueDisplayProperties } from "types";
|
||||||
|
import { IIssueResponse } from "store/issues/types";
|
||||||
// helper
|
// helper
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
|
|
||||||
@ -26,6 +27,16 @@ export class IssueService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getV3Issues(workspaceSlug: string, projectId: string, queries?: any): Promise<IIssueResponse> {
|
||||||
|
return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, {
|
||||||
|
params: queries,
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getIssuesWithParams(
|
async getIssuesWithParams(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
@ -17,6 +17,16 @@ 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,
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async unarchiveIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async unarchiveIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
||||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/unarchive/${issueId}/`)
|
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/unarchive/${issueId}/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
|
@ -17,6 +17,16 @@ 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,
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async createDraftIssue(workspaceSlug: string, projectId: string, data: any): Promise<any> {
|
async createDraftIssue(workspaceSlug: string, projectId: string, data: any): Promise<any> {
|
||||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, data)
|
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, data)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// services
|
// services
|
||||||
import { APIService } from "services/api.service";
|
import { APIService } from "services/api.service";
|
||||||
// types
|
// types
|
||||||
import type { IModule, IIssue } from "types";
|
import type { IModule, IIssue, IUser } from "types";
|
||||||
|
import { IIssueResponse } from "store/issues/types";
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
|
|
||||||
export class ModuleService extends APIService {
|
export class ModuleService extends APIService {
|
||||||
@ -70,17 +71,27 @@ export class ModuleService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getV3ModuleIssues(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
moduleId: string,
|
||||||
|
queries?: any
|
||||||
|
): Promise<IIssueResponse> {
|
||||||
|
return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, {
|
||||||
|
params: queries,
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getModuleIssuesWithParams(
|
async getModuleIssuesWithParams(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
moduleId: string,
|
moduleId: string,
|
||||||
queries?: any
|
queries?: any
|
||||||
): Promise<
|
): Promise<IIssue[] | { [key: string]: IIssue[] }> {
|
||||||
| IIssue[]
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
> {
|
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, {
|
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, {
|
||||||
params: queries,
|
params: queries,
|
||||||
})
|
})
|
||||||
|
1
web/store/cycle-issues/index.ts
Normal file
1
web/store/cycle-issues/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./issue_filters.store";
|
201
web/store/cycle-issues/issue_filters.store.ts
Normal file
201
web/store/cycle-issues/issue_filters.store.ts
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||||
|
// services
|
||||||
|
import { CycleService } from "services/cycle.service";
|
||||||
|
// helpers
|
||||||
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "../root";
|
||||||
|
import { IIssueFilterOptions, TIssueParams } from "types";
|
||||||
|
|
||||||
|
export interface ICycleIssueFiltersStore {
|
||||||
|
loader: boolean;
|
||||||
|
error: any | null;
|
||||||
|
|
||||||
|
// observables
|
||||||
|
userCycleFilters: {
|
||||||
|
[cycleId: string]: {
|
||||||
|
filters?: IIssueFilterOptions;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// action
|
||||||
|
fetchCycleFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||||
|
updateCycleFilters: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
filterToUpdate: Partial<IIssueFilterOptions>
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
// computed
|
||||||
|
appliedFilters: TIssueParams[] | undefined;
|
||||||
|
cycleFilters:
|
||||||
|
| {
|
||||||
|
filters: IIssueFilterOptions;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CycleIssueFiltersStore implements ICycleIssueFiltersStore {
|
||||||
|
// observables
|
||||||
|
loader: boolean = false;
|
||||||
|
error: any | null = null;
|
||||||
|
userCycleFilters: {
|
||||||
|
[cycleId: string]: {
|
||||||
|
filters?: IIssueFilterOptions;
|
||||||
|
};
|
||||||
|
} = {};
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
// services
|
||||||
|
cycleService;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
makeObservable(this, {
|
||||||
|
// states
|
||||||
|
loader: observable.ref,
|
||||||
|
error: observable.ref,
|
||||||
|
// observables
|
||||||
|
userCycleFilters: observable.ref,
|
||||||
|
// actions
|
||||||
|
fetchCycleFilters: action,
|
||||||
|
updateCycleFilters: action,
|
||||||
|
// computed
|
||||||
|
appliedFilters: computed,
|
||||||
|
cycleFilters: computed,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
this.cycleService = new CycleService();
|
||||||
|
}
|
||||||
|
|
||||||
|
computedFilter = (filters: any, filteredParams: any) => {
|
||||||
|
const computedFilters: any = {};
|
||||||
|
Object.keys(filters).map((key) => {
|
||||||
|
if (filters[key] != undefined && filteredParams.includes(key))
|
||||||
|
computedFilters[key] =
|
||||||
|
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
|
||||||
|
});
|
||||||
|
|
||||||
|
return computedFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
get appliedFilters(): TIssueParams[] | undefined {
|
||||||
|
const userDisplayFilters = this.rootStore?.projectIssuesFilter.issueFilters?.displayFilters;
|
||||||
|
|
||||||
|
const cycleId = this.rootStore.cycle.cycleId;
|
||||||
|
|
||||||
|
if (!cycleId) return undefined;
|
||||||
|
|
||||||
|
const cycleFilters = this.userCycleFilters[cycleId]?.filters;
|
||||||
|
|
||||||
|
if (!cycleFilters || !userDisplayFilters) return undefined;
|
||||||
|
|
||||||
|
let filteredRouteParams: any = {
|
||||||
|
priority: cycleFilters?.priority || undefined,
|
||||||
|
state_group: cycleFilters?.state_group || undefined,
|
||||||
|
state: cycleFilters?.state || undefined,
|
||||||
|
assignees: cycleFilters?.assignees || undefined,
|
||||||
|
created_by: cycleFilters?.created_by || undefined,
|
||||||
|
labels: cycleFilters?.labels || undefined,
|
||||||
|
start_date: cycleFilters?.start_date || undefined,
|
||||||
|
target_date: cycleFilters?.target_date || undefined,
|
||||||
|
type: userDisplayFilters?.type || undefined,
|
||||||
|
sub_issue: userDisplayFilters?.sub_issue || true,
|
||||||
|
show_empty_groups: userDisplayFilters?.show_empty_groups || true,
|
||||||
|
start_target_date: userDisplayFilters?.start_target_date || true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredParams = handleIssueQueryParamsByLayout(userDisplayFilters.layout, "issues");
|
||||||
|
|
||||||
|
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||||
|
|
||||||
|
if (userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date";
|
||||||
|
if (userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||||
|
|
||||||
|
return filteredRouteParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
get cycleFilters():
|
||||||
|
| {
|
||||||
|
filters: IIssueFilterOptions;
|
||||||
|
}
|
||||||
|
| undefined {
|
||||||
|
const cycleId = this.rootStore.cycle.cycleId;
|
||||||
|
|
||||||
|
if (!cycleId) return undefined;
|
||||||
|
|
||||||
|
const activeCycleFilters = this.userCycleFilters[cycleId];
|
||||||
|
|
||||||
|
if (!activeCycleFilters) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: activeCycleFilters?.filters ?? {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCycleFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||||
|
try {
|
||||||
|
const cycleResponse = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.userCycleFilters = {
|
||||||
|
...this.userCycleFilters,
|
||||||
|
[cycleId]: {
|
||||||
|
filters: cycleResponse?.view_props?.filters ?? {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Failed to fetch user filters in issue filter store", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCycleFilters = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
properties: Partial<IIssueFilterOptions>
|
||||||
|
) => {
|
||||||
|
const newViewProps = {
|
||||||
|
filters: {
|
||||||
|
...this.userCycleFilters[cycleId]?.filters,
|
||||||
|
...properties,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let updatedCycleFilters = this.userCycleFilters;
|
||||||
|
if (!updatedCycleFilters) updatedCycleFilters = {};
|
||||||
|
if (!updatedCycleFilters[cycleId]) updatedCycleFilters[cycleId] = {};
|
||||||
|
|
||||||
|
updatedCycleFilters[cycleId] = newViewProps;
|
||||||
|
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.userCycleFilters = { ...updatedCycleFilters };
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
view_props: {
|
||||||
|
filters: newViewProps.filters,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const user = this.rootStore.user.currentUser ?? undefined;
|
||||||
|
|
||||||
|
await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, payload);
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Failed to update user filters in issue filter store", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -5,7 +5,6 @@ import { RootStore } from "../root";
|
|||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
|
|
||||||
import { IBlockUpdateData } from "components/gantt-chart";
|
import { IBlockUpdateData } from "components/gantt-chart";
|
||||||
|
|
||||||
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
|
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
|
||||||
@ -18,8 +17,9 @@ export type IIssueGroupWithSubGroupsStructure = {
|
|||||||
export type IIssueUnGroupedStructure = IIssue[];
|
export type IIssueUnGroupedStructure = IIssue[];
|
||||||
|
|
||||||
export interface IIssueStore {
|
export interface IIssueStore {
|
||||||
loader: boolean;
|
loader: "initial-load" | "mutation" | null;
|
||||||
error: any | null;
|
error: any | null;
|
||||||
|
|
||||||
// issues
|
// issues
|
||||||
issues: {
|
issues: {
|
||||||
[project_id: string]: {
|
[project_id: string]: {
|
||||||
@ -33,15 +33,14 @@ export interface IIssueStore {
|
|||||||
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
|
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
|
||||||
getIssuesCount: number;
|
getIssuesCount: number;
|
||||||
// action
|
// action
|
||||||
fetchIssues: (workspaceSlug: string, projectId: string) => Promise<any>;
|
fetchIssues: (workspaceSlug: string, projectId: string, loadType?: "initial-load" | "mutation") => Promise<any>;
|
||||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
||||||
removeIssueFromStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
removeIssueFromStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
||||||
deleteIssue: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
|
||||||
updateGanttIssueStructure: (workspaceSlug: string, issue: IIssue, payload: IBlockUpdateData) => void;
|
updateGanttIssueStructure: (workspaceSlug: string, issue: IIssue, payload: IBlockUpdateData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssueStore implements IIssueStore {
|
export class IssueStore implements IIssueStore {
|
||||||
loader: boolean = false;
|
loader: "initial-load" | "mutation" | null = null;
|
||||||
error: any | null = null;
|
error: any | null = null;
|
||||||
issues: {
|
issues: {
|
||||||
[project_id: string]: {
|
[project_id: string]: {
|
||||||
@ -74,7 +73,6 @@ export class IssueStore implements IIssueStore {
|
|||||||
fetchIssues: action,
|
fetchIssues: action,
|
||||||
updateIssueStructure: action,
|
updateIssueStructure: action,
|
||||||
removeIssueFromStructure: action,
|
removeIssueFromStructure: action,
|
||||||
deleteIssue: action,
|
|
||||||
updateGanttIssueStructure: action,
|
updateGanttIssueStructure: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,14 +82,13 @@ export class IssueStore implements IIssueStore {
|
|||||||
autorun(() => {
|
autorun(() => {
|
||||||
const workspaceSlug = this.rootStore.workspace.workspaceSlug;
|
const workspaceSlug = this.rootStore.workspace.workspaceSlug;
|
||||||
const projectId = this.rootStore.project.projectId;
|
const projectId = this.rootStore.project.projectId;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
workspaceSlug &&
|
workspaceSlug &&
|
||||||
projectId &&
|
projectId &&
|
||||||
this.rootStore.issueFilter.userFilters &&
|
this.rootStore.issueFilter.userFilters &&
|
||||||
this.rootStore.issueFilter.userDisplayFilters
|
this.rootStore.issueFilter.userDisplayFilters
|
||||||
)
|
)
|
||||||
this.fetchIssues(workspaceSlug, projectId);
|
this.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,13 +97,16 @@ export class IssueStore implements IIssueStore {
|
|||||||
const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
|
const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
|
||||||
|
|
||||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||||
|
const issueGroup = this.rootStore?.issueFilter?.userDisplayFilters?.group_by || null;
|
||||||
const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
|
const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
|
||||||
if (!issueLayout) return null;
|
if (!issueLayout) return null;
|
||||||
|
|
||||||
const _issueState = groupedLayouts.includes(issueLayout)
|
const _issueState = groupedLayouts.includes(issueLayout)
|
||||||
? issueSubGroup
|
? issueGroup
|
||||||
? "groupWithSubGroups"
|
? issueSubGroup
|
||||||
: "grouped"
|
? "groupWithSubGroups"
|
||||||
|
: "grouped"
|
||||||
|
: "ungrouped"
|
||||||
: ungroupedLayouts.includes(issueLayout)
|
: ungroupedLayouts.includes(issueLayout)
|
||||||
? "ungrouped"
|
? "ungrouped"
|
||||||
: null;
|
: null;
|
||||||
@ -200,20 +200,6 @@ export class IssueStore implements IIssueStore {
|
|||||||
: [...(issues ?? []), issue];
|
: [...(issues ?? []), issue];
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
|
||||||
if (orderBy === "-created_at") {
|
|
||||||
issues = sortArrayByDate(issues as any, "created_at");
|
|
||||||
}
|
|
||||||
if (orderBy === "-updated_at") {
|
|
||||||
issues = sortArrayByDate(issues as any, "updated_at");
|
|
||||||
}
|
|
||||||
if (orderBy === "start_date") {
|
|
||||||
issues = sortArrayByDate(issues as any, "updated_at");
|
|
||||||
}
|
|
||||||
if (orderBy === "priority") {
|
|
||||||
issues = sortArrayByPriority(issues as any, "priority");
|
|
||||||
}
|
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } };
|
this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } };
|
||||||
});
|
});
|
||||||
@ -222,7 +208,6 @@ export class IssueStore implements IIssueStore {
|
|||||||
removeIssueFromStructure = (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
removeIssueFromStructure = (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
||||||
const projectId: string | null = issue?.project;
|
const projectId: string | null = issue?.project;
|
||||||
const issueType = this.getIssueType;
|
const issueType = this.getIssueType;
|
||||||
|
|
||||||
if (!projectId || !issueType) return null;
|
if (!projectId || !issueType) return null;
|
||||||
|
|
||||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
||||||
@ -257,7 +242,7 @@ export class IssueStore implements IIssueStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateGanttIssueStructure = async (workspaceSlug: string, issue: IIssue, payload: IBlockUpdateData) => {
|
updateGanttIssueStructure = async (workspaceSlug: string, issue: IIssue, payload: IBlockUpdateData) => {
|
||||||
if (!issue || !workspaceSlug) return;
|
if (!issue || !workspaceSlug || !this.getIssues) return;
|
||||||
|
|
||||||
const issues = this.getIssues as IIssueUnGroupedStructure;
|
const issues = this.getIssues as IIssueUnGroupedStructure;
|
||||||
|
|
||||||
@ -296,45 +281,13 @@ export class IssueStore implements IIssueStore {
|
|||||||
this.rootStore.issueDetail.updateIssue(workspaceSlug, issue.project, issue.id, newPayload);
|
this.rootStore.issueDetail.updateIssue(workspaceSlug, issue.project, issue.id, newPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteIssue = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
fetchIssues = async (
|
||||||
const projectId: string | null = issue?.project;
|
workspaceSlug: string,
|
||||||
const issueType = this.getIssueType;
|
projectId: string,
|
||||||
if (!projectId || !issueType) return null;
|
loadType: "initial-load" | "mutation" = "initial-load"
|
||||||
|
) => {
|
||||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
|
||||||
this.getIssues;
|
|
||||||
if (!issues) return null;
|
|
||||||
|
|
||||||
if (issueType === "grouped" && group_id) {
|
|
||||||
issues = issues as IIssueGroupedStructure;
|
|
||||||
issues = {
|
|
||||||
...issues,
|
|
||||||
[group_id]: issues[group_id].filter((i) => i?.id !== issue?.id),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
|
||||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
|
||||||
issues = {
|
|
||||||
...issues,
|
|
||||||
[sub_group_id]: {
|
|
||||||
...issues[sub_group_id],
|
|
||||||
[group_id]: issues[sub_group_id][group_id].filter((i) => i?.id !== issue?.id),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (issueType === "ungrouped") {
|
|
||||||
issues = issues as IIssueUnGroupedStructure;
|
|
||||||
issues = issues.filter((i) => i?.id !== issue?.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchIssues = async (workspaceSlug: string, projectId: string) => {
|
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
this.loader = loadType;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
|
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
|
||||||
@ -354,7 +307,7 @@ export class IssueStore implements IIssueStore {
|
|||||||
};
|
};
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues = _issues;
|
this.issues = _issues;
|
||||||
this.loader = false;
|
this.loader = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -362,7 +315,7 @@ export class IssueStore implements IIssueStore {
|
|||||||
return issueResponse;
|
return issueResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error: Fetching error in issues", error);
|
console.error("Error: Fetching error in issues", error);
|
||||||
this.loader = false;
|
this.loader = null;
|
||||||
this.error = error;
|
this.error = error;
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -1,222 +1,123 @@
|
|||||||
import { observable, action, makeObservable, runInAction } from "mobx";
|
import { action, makeObservable, runInAction } from "mobx";
|
||||||
// services
|
|
||||||
import { IssueService } from "services/issue";
|
|
||||||
// types
|
// types
|
||||||
import { RootStore } from "../root";
|
import { RootStore } from "../root";
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// uuid
|
// uuid
|
||||||
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
|
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
|
||||||
import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "./issue.store";
|
import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "./issue.store";
|
||||||
|
// services
|
||||||
|
import { IssueService } from "services/issue";
|
||||||
|
|
||||||
export interface IIssueQuickAddStore {
|
export interface IIssueQuickAddStore {
|
||||||
loader: boolean;
|
updateQuickAddIssueStructure: (
|
||||||
error: any | null;
|
|
||||||
|
|
||||||
createIssue: (
|
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
group_id: string | null,
|
||||||
grouping: {
|
sub_group_id: string | null,
|
||||||
group_id: string | null;
|
issue: IIssue
|
||||||
sub_group_id: string | null;
|
) => void;
|
||||||
},
|
|
||||||
data: Partial<IIssue>
|
|
||||||
) => Promise<IIssue>;
|
|
||||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
|
||||||
updateQuickAddIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssueQuickAddStore implements IIssueQuickAddStore {
|
export class IssueQuickAddStore implements IIssueQuickAddStore {
|
||||||
loader: boolean = false;
|
|
||||||
error: any | null = null;
|
|
||||||
|
|
||||||
// root store
|
|
||||||
rootStore;
|
rootStore;
|
||||||
// service
|
|
||||||
issueService;
|
issueService;
|
||||||
|
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(_rootStore: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
updateQuickAddIssueStructure: action,
|
||||||
loader: observable.ref,
|
|
||||||
error: observable.ref,
|
|
||||||
|
|
||||||
createIssue: action,
|
|
||||||
updateIssueStructure: action,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = _rootStore;
|
this.rootStore = _rootStore;
|
||||||
this.issueService = new IssueService();
|
this.issueService = new IssueService();
|
||||||
}
|
}
|
||||||
|
createIssue = async (workspaceSlug: string, projectId: string, data: Partial<IIssue>) => {
|
||||||
createIssue = async (
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
grouping: {
|
|
||||||
group_id: string | null;
|
|
||||||
sub_group_id: string | null;
|
|
||||||
},
|
|
||||||
data: Partial<IIssue>
|
|
||||||
) => {
|
|
||||||
runInAction(() => {
|
|
||||||
this.loader = true;
|
|
||||||
this.error = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const { group_id, sub_group_id } = grouping;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.updateIssueStructure(group_id, sub_group_id, data as IIssue);
|
const user = this.rootStore.user.currentUser ?? undefined;
|
||||||
|
|
||||||
const response = await this.issueService.createIssue(workspaceSlug, projectId, data);
|
const response = await this.issueService.createIssue(workspaceSlug, projectId, data);
|
||||||
|
|
||||||
this.updateQuickAddIssueStructure(group_id, sub_group_id, {
|
|
||||||
...data,
|
|
||||||
...response,
|
|
||||||
});
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.loader = false;
|
|
||||||
this.error = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = false;
|
|
||||||
this.error = error;
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
// same as above function but will use temp id instead of real id
|
||||||
const projectId: string | null = issue?.project;
|
updateQuickAddIssueStructure = async (
|
||||||
const issueType = this.rootStore.issue.getIssueType;
|
workspaceSlug: string,
|
||||||
if (!projectId || !issueType) return null;
|
group_id: string | null,
|
||||||
|
sub_group_id: string | null,
|
||||||
|
issue: IIssue
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const response: any = await this.createIssue(workspaceSlug, issue?.project, issue);
|
||||||
|
issue = { ...response, tempId: issue?.tempId };
|
||||||
|
|
||||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
const projectId: string | null = issue?.project;
|
||||||
this.rootStore.issue.getIssues;
|
const issueType = this.rootStore.issue.getIssueType;
|
||||||
if (!issues) return null;
|
if (!projectId || !issueType) return null;
|
||||||
|
|
||||||
if (group_id === "null") group_id = null;
|
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
||||||
if (sub_group_id === "null") sub_group_id = null;
|
this.rootStore.issue.getIssues;
|
||||||
|
if (!issues) return null;
|
||||||
|
|
||||||
if (issueType === "grouped" && group_id) {
|
if (issueType === "grouped" && group_id) {
|
||||||
issues = issues as IIssueGroupedStructure;
|
issues = issues as IIssueGroupedStructure;
|
||||||
const _currentIssueId = issues?.[group_id]?.find((_i) => _i?.id === issue.id);
|
const _currentIssueId = issues?.[group_id]?.find((_i) => _i?.tempId === issue.tempId);
|
||||||
issues = {
|
issues = {
|
||||||
...issues,
|
...issues,
|
||||||
[group_id]: _currentIssueId
|
|
||||||
? issues[group_id]?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i))
|
|
||||||
: [...(issues?.[group_id] ?? []), issue],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
|
||||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
|
||||||
const _currentIssueId = issues?.[sub_group_id]?.[group_id]?.find((_i) => _i?.id === issue.id);
|
|
||||||
issues = {
|
|
||||||
...issues,
|
|
||||||
[sub_group_id]: {
|
|
||||||
...issues[sub_group_id],
|
|
||||||
[group_id]: _currentIssueId
|
[group_id]: _currentIssueId
|
||||||
? issues?.[sub_group_id]?.[group_id]?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i))
|
? issues[group_id]?.map((i: IIssue) =>
|
||||||
: [...(issues?.[sub_group_id]?.[group_id] ?? []), issue],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (issueType === "ungrouped") {
|
|
||||||
issues = issues as IIssueUnGroupedStructure;
|
|
||||||
const _currentIssueId = issues?.find((_i) => _i?.id === issue.id);
|
|
||||||
issues = _currentIssueId
|
|
||||||
? issues?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i))
|
|
||||||
: [...(issues ?? []), issue];
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
|
||||||
if (orderBy === "-created_at") {
|
|
||||||
issues = sortArrayByDate(issues as any, "created_at");
|
|
||||||
}
|
|
||||||
if (orderBy === "-updated_at") {
|
|
||||||
issues = sortArrayByDate(issues as any, "updated_at");
|
|
||||||
}
|
|
||||||
if (orderBy === "start_date") {
|
|
||||||
issues = sortArrayByDate(issues as any, "updated_at");
|
|
||||||
}
|
|
||||||
if (orderBy === "priority") {
|
|
||||||
issues = sortArrayByPriority(issues as any, "priority");
|
|
||||||
}
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.rootStore.issue.issues = {
|
|
||||||
...this.rootStore.issue.issues,
|
|
||||||
[projectId]: { ...this.rootStore.issue.issues[projectId], [issueType]: issues },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// same as above function but will use temp id instead of real id
|
|
||||||
updateQuickAddIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
|
||||||
const projectId: string | null = issue?.project;
|
|
||||||
const issueType = this.rootStore.issue.getIssueType;
|
|
||||||
if (!projectId || !issueType) return null;
|
|
||||||
|
|
||||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
|
||||||
this.rootStore.issue.getIssues;
|
|
||||||
if (!issues) return null;
|
|
||||||
|
|
||||||
if (issueType === "grouped" && group_id) {
|
|
||||||
issues = issues as IIssueGroupedStructure;
|
|
||||||
const _currentIssueId = issues?.[group_id]?.find((_i) => _i?.tempId === issue.tempId);
|
|
||||||
issues = {
|
|
||||||
...issues,
|
|
||||||
[group_id]: _currentIssueId
|
|
||||||
? issues[group_id]?.map((i: IIssue) =>
|
|
||||||
i?.tempId === issue?.tempId ? { ...i, ...issue, tempId: undefined } : i
|
|
||||||
)
|
|
||||||
: [...(issues?.[group_id] ?? []), issue],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
|
||||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
|
||||||
const _currentIssueId = issues?.[sub_group_id]?.[group_id]?.find((_i) => _i?.tempId === issue.tempId);
|
|
||||||
issues = {
|
|
||||||
...issues,
|
|
||||||
[sub_group_id]: {
|
|
||||||
...issues[sub_group_id],
|
|
||||||
[group_id]: _currentIssueId
|
|
||||||
? issues?.[sub_group_id]?.[group_id]?.map((i: IIssue) =>
|
|
||||||
i?.tempId === issue?.tempId ? { ...i, ...issue, tempId: undefined } : i
|
i?.tempId === issue?.tempId ? { ...i, ...issue, tempId: undefined } : i
|
||||||
)
|
)
|
||||||
: [...(issues?.[sub_group_id]?.[group_id] ?? []), issue],
|
: [...(issues?.[group_id] ?? []), issue],
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
}
|
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
||||||
if (issueType === "ungrouped") {
|
issues = issues as IIssueGroupWithSubGroupsStructure;
|
||||||
issues = issues as IIssueUnGroupedStructure;
|
const _currentIssueId = issues?.[sub_group_id]?.[group_id]?.find((_i) => _i?.tempId === issue.tempId);
|
||||||
const _currentIssueId = issues?.find((_i) => _i?.tempId === issue.tempId);
|
issues = {
|
||||||
issues = _currentIssueId
|
...issues,
|
||||||
? issues?.map((i: IIssue) => (i?.tempId === issue?.tempId ? { ...i, ...issue, tempId: undefined } : i))
|
[sub_group_id]: {
|
||||||
: [...(issues ?? []), issue];
|
...issues[sub_group_id],
|
||||||
}
|
[group_id]: _currentIssueId
|
||||||
|
? issues?.[sub_group_id]?.[group_id]?.map((i: IIssue) =>
|
||||||
|
i?.tempId === issue?.tempId ? { ...i, ...issue, tempId: undefined } : i
|
||||||
|
)
|
||||||
|
: [...(issues?.[sub_group_id]?.[group_id] ?? []), issue],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (issueType === "ungrouped") {
|
||||||
|
issues = issues as IIssueUnGroupedStructure;
|
||||||
|
const _currentIssueId = issues?.find((_i) => _i?.tempId === issue.tempId);
|
||||||
|
issues = _currentIssueId
|
||||||
|
? issues?.map((i: IIssue) => (i?.tempId === issue?.tempId ? { ...i, ...issue, tempId: undefined } : i))
|
||||||
|
: [...(issues ?? []), issue];
|
||||||
|
}
|
||||||
|
|
||||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
||||||
if (orderBy === "-created_at") {
|
if (orderBy === "-created_at") {
|
||||||
issues = sortArrayByDate(issues as any, "created_at");
|
issues = sortArrayByDate(issues as any, "created_at");
|
||||||
}
|
}
|
||||||
if (orderBy === "-updated_at") {
|
if (orderBy === "-updated_at") {
|
||||||
issues = sortArrayByDate(issues as any, "updated_at");
|
issues = sortArrayByDate(issues as any, "updated_at");
|
||||||
}
|
}
|
||||||
if (orderBy === "start_date") {
|
if (orderBy === "start_date") {
|
||||||
issues = sortArrayByDate(issues as any, "updated_at");
|
issues = sortArrayByDate(issues as any, "updated_at");
|
||||||
}
|
}
|
||||||
if (orderBy === "priority") {
|
if (orderBy === "priority") {
|
||||||
issues = sortArrayByPriority(issues as any, "priority");
|
issues = sortArrayByPriority(issues as any, "priority");
|
||||||
}
|
}
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.rootStore.issue.issues = {
|
this.rootStore.issue.issues = {
|
||||||
...this.rootStore.issue.issues,
|
...this.rootStore.issue.issues,
|
||||||
[projectId]: { ...this.rootStore.issue.issues[projectId], [issueType]: issues },
|
[projectId]: { ...this.rootStore.issue.issues[projectId], [issueType]: issues },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
40
web/store/issues/index.ts
Normal file
40
web/store/issues/index.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/** project issues and issue-filters starts */
|
||||||
|
|
||||||
|
// issue and filter helpers
|
||||||
|
export * from "./project-issues/base-issue.store";
|
||||||
|
export * from "./project-issues/base-issue-filter.store";
|
||||||
|
|
||||||
|
// project display filters and display properties
|
||||||
|
export * from "./project-issues/issue-filters.store";
|
||||||
|
|
||||||
|
// project issues and filters
|
||||||
|
export * from "./project-issues/project/issue.store";
|
||||||
|
export * from "./project-issues/project/filter.store";
|
||||||
|
|
||||||
|
// module issues and filters
|
||||||
|
export * from "./project-issues/module/issue.store";
|
||||||
|
export * from "./project-issues/module/filter.store";
|
||||||
|
|
||||||
|
// cycle
|
||||||
|
export * from "./project-issues/cycle/issue.store";
|
||||||
|
export * from "./project-issues/cycle/filter.store";
|
||||||
|
|
||||||
|
// project views
|
||||||
|
export * from "./project-issues/project-view/issue.store";
|
||||||
|
export * from "./project-issues/project-view/filter.store";
|
||||||
|
|
||||||
|
// archived
|
||||||
|
export * from "./project-issues/archived/issue.store";
|
||||||
|
export * from "./project-issues/archived/filter.store";
|
||||||
|
|
||||||
|
// draft
|
||||||
|
export * from "./project-issues/draft/issue.store";
|
||||||
|
export * from "./project-issues/draft/filter.store";
|
||||||
|
|
||||||
|
/** project issues and issue-filters ends */
|
||||||
|
|
||||||
|
/** profile issues and issue-filters starts */
|
||||||
|
/** profile issues and issue-filters ends */
|
||||||
|
|
||||||
|
/** global issues and issue-filters starts */
|
||||||
|
/** global issues and issue-filters ends */
|
140
web/store/issues/project-issues/archived/filter.store.ts
Normal file
140
web/store/issues/project-issues/archived/filter.store.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { computed, makeObservable } from "mobx";
|
||||||
|
// base class
|
||||||
|
import { IssueFilterBaseStore } from "store/issues";
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
|
interface IProjectIssuesFilters {
|
||||||
|
filters: IIssueFilterOptions | undefined;
|
||||||
|
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||||
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectArchivedIssuesFilterStore {
|
||||||
|
// computed
|
||||||
|
issueFilters: IProjectIssuesFilters | undefined;
|
||||||
|
appliedFilters: TIssueParams[] | undefined;
|
||||||
|
// action
|
||||||
|
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||||
|
updateFilters: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
filterType: EFilterType,
|
||||||
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
|
||||||
|
) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectArchivedIssuesFilterStore
|
||||||
|
extends IssueFilterBaseStore
|
||||||
|
implements IProjectArchivedIssuesFilterStore
|
||||||
|
{
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
super(_rootStore);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
// computed
|
||||||
|
issueFilters: computed,
|
||||||
|
appliedFilters: computed,
|
||||||
|
});
|
||||||
|
|
||||||
|
// root store
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
get issueFilters() {
|
||||||
|
const projectId = this.rootStore.project.projectId;
|
||||||
|
if (!projectId) return undefined;
|
||||||
|
const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId);
|
||||||
|
|
||||||
|
const _filters: IProjectIssuesFilters = {
|
||||||
|
filters: displayFilters?.filters,
|
||||||
|
displayFilters: displayFilters?.displayFilters,
|
||||||
|
displayProperties: displayFilters?.displayProperties,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
get appliedFilters() {
|
||||||
|
const userFilters = this.issueFilters;
|
||||||
|
if (!userFilters) return undefined;
|
||||||
|
|
||||||
|
let filteredRouteParams: any = {
|
||||||
|
priority: userFilters?.filters?.priority || undefined,
|
||||||
|
state_group: userFilters?.filters?.state_group || undefined,
|
||||||
|
state: userFilters?.filters?.state || undefined,
|
||||||
|
assignees: userFilters?.filters?.assignees || undefined,
|
||||||
|
mentions: userFilters?.filters?.mentions || undefined,
|
||||||
|
created_by: userFilters?.filters?.created_by || undefined,
|
||||||
|
labels: userFilters?.filters?.labels || undefined,
|
||||||
|
start_date: userFilters?.filters?.start_date || undefined,
|
||||||
|
target_date: userFilters?.filters?.target_date || undefined,
|
||||||
|
type: userFilters?.displayFilters?.type || undefined,
|
||||||
|
sub_issue: userFilters?.displayFilters?.sub_issue || true,
|
||||||
|
show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true,
|
||||||
|
start_target_date: userFilters?.displayFilters?.start_target_date || true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||||
|
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||||
|
|
||||||
|
return filteredRouteParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
||||||
|
try {
|
||||||
|
await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId);
|
||||||
|
await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw Error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFilters = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
filterType: EFilterType,
|
||||||
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
switch (filterType) {
|
||||||
|
case EFilterType.FILTERS:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayFilters(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filterType,
|
||||||
|
filters as IIssueFilterOptions
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EFilterType.DISPLAY_FILTERS:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayFilters(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filterType,
|
||||||
|
filters as IIssueDisplayFilterOptions
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EFilterType.DISPLAY_PROPERTIES:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayProperties(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filters as IIssueDisplayProperties
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
127
web/store/issues/project-issues/archived/issue.store.ts
Normal file
127
web/store/issues/project-issues/archived/issue.store.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { action, observable, makeObservable, computed, runInAction, autorun } from "mobx";
|
||||||
|
// base class
|
||||||
|
import { IssueBaseStore } from "store/issues";
|
||||||
|
// services
|
||||||
|
import { IssueArchiveService } from "services/issue";
|
||||||
|
// types
|
||||||
|
import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types";
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
|
||||||
|
export interface IProjectArchivedIssuesStore {
|
||||||
|
// observable
|
||||||
|
loader: TLoader;
|
||||||
|
issues: { [project_id: string]: IIssueResponse } | undefined;
|
||||||
|
// computed
|
||||||
|
getIssues: IIssueResponse | undefined;
|
||||||
|
getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined;
|
||||||
|
// actions
|
||||||
|
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<IIssueResponse>;
|
||||||
|
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
|
removeIssueFromArchived: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProjectArchivedIssuesStore {
|
||||||
|
loader: TLoader = "init-loader";
|
||||||
|
issues: { [project_id: string]: IIssueResponse } | undefined = undefined;
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
// service
|
||||||
|
archivedIssueService;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
super(_rootStore);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
// observable
|
||||||
|
loader: observable.ref,
|
||||||
|
issues: observable.ref,
|
||||||
|
// computed
|
||||||
|
getIssues: computed,
|
||||||
|
getIssuesIds: computed,
|
||||||
|
// action
|
||||||
|
fetchIssues: action,
|
||||||
|
removeIssue: action,
|
||||||
|
removeIssueFromArchived: action,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
this.archivedIssueService = new IssueArchiveService();
|
||||||
|
|
||||||
|
autorun(() => {
|
||||||
|
const workspaceSlug = this.rootStore.workspace.workspaceSlug;
|
||||||
|
const projectId = this.rootStore.project.projectId;
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
const userFilters = this.rootStore?.projectArchivedIssuesFilter?.issueFilters?.filters;
|
||||||
|
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get getIssues() {
|
||||||
|
const projectId = this.rootStore?.project.projectId;
|
||||||
|
if (!projectId || !this.issues || !this.issues[projectId]) return undefined;
|
||||||
|
|
||||||
|
return this.issues[projectId];
|
||||||
|
}
|
||||||
|
|
||||||
|
get getIssuesIds() {
|
||||||
|
const projectId = this.rootStore?.project.projectId;
|
||||||
|
const displayFilters = this.rootStore?.projectArchivedIssuesFilter?.issueFilters?.displayFilters;
|
||||||
|
if (!displayFilters) return undefined;
|
||||||
|
|
||||||
|
const groupBy = displayFilters?.group_by;
|
||||||
|
const orderBy = displayFilters?.order_by;
|
||||||
|
const layout = displayFilters?.layout;
|
||||||
|
|
||||||
|
if (!projectId || !this.issues || !this.issues[projectId]) return undefined;
|
||||||
|
|
||||||
|
let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined;
|
||||||
|
|
||||||
|
if (layout === "list" && orderBy) {
|
||||||
|
if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]);
|
||||||
|
else issues = this.unGroupedIssues(orderBy, this.issues[projectId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => {
|
||||||
|
try {
|
||||||
|
this.loader = loadType;
|
||||||
|
|
||||||
|
const params = this.rootStore?.projectArchivedIssuesFilter?.appliedFilters;
|
||||||
|
const response = await this.archivedIssueService.getV3ArchivedIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
|
const _issues = { ...this.issues, [projectId]: { ...response } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
this.loader = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId);
|
||||||
|
this.loader = undefined;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
|
try {
|
||||||
|
await this.archivedIssueService.unarchiveIssue(workspaceSlug, projectId, issueId);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeIssueFromArchived = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
|
try {
|
||||||
|
await this.archivedIssueService.deleteArchivedIssue(workspaceSlug, projectId, issueId);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
29
web/store/issues/project-issues/base-issue-filter.store.ts
Normal file
29
web/store/issues/project-issues/base-issue-filter.store.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// types
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
|
||||||
|
export interface IIssueFilterBaseStore {
|
||||||
|
// helper methods
|
||||||
|
computedFilter(filters: any, filteredParams: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IssueFilterBaseStore implements IIssueFilterBaseStore {
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
// root store
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper methods
|
||||||
|
computedFilter = (filters: any, filteredParams: any) => {
|
||||||
|
const computedFilters: any = {};
|
||||||
|
Object.keys(filters).map((key) => {
|
||||||
|
if (filters[key] != undefined && filteredParams.includes(key))
|
||||||
|
computedFilters[key] =
|
||||||
|
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
|
||||||
|
});
|
||||||
|
|
||||||
|
return computedFilters;
|
||||||
|
};
|
||||||
|
}
|
152
web/store/issues/project-issues/base-issue.store.ts
Normal file
152
web/store/issues/project-issues/base-issue.store.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import _ from "lodash";
|
||||||
|
// types
|
||||||
|
import { IIssue, TIssueGroupByOptions, TIssueOrderByOptions } from "types";
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
import { IIssueResponse } from "../types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
|
||||||
|
// helpers
|
||||||
|
import { renderDateFormat } from "helpers/date-time.helper";
|
||||||
|
|
||||||
|
export interface IIssueBaseStore {
|
||||||
|
groupedIssues(
|
||||||
|
groupBy: TIssueGroupByOptions,
|
||||||
|
orderBy: TIssueOrderByOptions,
|
||||||
|
issues: IIssueResponse,
|
||||||
|
isCalendarIssues?: boolean
|
||||||
|
): { [group_id: string]: string[] };
|
||||||
|
subGroupedIssues(
|
||||||
|
subGroupBy: TIssueGroupByOptions,
|
||||||
|
groupBy: TIssueGroupByOptions,
|
||||||
|
orderBy: TIssueOrderByOptions,
|
||||||
|
issues: IIssueResponse
|
||||||
|
): { [sub_group_id: string]: { [group_id: string]: string[] } };
|
||||||
|
unGroupedIssues(orderBy: TIssueOrderByOptions, issues: IIssueResponse): string[];
|
||||||
|
issueDisplayFiltersDefaultData(groupBy: string | null): string[];
|
||||||
|
issuesSortWithOrderBy(issueObject: IIssueResponse, key: Partial<TIssueOrderByOptions>): IIssue[];
|
||||||
|
getGroupArray(value: string[] | string | null, isDate?: boolean): string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IssueBaseStore implements IIssueBaseStore {
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupedIssues = (
|
||||||
|
groupBy: TIssueGroupByOptions,
|
||||||
|
orderBy: TIssueOrderByOptions,
|
||||||
|
issues: IIssueResponse,
|
||||||
|
isCalendarIssues: boolean = false
|
||||||
|
) => {
|
||||||
|
const _issues: { [group_id: string]: string[] } = {};
|
||||||
|
|
||||||
|
this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => {
|
||||||
|
_issues[group] = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectIssues = this.issuesSortWithOrderBy(issues, orderBy);
|
||||||
|
|
||||||
|
for (const issue in projectIssues) {
|
||||||
|
const _issue = projectIssues[issue];
|
||||||
|
const groupArray = this.getGroupArray(_.get(_issue, groupBy as keyof IIssue), isCalendarIssues);
|
||||||
|
|
||||||
|
for (const group of groupArray) {
|
||||||
|
if (group && _issues[group]) _issues[group].push(_issue.id);
|
||||||
|
else if (group) _issues[group] = [_issue.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _issues;
|
||||||
|
};
|
||||||
|
|
||||||
|
subGroupedIssues = (
|
||||||
|
subGroupBy: TIssueGroupByOptions,
|
||||||
|
groupBy: TIssueGroupByOptions,
|
||||||
|
orderBy: TIssueOrderByOptions,
|
||||||
|
issues: IIssueResponse
|
||||||
|
) => {
|
||||||
|
const _issues: { [sub_group_id: string]: { [group_id: string]: string[] } } = {};
|
||||||
|
|
||||||
|
this.issueDisplayFiltersDefaultData(subGroupBy).forEach((sub_group: any) => {
|
||||||
|
const groupByIssues: { [group_id: string]: string[] } = {};
|
||||||
|
this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => {
|
||||||
|
groupByIssues[group] = [];
|
||||||
|
});
|
||||||
|
_issues[sub_group] = groupByIssues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectIssues = this.issuesSortWithOrderBy(issues, orderBy);
|
||||||
|
|
||||||
|
for (const issue in projectIssues) {
|
||||||
|
const _issue = projectIssues[issue];
|
||||||
|
const subGroupArray = this.getGroupArray(_.get(_issue, subGroupBy as keyof IIssue));
|
||||||
|
const groupArray = this.getGroupArray(_.get(_issue, groupBy as keyof IIssue));
|
||||||
|
|
||||||
|
for (const subGroup of subGroupArray) {
|
||||||
|
for (const group of groupArray) {
|
||||||
|
if (subGroup && group && issues[subGroup]) {
|
||||||
|
_issues[subGroup][group].push(_issue.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _issues;
|
||||||
|
};
|
||||||
|
|
||||||
|
unGroupedIssues = (orderBy: TIssueOrderByOptions, issues: IIssueResponse) =>
|
||||||
|
this.issuesSortWithOrderBy(issues, orderBy).map((issue) => issue.id);
|
||||||
|
|
||||||
|
issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => {
|
||||||
|
switch (groupBy) {
|
||||||
|
case "state":
|
||||||
|
return this.rootStore?.projectState.projectStateIds();
|
||||||
|
case "state_detail.group":
|
||||||
|
return ISSUE_STATE_GROUPS.map((i) => i.key);
|
||||||
|
case "priority":
|
||||||
|
return ISSUE_PRIORITIES.map((i) => i.key);
|
||||||
|
case "labels":
|
||||||
|
return this.rootStore?.projectLabel?.projectLabelIds(true);
|
||||||
|
case "created_by":
|
||||||
|
return this.rootStore?.projectMember?.projectMemberIds(true);
|
||||||
|
case "assignees":
|
||||||
|
return this.rootStore?.projectMember?.projectMemberIds(true);
|
||||||
|
case "project":
|
||||||
|
return this.rootStore?.project?.workspaceProjectIds();
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
issuesSortWithOrderBy = (issueObject: IIssueResponse, key: Partial<TIssueOrderByOptions>): IIssue[] => {
|
||||||
|
let array = _.values(issueObject);
|
||||||
|
array = _.sortBy(array, "created_at");
|
||||||
|
switch (key) {
|
||||||
|
case "sort_order":
|
||||||
|
return _.sortBy(array, "sort_order");
|
||||||
|
case "-created_at":
|
||||||
|
return _.reverse(_.sortBy(array, "created_at"));
|
||||||
|
case "-updated_at":
|
||||||
|
return _.reverse(_.sortBy(array, "updated_at"));
|
||||||
|
case "start_date":
|
||||||
|
return _.sortBy(array, "start_date");
|
||||||
|
case "target_date":
|
||||||
|
return _.sortBy(array, "target_date");
|
||||||
|
case "priority": {
|
||||||
|
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
||||||
|
return _.sortBy(array, (_issue: IIssue) => _.indexOf(sortArray, _issue.priority));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getGroupArray(value: string[] | string | null, isDate: boolean = false) {
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
else if (isDate) return [renderDateFormat(value) || "None"];
|
||||||
|
else return [value || "None"];
|
||||||
|
}
|
||||||
|
}
|
253
web/store/issues/project-issues/cycle/filter.store.ts
Normal file
253
web/store/issues/project-issues/cycle/filter.store.ts
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||||
|
// base class
|
||||||
|
import { IssueFilterBaseStore } from "store/issues";
|
||||||
|
// services
|
||||||
|
import { ProjectService, ProjectMemberService } from "services/project";
|
||||||
|
import { IssueService } from "services/issue";
|
||||||
|
import { CycleService } from "services/cycle.service";
|
||||||
|
// helpers
|
||||||
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
|
interface ICycleIssuesFilterOptions {
|
||||||
|
filters: IIssueFilterOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProjectIssuesFilters {
|
||||||
|
filters: IIssueFilterOptions | undefined;
|
||||||
|
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||||
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICycleIssuesFilterStore {
|
||||||
|
// observable
|
||||||
|
loader: boolean;
|
||||||
|
filters: { [cycleId: string]: ICycleIssuesFilterOptions } | undefined;
|
||||||
|
// computed
|
||||||
|
issueFilters: IProjectIssuesFilters | undefined;
|
||||||
|
appliedFilters: TIssueParams[] | undefined;
|
||||||
|
// actions
|
||||||
|
fetchCycleFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<IIssueFilterOptions>;
|
||||||
|
updateCycleFilters: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
type: EFilterType,
|
||||||
|
filters: IIssueFilterOptions
|
||||||
|
) => Promise<ICycleIssuesFilterOptions>;
|
||||||
|
|
||||||
|
fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||||
|
updateFilters: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
filterType: EFilterType,
|
||||||
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CycleIssuesFilterStore extends IssueFilterBaseStore implements ICycleIssuesFilterStore {
|
||||||
|
// observables
|
||||||
|
loader: boolean = false;
|
||||||
|
filters: { [projectId: string]: ICycleIssuesFilterOptions } | undefined = undefined;
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
// services
|
||||||
|
projectService;
|
||||||
|
projectMemberService;
|
||||||
|
issueService;
|
||||||
|
cycleService;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
super(_rootStore);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
// observables
|
||||||
|
loader: observable.ref,
|
||||||
|
filters: observable.ref,
|
||||||
|
// computed
|
||||||
|
issueFilters: computed,
|
||||||
|
appliedFilters: computed,
|
||||||
|
// actions
|
||||||
|
fetchCycleFilters: action,
|
||||||
|
updateCycleFilters: action,
|
||||||
|
fetchFilters: action,
|
||||||
|
updateFilters: action,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
|
||||||
|
this.projectService = new ProjectService();
|
||||||
|
this.projectMemberService = new ProjectMemberService();
|
||||||
|
this.issueService = new IssueService();
|
||||||
|
this.cycleService = new CycleService();
|
||||||
|
}
|
||||||
|
|
||||||
|
get issueFilters() {
|
||||||
|
const projectId = this.rootStore.project.projectId;
|
||||||
|
const cycleId = this.rootStore.cycle.cycleId;
|
||||||
|
if (!projectId || !cycleId) return undefined;
|
||||||
|
|
||||||
|
const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId);
|
||||||
|
const cycleFilters = this.filters?.[cycleId];
|
||||||
|
|
||||||
|
const _filters: IProjectIssuesFilters = {
|
||||||
|
filters: cycleFilters?.filters,
|
||||||
|
displayFilters: displayFilters?.displayFilters,
|
||||||
|
displayProperties: displayFilters?.displayProperties,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
get appliedFilters() {
|
||||||
|
const userFilters = this.issueFilters;
|
||||||
|
if (!userFilters) return undefined;
|
||||||
|
|
||||||
|
let filteredRouteParams: any = {
|
||||||
|
priority: userFilters?.filters?.priority || undefined,
|
||||||
|
state_group: userFilters?.filters?.state_group || undefined,
|
||||||
|
state: userFilters?.filters?.state || undefined,
|
||||||
|
assignees: userFilters?.filters?.assignees || undefined,
|
||||||
|
mentions: userFilters?.filters?.mentions || undefined,
|
||||||
|
created_by: userFilters?.filters?.created_by || undefined,
|
||||||
|
labels: userFilters?.filters?.labels || undefined,
|
||||||
|
start_date: userFilters?.filters?.start_date || undefined,
|
||||||
|
target_date: userFilters?.filters?.target_date || undefined,
|
||||||
|
type: userFilters?.displayFilters?.type || undefined,
|
||||||
|
sub_issue: userFilters?.displayFilters?.sub_issue || true,
|
||||||
|
show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true,
|
||||||
|
start_target_date: userFilters?.displayFilters?.start_target_date || true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||||
|
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||||
|
|
||||||
|
if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date";
|
||||||
|
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||||
|
|
||||||
|
return filteredRouteParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCycleFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||||
|
try {
|
||||||
|
const cycleFilters = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId);
|
||||||
|
|
||||||
|
const filters: IIssueFilterOptions = {
|
||||||
|
assignees: cycleFilters?.view_props?.filters?.assignees || null,
|
||||||
|
mentions: cycleFilters?.view_props?.filters?.mentions || null,
|
||||||
|
created_by: cycleFilters?.view_props?.filters?.created_by || null,
|
||||||
|
labels: cycleFilters?.view_props?.filters?.labels || null,
|
||||||
|
priority: cycleFilters?.view_props?.filters?.priority || null,
|
||||||
|
project: cycleFilters?.view_props?.filters?.project || null,
|
||||||
|
start_date: cycleFilters?.view_props?.filters?.start_date || null,
|
||||||
|
state: cycleFilters?.view_props?.filters?.state || null,
|
||||||
|
state_group: cycleFilters?.view_props?.filters?.state_group || null,
|
||||||
|
subscriber: cycleFilters?.view_props?.filters?.subscriber || null,
|
||||||
|
target_date: cycleFilters?.view_props?.filters?.target_date || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const issueFilters: ICycleIssuesFilterOptions = {
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _filters = { ...this.filters };
|
||||||
|
if (!_filters) _filters = {};
|
||||||
|
if (!_filters[cycleId]) _filters[cycleId] = { filters: {} };
|
||||||
|
_filters[cycleId] = issueFilters;
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.filters = _filters;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchFilters(workspaceSlug, projectId, cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCycleFilters = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
type: EFilterType,
|
||||||
|
filters: IIssueFilterOptions
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
let _cycleIssueFilters = { ...this.filters };
|
||||||
|
if (!_cycleIssueFilters) _cycleIssueFilters = {};
|
||||||
|
if (!_cycleIssueFilters[cycleId]) _cycleIssueFilters[cycleId] = { filters: {} };
|
||||||
|
|
||||||
|
const _filters = { filters: { ..._cycleIssueFilters[cycleId].filters } };
|
||||||
|
|
||||||
|
if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters };
|
||||||
|
|
||||||
|
_cycleIssueFilters[cycleId] = { filters: _filters.filters };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.filters = _cycleIssueFilters;
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, {
|
||||||
|
view_props: { filters: _filters.filters },
|
||||||
|
});
|
||||||
|
|
||||||
|
return _filters;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchFilters(workspaceSlug, projectId, cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||||
|
try {
|
||||||
|
await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId);
|
||||||
|
await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId);
|
||||||
|
await this.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchFilters(workspaceSlug, projectId, cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFilters = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
filterType: EFilterType,
|
||||||
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
if (!cycleId) throw new Error();
|
||||||
|
switch (filterType) {
|
||||||
|
case EFilterType.FILTERS:
|
||||||
|
await this.updateCycleFilters(workspaceSlug, projectId, cycleId, filterType, filters as IIssueFilterOptions);
|
||||||
|
break;
|
||||||
|
case EFilterType.DISPLAY_FILTERS:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayFilters(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filterType,
|
||||||
|
filters as IIssueDisplayFilterOptions
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EFilterType.DISPLAY_PROPERTIES:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayProperties(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filters as IIssueDisplayProperties
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
315
web/store/issues/project-issues/cycle/issue.store.ts
Normal file
315
web/store/issues/project-issues/cycle/issue.store.ts
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
import { action, observable, makeObservable, computed, runInAction, autorun } from "mobx";
|
||||||
|
// base class
|
||||||
|
import { IssueBaseStore } from "store/issues";
|
||||||
|
// services
|
||||||
|
import { IssueService } from "services/issue";
|
||||||
|
import { CycleService } from "services/cycle.service";
|
||||||
|
// types
|
||||||
|
import { TIssueGroupByOptions } from "types";
|
||||||
|
import { IIssue } from "types/issues";
|
||||||
|
import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types";
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
|
||||||
|
export interface ICycleIssuesStore {
|
||||||
|
// observable
|
||||||
|
loader: TLoader;
|
||||||
|
issues: { [cycle_id: string]: IIssueResponse } | undefined;
|
||||||
|
// computed
|
||||||
|
getIssues: IIssueResponse | undefined;
|
||||||
|
getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined;
|
||||||
|
// actions
|
||||||
|
fetchIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
loadType: TLoader,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => Promise<IIssueResponse | undefined>;
|
||||||
|
createIssue: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: Partial<IIssue>,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
updateIssue: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
data: Partial<IIssue>,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
removeIssue: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
quickAddIssue: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
cycleId?: string | undefined
|
||||||
|
) => Promise<IIssue | undefined>;
|
||||||
|
removeIssueFromCycle: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
issueId: string,
|
||||||
|
issueBridgeId: string
|
||||||
|
) => Promise<IIssue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStore {
|
||||||
|
loader: TLoader = "init-loader";
|
||||||
|
issues: { [cycle_id: string]: IIssueResponse } | undefined = undefined;
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
// service
|
||||||
|
cycleService;
|
||||||
|
issueService;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
super(_rootStore);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
// observable
|
||||||
|
loader: observable.ref,
|
||||||
|
issues: observable.ref,
|
||||||
|
// computed
|
||||||
|
getIssues: computed,
|
||||||
|
getIssuesIds: computed,
|
||||||
|
// action
|
||||||
|
fetchIssues: action,
|
||||||
|
createIssue: action,
|
||||||
|
updateIssue: action,
|
||||||
|
removeIssue: action,
|
||||||
|
quickAddIssue: action,
|
||||||
|
removeIssueFromCycle: action,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
this.issueService = new IssueService();
|
||||||
|
this.cycleService = new CycleService();
|
||||||
|
|
||||||
|
autorun(() => {
|
||||||
|
const workspaceSlug = this.rootStore.workspace.workspaceSlug;
|
||||||
|
const projectId = this.rootStore.project.projectId;
|
||||||
|
const cycleId = this.rootStore.cycle.cycleId;
|
||||||
|
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||||
|
|
||||||
|
const userFilters = this.rootStore?.cycleIssuesFilter?.issueFilters?.filters;
|
||||||
|
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get getIssues() {
|
||||||
|
const cycleId = this.rootStore?.cycle?.cycleId;
|
||||||
|
if (!cycleId || !this.issues || !this.issues[cycleId]) return undefined;
|
||||||
|
|
||||||
|
return this.issues[cycleId];
|
||||||
|
}
|
||||||
|
|
||||||
|
get getIssuesIds() {
|
||||||
|
const cycleId = this.rootStore?.cycle?.cycleId;
|
||||||
|
const displayFilters = this.rootStore?.cycleIssuesFilter?.issueFilters?.displayFilters;
|
||||||
|
|
||||||
|
const subGroupBy = displayFilters?.sub_group_by;
|
||||||
|
const groupBy = displayFilters?.group_by;
|
||||||
|
const orderBy = displayFilters?.order_by;
|
||||||
|
const layout = displayFilters?.layout;
|
||||||
|
|
||||||
|
if (!cycleId || !this.issues || !this.issues[cycleId]) return undefined;
|
||||||
|
|
||||||
|
let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined;
|
||||||
|
|
||||||
|
if (layout === "list" && orderBy) {
|
||||||
|
if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[cycleId]);
|
||||||
|
else issues = this.unGroupedIssues(orderBy, this.issues[cycleId]);
|
||||||
|
} else if (layout === "kanban" && groupBy && orderBy) {
|
||||||
|
if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, this.issues[cycleId]);
|
||||||
|
else issues = this.groupedIssues(groupBy, orderBy, this.issues[cycleId]);
|
||||||
|
} else if (layout === "calendar")
|
||||||
|
issues = this.groupedIssues("target_date" as TIssueGroupByOptions, "target_date", this.issues[cycleId], true);
|
||||||
|
else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[cycleId]);
|
||||||
|
else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", this.issues[cycleId]);
|
||||||
|
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchIssues = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
loadType: TLoader = "init-loader",
|
||||||
|
cycleId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!cycleId) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loader = loadType;
|
||||||
|
|
||||||
|
const params = this.rootStore?.cycleIssuesFilter?.appliedFilters;
|
||||||
|
const response = await this.cycleService.getV3CycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||||
|
|
||||||
|
const _issues = { ...this.issues, [cycleId]: { ...response } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
this.loader = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
this.loader = undefined;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
createIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: Partial<IIssue>,
|
||||||
|
cycleId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!cycleId) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
||||||
|
const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, projectId, cycleId, {
|
||||||
|
issues: [response.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
let _issues = this.issues;
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
_issues[cycleId] = { ..._issues[cycleId], ...{ [response.id]: response } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
return issueToCycle;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
data: Partial<IIssue>,
|
||||||
|
cycleId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!cycleId) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let _issues = { ...this.issues };
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
_issues[cycleId][issueId] = { ..._issues[cycleId][issueId], ...data };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.rootStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
cycleId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!cycleId) return undefined;
|
||||||
|
try {
|
||||||
|
let _issues = { ...this.issues };
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
delete _issues?.[cycleId]?.[issueId];
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quickAddIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: IIssue,
|
||||||
|
cycleId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!cycleId) return;
|
||||||
|
try {
|
||||||
|
let _issues = { ...this.issues };
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
_issues[cycleId] = { ..._issues[cycleId], ...{ [data.id as keyof IIssue]: data } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.createIssue(workspaceSlug, projectId, data, cycleId);
|
||||||
|
|
||||||
|
if (this.issues) {
|
||||||
|
delete this.issues[cycleId][data.id as keyof IIssue];
|
||||||
|
|
||||||
|
let _issues = { ...this.issues };
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
_issues[cycleId] = { ..._issues[cycleId], ...{ [response.id as keyof IIssue]: response } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeIssueFromCycle = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
issueId: string,
|
||||||
|
issueBridgeId: string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
let _issues = { ...this.issues };
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
delete _issues?.[cycleId]?.[issueId];
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueBridgeId);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
137
web/store/issues/project-issues/draft/filter.store.ts
Normal file
137
web/store/issues/project-issues/draft/filter.store.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { computed, makeObservable } from "mobx";
|
||||||
|
// base class
|
||||||
|
import { IssueFilterBaseStore } from "store/issues";
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types";
|
||||||
|
import { EFilterType } from "store/issues/types";
|
||||||
|
|
||||||
|
interface IProjectIssuesFilters {
|
||||||
|
filters: IIssueFilterOptions | undefined;
|
||||||
|
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||||
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectDraftIssuesFilterStore {
|
||||||
|
// computed
|
||||||
|
issueFilters: IProjectIssuesFilters | undefined;
|
||||||
|
appliedFilters: TIssueParams[] | undefined;
|
||||||
|
// action
|
||||||
|
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||||
|
updateFilters: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
filterType: EFilterType,
|
||||||
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
|
||||||
|
) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectDraftIssuesFilterStore extends IssueFilterBaseStore implements IProjectDraftIssuesFilterStore {
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
super(_rootStore);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
// computed
|
||||||
|
issueFilters: computed,
|
||||||
|
appliedFilters: computed,
|
||||||
|
});
|
||||||
|
|
||||||
|
// root store
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
get issueFilters() {
|
||||||
|
const projectId = this.rootStore.project.projectId;
|
||||||
|
if (!projectId) return undefined;
|
||||||
|
const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId);
|
||||||
|
|
||||||
|
const _filters: IProjectIssuesFilters = {
|
||||||
|
filters: displayFilters?.filters,
|
||||||
|
displayFilters: displayFilters?.displayFilters,
|
||||||
|
displayProperties: displayFilters?.displayProperties,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
get appliedFilters() {
|
||||||
|
const userFilters = this.issueFilters;
|
||||||
|
if (!userFilters) return undefined;
|
||||||
|
|
||||||
|
let filteredRouteParams: any = {
|
||||||
|
priority: userFilters?.filters?.priority || undefined,
|
||||||
|
state_group: userFilters?.filters?.state_group || undefined,
|
||||||
|
state: userFilters?.filters?.state || undefined,
|
||||||
|
assignees: userFilters?.filters?.assignees || undefined,
|
||||||
|
mentions: userFilters?.filters?.mentions || undefined,
|
||||||
|
created_by: userFilters?.filters?.created_by || undefined,
|
||||||
|
labels: userFilters?.filters?.labels || undefined,
|
||||||
|
start_date: userFilters?.filters?.start_date || undefined,
|
||||||
|
target_date: userFilters?.filters?.target_date || undefined,
|
||||||
|
type: userFilters?.displayFilters?.type || undefined,
|
||||||
|
sub_issue: userFilters?.displayFilters?.sub_issue || true,
|
||||||
|
show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true,
|
||||||
|
start_target_date: userFilters?.displayFilters?.start_target_date || true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||||
|
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||||
|
|
||||||
|
return filteredRouteParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
||||||
|
try {
|
||||||
|
await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId);
|
||||||
|
await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw Error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFilters = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
filterType: EFilterType,
|
||||||
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
switch (filterType) {
|
||||||
|
case EFilterType.FILTERS:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayFilters(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filterType,
|
||||||
|
filters as IIssueFilterOptions
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EFilterType.DISPLAY_FILTERS:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayFilters(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filterType,
|
||||||
|
filters as IIssueDisplayFilterOptions
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EFilterType.DISPLAY_PROPERTIES:
|
||||||
|
await this.rootStore.issuesFilter.updateDisplayProperties(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
filters as IIssueDisplayProperties
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user