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
27 changed files with 732 additions and 695 deletions
|
|
@ -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<Props> = 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<IIssueDisplayFilterOptions>) => {
|
||||
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<IIssueDisplayProperties>) => {
|
||||
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<Props> = observer((props) => {
|
|||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{activeLayout === "spreadsheet" && (
|
||||
<>
|
||||
{!STATIC_VIEW_TYPES.some((word) => router.pathname.includes(word)) && (
|
||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||
<FilterSelection
|
||||
filters={storedFilters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
labels={workspaceStore.workspaceLabels ?? undefined}
|
||||
members={workspaceMembers?.map((m) => m.member) ?? undefined}
|
||||
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
)}
|
||||
|
||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
labels={workspaceLabels ?? undefined}
|
||||
members={workspaceMembers?.map((m) => m.member)}
|
||||
projects={workspaceProjects ?? undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
displayFilters={workspaceFilterStore.workspaceDisplayFilters}
|
||||
displayProperties={workspaceFilterStore.workspaceDisplayProperties}
|
||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
||||
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button variant="primary" size="sm" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
|
||||
New View
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex items-start justify-between gap-4 p-4">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={storedFilters ?? {}}
|
||||
labels={workspaceLabels ?? undefined}
|
||||
members={workspaceMembers?.map((m) => 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 ?? {}) && (
|
||||
<Button variant="primary" onClick={handleUpdateView}>
|
||||
Update view
|
||||
</Button>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 "./project-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 "./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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -77,8 +77,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
console.log("spreadsheet issues", issues);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-200">
|
||||
<div className="h-full w-full flex flex-col">
|
||||
|
|
|
|||
|
|
@ -229,9 +229,7 @@ export const OnboardingSidebar: React.FC<Props> = (props) => {
|
|||
<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={`relative flex items-center justify-between w-full rounded gap-1 group
|
||||
px-3 shadow-custom-shadow-2xs border-onboarding-border-100 border
|
||||
`}
|
||||
className={`relative flex items-center justify-between w-full rounded gap-1 group 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`}>
|
||||
<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" />
|
||||
<p className="text-sm text-left text-onboarding-text-100">
|
||||
Pages gets a facelift! Write anything and use Galileo to help you start.{" "}
|
||||
<Link href="https://plane.so/changelog">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-sm underline hover:cursor-pointer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
<Link
|
||||
href="https://plane.so/changelog"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-sm underline hover:cursor-pointer"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue