diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index bb692b4f4..964e14c8b 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -2,8 +2,6 @@ import { useCallback, useState } from "react"; import Link from "next/link"; 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 @@ -17,6 +15,7 @@ import { List, PlusIcon, Sheet } from "lucide-react"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TStaticViewTypes } from "types"; // constants import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; +import { EFilterType } from "store/issues/types"; const GLOBAL_VIEW_LAYOUTS = [ { key: "list", title: "List", link: "/workspace-views", icon: List }, @@ -27,73 +26,55 @@ type Props = { activeLayout: "list" | "spreadsheet"; }; -const STATIC_VIEW_TYPES: TStaticViewTypes[] = ["all-issues", "assigned", "created", "subscribed"]; - export const GlobalIssuesHeader: React.FC = observer((props) => { const { activeLayout } = props; const [createViewModal, setCreateViewModal] = useState(false); const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; + const { workspaceSlug } = router.query as { workspaceSlug: string }; const { - globalViewFilters: globalViewFiltersStore, - workspaceFilter: workspaceFilterStore, - workspace: workspaceStore, + workspace: { workspaceLabels }, workspaceMember: { workspaceMembers }, - project: projectStore, - } = useMobxStore(); + project: { workspaceProjects }, - const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined; + workspaceGlobalIssuesFilter: { issueFilters, updateFilters }, + } = useMobxStore(); const handleFiltersUpdate = useCallback( (key: keyof IIssueFilterOptions, value: string | string[]) => { - if (!workspaceSlug || !globalViewId) return; - - const newValues = storedFilters?.[key] ?? []; + if (!workspaceSlug) return; + const newValues = issueFilters?.filters?.[key] ?? []; if (Array.isArray(value)) { value.forEach((val) => { if (!newValues.includes(val)) newValues.push(val); }); } 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); } - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - [key]: newValues, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues }); }, - [globalViewId, globalViewFiltersStore, storedFilters, workspaceSlug] + [workspaceSlug, issueFilters, updateFilters] ); - const handleDisplayFiltersUpdate = useCallback( + const handleDisplayFilters = useCallback( (updatedDisplayFilter: Partial) => { if (!workspaceSlug) return; - - workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), { - display_filters: updatedDisplayFilter, - }); + updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter); }, - [workspaceFilterStore, workspaceSlug] + [workspaceSlug, updateFilters] ); - const handleDisplayPropertiesUpdate = useCallback( + const handleDisplayProperties = useCallback( (property: Partial) => { if (!workspaceSlug) return; - - workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), { - display_properties: property, - }); + updateFilters(workspaceSlug, EFilterType.DISPLAY_PROPERTIES, property); }, - [workspaceFilterStore, workspaceSlug] - ); - - useSWR( - workspaceSlug ? "USER_WORKSPACE_DISPLAY_FILTERS" : null, - workspaceSlug ? () => workspaceFilterStore.fetchUserWorkspaceFilters(workspaceSlug.toString()) : null + [workspaceSlug, updateFilters] ); return ( @@ -137,32 +118,31 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { ))} + {activeLayout === "spreadsheet" && ( <> - {!STATIC_VIEW_TYPES.some((word) => router.pathname.includes(word)) && ( - - m.member) ?? undefined} - projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined} - /> - - )} - + + m.member)} + projects={workspaceProjects ?? undefined} + /> + )} + diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx index b2f4cf426..d69691426 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx @@ -25,9 +25,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => { const appliedFilters: IIssueFilterOptions = {}; Object.entries(userFilters ?? {}).forEach(([key, value]) => { if (!value) return; - if (Array.isArray(value) && value.length === 0) return; - appliedFilters[key as keyof IIssueFilterOptions] = value; }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx index 9bc94b0c9..faf836d87 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx @@ -12,85 +12,73 @@ import { Button } from "@plane/ui"; import { areFiltersDifferent } from "helpers/filter.helper"; // types import { IIssueFilterOptions } from "types"; +import { EFilterType } from "store/issues/types"; export const GlobalViewsAppliedFiltersRoot = observer(() => { const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; + const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string }; const { globalViews: globalViewsStore, globalViewFilters: globalViewFiltersStore, - project: projectStore, - workspace: workspaceStore, + project: { workspaceProjects }, + workspace: { workspaceLabels }, workspaceMember: { workspaceMembers }, + workspaceGlobalIssuesFilter: { issueFilters, updateFilters }, } = useMobxStore(); 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 const appliedFilters: IIssueFilterOptions = {}; - Object.entries(storedFilters ?? {}).forEach(([key, value]) => { + Object.entries(userFilters ?? {}).forEach(([key, value]) => { if (!value) return; - if (Array.isArray(value) && value.length === 0) return; - appliedFilters[key as keyof IIssueFilterOptions] = value; }); const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { - if (!globalViewId) return; - - // remove all values of the key if value is null if (!value) { - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - [key]: null, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: null }); return; } - // remove the passed value from the key - let newValues = globalViewFiltersStore.storedFilters?.[globalViewId.toString()]?.[key] ?? []; + let newValues = userFilters?.[key] ?? []; newValues = newValues.filter((val) => val !== value); - - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - [key]: newValues, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues }); }; const handleClearAllFilters = () => { - if (!globalViewId || !storedFilters) return; - + if (!workspaceSlug) return; const newFilters: IIssueFilterOptions = {}; - Object.keys(storedFilters).forEach((key) => { + Object.keys(userFilters ?? {}).forEach((key) => { newFilters[key as keyof IIssueFilterOptions] = null; }); - - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - ...newFilters, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { ...newFilters }); }; - const handleUpdateView = () => { - if (!workspaceSlug || !globalViewId || !viewDetails) return; + // const handleUpdateView = () => { + // if (!workspaceSlug || !globalViewId || !viewDetails) return; - globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { - query_data: { - ...viewDetails.query_data, - filters: { - ...(storedFilters ?? {}), - }, - }, - }); - }; + // globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { + // query_data: { + // ...viewDetails.query_data, + // filters: { + // ...(storedFilters ?? {}), + // }, + // }, + // }); + // }; // update stored filters when view details are fetched - useEffect(() => { - if (!globalViewId || !viewDetails) return; + // useEffect(() => { + // if (!globalViewId || !viewDetails) return; - if (!globalViewFiltersStore.storedFilters[globalViewId.toString()]) - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {}); - }, [globalViewId, globalViewFiltersStore, viewDetails]); + // if (!globalViewFiltersStore.storedFilters[globalViewId.toString()]) + // globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {}); + // }, [globalViewId, globalViewFiltersStore, viewDetails]); // return if no filters are applied if (Object.keys(appliedFilters).length === 0) return null; @@ -98,18 +86,19 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => { return (
m.member)} + projects={workspaceProjects ?? undefined} + appliedFilters={appliedFilters ?? {}} handleClearAllFilters={handleClearAllFilters} 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 ?? {}) && ( - )} + )} */}
); }); diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index aa82dc686..3a8a2baac 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -3,6 +3,9 @@ export * from "./filters"; export * from "./empty-states"; export * from "./quick-action-dropdowns"; +// roots +export * from "./roots"; + // layouts export * from "./list"; export * from "./calendar"; @@ -10,6 +13,5 @@ export * from "./gantt"; export * from "./kanban"; export * from "./spreadsheet"; +// properties export * from "./properties"; - -export * from "./roots"; diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx new file mode 100644 index 000000000..f823a3e15 --- /dev/null +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -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 = (props) => { + const { issue, handleDelete, handleUpdate } = props; + + const router = useRouter(); + const { workspaceSlug } = router.query; + + // states + const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); + const [issueToEdit, setIssueToEdit] = useState(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 ( + <> + setDeleteIssueModal(false)} + onSubmit={handleDelete} + /> + { + 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} + /> + + { + e.preventDefault(); + e.stopPropagation(); + handleCopyIssueLink(); + }} + > +
+ + Copy link +
+
+ { + e.preventDefault(); + e.stopPropagation(); + setIssueToEdit(issue); + setCreateUpdateIssueModal(true); + }} + > +
+ + Edit issue +
+
+ { + e.preventDefault(); + e.stopPropagation(); + setCreateUpdateIssueModal(true); + }} + > +
+ + Make a copy +
+
+ { + e.preventDefault(); + e.stopPropagation(); + setDeleteIssueModal(true); + }} + > +
+ + Delete issue +
+
+
+ + ); +}; diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts b/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts index 642c39032..e38440017 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts @@ -2,3 +2,4 @@ export * from "./cycle-issue"; export * from "./module-issue"; export * from "./project-issue"; export * from "./archived-issue"; +export * from "./all-issue"; diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx new file mode 100644 index 000000000..a49af10ae --- /dev/null +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -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 = 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) => { + if (!workspaceSlug) return; + + updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter }); + }, + [updateFilters, workspaceSlug] + ); + + return ( +
+ {currentView != currentIssueView && loader === "init-loader" ? ( +
+ +
+ ) : ( + <> + + + {Object.keys(getIssues ?? {}).length == 0 && !loader ? ( + <>{/* */} + ) : ( +
+ ( + 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} + /> +
+ )} + + )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/roots/global-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/global-view-layout-root.tsx deleted file mode 100644 index 247a2e925..000000000 --- a/web/components/issues/issue-layouts/roots/global-view-layout-root.tsx +++ /dev/null @@ -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 = 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) => { - if (!workspaceSlug) return; - - workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), { - display_filters: updatedDisplayFilter, - }); - }, - [workspaceFilterStore, workspaceSlug] - ); - - const handleUpdateIssue = useCallback( - (issue: IIssue, data: Partial) => { - 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 ( -
- -
- ); - - return ( -
- - {issues?.length === 0 || !projects || projects?.length === 0 ? ( - - ) : ( -
- {/* m.member)} - labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined} - disableUserActions={false} - /> */} -
- )} -
- ); -}); diff --git a/web/components/issues/issue-layouts/roots/index.ts b/web/components/issues/issue-layouts/roots/index.ts index 515d73403..72f71aae2 100644 --- a/web/components/issues/issue-layouts/roots/index.ts +++ b/web/components/issues/issue-layouts/roots/index.ts @@ -1,5 +1,5 @@ export * from "./cycle-layout-root"; -export * from "./global-view-layout-root"; +export * from "./all-issue-layout-root"; export * from "./module-layout-root"; export * from "./project-layout-root"; export * from "./project-view-layout-root"; diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index 704e69db0..4255c9280 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -26,20 +26,12 @@ export const ProjectLayoutRoot: React.FC = observer(() => { projectIssuesFilter: { issueFilters, fetchFilters }, } = useMobxStore(); - useSWR( - workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, - async () => { - if (workspaceSlug && projectId) { - await fetchFilters(workspaceSlug, projectId); - await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); - } - }, - { - onErrorRetry: (error) => { - if (error.status === 404) return; - }, + useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { + if (workspaceSlug && projectId) { + await fetchFilters(workspaceSlug, projectId); + await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); } - ); + }); const activeLayout = issueFilters?.displayFilters?.layout; diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 680198ac3..f18336e45 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -77,8 +77,6 @@ export const SpreadsheetView: React.FC = observer((props) => { }; }, []); - console.log("spreadsheet issues", issues); - return (
diff --git a/web/components/onboarding/onboarding-sidebar.tsx b/web/components/onboarding/onboarding-sidebar.tsx index feca84bf6..0a6b909ec 100644 --- a/web/components/onboarding/onboarding-sidebar.tsx +++ b/web/components/onboarding/onboarding-sidebar.tsx @@ -229,9 +229,7 @@ export const OnboardingSidebar: React.FC = (props) => {
diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx index bf4db894a..4aa07147e 100644 --- a/web/components/page-views/signin.tsx +++ b/web/components/page-views/signin.tsx @@ -108,14 +108,13 @@ export const SignInView = observer(() => {

Pages gets a facelift! Write anything and use Galileo to help you start.{" "} - - - Learn more - + + Learn more

diff --git a/web/components/workspace/views/header.tsx b/web/components/workspace/views/header.tsx index aba978094..5cea9b683 100644 --- a/web/components/workspace/views/header.tsx +++ b/web/components/workspace/views/header.tsx @@ -2,8 +2,6 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; import { observer } from "mobx-react-lite"; -import useSWR from "swr"; - // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components @@ -21,11 +19,6 @@ export const GlobalViewsHeader: React.FC = observer(() => { 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 useEffect(() => { if (!globalViewId) return; diff --git a/web/constants/issue.ts b/web/constants/issue.ts index b47321881..965ab69cc 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -297,7 +297,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { filters: ["priority", "state_group", "labels", "assignees", "created_by", "project", "start_date", "target_date"], display_properties: true, display_filters: { - order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], type: [null, "active", "backlog"], }, extra_options: { diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx index 487c53078..759c29429 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -102,19 +102,18 @@ export const ProfileLayoutSidebar = observer(() => { } ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`} >
- - - - - - {!sidebarCollapsed && ( -

Profile settings

- )} -
+ + + + + {!sidebarCollapsed && ( +

Profile settings

+ )}
{!sidebarCollapsed && ( @@ -125,21 +124,19 @@ export const ProfileLayoutSidebar = observer(() => { if (link.key === "change-password" && currentUser?.is_password_autoset) return null; return ( - - - -
- {} - {!sidebarCollapsed && link.label} -
-
-
+ + +
+ {} + {!sidebarCollapsed && link.label} +
+
); })} @@ -189,19 +186,17 @@ export const ProfileLayoutSidebar = observer(() => { )}
{WORKSPACE_ACTION_LINKS.map((link) => ( - - - -
- {} - {!sidebarCollapsed && link.label} -
-
-
+ + +
+ {} + {!sidebarCollapsed && link.label} +
+
))}
diff --git a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx index a7ccc4c3b..428f0a14c 100644 --- a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx +++ b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx @@ -1,41 +1,21 @@ import { ReactElement } from "react"; -import { useRouter } from "next/router"; -import useSWR from "swr"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { AppLayout } from "layouts/app-layout"; // components import { GlobalViewsHeader } from "components/workspace"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues"; import { GlobalIssuesHeader } from "components/headers"; // types import { NextPageWithLayout } from "types/app"; -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 ( -
-
- - -
+const GlobalViewIssuesPage: NextPageWithLayout = () => ( +
+
+ +
- ); -}; +
+); GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx b/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx index 3539beb47..756dde493 100644 --- a/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues/issue-layouts"; // layouts import { AppLayout } from "layouts/app-layout"; // types @@ -12,7 +12,7 @@ const GlobalViewAllIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/[workspaceSlug]/workspace-views/assigned.tsx b/web/pages/[workspaceSlug]/workspace-views/assigned.tsx index 7c1f56bb0..854f0ed6a 100644 --- a/web/pages/[workspaceSlug]/workspace-views/assigned.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/assigned.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues/issue-layouts"; // layouts import { AppLayout } from "layouts/app-layout"; // types @@ -12,7 +12,7 @@ const GlobalViewAssignedIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/[workspaceSlug]/workspace-views/created.tsx b/web/pages/[workspaceSlug]/workspace-views/created.tsx index e8a33cf39..5f5fb46b5 100644 --- a/web/pages/[workspaceSlug]/workspace-views/created.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/created.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues"; // layouts import { AppLayout } from "layouts/app-layout"; // types @@ -12,7 +12,7 @@ const GlobalViewCreatedIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx b/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx index 02d980074..4bb5d4864 100644 --- a/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx @@ -4,7 +4,7 @@ import { AppLayout } from "layouts/app-layout"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues"; // types import { NextPageWithLayout } from "types/app"; @@ -12,7 +12,7 @@ const GlobalViewSubscribedIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/accounts/password.tsx b/web/pages/accounts/password.tsx index 47c74f784..8c031a408 100644 --- a/web/pages/accounts/password.tsx +++ b/web/pages/accounts/password.tsx @@ -142,8 +142,13 @@ const HomePage: NextPageWithLayout = () => {

When you click the button above, you agree with our{" "} - - terms and conditions of service. + + terms and conditions of service.

diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index 3fb3b4b2f..812e67735 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -17,6 +17,7 @@ import { import { IWorkspaceView } from "types/workspace-views"; // store import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store/issue"; +import { IIssueResponse } from "store/issues/types"; export class WorkspaceService extends APIService { constructor() { @@ -245,10 +246,7 @@ export class WorkspaceService extends APIService { }); } - async getViewIssues( - workspaceSlug: string, - params: any - ): Promise { + async getViewIssues(workspaceSlug: string, params: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/issues/`, { params, }) diff --git a/web/store/global-view/global_view_issues.store.ts b/web/store/global-view/global_view_issues.store.ts index 006c9b380..313f7597d 100644 --- a/web/store/global-view/global_view_issues.store.ts +++ b/web/store/global-view/global_view_issues.store.ts @@ -118,7 +118,7 @@ export class GlobalViewIssuesStore implements IGlobalViewIssuesStore { this.loader = false; 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.viewIssues = { ...this.viewIssues, - [type]: response as IIssue[], + [type]: Object.values(response) as IIssue[], }; }); diff --git a/web/store/issues/global/filter.store.ts b/web/store/issues/global/filter.store.ts index 81d661972..b495bb816 100644 --- a/web/store/issues/global/filter.store.ts +++ b/web/store/issues/global/filter.store.ts @@ -3,48 +3,55 @@ import { action, makeObservable, observable, runInAction } from "mobx"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; -import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; 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; - 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; displayFilters: IIssueDisplayFilterOptions; displayProperties: IIssueDisplayProperties; } -interface IProjectIssuesFilters { - filters: IIssueFilterOptions | undefined; - displayFilters: IIssueDisplayFilterOptions | undefined; - displayProperties: IIssueDisplayProperties | undefined; -} - export interface IGlobalIssuesFilterStore { // observables - projectIssueFilters: { [workspaceId: string]: IProjectIssuesDisplayOptions } | undefined; + currentView: TIssueViewTypes; + workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined; + workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined; // computed - issueFilters: IProjectIssuesFilters | undefined; + issueFilters: IWorkspaceProperties | undefined; appliedFilters: TIssueParams[] | undefined; // helpers - issueDisplayFilters: (workspaceId: string) => IProjectIssuesDisplayOptions | undefined; + issueDisplayFilters: (workspaceId: string) => IIssuesDisplayOptions | undefined; // actions - fetchDisplayFilters: (workspaceSlug: string) => Promise; - updateDisplayFilters: ( + setCurrentView: (view: TIssueViewTypes) => void; + fetchWorkspaceProperties: (workspaceSlug: string) => Promise; + updateWorkspaceProperties: ( workspaceSlug: string, type: EFilterType, - filters: IIssueFilterOptions | IIssueDisplayFilterOptions - ) => Promise; - fetchDisplayProperties: (workspaceSlug: string) => Promise; - updateDisplayProperties: ( - workspaceSlug: string, - properties: IIssueDisplayProperties - ) => Promise; - fetchFilters: (workspaceSlug: string) => Promise; + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; + + fetchWorkspaceViewFilters: (workspaceId: string, view: TIssueViewTypes) => Promise; + updateWorkspaceViewFilters: (workspaceId: string, filters: IIssueFilterOptions) => Promise; + + fetchFilters: (workspaceSlug: string, view: TIssueViewTypes) => Promise; updateFilters: ( workspaceSlug: string, filterType: EFilterType, @@ -54,89 +61,160 @@ export interface IGlobalIssuesFilterStore { export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGlobalIssuesFilterStore { // 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 rootStore; + // service + workspaceService; constructor(_rootStore: RootStore) { super(_rootStore); makeObservable(this, { // observables - projectIssueFilters: observable.ref, + currentView: observable.ref, + workspaceProperties: observable.ref, + workspaceViewFilters: observable.ref, // computed // actions - fetchDisplayFilters: action, - updateDisplayFilters: action, - fetchDisplayProperties: action, - updateDisplayProperties: action, + setCurrentView: action, + fetchWorkspaceProperties: action, + updateWorkspaceProperties: action, + fetchWorkspaceViewFilters: action, + updateWorkspaceViewFilters: action, }); // root store this.rootStore = _rootStore; + // services + this.workspaceService = new WorkspaceService(); } // computed // helpers issueDisplayFilters = (workspaceId: string) => { - if (!workspaceId) return undefined; - return this.projectIssueFilters?.[workspaceId] || undefined; + if (!workspaceId || !this.currentView) return 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 - fetchDisplayFilters = async (workspaceSlug: string) => { + setCurrentView = (view: TIssueViewTypes) => { + this.currentView = view; + }; + + fetchWorkspaceProperties = async (workspaceSlug: string) => { try { - const filters: IIssueFilterOptions = { - assignees: null, - mentions: null, - created_by: null, - labels: null, - priority: null, - project: null, - start_date: null, - state: null, - state_group: null, - subscriber: null, - target_date: null, + let _filters: IWorkspaceProperties = {} as IWorkspaceProperties; + + 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, + subscriber: null, + }; + + if (currentUserId && this.currentView === "created") + filters = { + ...filters, + assignees: null, + created_by: [currentUserId], + subscriber: null, + }; + if (currentUserId && this.currentView === "subscribed") + filters = { + ...filters, + assignees: null, + created_by: null, + subscriber: [currentUserId], + }; + const displayFilters: IIssueDisplayFilterOptions = { calendar: { - show_weekends: false, - layout: "month", + show_weekends: _filters?.displayFilters?.calendar?.show_weekends || false, + layout: _filters?.displayFilters?.calendar?.layout || "month", }, - group_by: "state_detail.group", - sub_group_by: null, - layout: "list", - order_by: "-created_at", - show_empty_groups: false, - start_target_date: false, - sub_issue: false, - type: null, + group_by: _filters?.displayFilters?.group_by || null, + sub_group_by: _filters?.displayFilters?.sub_group_by || null, + layout: _filters?.displayFilters?.layout || "list", + order_by: _filters?.displayFilters?.order_by || "-created_at", + show_empty_groups: _filters?.displayFilters?.show_empty_groups || false, + start_target_date: _filters?.displayFilters?.start_target_date || false, + sub_issue: _filters?.displayFilters?.sub_issue || false, + 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, displayFilters: displayFilters, + displayProperties: displayProperties, }; - let _projectIssueFilters = this.projectIssueFilters; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[workspaceSlug]) { - _projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; - } - if ( - isEmpty(_projectIssueFilters[workspaceSlug].filters) || - isEmpty(_projectIssueFilters[workspaceSlug].displayFilters) - ) { - _projectIssueFilters[workspaceSlug] = { - ..._projectIssueFilters[workspaceSlug], - ...issueFilters, + let _workspaceProperties = { ...this.workspaceProperties }; + if (!_workspaceProperties) _workspaceProperties = {}; + if (!_workspaceProperties[workspaceSlug]) + _workspaceProperties[workspaceSlug] = { + filters: {}, + displayFilters: {}, + displayProperties: {}, }; - } + _workspaceProperties[workspaceSlug] = { ...issueFilters }; runInAction(() => { - this.projectIssueFilters = _projectIssueFilters; + this.workspaceProperties = _workspaceProperties; }); return issueFilters; @@ -145,114 +223,126 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl } }; - updateDisplayFilters = async ( + updateWorkspaceProperties = async ( workspaceSlug: string, type: EFilterType, - filters: IIssueFilterOptions | IIssueDisplayFilterOptions + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties ) => { try { - let _projectIssueFilters = { ...this.projectIssueFilters }; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[workspaceSlug]) - _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + let _workspaceProperties = { ...this.workspaceProperties }; + if (!_workspaceProperties) _workspaceProperties = {}; + if (!_workspaceProperties[workspaceSlug]) + _workspaceProperties[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; const _filters = { - filters: { ..._projectIssueFilters[workspaceSlug].filters }, - displayFilters: { ..._projectIssueFilters[workspaceSlug].displayFilters }, + filters: { ..._workspaceProperties[workspaceSlug].filters }, + displayFilters: { ..._workspaceProperties[workspaceSlug].displayFilters }, + displayProperties: { ..._workspaceProperties[workspaceSlug].displayProperties }, }; - if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; - else if (type === EFilterType.DISPLAY_FILTERS) - _filters.displayFilters = { ..._filters.displayFilters, ...filters }; + switch (type) { + case EFilterType.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 - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; - - // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same - if ( - _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, + _workspaceProperties[workspaceSlug] = { + ..._workspaceProperties[workspaceSlug], + filters: _filters?.filters, + displayFilters: _filters?.displayFilters, + displayProperties: _filters?.displayProperties, }; 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; } catch (error) { - this.fetchDisplayFilters(workspaceSlug); + this.fetchWorkspaceProperties(workspaceSlug); throw error; } }; - fetchDisplayProperties = async (workspaceSlug: string) => { + fetchWorkspaceViewFilters = async (workspaceSlug: string, view: TIssueViewTypes) => { try { - const displayProperties: IIssueDisplayProperties = { - assignee: true, - start_date: true, - due_date: true, - labels: false, - key: false, - priority: true, - state: false, - sub_issue_count: true, - link: true, - attachment_count: false, - estimate: false, - created_on: false, - updated_on: false, + let _workspaceViewFilters = { ...this.workspaceViewFilters }; + if (!_workspaceViewFilters) _workspaceViewFilters = {}; + if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions; + if (!_workspaceViewFilters[workspaceSlug][view]) _workspaceViewFilters[workspaceSlug][view] = { filters: {} }; + + const filtersResponse = await this.workspaceService.getViewDetails(workspaceSlug, view); + + const _filters: IIssueFilterOptions = { + assignees: filtersResponse?.query_data?.filters?.assignees || null, + mentions: filtersResponse?.query_data?.filters?.mentions || null, + created_by: filtersResponse?.query_data?.filters?.created_by || null, + labels: filtersResponse?.query_data?.filters?.labels || null, + priority: filtersResponse?.query_data?.filters?.priority || null, + 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 }; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[workspaceSlug]) { - _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {} } as IProjectIssuesDisplayOptions; - } - if (isEmpty(_projectIssueFilters[workspaceSlug].displayProperties)) { - _projectIssueFilters[workspaceSlug] = { - ..._projectIssueFilters[workspaceSlug], - displayProperties: displayProperties, - }; - } + _workspaceViewFilters[workspaceSlug][view].filters = { ..._filters }; runInAction(() => { - this.projectIssueFilters = _projectIssueFilters; + this.workspaceViewFilters = _workspaceViewFilters; }); - return displayProperties; + return _filters; } catch (error) { throw error; } }; - updateDisplayProperties = async (workspaceSlug: string, properties: IIssueDisplayProperties) => { + updateWorkspaceViewFilters = async (workspaceSlug: string, filters: IIssueFilterOptions) => { try { - let _issueFilters = { ...this.projectIssueFilters }; - if (!_issueFilters) _issueFilters = {}; - if (!_issueFilters[workspaceSlug]) - _issueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + let _workspaceViewFilters = { ...this.workspaceViewFilters }; + if (!_workspaceViewFilters) _workspaceViewFilters = {}; + if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions; + if (!_workspaceViewFilters[workspaceSlug][this.currentView]) + _workspaceViewFilters[workspaceSlug][this.currentView] = { filters: {} }; - const updatedDisplayProperties = { ..._issueFilters[workspaceSlug].displayProperties, ...properties }; - _issueFilters[workspaceSlug] = { ..._issueFilters[workspaceSlug], displayProperties: updatedDisplayProperties }; + const _filters = { + filters: { ..._workspaceViewFilters[workspaceSlug][this.currentView].filters, ...filters }, + }; + + _workspaceViewFilters[workspaceSlug][this.currentView] = { + ..._workspaceViewFilters[workspaceSlug][this.currentView], + filters: _filters?.filters, + }; 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) { - this.fetchDisplayProperties(workspaceSlug); + this.fetchWorkspaceViewFilters(workspaceSlug, this.currentView); throw error; } }; @@ -262,10 +352,10 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl if (!workspaceSlug) return undefined; const displayFilters = this.issueDisplayFilters(workspaceSlug); - const _filters: IProjectIssuesFilters = { - filters: displayFilters?.filters, - displayFilters: displayFilters?.displayFilters, - displayProperties: displayFilters?.displayProperties, + const _filters: IWorkspaceProperties = { + filters: displayFilters?.filters || {}, + displayFilters: displayFilters?.displayFilters || {}, + displayProperties: displayFilters?.displayProperties || {}, }; return _filters; @@ -277,6 +367,7 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl let filteredRouteParams: any = { priority: userFilters?.filters?.priority || undefined, + project: userFilters?.filters?.project || undefined, state_group: userFilters?.filters?.state_group || undefined, state: userFilters?.filters?.state || undefined, assignees: userFilters?.filters?.assignees || undefined, @@ -286,24 +377,20 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: false, }; - const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); + const filteredParams = handleIssueQueryParamsByLayout("spreadsheet", "my_issues"); if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); - if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; - if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; - return filteredRouteParams; } - fetchFilters = async (workspaceSlug: string) => { + fetchFilters = async (workspaceSlug: string, view: TIssueViewTypes) => { try { - await this.fetchDisplayFilters(workspaceSlug); - await this.fetchDisplayProperties(workspaceSlug); + await this.fetchWorkspaceProperties(workspaceSlug); + if (!["all-issues", "assigned", "created", "subscribed"].includes(view)) + await this.fetchWorkspaceViewFilters(workspaceSlug, view); return; } catch (error) { throw Error; @@ -312,20 +399,21 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl updateFilters = async ( workspaceSlug: string, - filterType: EFilterType, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties ) => { try { switch (filterType) { 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; case EFilterType.DISPLAY_FILTERS: - await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); break; case EFilterType.DISPLAY_PROPERTIES: - await this.updateDisplayProperties(workspaceSlug, filters as IIssueDisplayProperties); + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayProperties); break; } diff --git a/web/store/issues/global/issue.store.ts b/web/store/issues/global/issue.store.ts index b6f9177dd..5610705b7 100644 --- a/web/store/issues/global/issue.store.ts +++ b/web/store/issues/global/issue.store.ts @@ -2,60 +2,60 @@ import { action, observable, makeObservable, computed, runInAction, autorun } fr // base class import { IssueBaseStore } from "store/issues"; // services -import { UserService } from "services/user.service"; +import { WorkspaceService } from "services/workspace.service"; +import { IssueService } from "services/issue"; // 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 { IIssue } from "types"; - -interface IProfileIssueTabTypes { - assigned: IIssueResponse; - created: IIssueResponse; - subscribed: IIssueResponse; -} +import isEmpty from "lodash/isEmpty"; export interface IGlobalIssuesStore { // observable loader: TLoader; - issues: { [user_id: string]: IProfileIssueTabTypes } | undefined; - currentUserId: string | null; - currentUserIssueTab: "assigned" | "created" | "subscribed" | null; + issues: { [workspace_view: string]: IIssueResponse } | undefined; // computed getIssues: IIssueResponse | undefined; - getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; + getIssuesIds: TUnGroupedIssues | undefined; // actions - fetchIssues: ( + fetchIssues: (workspaceSlug: string, workspaceViewId: string, loadType: TLoader) => Promise; + createIssue: ( workspaceSlug: string, - userId: string, - loadType: TLoader, - type: "assigned" | "created" | "subscribed" - ) => Promise; - createIssue: (workspaceSlug: string, userId: string, data: Partial) => Promise; + projectId: string, + data: Partial, + workspaceViewId?: string | undefined + ) => Promise; updateIssue: ( workspaceSlug: string, - userId: string, + projectId: string, issueId: string, - data: Partial + data: Partial, + workspaceViewId?: string | undefined ) => Promise; removeIssue: ( workspaceSlug: string, - userId: string, projectId: string, - issueId: string + issueId: string, + workspaceViewId?: string | undefined ) => Promise; - quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise; + viewFlags: ViewFlags; } export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesStore { loader: TLoader = "init-loader"; - issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined; - currentUserId: string | null = null; - currentUserIssueTab: "assigned" | "created" | "subscribed" | null = null; + issues: { [workspace_view: string]: IIssueResponse } | undefined = undefined; // root store rootStore; // service - userService; + workspaceService; + issueService; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; constructor(_rootStore: RootStore) { super(_rootStore); @@ -64,146 +64,91 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt // observable loader: observable.ref, issues: observable.ref, - currentUserId: observable.ref, - currentUserIssueTab: observable.ref, // computed getIssues: computed, getIssuesIds: computed, - viewFlags: computed, // action fetchIssues: action, createIssue: action, updateIssue: action, removeIssue: action, - quickAddIssue: action, }); this.rootStore = _rootStore; - this.userService = new UserService(); + this.workspaceService = new WorkspaceService(); + this.issueService = new IssueService(); autorun(() => { 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; - if (userFilters) this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab); + const userFilters = this.rootStore?.workspaceGlobalIssuesFilter?.issueFilters?.filters; + + if (!isEmpty(userFilters)) this.fetchIssues(workspaceSlug, currentView, "mutation"); }); } get getIssues() { - if (!this.currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[this.currentUserId]) - return undefined; + const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView; + if (currentView === "" || !this.issues || !this.issues[currentView]) return undefined; - return this.issues[this.currentUserId][this.currentUserIssueTab]; + return this.issues[currentView]; } get getIssuesIds() { - const currentUserId = this.currentUserId; - const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters; + const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView; + const displayFilters = this.rootStore?.workspaceGlobalIssuesFilter?.issueFilters?.displayFilters; if (!displayFilters) return undefined; - const groupBy = displayFilters?.group_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) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]); - else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]); - } + issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[currentView]); return issues; } - get viewFlags() { - 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" - ) => { + fetchIssues = async (workspaceSlug: string, workspaceViewId: string, loadType: TLoader = "init-loader") => { try { this.loader = loadType; - this.currentUserId = userId; - if (type) this.currentUserIssueTab = type; - let params: any = this.rootStore?.workspaceProfileIssuesFilter?.appliedFilters; - 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 params = this.rootStore?.workspaceGlobalIssuesFilter?.appliedFilters; + const response = await this.workspaceService.getViewIssues(workspaceSlug, params); - const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params); - - if (!this.currentUserIssueTab) return; - - const _issues: any = { - ...this.issues, - [userId]: { - ...this.issues?.[userId], - ...{ [this.currentUserIssueTab]: response }, - }, - }; + const _issues = { ...this.issues, [workspaceViewId]: { ...response } }; runInAction(() => { this.issues = _issues; this.loader = undefined; }); - return _issues; + return response; } catch (error) { + console.error(error); this.loader = undefined; throw error; } }; - createIssue = async (workspaceSlug: string, userId: string, data: Partial) => { + createIssue = async ( + workspaceSlug: string, + projectId: string, + data: Partial, + workspaceViewId: string | undefined = undefined + ) => { + if (!workspaceViewId) return; + try { - const projectId = data.project; - 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); + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); let _issues = this.issues; if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId] = { ..._issues[userId], ...{ [response.id]: response } }; + if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {}; + _issues[workspaceViewId] = { ..._issues[workspaceViewId], ...{ [response.id]: response } }; runInAction(() => { this.issues = _issues; @@ -211,116 +156,62 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt return response; } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); throw error; } }; - updateIssue = async (workspaceSlug: string, userId: string, issueId: string, data: Partial) => { + updateIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial, + workspaceViewId: string | undefined = undefined + ) => { + if (!workspaceViewId) return; + try { - const projectId = data.project; - const moduleId = data.module_id; - const cycleId = data.cycle_id; - - 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, - }; + if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {}; + _issues[workspaceViewId][issueId] = { ..._issues[workspaceViewId][issueId], ...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 - ); + const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); return response; } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); throw error; } }; - removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => { + removeIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + workspaceViewId: string | undefined = undefined + ) => { + if (!workspaceViewId) return; + try { let _issues = { ...this.issues }; if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - - if (this.currentUserIssueTab) delete _issues?.[userId]?.[this.currentUserIssueTab]?.[issueId]; + if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {}; + delete _issues?.[workspaceViewId]?.[issueId]; runInAction(() => { this.issues = _issues; }); - const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); return response; } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); - throw error; - } - }; - - quickAddIssue = async (workspaceSlug: string, userId: string, data: IIssue) => { - try { - const projectId = data.project; - - let _issues = { ...this.issues }; - if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId] = { ..._issues[userId], ...{ [data.id as keyof IIssue]: data } }; - - runInAction(() => { - this.issues = _issues; - }); - - const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); - - if (this.issues && this.currentUserIssueTab) { - delete this.issues[userId][this.currentUserIssueTab][data.id as keyof IIssue]; - - let _issues = { ...this.issues }; - if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId] = { ..._issues[userId], ...{ [response.id as keyof IIssue]: response } }; - - runInAction(() => { - this.issues = _issues; - }); - } - - return response; - } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); throw error; } }; diff --git a/web/store/issues/profile/filter.store.ts b/web/store/issues/profile/filter.store.ts index e8567b042..0b684f7a7 100644 --- a/web/store/issues/profile/filter.store.ts +++ b/web/store/issues/profile/filter.store.ts @@ -125,6 +125,7 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP if (!_projectIssueFilters[workspaceSlug]) { _projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; } + if ( isEmpty(_projectIssueFilters[workspaceSlug].filters) || isEmpty(_projectIssueFilters[workspaceSlug].displayFilters)