mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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:
parent
83026e8b2f
commit
a276bd2301
@ -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
|
||||||
filters={storedFilters ?? {}}
|
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
|
||||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||||
labels={workspaceStore.workspaceLabels ?? undefined}
|
filters={issueFilters?.filters ?? {}}
|
||||||
members={workspaceMembers?.map((m) => m.member) ?? undefined}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
|
labels={workspaceLabels ?? undefined}
|
||||||
|
members={workspaceMembers?.map((m) => m.member)}
|
||||||
|
projects={workspaceProjects ?? 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>
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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";
|
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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";
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
});
|
@ -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>
|
|
||||||
);
|
|
||||||
});
|
|
@ -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";
|
||||||
|
@ -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,
|
|
||||||
async () => {
|
|
||||||
if (workspaceSlug && projectId) {
|
if (workspaceSlug && projectId) {
|
||||||
await fetchFilters(workspaceSlug, projectId);
|
await fetchFilters(workspaceSlug, projectId);
|
||||||
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
|
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;
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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: {
|
||||||
|
@ -102,8 +102,8 @@ 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" : ""
|
||||||
}`}
|
}`}
|
||||||
@ -114,7 +114,6 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||||||
{!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,8 +124,7 @@ 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 ${
|
||||||
@ -139,7 +137,6 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||||||
{!sidebarCollapsed && link.label}
|
{!sidebarCollapsed && link.label}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -189,8 +186,7 @@ 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 ${
|
||||||
@ -201,7 +197,6 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||||||
{!sidebarCollapsed && link.label}
|
{!sidebarCollapsed && link.label}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -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();
|
|
||||||
const { workspaceSlug, globalViewId } = router.query;
|
|
||||||
|
|
||||||
const {
|
|
||||||
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 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 />
|
<AllIssueLayoutRoot />
|
||||||
</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>;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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[],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
_filters = {
|
||||||
|
filters: { ...filtersResponse?.view_props?.filters } || null,
|
||||||
|
displayFilters: { ...filtersResponse?.view_props?.display_filters } || null,
|
||||||
|
displayProperties: { ...filtersResponse?.view_props?.display_properties } || 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,
|
created_by: null,
|
||||||
labels: null,
|
|
||||||
priority: null,
|
|
||||||
project: null,
|
|
||||||
start_date: null,
|
|
||||||
state: null,
|
|
||||||
state_group: null,
|
|
||||||
subscriber: null,
|
subscriber: null,
|
||||||
target_date: 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
try {
|
workspaceSlug: string,
|
||||||
const projectId = data.project;
|
projectId: string,
|
||||||
const moduleId = data.module_id;
|
issueId: string,
|
||||||
const cycleId = data.cycle_id;
|
data: Partial<IIssue>,
|
||||||
|
workspaceViewId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!workspaceViewId) return;
|
||||||
|
|
||||||
if (!projectId || !this.currentUserIssueTab) return;
|
|
||||||
|
|
||||||
let _issues = { ...this.issues };
|
|
||||||
if (!_issues) _issues = {};
|
|
||||||
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
|
|
||||||
_issues[projectId][this.currentUserIssueTab][userId] = {
|
|
||||||
..._issues[projectId][this.currentUserIssueTab][userId],
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.issues = _issues;
|
|
||||||
});
|
|
||||||
|
|
||||||
let response = data as IIssue | undefined;
|
|
||||||
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;
|
|
||||||
} catch (error) {
|
|
||||||
if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => {
|
|
||||||
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] = {};
|
||||||
|
_issues[workspaceViewId][issueId] = { ..._issues[workspaceViewId][issueId], ...data };
|
||||||
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.patchIssue(workspaceSlug, projectId, issueId, data);
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
quickAddIssue = async (workspaceSlug: string, userId: string, data: IIssue) => {
|
removeIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
workspaceViewId: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
if (!workspaceViewId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const projectId = data.project;
|
|
||||||
|
|
||||||
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], ...{ [data.id as keyof IIssue]: data } };
|
delete _issues?.[workspaceViewId]?.[issueId];
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues = _issues;
|
this.issues = _issues;
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
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;
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user