chore: workspace global issues (#2964)

* dev: global issues store

* build-error: all issues render

* build-error: build error resolved in global view store
This commit is contained in:
guru_sainath 2023-12-01 15:16:36 +05:30 committed by sriram veeraghanta
parent 83026e8b2f
commit a276bd2301
27 changed files with 732 additions and 695 deletions

View File

@ -2,8 +2,6 @@ import { useCallback, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
@ -17,6 +15,7 @@ import { List, PlusIcon, Sheet } from "lucide-react";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TStaticViewTypes } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TStaticViewTypes } 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";
const GLOBAL_VIEW_LAYOUTS = [ const GLOBAL_VIEW_LAYOUTS = [
{ key: "list", title: "List", link: "/workspace-views", icon: List }, { key: "list", title: "List", link: "/workspace-views", icon: List },
@ -27,73 +26,55 @@ type Props = {
activeLayout: "list" | "spreadsheet"; activeLayout: "list" | "spreadsheet";
}; };
const STATIC_VIEW_TYPES: TStaticViewTypes[] = ["all-issues", "assigned", "created", "subscribed"];
export const GlobalIssuesHeader: React.FC<Props> = observer((props) => { export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
const { activeLayout } = props; const { activeLayout } = props;
const [createViewModal, setCreateViewModal] = useState(false); const [createViewModal, setCreateViewModal] = useState(false);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, globalViewId } = router.query; const { workspaceSlug } = router.query as { workspaceSlug: string };
const { const {
globalViewFilters: globalViewFiltersStore, workspace: { workspaceLabels },
workspaceFilter: workspaceFilterStore,
workspace: workspaceStore,
workspaceMember: { workspaceMembers }, workspaceMember: { workspaceMembers },
project: projectStore, project: { workspaceProjects },
} = useMobxStore();
const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined; workspaceGlobalIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore();
const handleFiltersUpdate = useCallback( const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => { (key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !globalViewId) return; if (!workspaceSlug) 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);
} }
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues });
[key]: newValues,
});
}, },
[globalViewId, globalViewFiltersStore, storedFilters, workspaceSlug] [workspaceSlug, issueFilters, updateFilters]
); );
const handleDisplayFiltersUpdate = useCallback( const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), {
display_filters: updatedDisplayFilter,
});
}, },
[workspaceFilterStore, workspaceSlug] [workspaceSlug, updateFilters]
); );
const handleDisplayPropertiesUpdate = useCallback( const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => { (property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_PROPERTIES, property);
workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), {
display_properties: property,
});
}, },
[workspaceFilterStore, workspaceSlug] [workspaceSlug, updateFilters]
);
useSWR(
workspaceSlug ? "USER_WORKSPACE_DISPLAY_FILTERS" : null,
workspaceSlug ? () => workspaceFilterStore.fetchUserWorkspaceFilters(workspaceSlug.toString()) : null
); );
return ( return (
@ -137,32 +118,31 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
</Link> </Link>
))} ))}
</div> </div>
{activeLayout === "spreadsheet" && ( {activeLayout === "spreadsheet" && (
<> <>
{!STATIC_VIEW_TYPES.some((word) => router.pathname.includes(word)) && ( <FiltersDropdown title="Filters" placement="bottom-end">
<FiltersDropdown title="Filters" placement="bottom-end"> <FilterSelection
<FilterSelection layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
filters={storedFilters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet} labels={workspaceLabels ?? undefined}
labels={workspaceStore.workspaceLabels ?? undefined} members={workspaceMembers?.map((m) => m.member)}
members={workspaceMembers?.map((m) => m.member) ?? undefined} projects={workspaceProjects ?? undefined}
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined} />
/> </FiltersDropdown>
</FiltersDropdown>
)}
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection <DisplayFiltersSelection
displayFilters={workspaceFilterStore.workspaceDisplayFilters}
displayProperties={workspaceFilterStore.workspaceDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet} layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/> />
</FiltersDropdown> </FiltersDropdown>
</> </>
)} )}
<Button variant="primary" size="sm" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}> <Button variant="primary" size="sm" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
New View New View
</Button> </Button>

View File

@ -25,9 +25,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
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;
}); });

View File

@ -12,85 +12,73 @@ 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 GlobalViewsAppliedFiltersRoot = observer(() => { export const GlobalViewsAppliedFiltersRoot = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, globalViewId } = router.query; const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string };
const { const {
globalViews: globalViewsStore, globalViews: globalViewsStore,
globalViewFilters: globalViewFiltersStore, globalViewFilters: globalViewFiltersStore,
project: projectStore, project: { workspaceProjects },
workspace: workspaceStore, workspace: { workspaceLabels },
workspaceMember: { workspaceMembers }, workspaceMember: { workspaceMembers },
workspaceGlobalIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore(); } = useMobxStore();
const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined; const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined;
const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.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 (!globalViewId) return;
// remove all values of the key if value is null
if (!value) { if (!value) {
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: null });
[key]: null,
});
return; return;
} }
// remove the passed value from the key let newValues = userFilters?.[key] ?? [];
let newValues = globalViewFiltersStore.storedFilters?.[globalViewId.toString()]?.[key] ?? [];
newValues = newValues.filter((val) => val !== value); newValues = newValues.filter((val) => val !== value);
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues });
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), {
[key]: newValues,
});
}; };
const handleClearAllFilters = () => { const handleClearAllFilters = () => {
if (!globalViewId || !storedFilters) return; if (!workspaceSlug) 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, EFilterType.FILTERS, { ...newFilters });
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), {
...newFilters,
});
}; };
const handleUpdateView = () => { // const handleUpdateView = () => {
if (!workspaceSlug || !globalViewId || !viewDetails) return; // if (!workspaceSlug || !globalViewId || !viewDetails) return;
globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { // globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), {
query_data: { // query_data: {
...viewDetails.query_data, // ...viewDetails.query_data,
filters: { // filters: {
...(storedFilters ?? {}), // ...(storedFilters ?? {}),
}, // },
}, // },
}); // });
}; // };
// update stored filters when view details are fetched // update stored filters when view details are fetched
useEffect(() => { // useEffect(() => {
if (!globalViewId || !viewDetails) return; // if (!globalViewId || !viewDetails) return;
if (!globalViewFiltersStore.storedFilters[globalViewId.toString()]) // if (!globalViewFiltersStore.storedFilters[globalViewId.toString()])
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {}); // globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {});
}, [globalViewId, globalViewFiltersStore, viewDetails]); // }, [globalViewId, globalViewFiltersStore, viewDetails]);
// return if no filters are applied // return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null; if (Object.keys(appliedFilters).length === 0) return null;
@ -98,18 +86,19 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
return ( return (
<div className="flex items-start justify-between gap-4 p-4"> <div className="flex items-start justify-between gap-4 p-4">
<AppliedFiltersList <AppliedFiltersList
appliedFilters={storedFilters ?? {}} labels={workspaceLabels ?? undefined}
members={workspaceMembers?.map((m) => m.member)}
projects={workspaceProjects ?? undefined}
appliedFilters={appliedFilters ?? {}}
handleClearAllFilters={handleClearAllFilters} handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={workspaceStore.workspaceLabels ?? undefined}
members={workspaceMembers?.map((m) => m.member)}
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
/> />
{storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && (
{/* {storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && (
<Button variant="primary" onClick={handleUpdateView}> <Button variant="primary" onClick={handleUpdateView}>
Update view Update view
</Button> </Button>
)} )} */}
</div> </div>
); );
}); });

View File

@ -3,6 +3,9 @@ export * from "./filters";
export * from "./empty-states"; export * from "./empty-states";
export * from "./quick-action-dropdowns"; export * from "./quick-action-dropdowns";
// roots
export * from "./roots";
// layouts // layouts
export * from "./list"; export * from "./list";
export * from "./calendar"; export * from "./calendar";
@ -10,6 +13,5 @@ export * from "./gantt";
export * from "./kanban"; export * from "./kanban";
export * from "./spreadsheet"; export * from "./spreadsheet";
// properties
export * from "./properties"; export * from "./properties";
export * from "./roots";

View File

@ -0,0 +1,114 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { Copy, Link, Pencil, Trash2 } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
// components
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
// types
import { IIssue } from "types";
import { IQuickActionProps } from "../list/list-view-types";
import { EProjectStore } from "store/command-palette.store";
export const AllIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
const { issue, handleDelete, handleUpdate } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
// states
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<IIssue | null>(null);
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const { setToastAlert } = useToast();
const handleCopyIssueLink = () => {
copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() =>
setToastAlert({
type: "success",
title: "Link copied",
message: "Issue link copied to clipboard",
})
);
};
return (
<>
<DeleteIssueModal
data={issue}
isOpen={deleteIssueModal}
handleClose={() => setDeleteIssueModal(false)}
onSubmit={handleDelete}
/>
<CreateUpdateIssueModal
isOpen={createUpdateIssueModal}
handleClose={() => {
setCreateUpdateIssueModal(false);
setIssueToEdit(null);
}}
// pre-populate date only if not editing
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}}
data={issueToEdit}
onSubmit={async (data) => {
if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data });
}}
currentStore={EProjectStore.PROJECT}
/>
<CustomMenu placement="bottom-start" ellipsis>
<CustomMenu.MenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleCopyIssueLink();
}}
>
<div className="flex items-center gap-2">
<Link className="h-3 w-3" />
Copy link
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIssueToEdit(issue);
setCreateUpdateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Pencil className="h-3 w-3" />
Edit issue
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setCreateUpdateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Copy className="h-3 w-3" />
Make a copy
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setDeleteIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Trash2 className="h-3 w-3" />
Delete issue
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</>
);
};

View File

@ -2,3 +2,4 @@ export * from "./cycle-issue";
export * from "./module-issue"; export * from "./module-issue";
export * from "./project-issue"; export * from "./project-issue";
export * from "./archived-issue"; export * from "./archived-issue";
export * from "./all-issue";

View File

@ -0,0 +1,134 @@
import React, { useCallback } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { GlobalViewEmptyState, GlobalViewsAppliedFiltersRoot } from "components/issues";
import { SpreadsheetView } from "components/issues/issue-layouts";
import { AllIssueQuickActions } from "components/issues/issue-layouts/quick-action-dropdowns";
// ui
import { Spinner } from "@plane/ui";
// types
import { IIssue, IIssueDisplayFilterOptions, TStaticViewTypes } from "types";
import { IIssueUnGroupedStructure } from "store/issue";
import { EIssueActions } from "../types";
import { EFilterType, TUnGroupedIssues } from "store/issues/types";
type Props = {
type?: TStaticViewTypes | null;
};
export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
const { type = null } = props;
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string };
const currentIssueView = type ?? globalViewId;
const {
workspaceMember: { workspaceMembers },
workspace: { workspaceLabels },
globalViews: { fetchAllGlobalViews },
workspaceGlobalIssues: { loader, getIssues, getIssuesIds, fetchIssues, updateIssue, removeIssue },
workspaceGlobalIssuesFilter: { currentView, issueFilters, fetchFilters, updateFilters, setCurrentView },
} = useMobxStore();
useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => {
if (workspaceSlug) {
await fetchAllGlobalViews(workspaceSlug);
}
});
useSWR(
workspaceSlug && currentIssueView ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${currentIssueView}` : null,
async () => {
if (workspaceSlug && currentIssueView) {
setCurrentView(currentIssueView);
await fetchAllGlobalViews(workspaceSlug);
await fetchFilters(workspaceSlug, currentIssueView);
await fetchIssues(workspaceSlug, currentIssueView, getIssues ? "mutation" : "init-loader");
}
}
);
const isEditingAllowed = false;
const issuesResponse = getIssues;
const issueIds = (getIssuesIds ?? []) as TUnGroupedIssues;
const issues = issueIds?.filter((id) => id && issuesResponse?.[id]).map((id) => issuesResponse?.[id]);
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await updateIssue(workspaceSlug, issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await removeIssue(workspaceSlug, issue.project, issue.id);
},
};
const handleIssues = useCallback(
async (issue: IIssue, action: EIssueActions) => {
if (issueActions && action && issue) {
if (action === EIssueActions.UPDATE) await issueActions[action]!(issue);
if (action === EIssueActions.DELETE) await issueActions[action]!(issue);
}
},
[getIssues]
);
const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter });
},
[updateFilters, workspaceSlug]
);
return (
<div className="relative w-full h-full flex flex-col overflow-hidden">
{currentView != currentIssueView && loader === "init-loader" ? (
<div className="w-full h-full flex justify-center items-center">
<Spinner />
</div>
) : (
<>
<GlobalViewsAppliedFiltersRoot />
{Object.keys(getIssues ?? {}).length == 0 && !loader ? (
<>{/* <GlobalViewEmptyState /> */}</>
) : (
<div className="w-full h-full relative overflow-auto">
<SpreadsheetView
displayProperties={issueFilters?.displayProperties ?? {}}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues as IIssueUnGroupedStructure}
quickActions={(issue) => (
<AllIssueQuickActions
issue={issue}
handleUpdate={async () => handleIssues({ ...issue }, EIssueActions.UPDATE)}
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
/>
)}
members={workspaceMembers?.map((m) => m.member)}
labels={workspaceLabels || undefined}
handleIssues={handleIssues}
disableUserActions={isEditingAllowed}
viewId={currentIssueView}
/>
</div>
)}
</>
)}
</div>
);
});

View File

@ -1,118 +0,0 @@
import React, { useCallback } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { GlobalViewEmptyState, GlobalViewsAppliedFiltersRoot, SpreadsheetView } from "components/issues";
// ui
import { Spinner } from "@plane/ui";
// types
import { IIssue, IIssueDisplayFilterOptions, TStaticViewTypes } from "types";
type Props = {
type?: TStaticViewTypes;
};
export const GlobalViewLayoutRoot: React.FC<Props> = observer((props) => {
const { type } = props;
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;
const {
globalViews: globalViewsStore,
globalViewIssues: globalViewIssuesStore,
globalViewFilters: globalViewFiltersStore,
workspaceFilter: workspaceFilterStore,
workspace: workspaceStore,
workspaceMember: { workspaceMembers },
issueDetail: issueDetailStore,
project: projectStore,
} = useMobxStore();
const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined;
const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined;
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
useSWR(
workspaceSlug && globalViewId && viewDetails ? `GLOBAL_VIEW_ISSUES_${globalViewId.toString()}` : null,
workspaceSlug && globalViewId && viewDetails
? () => {
globalViewIssuesStore.fetchViewIssues(workspaceSlug.toString(), globalViewId.toString(), storedFilters ?? {});
}
: null
);
useSWR(
workspaceSlug && type ? `GLOBAL_VIEW_ISSUES_${type.toString()}` : null,
workspaceSlug && type
? () => {
globalViewIssuesStore.fetchStaticIssues(workspaceSlug.toString(), type);
}
: null
);
const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug) return;
workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), {
display_filters: updatedDisplayFilter,
});
},
[workspaceFilterStore, workspaceSlug]
);
const handleUpdateIssue = useCallback(
(issue: IIssue, data: Partial<IIssue>) => {
if (!workspaceSlug) return;
const payload = {
...issue,
...data,
};
globalViewIssuesStore.updateIssueStructure(type ?? globalViewId!.toString(), payload);
// issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, data);
},
[globalViewId, globalViewIssuesStore, workspaceSlug, issueDetailStore]
);
const issues = type
? globalViewIssuesStore.viewIssues?.[type]
: globalViewId
? globalViewIssuesStore.viewIssues?.[globalViewId.toString()]
: undefined;
if (!issues)
return (
<div className="h-full w-full grid place-items-center">
<Spinner />
</div>
);
return (
<div className="relative w-full h-full flex flex-col overflow-hidden">
<GlobalViewsAppliedFiltersRoot />
{issues?.length === 0 || !projects || projects?.length === 0 ? (
<GlobalViewEmptyState />
) : (
<div className="h-full w-full overflow-auto">
{/* <SpreadsheetView
displayProperties={workspaceFilterStore.workspaceDisplayProperties}
displayFilters={workspaceFilterStore.workspaceDisplayFilters}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues}
members={workspaceMembers?.map((m) => m.member)}
labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined}
disableUserActions={false}
/> */}
</div>
)}
</div>
);
});

View File

@ -1,5 +1,5 @@
export * from "./cycle-layout-root"; export * from "./cycle-layout-root";
export * from "./global-view-layout-root"; export * from "./all-issue-layout-root";
export * from "./module-layout-root"; export * from "./module-layout-root";
export * from "./project-layout-root"; export * from "./project-layout-root";
export * from "./project-view-layout-root"; export * from "./project-view-layout-root";

View File

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

View File

@ -77,8 +77,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
}; };
}, []); }, []);
console.log("spreadsheet issues", issues);
return ( return (
<div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200"> <div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200">
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">

View File

@ -229,9 +229,7 @@ export const OnboardingSidebar: React.FC<Props> = (props) => {
<div className={`space-y-1 p-4`}> <div className={`space-y-1 p-4`}>
<div className={`flex items-center justify-between w-full px-1 mb-3 gap-2 mt-4 `}> <div className={`flex items-center justify-between w-full px-1 mb-3 gap-2 mt-4 `}>
<div <div
className={`relative flex items-center justify-between w-full rounded gap-1 group className={`relative flex items-center justify-between w-full rounded gap-1 group px-3 shadow-custom-shadow-2xs border-onboarding-border-100 border`}
px-3 shadow-custom-shadow-2xs border-onboarding-border-100 border
`}
> >
<div className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none`}> <div className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none`}>
<PenSquare className="h-4 w-4 text-custom-sidebar-text-300" /> <PenSquare className="h-4 w-4 text-custom-sidebar-text-300" />

View File

@ -108,14 +108,13 @@ export const SignInView = observer(() => {
<Lightbulb className="h-7 w-7 mr-2 mx-3" /> <Lightbulb className="h-7 w-7 mr-2 mx-3" />
<p className="text-sm text-left text-onboarding-text-100"> <p className="text-sm text-left text-onboarding-text-100">
Pages gets a facelift! Write anything and use Galileo to help you start.{" "} Pages gets a facelift! Write anything and use Galileo to help you start.{" "}
<Link href="https://plane.so/changelog"> <Link
<a href="https://plane.so/changelog"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-sm underline hover:cursor-pointer" className="font-medium text-sm underline hover:cursor-pointer"
> >
Learn more Learn more
</a>
</Link> </Link>
</p> </p>
</div> </div>

View File

@ -2,8 +2,6 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
@ -21,11 +19,6 @@ export const GlobalViewsHeader: React.FC = observer(() => {
const { globalViews: globalViewsStore } = useMobxStore(); const { globalViews: globalViewsStore } = useMobxStore();
useSWR(
workspaceSlug ? `GLOBAL_VIEWS_LIST_${workspaceSlug.toString()}` : null,
workspaceSlug ? () => globalViewsStore.fetchAllGlobalViews(workspaceSlug.toString()) : null
);
// bring the active view to the centre of the header // bring the active view to the centre of the header
useEffect(() => { useEffect(() => {
if (!globalViewId) return; if (!globalViewId) return;

View File

@ -297,7 +297,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
filters: ["priority", "state_group", "labels", "assignees", "created_by", "project", "start_date", "target_date"], filters: ["priority", "state_group", "labels", "assignees", "created_by", "project", "start_date", "target_date"],
display_properties: true, display_properties: true,
display_filters: { display_filters: {
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {

View File

@ -102,19 +102,18 @@ export const ProfileLayoutSidebar = observer(() => {
} ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`} } ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`}
> >
<div className="h-full w-full flex flex-col gap-y-4"> <div className="h-full w-full flex flex-col gap-y-4">
<Link href={`/${redirectWorkspaceSlug}`}> <Link
<a href={`/${redirectWorkspaceSlug}`}
className={`flex-shrink-0 flex items-center gap-2 px-4 pt-4 truncate ${ className={`flex-shrink-0 flex items-center gap-2 px-4 pt-4 truncate ${
sidebarCollapsed ? "justify-center" : "" sidebarCollapsed ? "justify-center" : ""
}`} }`}
> >
<span className="flex-shrink-0 grid place-items-center h-5 w-5"> <span className="flex-shrink-0 grid place-items-center h-5 w-5">
<ChevronLeft className="h-5 w-5" strokeWidth={1} /> <ChevronLeft className="h-5 w-5" strokeWidth={1} />
</span> </span>
{!sidebarCollapsed && ( {!sidebarCollapsed && (
<h4 className="text-custom-text-200 font-semibold text-lg truncate">Profile settings</h4> <h4 className="text-custom-text-200 font-semibold text-lg truncate">Profile settings</h4>
)} )}
</a>
</Link> </Link>
<div className="flex-shrink-0 flex flex-col overflow-x-hidden px-4"> <div className="flex-shrink-0 flex flex-col overflow-x-hidden px-4">
{!sidebarCollapsed && ( {!sidebarCollapsed && (
@ -125,21 +124,19 @@ export const ProfileLayoutSidebar = observer(() => {
if (link.key === "change-password" && currentUser?.is_password_autoset) return null; if (link.key === "change-password" && currentUser?.is_password_autoset) return null;
return ( return (
<Link key={link.key} href={link.href}> <Link key={link.key} href={link.href} className="block w-full">
<a className="block w-full"> <Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}>
<Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}> <div
<div className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${ router.pathname === link.href
router.pathname === link.href ? "bg-custom-primary-100/10 text-custom-primary-100"
? "bg-custom-primary-100/10 text-custom-primary-100" : "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80" } ${sidebarCollapsed ? "justify-center" : ""}`}
} ${sidebarCollapsed ? "justify-center" : ""}`} >
> {<link.Icon className="h-4 w-4" />}
{<link.Icon className="h-4 w-4" />} {!sidebarCollapsed && link.label}
{!sidebarCollapsed && link.label} </div>
</div> </Tooltip>
</Tooltip>
</a>
</Link> </Link>
); );
})} })}
@ -189,19 +186,17 @@ export const ProfileLayoutSidebar = observer(() => {
)} )}
<div className="mt-1.5"> <div className="mt-1.5">
{WORKSPACE_ACTION_LINKS.map((link) => ( {WORKSPACE_ACTION_LINKS.map((link) => (
<Link key={link.key} href={link.href}> <Link className="block w-full" key={link.key} href={link.href}>
<a className="block w-full"> <Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}>
<Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}> <div
<div className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80 ${
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80 ${ sidebarCollapsed ? "justify-center" : ""
sidebarCollapsed ? "justify-center" : "" }`}
}`} >
> {<link.Icon className="h-4 w-4" />}
{<link.Icon className="h-4 w-4" />} {!sidebarCollapsed && link.label}
{!sidebarCollapsed && link.label} </div>
</div> </Tooltip>
</Tooltip>
</a>
</Link> </Link>
))} ))}
</div> </div>

View File

@ -1,41 +1,21 @@
import { ReactElement } from "react"; import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
// components // components
import { GlobalViewsHeader } from "components/workspace"; import { GlobalViewsHeader } from "components/workspace";
import { GlobalViewLayoutRoot } from "components/issues"; import { AllIssueLayoutRoot } from "components/issues";
import { GlobalIssuesHeader } from "components/headers"; import { GlobalIssuesHeader } from "components/headers";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
const GlobalViewIssuesPage: NextPageWithLayout = () => { const GlobalViewIssuesPage: NextPageWithLayout = () => (
const router = useRouter(); <div className="h-full overflow-hidden bg-custom-background-100">
const { workspaceSlug, globalViewId } = router.query; <div className="h-full w-full flex flex-col border-b border-custom-border-300">
<GlobalViewsHeader />
const { <AllIssueLayoutRoot />
globalViews: { fetchGlobalViewDetails },
} = useMobxStore();
useSWR(
workspaceSlug && globalViewId ? `GLOBAL_VIEW_DETAILS_${globalViewId.toString()}` : null,
workspaceSlug && globalViewId
? () => fetchGlobalViewDetails(workspaceSlug.toString(), globalViewId.toString())
: null
);
return (
<div className="h-full overflow-hidden bg-custom-background-100">
<div className="h-full w-full flex flex-col border-b border-custom-border-300">
<GlobalViewsHeader />
<GlobalViewLayoutRoot />
</div>
</div> </div>
); </div>
}; );
GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>; return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;

View File

@ -2,7 +2,7 @@ import { ReactElement } from "react";
// components // components
import { GlobalViewsHeader } from "components/workspace"; import { GlobalViewsHeader } from "components/workspace";
import { GlobalIssuesHeader } from "components/headers"; import { GlobalIssuesHeader } from "components/headers";
import { GlobalViewLayoutRoot } from "components/issues"; import { AllIssueLayoutRoot } from "components/issues/issue-layouts";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
// types // types
@ -12,7 +12,7 @@ const GlobalViewAllIssuesPage: NextPageWithLayout = () => (
<div className="h-full overflow-hidden bg-custom-background-100"> <div className="h-full overflow-hidden bg-custom-background-100">
<div className="h-full w-full flex flex-col border-b border-custom-border-300"> <div className="h-full w-full flex flex-col border-b border-custom-border-300">
<GlobalViewsHeader /> <GlobalViewsHeader />
<GlobalViewLayoutRoot type="all-issues" /> <AllIssueLayoutRoot type="all-issues" />
</div> </div>
</div> </div>
); );

View File

@ -2,7 +2,7 @@ import { ReactElement } from "react";
// components // components
import { GlobalViewsHeader } from "components/workspace"; import { GlobalViewsHeader } from "components/workspace";
import { GlobalIssuesHeader } from "components/headers"; import { GlobalIssuesHeader } from "components/headers";
import { GlobalViewLayoutRoot } from "components/issues"; import { AllIssueLayoutRoot } from "components/issues/issue-layouts";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
// types // types
@ -12,7 +12,7 @@ const GlobalViewAssignedIssuesPage: NextPageWithLayout = () => (
<div className="h-full overflow-hidden bg-custom-background-100"> <div className="h-full overflow-hidden bg-custom-background-100">
<div className="h-full w-full flex flex-col border-b border-custom-border-300"> <div className="h-full w-full flex flex-col border-b border-custom-border-300">
<GlobalViewsHeader /> <GlobalViewsHeader />
<GlobalViewLayoutRoot type="assigned" /> <AllIssueLayoutRoot type="assigned" />
</div> </div>
</div> </div>
); );

View File

@ -2,7 +2,7 @@ import { ReactElement } from "react";
// components // components
import { GlobalViewsHeader } from "components/workspace"; import { GlobalViewsHeader } from "components/workspace";
import { GlobalIssuesHeader } from "components/headers"; import { GlobalIssuesHeader } from "components/headers";
import { GlobalViewLayoutRoot } from "components/issues"; import { AllIssueLayoutRoot } from "components/issues";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
// types // types
@ -12,7 +12,7 @@ const GlobalViewCreatedIssuesPage: NextPageWithLayout = () => (
<div className="h-full overflow-hidden bg-custom-background-100"> <div className="h-full overflow-hidden bg-custom-background-100">
<div className="h-full w-full flex flex-col border-b border-custom-border-300"> <div className="h-full w-full flex flex-col border-b border-custom-border-300">
<GlobalViewsHeader /> <GlobalViewsHeader />
<GlobalViewLayoutRoot type="created" /> <AllIssueLayoutRoot type="created" />
</div> </div>
</div> </div>
); );

View File

@ -4,7 +4,7 @@ import { AppLayout } from "layouts/app-layout";
// components // components
import { GlobalViewsHeader } from "components/workspace"; import { GlobalViewsHeader } from "components/workspace";
import { GlobalIssuesHeader } from "components/headers"; import { GlobalIssuesHeader } from "components/headers";
import { GlobalViewLayoutRoot } from "components/issues"; import { AllIssueLayoutRoot } from "components/issues";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
@ -12,7 +12,7 @@ const GlobalViewSubscribedIssuesPage: NextPageWithLayout = () => (
<div className="h-full overflow-hidden bg-custom-background-100"> <div className="h-full overflow-hidden bg-custom-background-100">
<div className="h-full w-full flex flex-col border-b border-custom-border-300"> <div className="h-full w-full flex flex-col border-b border-custom-border-300">
<GlobalViewsHeader /> <GlobalViewsHeader />
<GlobalViewLayoutRoot type="subscribed" /> <AllIssueLayoutRoot type="subscribed" />
</div> </div>
</div> </div>
); );

View File

@ -142,8 +142,13 @@ const HomePage: NextPageWithLayout = () => {
</Button> </Button>
<p className="text-xs text-onboarding-text-200"> <p className="text-xs text-onboarding-text-200">
When you click the button above, you agree with our{" "} When you click the button above, you agree with our{" "}
<Link href="https://plane.so/terms-and-conditions" target="_blank" rel="noopener noreferrer"> <Link
<a className="font-semibold underline">terms and conditions of service.</a> href="https://plane.so/terms-and-conditions"
target="_blank"
rel="noopener noreferrer"
className="font-semibold underline"
>
terms and conditions of service.
</Link> </Link>
</p> </p>
</form> </form>

View File

@ -17,6 +17,7 @@ import {
import { IWorkspaceView } from "types/workspace-views"; import { IWorkspaceView } from "types/workspace-views";
// store // store
import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store/issue"; import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store/issue";
import { IIssueResponse } from "store/issues/types";
export class WorkspaceService extends APIService { export class WorkspaceService extends APIService {
constructor() { constructor() {
@ -245,10 +246,7 @@ export class WorkspaceService extends APIService {
}); });
} }
async getViewIssues( async getViewIssues(workspaceSlug: string, params: any): Promise<IIssueResponse> {
workspaceSlug: string,
params: any
): Promise<IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure> {
return this.get(`/api/workspaces/${workspaceSlug}/issues/`, { return this.get(`/api/workspaces/${workspaceSlug}/issues/`, {
params, params,
}) })

View File

@ -118,7 +118,7 @@ export class GlobalViewIssuesStore implements IGlobalViewIssuesStore {
this.loader = false; this.loader = false;
this.viewIssues = { this.viewIssues = {
...this.viewIssues, ...this.viewIssues,
[viewId]: response as IIssue[], [viewId]: Object.values(response) as IIssue[],
}; };
}); });
@ -163,7 +163,7 @@ export class GlobalViewIssuesStore implements IGlobalViewIssuesStore {
this.loader = false; this.loader = false;
this.viewIssues = { this.viewIssues = {
...this.viewIssues, ...this.viewIssues,
[type]: response as IIssue[], [type]: Object.values(response) as IIssue[],
}; };
}); });

View File

@ -3,48 +3,55 @@ import { action, makeObservable, observable, runInAction } from "mobx";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types";
import { EFilterType } from "store/issues/types"; import { EFilterType } from "store/issues/types";
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store";
import { isEmpty } from "lodash"; // helpers
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
// services
import { WorkspaceService } from "services/workspace.service";
interface IProjectIssuesFiltersOptions { interface IIssuesDisplayOptions {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
displayFilters: IIssueDisplayFilterOptions;
} }
interface IProjectIssuesDisplayOptions { type TIssueViewTypes = "all-issues" | "assigned" | "created" | "subscribed" | string;
interface IIssueViewOptions {
"all-issues": IIssuesDisplayOptions;
assigned: IIssuesDisplayOptions;
created: IIssuesDisplayOptions;
subscribed: IIssuesDisplayOptions;
[view_id: string]: IIssuesDisplayOptions;
}
interface IWorkspaceProperties {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
displayFilters: IIssueDisplayFilterOptions; displayFilters: IIssueDisplayFilterOptions;
displayProperties: IIssueDisplayProperties; displayProperties: IIssueDisplayProperties;
} }
interface IProjectIssuesFilters {
filters: IIssueFilterOptions | undefined;
displayFilters: IIssueDisplayFilterOptions | undefined;
displayProperties: IIssueDisplayProperties | undefined;
}
export interface IGlobalIssuesFilterStore { export interface IGlobalIssuesFilterStore {
// observables // observables
projectIssueFilters: { [workspaceId: string]: IProjectIssuesDisplayOptions } | undefined; currentView: TIssueViewTypes;
workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined;
workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined;
// computed // computed
issueFilters: IProjectIssuesFilters | undefined; issueFilters: IWorkspaceProperties | undefined;
appliedFilters: TIssueParams[] | undefined; appliedFilters: TIssueParams[] | undefined;
// helpers // helpers
issueDisplayFilters: (workspaceId: string) => IProjectIssuesDisplayOptions | undefined; issueDisplayFilters: (workspaceId: string) => IIssuesDisplayOptions | undefined;
// actions // actions
fetchDisplayFilters: (workspaceSlug: string) => Promise<IProjectIssuesFiltersOptions>; setCurrentView: (view: TIssueViewTypes) => void;
updateDisplayFilters: ( fetchWorkspaceProperties: (workspaceSlug: string) => Promise<IWorkspaceProperties>;
updateWorkspaceProperties: (
workspaceSlug: string, workspaceSlug: string,
type: EFilterType, type: EFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
) => Promise<IProjectIssuesFiltersOptions>; ) => Promise<IWorkspaceProperties>;
fetchDisplayProperties: (workspaceSlug: string) => Promise<IIssueDisplayProperties>;
updateDisplayProperties: ( fetchWorkspaceViewFilters: (workspaceId: string, view: TIssueViewTypes) => Promise<IIssueFilterOptions>;
workspaceSlug: string, updateWorkspaceViewFilters: (workspaceId: string, filters: IIssueFilterOptions) => Promise<IIssueFilterOptions>;
properties: IIssueDisplayProperties
) => Promise<IIssueDisplayProperties>; fetchFilters: (workspaceSlug: string, view: TIssueViewTypes) => Promise<void>;
fetchFilters: (workspaceSlug: string) => Promise<void>;
updateFilters: ( updateFilters: (
workspaceSlug: string, workspaceSlug: string,
filterType: EFilterType, filterType: EFilterType,
@ -54,89 +61,160 @@ export interface IGlobalIssuesFilterStore {
export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGlobalIssuesFilterStore { export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGlobalIssuesFilterStore {
// observables // observables
projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined = undefined; currentView: TIssueViewTypes = "all-issues";
workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined = undefined;
workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined = undefined;
// root store // root store
rootStore; rootStore;
// service
workspaceService;
constructor(_rootStore: RootStore) { constructor(_rootStore: RootStore) {
super(_rootStore); super(_rootStore);
makeObservable(this, { makeObservable(this, {
// observables // observables
projectIssueFilters: observable.ref, currentView: observable.ref,
workspaceProperties: observable.ref,
workspaceViewFilters: observable.ref,
// computed // computed
// actions // actions
fetchDisplayFilters: action, setCurrentView: action,
updateDisplayFilters: action, fetchWorkspaceProperties: action,
fetchDisplayProperties: action, updateWorkspaceProperties: action,
updateDisplayProperties: action, fetchWorkspaceViewFilters: action,
updateWorkspaceViewFilters: action,
}); });
// root store // root store
this.rootStore = _rootStore; this.rootStore = _rootStore;
// services
this.workspaceService = new WorkspaceService();
} }
// computed // computed
// helpers // helpers
issueDisplayFilters = (workspaceId: string) => { issueDisplayFilters = (workspaceId: string) => {
if (!workspaceId) return undefined; if (!workspaceId || !this.currentView) return undefined;
return this.projectIssueFilters?.[workspaceId] || undefined; const filters: IWorkspaceProperties = {
filters: this.workspaceProperties?.[workspaceId]?.filters || {},
displayFilters: this.workspaceProperties?.[workspaceId]?.displayFilters || {},
displayProperties: this.workspaceProperties?.[workspaceId]?.displayProperties || {},
};
if (!["all-issues", "assigned", "created", "subscribed"].includes(this.currentView)) {
const viewFilters = this.workspaceViewFilters?.[workspaceId]?.[this.currentView];
if (viewFilters) {
filters.filters = { ...filters.filters, ...viewFilters?.filters };
}
}
return filters;
}; };
// actions // actions
fetchDisplayFilters = async (workspaceSlug: string) => { setCurrentView = (view: TIssueViewTypes) => {
this.currentView = view;
};
fetchWorkspaceProperties = async (workspaceSlug: string) => {
try { try {
const filters: IIssueFilterOptions = { let _filters: IWorkspaceProperties = {} as IWorkspaceProperties;
assignees: null,
mentions: null, const filtersResponse = await this.workspaceService.workspaceMemberMe(workspaceSlug);
created_by: null, _filters = {
labels: null, filters: { ...filtersResponse?.view_props?.filters } || null,
priority: null, displayFilters: { ...filtersResponse?.view_props?.display_filters } || null,
project: null, displayProperties: { ...filtersResponse?.view_props?.display_properties } || null,
start_date: null,
state: null,
state_group: null,
subscriber: null,
target_date: null,
}; };
let filters: IIssueFilterOptions = {
assignees: _filters?.filters?.assignees || null,
mentions: _filters?.filters?.mentions || null,
created_by: _filters?.filters?.created_by || null,
labels: _filters?.filters?.labels || null,
priority: _filters?.filters?.priority || null,
project: _filters?.filters?.project || null,
start_date: _filters?.filters?.start_date || null,
state: _filters?.filters?.state || null,
state_group: _filters?.filters?.state_group || null,
subscriber: _filters?.filters?.subscriber || null,
target_date: _filters?.filters?.target_date || null,
};
const currentUserId = this.rootStore.user.currentUser?.id;
if (currentUserId && this.currentView === "assigned")
filters = {
...filters,
assignees: [currentUserId],
created_by: null,
subscriber: null,
};
if (currentUserId && this.currentView === "created")
filters = {
...filters,
assignees: null,
created_by: [currentUserId],
subscriber: null,
};
if (currentUserId && this.currentView === "subscribed")
filters = {
...filters,
assignees: null,
created_by: null,
subscriber: [currentUserId],
};
const displayFilters: IIssueDisplayFilterOptions = { const displayFilters: IIssueDisplayFilterOptions = {
calendar: { calendar: {
show_weekends: false, show_weekends: _filters?.displayFilters?.calendar?.show_weekends || false,
layout: "month", layout: _filters?.displayFilters?.calendar?.layout || "month",
}, },
group_by: "state_detail.group", group_by: _filters?.displayFilters?.group_by || null,
sub_group_by: null, sub_group_by: _filters?.displayFilters?.sub_group_by || null,
layout: "list", layout: _filters?.displayFilters?.layout || "list",
order_by: "-created_at", order_by: _filters?.displayFilters?.order_by || "-created_at",
show_empty_groups: false, show_empty_groups: _filters?.displayFilters?.show_empty_groups || false,
start_target_date: false, start_target_date: _filters?.displayFilters?.start_target_date || false,
sub_issue: false, sub_issue: _filters?.displayFilters?.sub_issue || false,
type: null, type: _filters?.displayFilters?.type || null,
}; };
const issueFilters: IProjectIssuesFiltersOptions = { const displayProperties: IIssueDisplayProperties = {
assignee: _filters?.displayProperties?.assignee || false,
start_date: _filters?.displayProperties?.start_date || false,
due_date: _filters?.displayProperties?.due_date || false,
labels: _filters?.displayProperties?.labels || false,
key: _filters?.displayProperties?.key || false,
priority: _filters?.displayProperties?.priority || false,
state: _filters?.displayProperties?.state || false,
sub_issue_count: _filters?.displayProperties?.sub_issue_count || false,
link: _filters?.displayProperties?.link || false,
attachment_count: _filters?.displayProperties?.attachment_count || false,
estimate: _filters?.displayProperties?.estimate || false,
created_on: _filters?.displayProperties?.created_on || false,
updated_on: _filters?.displayProperties?.updated_on || false,
};
const issueFilters: IWorkspaceProperties = {
filters: filters, filters: filters,
displayFilters: displayFilters, displayFilters: displayFilters,
displayProperties: displayProperties,
}; };
let _projectIssueFilters = this.projectIssueFilters; let _workspaceProperties = { ...this.workspaceProperties };
if (!_projectIssueFilters) _projectIssueFilters = {}; if (!_workspaceProperties) _workspaceProperties = {};
if (!_projectIssueFilters[workspaceSlug]) { if (!_workspaceProperties[workspaceSlug])
_projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; _workspaceProperties[workspaceSlug] = {
} filters: {},
if ( displayFilters: {},
isEmpty(_projectIssueFilters[workspaceSlug].filters) || displayProperties: {},
isEmpty(_projectIssueFilters[workspaceSlug].displayFilters)
) {
_projectIssueFilters[workspaceSlug] = {
..._projectIssueFilters[workspaceSlug],
...issueFilters,
}; };
} _workspaceProperties[workspaceSlug] = { ...issueFilters };
runInAction(() => { runInAction(() => {
this.projectIssueFilters = _projectIssueFilters; this.workspaceProperties = _workspaceProperties;
}); });
return issueFilters; return issueFilters;
@ -145,114 +223,126 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
} }
}; };
updateDisplayFilters = async ( updateWorkspaceProperties = async (
workspaceSlug: string, workspaceSlug: string,
type: EFilterType, type: EFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
) => { ) => {
try { try {
let _projectIssueFilters = { ...this.projectIssueFilters }; let _workspaceProperties = { ...this.workspaceProperties };
if (!_projectIssueFilters) _projectIssueFilters = {}; if (!_workspaceProperties) _workspaceProperties = {};
if (!_projectIssueFilters[workspaceSlug]) if (!_workspaceProperties[workspaceSlug])
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; _workspaceProperties[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} };
const _filters = { const _filters = {
filters: { ..._projectIssueFilters[workspaceSlug].filters }, filters: { ..._workspaceProperties[workspaceSlug].filters },
displayFilters: { ..._projectIssueFilters[workspaceSlug].displayFilters }, displayFilters: { ..._workspaceProperties[workspaceSlug].displayFilters },
displayProperties: { ..._workspaceProperties[workspaceSlug].displayProperties },
}; };
if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; switch (type) {
else if (type === EFilterType.DISPLAY_FILTERS) case EFilterType.FILTERS:
_filters.displayFilters = { ..._filters.displayFilters, ...filters }; _filters.filters = { ..._filters.filters, ...(filters as IIssueFilterOptions) };
break;
case EFilterType.DISPLAY_FILTERS:
_filters.displayFilters = { ..._filters.displayFilters, ...(filters as IIssueDisplayFilterOptions) };
break;
case EFilterType.DISPLAY_PROPERTIES:
_filters.displayProperties = { ..._filters.displayProperties, ...(filters as IIssueDisplayProperties) };
break;
}
// set sub_group_by to null if group_by is set to null _workspaceProperties[workspaceSlug] = {
if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; ..._workspaceProperties[workspaceSlug],
filters: _filters?.filters,
// set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same displayFilters: _filters?.displayFilters,
if ( displayProperties: _filters?.displayProperties,
_filters.displayFilters.layout === "kanban" &&
_filters.displayFilters.group_by === _filters.displayFilters.sub_group_by
)
_filters.displayFilters.sub_group_by = null;
// set group_by to state if layout is switched to kanban and group_by is null
if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null)
_filters.displayFilters.group_by = "state";
_projectIssueFilters[workspaceSlug] = {
filters: _filters.filters,
displayFilters: _filters.displayFilters,
displayProperties: _projectIssueFilters[workspaceSlug].displayProperties,
}; };
runInAction(() => { runInAction(() => {
this.projectIssueFilters = _projectIssueFilters; this.workspaceProperties = _workspaceProperties;
});
await this.workspaceService.updateWorkspaceView(workspaceSlug, {
view_props: {
filters: _filters.filters,
display_filters: _filters.displayFilters,
display_properties: _filters.displayProperties,
},
}); });
return _filters; return _filters;
} catch (error) { } catch (error) {
this.fetchDisplayFilters(workspaceSlug); this.fetchWorkspaceProperties(workspaceSlug);
throw error; throw error;
} }
}; };
fetchDisplayProperties = async (workspaceSlug: string) => { fetchWorkspaceViewFilters = async (workspaceSlug: string, view: TIssueViewTypes) => {
try { try {
const displayProperties: IIssueDisplayProperties = { let _workspaceViewFilters = { ...this.workspaceViewFilters };
assignee: true, if (!_workspaceViewFilters) _workspaceViewFilters = {};
start_date: true, if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions;
due_date: true, if (!_workspaceViewFilters[workspaceSlug][view]) _workspaceViewFilters[workspaceSlug][view] = { filters: {} };
labels: false,
key: false, const filtersResponse = await this.workspaceService.getViewDetails(workspaceSlug, view);
priority: true,
state: false, const _filters: IIssueFilterOptions = {
sub_issue_count: true, assignees: filtersResponse?.query_data?.filters?.assignees || null,
link: true, mentions: filtersResponse?.query_data?.filters?.mentions || null,
attachment_count: false, created_by: filtersResponse?.query_data?.filters?.created_by || null,
estimate: false, labels: filtersResponse?.query_data?.filters?.labels || null,
created_on: false, priority: filtersResponse?.query_data?.filters?.priority || null,
updated_on: false, project: filtersResponse?.query_data?.filters?.project || null,
start_date: filtersResponse?.query_data?.filters?.start_date || null,
state: filtersResponse?.query_data?.filters?.state || null,
state_group: filtersResponse?.query_data?.filters?.state_group || null,
subscriber: filtersResponse?.query_data?.filters?.subscriber || null,
target_date: filtersResponse?.query_data?.filters?.target_date || null,
}; };
let _projectIssueFilters = { ...this.projectIssueFilters }; _workspaceViewFilters[workspaceSlug][view].filters = { ..._filters };
if (!_projectIssueFilters) _projectIssueFilters = {};
if (!_projectIssueFilters[workspaceSlug]) {
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {} } as IProjectIssuesDisplayOptions;
}
if (isEmpty(_projectIssueFilters[workspaceSlug].displayProperties)) {
_projectIssueFilters[workspaceSlug] = {
..._projectIssueFilters[workspaceSlug],
displayProperties: displayProperties,
};
}
runInAction(() => { runInAction(() => {
this.projectIssueFilters = _projectIssueFilters; this.workspaceViewFilters = _workspaceViewFilters;
}); });
return displayProperties; return _filters;
} catch (error) { } catch (error) {
throw error; throw error;
} }
}; };
updateDisplayProperties = async (workspaceSlug: string, properties: IIssueDisplayProperties) => { updateWorkspaceViewFilters = async (workspaceSlug: string, filters: IIssueFilterOptions) => {
try { try {
let _issueFilters = { ...this.projectIssueFilters }; let _workspaceViewFilters = { ...this.workspaceViewFilters };
if (!_issueFilters) _issueFilters = {}; if (!_workspaceViewFilters) _workspaceViewFilters = {};
if (!_issueFilters[workspaceSlug]) if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions;
_issueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; if (!_workspaceViewFilters[workspaceSlug][this.currentView])
_workspaceViewFilters[workspaceSlug][this.currentView] = { filters: {} };
const updatedDisplayProperties = { ..._issueFilters[workspaceSlug].displayProperties, ...properties }; const _filters = {
_issueFilters[workspaceSlug] = { ..._issueFilters[workspaceSlug], displayProperties: updatedDisplayProperties }; filters: { ..._workspaceViewFilters[workspaceSlug][this.currentView].filters, ...filters },
};
_workspaceViewFilters[workspaceSlug][this.currentView] = {
..._workspaceViewFilters[workspaceSlug][this.currentView],
filters: _filters?.filters,
};
runInAction(() => { runInAction(() => {
this.projectIssueFilters = _issueFilters; this.workspaceViewFilters = _workspaceViewFilters;
}); });
return properties; await this.workspaceService.updateView(workspaceSlug, this.currentView, {
query_data: {
filters: _filters.filters,
} as any,
});
return _filters.filters;
} catch (error) { } catch (error) {
this.fetchDisplayProperties(workspaceSlug); this.fetchWorkspaceViewFilters(workspaceSlug, this.currentView);
throw error; throw error;
} }
}; };
@ -262,10 +352,10 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
if (!workspaceSlug) return undefined; if (!workspaceSlug) return undefined;
const displayFilters = this.issueDisplayFilters(workspaceSlug); const displayFilters = this.issueDisplayFilters(workspaceSlug);
const _filters: IProjectIssuesFilters = { const _filters: IWorkspaceProperties = {
filters: displayFilters?.filters, filters: displayFilters?.filters || {},
displayFilters: displayFilters?.displayFilters, displayFilters: displayFilters?.displayFilters || {},
displayProperties: displayFilters?.displayProperties, displayProperties: displayFilters?.displayProperties || {},
}; };
return _filters; return _filters;
@ -277,6 +367,7 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
let filteredRouteParams: any = { let filteredRouteParams: any = {
priority: userFilters?.filters?.priority || undefined, priority: userFilters?.filters?.priority || undefined,
project: userFilters?.filters?.project || undefined,
state_group: userFilters?.filters?.state_group || undefined, state_group: userFilters?.filters?.state_group || undefined,
state: userFilters?.filters?.state || undefined, state: userFilters?.filters?.state || undefined,
assignees: userFilters?.filters?.assignees || undefined, assignees: userFilters?.filters?.assignees || undefined,
@ -286,24 +377,20 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
start_date: userFilters?.filters?.start_date || undefined, start_date: userFilters?.filters?.start_date || undefined,
target_date: userFilters?.filters?.target_date || undefined, target_date: userFilters?.filters?.target_date || undefined,
type: userFilters?.displayFilters?.type || undefined, type: userFilters?.displayFilters?.type || undefined,
sub_issue: userFilters?.displayFilters?.sub_issue || true, sub_issue: false,
show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true,
start_target_date: userFilters?.displayFilters?.start_target_date || true,
}; };
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); const filteredParams = handleIssueQueryParamsByLayout("spreadsheet", "my_issues");
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); 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; return filteredRouteParams;
} }
fetchFilters = async (workspaceSlug: string) => { fetchFilters = async (workspaceSlug: string, view: TIssueViewTypes) => {
try { try {
await this.fetchDisplayFilters(workspaceSlug); await this.fetchWorkspaceProperties(workspaceSlug);
await this.fetchDisplayProperties(workspaceSlug); if (!["all-issues", "assigned", "created", "subscribed"].includes(view))
await this.fetchWorkspaceViewFilters(workspaceSlug, view);
return; return;
} catch (error) { } catch (error) {
throw Error; throw Error;
@ -312,20 +399,21 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
updateFilters = async ( updateFilters = async (
workspaceSlug: string, workspaceSlug: string,
filterType: EFilterType, filterType: EFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties
) => { ) => {
try { try {
switch (filterType) { switch (filterType) {
case EFilterType.FILTERS: case EFilterType.FILTERS:
await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueFilterOptions); if (["all-issues", "assigned", "created", "subscribed"].includes(this.currentView))
await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions);
else await this.updateWorkspaceViewFilters(workspaceSlug, filters as IIssueFilterOptions);
break; break;
case EFilterType.DISPLAY_FILTERS: case EFilterType.DISPLAY_FILTERS:
await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions);
break; break;
case EFilterType.DISPLAY_PROPERTIES: case EFilterType.DISPLAY_PROPERTIES:
await this.updateDisplayProperties(workspaceSlug, filters as IIssueDisplayProperties); await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayProperties);
break; break;
} }

View File

@ -2,60 +2,60 @@ import { action, observable, makeObservable, computed, runInAction, autorun } fr
// base class // base class
import { IssueBaseStore } from "store/issues"; import { IssueBaseStore } from "store/issues";
// services // services
import { UserService } from "services/user.service"; import { WorkspaceService } from "services/workspace.service";
import { IssueService } from "services/issue";
// types // types
import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../types"; import { IIssue } from "types/issues";
import { IIssueResponse, TLoader, TUnGroupedIssues, ViewFlags } from "../types";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
import { IIssue } from "types"; import isEmpty from "lodash/isEmpty";
interface IProfileIssueTabTypes {
assigned: IIssueResponse;
created: IIssueResponse;
subscribed: IIssueResponse;
}
export interface IGlobalIssuesStore { export interface IGlobalIssuesStore {
// observable // observable
loader: TLoader; loader: TLoader;
issues: { [user_id: string]: IProfileIssueTabTypes } | undefined; issues: { [workspace_view: string]: IIssueResponse } | undefined;
currentUserId: string | null;
currentUserIssueTab: "assigned" | "created" | "subscribed" | null;
// computed // computed
getIssues: IIssueResponse | undefined; getIssues: IIssueResponse | undefined;
getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; getIssuesIds: TUnGroupedIssues | undefined;
// actions // actions
fetchIssues: ( fetchIssues: (workspaceSlug: string, workspaceViewId: string, loadType: TLoader) => Promise<IIssueResponse>;
createIssue: (
workspaceSlug: string, workspaceSlug: string,
userId: string, projectId: string,
loadType: TLoader, data: Partial<IIssue>,
type: "assigned" | "created" | "subscribed" workspaceViewId?: string | undefined
) => Promise<IIssueResponse>; ) => Promise<IIssue | undefined>;
createIssue: (workspaceSlug: string, userId: string, data: Partial<IIssue>) => Promise<IIssue | undefined>;
updateIssue: ( updateIssue: (
workspaceSlug: string, workspaceSlug: string,
userId: string, projectId: string,
issueId: string, issueId: string,
data: Partial<IIssue> data: Partial<IIssue>,
workspaceViewId?: string | undefined
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
removeIssue: ( removeIssue: (
workspaceSlug: string, workspaceSlug: string,
userId: string,
projectId: string, projectId: string,
issueId: string issueId: string,
workspaceViewId?: string | undefined
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise<IIssue | undefined>;
viewFlags: ViewFlags; viewFlags: ViewFlags;
} }
export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesStore { export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesStore {
loader: TLoader = "init-loader"; loader: TLoader = "init-loader";
issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined; issues: { [workspace_view: string]: IIssueResponse } | undefined = undefined;
currentUserId: string | null = null;
currentUserIssueTab: "assigned" | "created" | "subscribed" | null = null;
// root store // root store
rootStore; rootStore;
// service // service
userService; workspaceService;
issueService;
//viewData
viewFlags = {
enableQuickAdd: true,
enableIssueCreation: true,
enableInlineEditing: true,
};
constructor(_rootStore: RootStore) { constructor(_rootStore: RootStore) {
super(_rootStore); super(_rootStore);
@ -64,146 +64,91 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt
// observable // observable
loader: observable.ref, loader: observable.ref,
issues: observable.ref, issues: observable.ref,
currentUserId: observable.ref,
currentUserIssueTab: observable.ref,
// computed // computed
getIssues: computed, getIssues: computed,
getIssuesIds: computed, getIssuesIds: computed,
viewFlags: computed,
// action // action
fetchIssues: action, fetchIssues: action,
createIssue: action, createIssue: action,
updateIssue: action, updateIssue: action,
removeIssue: action, removeIssue: action,
quickAddIssue: action,
}); });
this.rootStore = _rootStore; this.rootStore = _rootStore;
this.userService = new UserService(); this.workspaceService = new WorkspaceService();
this.issueService = new IssueService();
autorun(() => { autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
if (!workspaceSlug || !this.currentUserId || !this.currentUserIssueTab) return; const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView;
if (!workspaceSlug || currentView === "") return;
const userFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.workspaceGlobalIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab);
if (!isEmpty(userFilters)) this.fetchIssues(workspaceSlug, currentView, "mutation");
}); });
} }
get getIssues() { get getIssues() {
if (!this.currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[this.currentUserId]) const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView;
return undefined; if (currentView === "" || !this.issues || !this.issues[currentView]) return undefined;
return this.issues[this.currentUserId][this.currentUserIssueTab]; return this.issues[currentView];
} }
get getIssuesIds() { get getIssuesIds() {
const currentUserId = this.currentUserId; const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView;
const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters; const displayFilters = this.rootStore?.workspaceGlobalIssuesFilter?.issueFilters?.displayFilters;
if (!displayFilters) return undefined; if (!displayFilters) return undefined;
const groupBy = displayFilters?.group_by;
const orderBy = displayFilters?.order_by; const orderBy = displayFilters?.order_by;
const layout = displayFilters?.layout;
if (!currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[currentUserId]) return undefined; if (currentView === "" || !this.issues || !this.issues[currentView]) return undefined;
let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; let issues: IIssueResponse | TUnGroupedIssues | undefined = undefined;
if (layout === "list" && orderBy) { issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[currentView]);
if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
}
return issues; return issues;
} }
get viewFlags() { fetchIssues = async (workspaceSlug: string, workspaceViewId: string, loadType: TLoader = "init-loader") => {
if (this.currentUserIssueTab === "subscribed") {
return {
enableQuickAdd: false,
enableIssueCreation: false,
enableInlineEditing: false,
};
}
return {
enableQuickAdd: false,
enableIssueCreation: true,
enableInlineEditing: true,
};
}
fetchIssues = async (
workspaceSlug: string,
userId: string,
loadType: TLoader = "init-loader",
type: "assigned" | "created" | "subscribed"
) => {
try { try {
this.loader = loadType; this.loader = loadType;
this.currentUserId = userId;
if (type) this.currentUserIssueTab = type;
let params: any = this.rootStore?.workspaceProfileIssuesFilter?.appliedFilters; const params = this.rootStore?.workspaceGlobalIssuesFilter?.appliedFilters;
params = { const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
...params,
assignees: undefined,
created_by: undefined,
subscriber: undefined,
};
if (this.currentUserIssueTab === "assigned")
params = params ? { ...params, assignees: userId } : { assignees: userId };
else if (this.currentUserIssueTab === "created")
params = params ? { ...params, created_by: userId } : { created_by: userId };
else if (this.currentUserIssueTab === "subscribed")
params = params ? { ...params, subscriber: userId } : { subscriber: userId };
const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params); const _issues = { ...this.issues, [workspaceViewId]: { ...response } };
if (!this.currentUserIssueTab) return;
const _issues: any = {
...this.issues,
[userId]: {
...this.issues?.[userId],
...{ [this.currentUserIssueTab]: response },
},
};
runInAction(() => { runInAction(() => {
this.issues = _issues; this.issues = _issues;
this.loader = undefined; this.loader = undefined;
}); });
return _issues; return response;
} catch (error) { } catch (error) {
console.error(error);
this.loader = undefined; this.loader = undefined;
throw error; throw error;
} }
}; };
createIssue = async (workspaceSlug: string, userId: string, data: Partial<IIssue>) => { createIssue = async (
workspaceSlug: string,
projectId: string,
data: Partial<IIssue>,
workspaceViewId: string | undefined = undefined
) => {
if (!workspaceViewId) return;
try { try {
const projectId = data.project; const response = await this.issueService.createIssue(workspaceSlug, projectId, data);
const moduleId = data.module_id;
const cycleId = data.cycle_id;
if (!projectId) return;
let response = {} as IIssue;
response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
// if (moduleId)
// response = await this.rootStore.moduleIssues.addIssueToModule(workspaceSlug, projectId, moduleId, response);
// if (cycleId)
// response = await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, cycleId, response);
let _issues = this.issues; let _issues = this.issues;
if (!_issues) _issues = {}; if (!_issues) _issues = {};
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {};
_issues[userId] = { ..._issues[userId], ...{ [response.id]: response } }; _issues[workspaceViewId] = { ..._issues[workspaceViewId], ...{ [response.id]: response } };
runInAction(() => { runInAction(() => {
this.issues = _issues; this.issues = _issues;
@ -211,116 +156,62 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt
return response; return response;
} catch (error) { } catch (error) {
if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); this.fetchIssues(workspaceSlug, workspaceViewId, "mutation");
throw error; throw error;
} }
}; };
updateIssue = async (workspaceSlug: string, userId: string, issueId: string, data: Partial<IIssue>) => { updateIssue = async (
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<IIssue>,
workspaceViewId: string | undefined = undefined
) => {
if (!workspaceViewId) return;
try { try {
const projectId = data.project;
const moduleId = data.module_id;
const cycleId = data.cycle_id;
if (!projectId || !this.currentUserIssueTab) return;
let _issues = { ...this.issues }; let _issues = { ...this.issues };
if (!_issues) _issues = {}; if (!_issues) _issues = {};
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {};
_issues[projectId][this.currentUserIssueTab][userId] = { _issues[workspaceViewId][issueId] = { ..._issues[workspaceViewId][issueId], ...data };
..._issues[projectId][this.currentUserIssueTab][userId],
...data,
};
runInAction(() => { runInAction(() => {
this.issues = _issues; this.issues = _issues;
}); });
let response = data as IIssue | undefined; const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
response = await this.rootStore.projectIssues.updateIssue(
workspaceSlug,
projectId,
data.id as keyof IIssue,
data
);
if (moduleId)
response = await this.rootStore.moduleIssues.updateIssue(
workspaceSlug,
projectId,
response.id as keyof IIssue,
response,
moduleId
);
if (cycleId)
response = await this.rootStore.cycleIssues.updateIssue(
workspaceSlug,
projectId,
data.id as keyof IIssue,
data,
cycleId
);
return response; return response;
} catch (error) { } catch (error) {
if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); this.fetchIssues(workspaceSlug, workspaceViewId, "mutation");
throw error; throw error;
} }
}; };
removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => { removeIssue = async (
workspaceSlug: string,
projectId: string,
issueId: string,
workspaceViewId: string | undefined = undefined
) => {
if (!workspaceViewId) return;
try { try {
let _issues = { ...this.issues }; let _issues = { ...this.issues };
if (!_issues) _issues = {}; if (!_issues) _issues = {};
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {};
delete _issues?.[workspaceViewId]?.[issueId];
if (this.currentUserIssueTab) delete _issues?.[userId]?.[this.currentUserIssueTab]?.[issueId];
runInAction(() => { runInAction(() => {
this.issues = _issues; this.issues = _issues;
}); });
const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);
return response; return response;
} catch (error) { } catch (error) {
if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); this.fetchIssues(workspaceSlug, workspaceViewId, "mutation");
throw error;
}
};
quickAddIssue = async (workspaceSlug: string, userId: string, data: IIssue) => {
try {
const projectId = data.project;
let _issues = { ...this.issues };
if (!_issues) _issues = {};
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
_issues[userId] = { ..._issues[userId], ...{ [data.id as keyof IIssue]: data } };
runInAction(() => {
this.issues = _issues;
});
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
if (this.issues && this.currentUserIssueTab) {
delete this.issues[userId][this.currentUserIssueTab][data.id as keyof IIssue];
let _issues = { ...this.issues };
if (!_issues) _issues = {};
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
_issues[userId] = { ..._issues[userId], ...{ [response.id as keyof IIssue]: response } };
runInAction(() => {
this.issues = _issues;
});
}
return response;
} catch (error) {
if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab);
throw error; throw error;
} }
}; };

View File

@ -125,6 +125,7 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
if (!_projectIssueFilters[workspaceSlug]) { if (!_projectIssueFilters[workspaceSlug]) {
_projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; _projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions;
} }
if ( if (
isEmpty(_projectIssueFilters[workspaceSlug].filters) || isEmpty(_projectIssueFilters[workspaceSlug].filters) ||
isEmpty(_projectIssueFilters[workspaceSlug].displayFilters) isEmpty(_projectIssueFilters[workspaceSlug].displayFilters)