mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: view ui and new store workflow
This commit is contained in:
parent
5661ae730a
commit
61f184a2ef
2
packages/types/src/view/base.d.ts
vendored
2
packages/types/src/view/base.d.ts
vendored
@ -43,8 +43,6 @@ export type TView = {
|
||||
updated_at: Date | undefined;
|
||||
// local view variables
|
||||
is_local_view: boolean;
|
||||
is_create: boolean;
|
||||
is_editable: boolean;
|
||||
};
|
||||
|
||||
export type TUpdateView = {
|
||||
|
2
packages/types/src/view/filter.d.ts
vendored
2
packages/types/src/view/filter.d.ts
vendored
@ -78,7 +78,7 @@ export type TViewDisplayFilters = {
|
||||
layout: TViewLayouts;
|
||||
group_by: TViewDisplayFiltersGrouped | undefined;
|
||||
sub_group_by: TViewDisplayFiltersGrouped | undefined;
|
||||
order_by: TViewDisplayFiltersOrderBy | string;
|
||||
order_by: TViewDisplayFiltersOrderBy;
|
||||
type: TViewDisplayFiltersType | undefined;
|
||||
sub_issue: boolean;
|
||||
show_empty_groups: boolean;
|
||||
|
@ -0,0 +1,128 @@
|
||||
import React, { Fragment, useCallback, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useIssues, useUser, useViewDetail } from "hooks/store";
|
||||
import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties";
|
||||
// components
|
||||
import { IssuePeekOverview } from "components/issues";
|
||||
import { SpreadsheetView } from "components/issues/issue-layouts";
|
||||
import { AllIssueQuickActions } from "components/issues/issue-layouts/quick-action-dropdowns";
|
||||
// ui
|
||||
import { SpreadsheetLayoutLoader } from "components/ui";
|
||||
// types
|
||||
import { TIssue, IIssueDisplayFilterOptions, TViewTypes } from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { EViewPageType } from "constants/view";
|
||||
|
||||
type TGlobalViewIssueLayoutRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewPageType: EViewPageType;
|
||||
};
|
||||
|
||||
export const GlobalViewIssueLayoutRoot: React.FC<TGlobalViewIssueLayoutRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType } = props;
|
||||
// hooks
|
||||
const {
|
||||
issues: { loader, groupedIssueIds, updateIssue, removeIssue },
|
||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const {
|
||||
membership: { currentWorkspaceAllProjectsRole },
|
||||
} = useUser();
|
||||
const { issueIds } = groupedIssueIds;
|
||||
|
||||
//swr hook for fetching issue properties
|
||||
useWorkspaceIssueProperties(workspaceSlug);
|
||||
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
const projectId = issue.project_id;
|
||||
if (!workspaceSlug || !projectId || !viewId) return;
|
||||
|
||||
await updateIssue(workspaceSlug.toString(), projectId, issue.id, issue, viewId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
const projectId = issue.project_id;
|
||||
if (!workspaceSlug || !projectId || !viewId) return;
|
||||
|
||||
await removeIssue(workspaceSlug.toString(), projectId, issue.id, viewId.toString());
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[updateIssue, removeIssue, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleIssues = useCallback(
|
||||
async (issue: TIssue, action: EIssueActions) => {
|
||||
if (action === EIssueActions.UPDATE) await issueActions[action]!(issue);
|
||||
if (action === EIssueActions.DELETE) await issueActions[action]!(issue);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
const handleDisplayFiltersUpdate = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !viewId) return;
|
||||
viewDetailStore?.setDisplayFilters({ order_by: updatedDisplayFilter?.order_by });
|
||||
},
|
||||
[viewDetailStore, workspaceSlug, viewId]
|
||||
);
|
||||
|
||||
const renderQuickActions = useCallback(
|
||||
(issue: TIssue, customActionButton?: React.ReactElement, portalElement?: HTMLDivElement | null) => (
|
||||
<AllIssueQuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleUpdate={async () => handleIssues({ ...issue }, EIssueActions.UPDATE)}
|
||||
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
|
||||
portalElement={portalElement}
|
||||
/>
|
||||
),
|
||||
[handleIssues]
|
||||
);
|
||||
|
||||
const canEditProperties = useCallback(
|
||||
(projectId: string | undefined) => {
|
||||
if (!projectId) return false;
|
||||
|
||||
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
|
||||
return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
},
|
||||
[currentWorkspaceAllProjectsRole]
|
||||
);
|
||||
|
||||
if (loader === "init-loader" || !issueIds) {
|
||||
return <SpreadsheetLayoutLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
{issueIds.length === 0 ? (
|
||||
<div>Empty state</div>
|
||||
) : (
|
||||
<Fragment>
|
||||
<SpreadsheetView
|
||||
displayProperties={viewDetailStore?.appliedFilters?.display_properties ?? {}}
|
||||
displayFilters={viewDetailStore?.appliedFilters?.filters as IIssueDisplayFilterOptions} // Fix: Update the type of displayFilters prop
|
||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
||||
issueIds={issueIds}
|
||||
quickActions={renderQuickActions}
|
||||
handleIssues={handleIssues}
|
||||
canEditProperties={canEditProperties}
|
||||
viewId={viewId}
|
||||
/>
|
||||
{/* peek overview */}
|
||||
<IssuePeekOverview />
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -5,3 +5,4 @@ export * from "./project-view-layout-root";
|
||||
export * from "./archived-issue-layout-root";
|
||||
export * from "./draft-issue-layout-root";
|
||||
export * from "./all-issue-layout-root";
|
||||
export * from "./global-view-issue-layout-root";
|
||||
|
@ -12,12 +12,13 @@ type TViewAppliedFiltersItem = {
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
propertyId: string;
|
||||
isLocalView: boolean;
|
||||
};
|
||||
|
||||
export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId, isLocalView } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
|
||||
|
||||
const propertyDetail = viewFilterHelper?.propertyDetails(filterKey, propertyId) || undefined;
|
||||
|
@ -16,12 +16,13 @@ type TViewAppliedFilters = {
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
propertyVisibleCount?: number | undefined;
|
||||
isLocalView: boolean;
|
||||
};
|
||||
|
||||
export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyVisibleCount } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyVisibleCount, isLocalView } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
const viewFilterStore = useViewFilter(workspaceSlug, projectId);
|
||||
|
||||
const currentDefaultFilterDetails = useMemo(
|
||||
@ -44,7 +45,7 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
||||
|
||||
if (!propertyValues || propertyValues.length <= 0) return <></>;
|
||||
return (
|
||||
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1 px-2 min-h-[32px]">
|
||||
<div className="relative flex items-center gap-2 border border-custom-border-200 rounded p-1 px-2 min-h-[32px]">
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-200 capitalize">{filterKey.replaceAll("_", " ")}</div>
|
||||
<div className="relative flex items-center gap-1.5 flex-wrap">
|
||||
{propertyVisibleCount && propertyValues.length >= propertyVisibleCount ? (
|
||||
@ -65,6 +66,7 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
propertyId={propertyId}
|
||||
isLocalView={isLocalView}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
@ -16,12 +16,21 @@ type TViewAppliedFiltersRoot = {
|
||||
viewType: TViewTypes;
|
||||
propertyVisibleCount?: number | undefined;
|
||||
showClearAll?: boolean;
|
||||
isLocalView?: boolean;
|
||||
};
|
||||
|
||||
export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, propertyVisibleCount = undefined, showClearAll = false } = props;
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewId,
|
||||
viewType,
|
||||
propertyVisibleCount = undefined,
|
||||
showClearAll = false,
|
||||
isLocalView = false,
|
||||
} = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
|
||||
const filterKeys = useMemo(
|
||||
() =>
|
||||
@ -52,6 +61,7 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
propertyVisibleCount={propertyVisibleCount}
|
||||
isLocalView={isLocalView}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -32,7 +32,7 @@ export const ViewDisplayFilterSelection: FC<TViewDisplayFilterSelection> = obser
|
||||
: "border-custom-border-400 bg-custom-background-100"
|
||||
}`}
|
||||
>
|
||||
{isSelected && <Check size={14} />}
|
||||
{isSelected && <Check size={14} className="text-white" />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -43,7 +43,7 @@ export const DisplayFilterExtraOptions: FC<TDisplayFilterExtraOptions> = observe
|
||||
: "border-custom-border-400 bg-custom-background-100"
|
||||
}`}
|
||||
>
|
||||
{isSelected && <Check size={14} />}
|
||||
{isSelected && <Check size={14} className="text-white" />}
|
||||
</div>
|
||||
<div className="text-xs block truncate line-clamp-1 text-custom-text-200 group-hover:text-custom-text-100">
|
||||
{optionTitle || "Extra Option"}
|
||||
|
@ -27,10 +27,10 @@ export const ViewDisplayPropertySelection: FC<TViewDisplayPropertySelection> = o
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex items-center gap-1 text-xs rounded p-0.5 px-2 border transition-all capitalize cursor-pointer
|
||||
className={`relative flex items-center gap-1 text-xs rounded p-0.5 px-2 border transition-all capitalize cursor-pointer
|
||||
${
|
||||
propertyIsSelected
|
||||
? `border-custom-primary-100 bg-custom-primary-100`
|
||||
? `border-custom-primary-100 bg-custom-primary-100 text-white`
|
||||
: `border-custom-border-300 hover:bg-custom-background-80`
|
||||
}`}
|
||||
onClick={handlePropertySelection}
|
||||
|
103
web/components/view/empty-states/empty-filter-state.tsx
Normal file
103
web/components/view/empty-states/empty-filter-state.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// hooks
|
||||
import { useApplication, useEventTracker, useIssues, useProject, useUser, useView } from "hooks/store";
|
||||
// components
|
||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
// constants
|
||||
import { ALL_ISSUES_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
type TViewEmptyStateRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ViewEmptyStateRoot: FC<TViewEmptyStateRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, children } = props;
|
||||
// hooks
|
||||
const { commandPalette } = useApplication();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const {
|
||||
currentUser,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issueMap } = useIssues();
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
|
||||
const isDefaultView = ["all-issues", "assigned", "created", "subscribed"].includes(viewId);
|
||||
const currentView = isDefaultView ? viewId : "custom-view";
|
||||
const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS];
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode);
|
||||
|
||||
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && viewId && viewType && viewStore ? `WORKSPACE_VIEWS_${workspaceSlug}_${viewType}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && viewType && viewStore)
|
||||
await viewStore?.fetch(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewStore?.viewIds.length > 0 ? "view-mutation-loader" : "view-loader"
|
||||
);
|
||||
await viewStore?.fetchById(workspaceSlug, projectId, viewId);
|
||||
}
|
||||
);
|
||||
|
||||
const issueIds = projectId ? true : (Object.values(issueMap) ?? []).length === 0;
|
||||
|
||||
if (!workspaceSlug) return <></>;
|
||||
return (
|
||||
<>
|
||||
{(workspaceProjectIds ?? []).length === 0 || issueIds ? (
|
||||
<div className="relative w-full h-full">
|
||||
<EmptyState
|
||||
image={emptyStateImage}
|
||||
title={(workspaceProjectIds ?? []).length > 0 ? currentViewDetails.title : "No project"}
|
||||
description={
|
||||
(workspaceProjectIds ?? []).length > 0
|
||||
? currentViewDetails.description
|
||||
: "To create issues or manage your work, you need to create a project or be a part of one."
|
||||
}
|
||||
size="sm"
|
||||
primaryButton={
|
||||
(workspaceProjectIds ?? []).length > 0
|
||||
? currentView !== "custom-view" && currentView !== "subscribed"
|
||||
? {
|
||||
text: "Create new issue",
|
||||
onClick: () => {
|
||||
setTrackElement("All issues empty state");
|
||||
commandPalette.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
: {
|
||||
text: "Start your first project",
|
||||
onClick: () => {
|
||||
setTrackElement("All issues empty state");
|
||||
commandPalette.toggleCreateProjectModal(true);
|
||||
},
|
||||
}
|
||||
}
|
||||
disabled={!isEditingAllowed}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
103
web/components/view/empty-states/empty-state.tsx
Normal file
103
web/components/view/empty-states/empty-state.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// hooks
|
||||
import { useApplication, useEventTracker, useIssues, useProject, useUser, useView } from "hooks/store";
|
||||
// components
|
||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
// constants
|
||||
import { ALL_ISSUES_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
type TViewEmptyStateRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ViewEmptyStateRoot: FC<TViewEmptyStateRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, children } = props;
|
||||
// hooks
|
||||
const { commandPalette } = useApplication();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const {
|
||||
currentUser,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { issueMap } = useIssues();
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
|
||||
const isDefaultView = ["all-issues", "assigned", "created", "subscribed"].includes(viewId);
|
||||
const currentView = isDefaultView ? viewId : "custom-view";
|
||||
const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS];
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode);
|
||||
|
||||
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && viewId && viewType && viewStore ? `WORKSPACE_VIEWS_${workspaceSlug}_${viewType}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && viewType && viewStore)
|
||||
await viewStore?.fetch(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewStore?.viewIds.length > 0 ? "view-mutation-loader" : "view-loader"
|
||||
);
|
||||
await viewStore?.fetchById(workspaceSlug, projectId, viewId);
|
||||
}
|
||||
);
|
||||
|
||||
const issueIds = projectId ? true : (Object.values(issueMap) ?? []).length === 0;
|
||||
|
||||
if (!workspaceSlug) return <></>;
|
||||
return (
|
||||
<>
|
||||
{(workspaceProjectIds ?? []).length === 0 || issueIds ? (
|
||||
<div className="relative w-full h-full">
|
||||
<EmptyState
|
||||
image={emptyStateImage}
|
||||
title={(workspaceProjectIds ?? []).length > 0 ? currentViewDetails.title : "No project"}
|
||||
description={
|
||||
(workspaceProjectIds ?? []).length > 0
|
||||
? currentViewDetails.description
|
||||
: "To create issues or manage your work, you need to create a project or be a part of one."
|
||||
}
|
||||
size="sm"
|
||||
primaryButton={
|
||||
(workspaceProjectIds ?? []).length > 0
|
||||
? currentView !== "custom-view" && currentView !== "subscribed"
|
||||
? {
|
||||
text: "Create new issue",
|
||||
onClick: () => {
|
||||
setTrackElement("All issues empty state");
|
||||
commandPalette.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
: {
|
||||
text: "Start your first project",
|
||||
onClick: () => {
|
||||
setTrackElement("All issues empty state");
|
||||
commandPalette.toggleCreateProjectModal(true);
|
||||
},
|
||||
}
|
||||
}
|
||||
disabled={!isEditingAllowed}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
37
web/components/view/empty-states/workspace-empty-state.tsx
Normal file
37
web/components/view/empty-states/workspace-empty-state.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useIssues, useProject } from "hooks/store";
|
||||
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewEmptyStateRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ViewEmptyStateRoot: FC<TViewEmptyStateRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, children } = props;
|
||||
// hooks
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { issueMap } = useIssues();
|
||||
|
||||
const areIssueAvailable = projectId ? true : (Object.values(issueMap) ?? []).length === 0 ? true : false;
|
||||
|
||||
if (!workspaceSlug) return <></>;
|
||||
return (
|
||||
<>
|
||||
{(workspaceProjectIds ?? []).length === 0 ? (
|
||||
<div className="relative w-full h-full">No Projects are available.</div>
|
||||
) : (
|
||||
<>
|
||||
{areIssueAvailable ? <>{children}</> : <div className="relative w-full h-full">No issues are available.</div>}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
@ -23,6 +23,7 @@ type TViewFiltersDropdown = {
|
||||
children?: ReactNode;
|
||||
displayDropdownText?: boolean;
|
||||
dropdownPlacement?: Placement;
|
||||
isLocalView?: boolean;
|
||||
};
|
||||
|
||||
export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) => {
|
||||
@ -35,6 +36,7 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
|
||||
children,
|
||||
displayDropdownText = true,
|
||||
dropdownPlacement = "bottom-start",
|
||||
isLocalView = false,
|
||||
} = props;
|
||||
// state
|
||||
const [dropdownToggle, setDropdownToggle] = useState(false);
|
||||
@ -139,6 +141,7 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
|
||||
viewPageType={viewPageType}
|
||||
dateCustomFilterToggle={dateCustomFilterToggle}
|
||||
setDateCustomFilterToggle={setDateCustomFilterToggle}
|
||||
isLocalView={isLocalView}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,19 +55,20 @@ export const ViewFiltersEditDropdown: FC<TViewFiltersEditDropdown> = observer((p
|
||||
key: "save_as_new",
|
||||
label: "Save as new view",
|
||||
onClick: () => {
|
||||
viewOperations.localViewCreateEdit(undefined, viewDetailStore?.filtersToUpdate);
|
||||
viewOperations.localViewCreateEdit(undefined, "SAVE_AS_NEW");
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: RotateCcw,
|
||||
key: "reset_changes",
|
||||
label: "Reset changes",
|
||||
label: "Reset filter changes",
|
||||
onClick: () => viewDetailStore?.resetChanges(),
|
||||
},
|
||||
],
|
||||
[viewOperations, viewDetailStore]
|
||||
);
|
||||
|
||||
if (viewDetailStore?.is_local_view) return <></>;
|
||||
if (!viewDetailStore?.isFiltersUpdateEnabled) return <></>;
|
||||
return (
|
||||
<Menu as="div" className="relative flex-shrink-0" ref={dropdownRef}>
|
||||
|
@ -16,13 +16,22 @@ type TViewFiltersItemRoot = {
|
||||
filterKey: keyof TViewFilters;
|
||||
dateCustomFilterToggle: string | undefined;
|
||||
setDateCustomFilterToggle: (value: string | undefined) => void;
|
||||
isLocalView: boolean;
|
||||
};
|
||||
|
||||
export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, dateCustomFilterToggle, setDateCustomFilterToggle } =
|
||||
props;
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewId,
|
||||
viewType,
|
||||
filterKey,
|
||||
dateCustomFilterToggle,
|
||||
setDateCustomFilterToggle,
|
||||
isLocalView,
|
||||
} = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
|
||||
// state
|
||||
const [viewAll, setViewAll] = useState(false);
|
||||
@ -76,6 +85,7 @@ export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) =>
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
propertyId={propertyId}
|
||||
isLocalView={isLocalView}
|
||||
/>
|
||||
<ViewFiltersItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
|
@ -8,7 +8,6 @@ import { TViewFilters } from "@plane/types";
|
||||
type TViewFiltersItem = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
|
||||
filterKey: keyof TViewFilters;
|
||||
propertyId: string;
|
||||
};
|
||||
|
@ -13,12 +13,13 @@ type TViewFilterSelection = {
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
propertyId: string;
|
||||
isLocalView: boolean;
|
||||
};
|
||||
|
||||
export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId, isLocalView } = props;
|
||||
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
|
||||
const propertyIds = useMemo(
|
||||
() => viewDetailStore?.appliedFilters?.filters?.[filterKey] || [],
|
||||
@ -33,18 +34,6 @@ export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) =>
|
||||
: propertyIds?.includes(propertyId)
|
||||
: propertyIds?.includes(propertyId) || false;
|
||||
|
||||
// const isSelected = useMemo(
|
||||
// () =>
|
||||
// ["start_date", "target_date"].includes(filterKey)
|
||||
// ? propertyId === "custom"
|
||||
// ? propertyIds.filter((id) => id.includes("-")).length > 0
|
||||
// ? true
|
||||
// : false
|
||||
// : propertyIds?.includes(propertyId)
|
||||
// : propertyIds?.includes(propertyId) || false,
|
||||
// [filterKey, propertyId, propertyIds]
|
||||
// );
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex-shrink-0 w-3 h-3 flex justify-center items-center border rounded text-bold ${
|
||||
@ -53,7 +42,7 @@ export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) =>
|
||||
: "border-custom-border-400 bg-custom-background-100"
|
||||
}`}
|
||||
>
|
||||
{isSelected && <Check size={14} />}
|
||||
{isSelected && <Check size={14} className="text-white" />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -20,6 +20,7 @@ type TViewFiltersRoot = {
|
||||
viewPageType: EViewPageType;
|
||||
dateCustomFilterToggle: string | undefined;
|
||||
setDateCustomFilterToggle: (value: string | undefined) => void;
|
||||
isLocalView: boolean;
|
||||
};
|
||||
|
||||
export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
|
||||
@ -31,9 +32,10 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
|
||||
viewPageType,
|
||||
dateCustomFilterToggle,
|
||||
setDateCustomFilterToggle,
|
||||
isLocalView,
|
||||
} = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
// state
|
||||
const [filterVisibility, setFilterVisibility] = useState<Partial<keyof TViewFilters>[]>([]);
|
||||
const handleFilterVisibility = useCallback((key: keyof TViewFilters) => {
|
||||
@ -73,6 +75,7 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
|
||||
filterKey={filterKey}
|
||||
dateCustomFilterToggle={dateCustomFilterToggle}
|
||||
setDateCustomFilterToggle={setDateCustomFilterToggle}
|
||||
isLocalView={isLocalView}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -2,6 +2,9 @@ export * from "./root";
|
||||
|
||||
export * from "./header-tabs";
|
||||
|
||||
// empty states
|
||||
export * from "./empty-states/empty-state";
|
||||
|
||||
// views
|
||||
export * from "./views/root";
|
||||
export * from "./views/view-item";
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { FC, Fragment, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
// hooks
|
||||
import { useView, useViewDetail } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
@ -17,14 +16,20 @@ import {
|
||||
ViewAppliedFiltersRoot,
|
||||
ViewDuplicateConfirmationModal,
|
||||
ViewDeleteConfirmationModal,
|
||||
} from ".";
|
||||
} from "./";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
import { Loader } from "@plane/ui";
|
||||
// constants
|
||||
import { EViewPageType, viewLocalPayload } from "constants/view";
|
||||
import {
|
||||
ELocalViews,
|
||||
EViewLayouts,
|
||||
EViewPageType,
|
||||
TViewCRUD,
|
||||
viewDefaultFilterParametersByViewTypeAndLayout,
|
||||
} from "constants/view";
|
||||
// types
|
||||
import { TViewOperations } from "./types";
|
||||
import { TView, TViewTypes } from "@plane/types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
type TGlobalViewRoot = {
|
||||
workspaceSlug: string;
|
||||
@ -36,11 +41,13 @@ type TGlobalViewRoot = {
|
||||
};
|
||||
|
||||
type TViewOperationsToggle = {
|
||||
type: "CREATE" | "EDIT" | "DUPLICATE" | "DELETE" | undefined;
|
||||
type: "DUPLICATE" | "DELETE" | undefined;
|
||||
viewId: string | undefined;
|
||||
};
|
||||
|
||||
export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewPageType, baseRoute } = props;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
@ -55,38 +62,11 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
(type: TViewOperationsToggle["type"], viewId: string | undefined) => setViewOperationsToggle({ type, viewId }),
|
||||
[]
|
||||
);
|
||||
// hooks
|
||||
const viewDetailCreateEditStore = useViewDetail(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewOperationsToggle?.viewId || viewId,
|
||||
viewType
|
||||
);
|
||||
|
||||
const viewOperations: TViewOperations = useMemo(
|
||||
() => ({
|
||||
localViewCreateEdit: (viewId: string | undefined, currentView = undefined) => {
|
||||
if (viewId === undefined) {
|
||||
if (currentView !== undefined) {
|
||||
// creating new view
|
||||
const currentViewPayload = cloneDeep({ ...currentView, id: uuidV4() });
|
||||
handleViewOperationsToggle("CREATE", currentViewPayload.id);
|
||||
viewStore?.localViewCreate(workspaceSlug, projectId, currentViewPayload as TView);
|
||||
} else {
|
||||
// if current view is available, create a new view with the same data
|
||||
const viewPayload = viewLocalPayload;
|
||||
handleViewOperationsToggle("CREATE", viewPayload.id);
|
||||
viewStore?.localViewCreate(workspaceSlug, projectId, viewPayload as TView);
|
||||
}
|
||||
} else {
|
||||
handleViewOperationsToggle("EDIT", viewId);
|
||||
viewDetailCreateEditStore?.setIsEditable(true);
|
||||
}
|
||||
},
|
||||
localViewCreateEditClear: async (viewId: string | undefined) => {
|
||||
if (viewDetailCreateEditStore?.is_create && viewId) viewStore?.remove(workspaceSlug, projectId, viewId);
|
||||
if (viewDetailCreateEditStore?.is_editable && viewId) viewDetailCreateEditStore.resetChanges();
|
||||
handleViewOperationsToggle(undefined, undefined);
|
||||
localViewCreateEdit: (viewId: string | undefined, status: TViewCRUD) => {
|
||||
viewStore?.localViewHandler(viewId, status);
|
||||
},
|
||||
|
||||
fetch: async () => {
|
||||
@ -100,9 +80,9 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
create: async (data: Partial<TView>) => {
|
||||
create: async () => {
|
||||
try {
|
||||
await viewStore?.create(workspaceSlug, projectId, data);
|
||||
await viewStore?.create();
|
||||
handleViewOperationsToggle(undefined, undefined);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
@ -117,23 +97,6 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
remove: async (viewId: string) => {
|
||||
try {
|
||||
await viewStore?.remove(workspaceSlug, projectId, viewId);
|
||||
handleViewOperationsToggle(undefined, undefined);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "View removed successfully.",
|
||||
});
|
||||
} catch {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Something went wrong. Please try again later or contact the support team.",
|
||||
});
|
||||
}
|
||||
},
|
||||
update: async () => {
|
||||
try {
|
||||
await viewDetailStore?.saveChanges();
|
||||
@ -151,25 +114,80 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
remove: async (viewId: string) => {
|
||||
try {
|
||||
await viewStore?.remove(viewId);
|
||||
handleViewOperationsToggle(undefined, undefined);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "View removed successfully.",
|
||||
});
|
||||
} catch {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Something went wrong. Please try again later or contact the support team.",
|
||||
});
|
||||
}
|
||||
},
|
||||
duplicate: async (viewId: string) => {
|
||||
try {
|
||||
await viewStore?.duplicate(viewId);
|
||||
handleViewOperationsToggle(undefined, undefined);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "View removed successfully.",
|
||||
});
|
||||
} catch {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Something went wrong. Please try again later or contact the support team.",
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
[
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewStore,
|
||||
viewDetailStore,
|
||||
setToastAlert,
|
||||
viewDetailCreateEditStore,
|
||||
handleViewOperationsToggle,
|
||||
]
|
||||
[viewStore, viewDetailStore, handleViewOperationsToggle, setToastAlert, workspaceSlug, projectId]
|
||||
);
|
||||
|
||||
const applyFIltersFromRouter = () => {
|
||||
if (workspaceSlug && viewId && Object.values(ELocalViews).includes(viewId as ELocalViews)) {
|
||||
const routerQueryParams = { ...router.query };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { ["workspaceSlug"]: _workspaceSlug, ["viewId"]: _viewId, ...filters } = routerQueryParams;
|
||||
|
||||
const acceptedFilters = viewDefaultFilterParametersByViewTypeAndLayout(
|
||||
viewPageType,
|
||||
EViewLayouts.SPREADSHEET,
|
||||
"filters"
|
||||
);
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
const filterKey: any = key;
|
||||
const filterValue = filters[key]?.toString() || undefined;
|
||||
if (filterKey && filterValue && acceptedFilters.includes(filterKey)) {
|
||||
const _filterValues = filterValue.split(",");
|
||||
_filterValues.forEach((element) => {
|
||||
console.log("filterKey", filterKey);
|
||||
console.log("element", element);
|
||||
viewDetailStore?.setFilters(filterKey, element);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
};
|
||||
|
||||
// fetch all views
|
||||
useEffect(() => {
|
||||
const fetchViews = async () => {
|
||||
await viewStore?.fetch(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewStore?.viewIds.length > 0 ? "mutation-loader" : "init-loader"
|
||||
viewStore?.viewIds.length > 0 ? "view-mutation-loader" : "view-loader"
|
||||
);
|
||||
};
|
||||
if (workspaceSlug && viewType && viewStore) fetchViews();
|
||||
@ -177,18 +195,34 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
|
||||
// fetch view by id
|
||||
useEffect(() => {
|
||||
const fetchViews = async () => {
|
||||
viewId && (await viewStore?.fetchById(workspaceSlug, projectId, viewId));
|
||||
const fetchViewByViewId = async () => {
|
||||
await viewStore?.fetchById(workspaceSlug, projectId, viewId);
|
||||
// applyFIltersFromRouter();
|
||||
};
|
||||
if (workspaceSlug && viewId && viewType && viewStore) fetchViews();
|
||||
if (workspaceSlug && viewId && viewType && viewStore) {
|
||||
fetchViewByViewId();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [workspaceSlug, projectId, viewId, viewType, viewStore]);
|
||||
|
||||
console.log("viewStore? -->", viewStore?.viewMapCEN?.id);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
{viewStore?.loader && viewStore?.loader === "init-loader" ? (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{viewStore?.loader && viewStore?.loader === "view-loader" ? (
|
||||
<Loader className="relative w-full flex items-center gap-2 pt-2 pb-1 px-5 border-b border-custom-border-300">
|
||||
<div>
|
||||
<Loader.Item height="30px" width="120px" />
|
||||
<div className="border-t-2 rounded-t border-custom-primary-100" />
|
||||
</div>
|
||||
<Loader.Item height="30px" width="120px" />
|
||||
<Loader.Item height="30px" width="120px" />
|
||||
<Loader.Item height="30px" width="120px" />
|
||||
<Loader.Item height="30px" width="120px" />
|
||||
<div className="ml-auto">
|
||||
<Loader.Item height="30px" width="120px" />
|
||||
</div>
|
||||
</Loader>
|
||||
) : (
|
||||
<>
|
||||
<div className="border-b border-custom-border-200 pt-2">
|
||||
@ -202,85 +236,102 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-5 py-2 border-b border-custom-border-200 relative flex items-start gap-1">
|
||||
<div className="w-full overflow-hidden">
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
propertyVisibleCount={5}
|
||||
/>
|
||||
</div>
|
||||
{viewStore?.loader === "view-detail-loader" ? (
|
||||
<Loader className="relative w-full flex items-center gap-2 py-3 px-5 border-b border-custom-border-300">
|
||||
<div className="mr-auto relative flex items-center gap-2">
|
||||
<Loader.Item width="140px" height="30px" />
|
||||
<Loader.Item width="140px" height="30px" />
|
||||
<Loader.Item width="140px" height="30px" />
|
||||
<Loader.Item width="140px" height="30px" />
|
||||
</div>
|
||||
<Loader.Item width="30px" height="30px" />
|
||||
<Loader.Item width="30px" height="30px" />
|
||||
<Loader.Item width="30px" height="30px" />
|
||||
<Loader.Item width="120px" height="30px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="p-5 py-2 border-b border-custom-border-200 relative flex items-start gap-1">
|
||||
<div className="w-full overflow-hidden">
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
propertyVisibleCount={5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<ViewLayoutRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<ViewLayoutRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<ViewFiltersDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
displayDropdownText={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<ViewFiltersDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
displayDropdownText={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<ViewDisplayFiltersDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
displayDropdownText={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<ViewDisplayFiltersDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
displayDropdownText={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<ViewEditDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<ViewEditDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<ViewFiltersEditDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
<div className="flex-shrink-0">
|
||||
<ViewFiltersEditDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* create edit modal */}
|
||||
{viewStore?.viewMapCEN?.id && (
|
||||
<ViewCreateEditForm
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewStore?.viewMapCEN?.id}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
viewOperations={viewOperations}
|
||||
isLocalView={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
{viewOperationsToggle.type && viewOperationsToggle.viewId && (
|
||||
<Fragment>
|
||||
{["CREATE", "EDIT"].includes(viewOperationsToggle.type) && (
|
||||
<ViewCreateEditForm
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewOperationsToggle.viewId}
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
)}
|
||||
|
||||
{["DUPLICATE"].includes(viewOperationsToggle.type) && (
|
||||
<ViewDuplicateConfirmationModal viewId={viewOperationsToggle.viewId} viewOperations={viewOperations} />
|
||||
)}
|
||||
|
6
web/components/view/types.d.ts
vendored
6
web/components/view/types.d.ts
vendored
@ -1,15 +1,17 @@
|
||||
import { LucideIcon } from "lucide-react";
|
||||
// types
|
||||
import { TView, TUpdateView } from "@plane/types";
|
||||
// constants
|
||||
import { TViewCRUD } from "constants/view";
|
||||
|
||||
export type TViewOperations = {
|
||||
localViewCreateEdit: (viewId: string | undefined, currentView?: TUpdateView | undefined) => void;
|
||||
localViewCreateEditClear: (viewId: string | undefined) => Promise<void>;
|
||||
localViewCreateEdit: (viewId: string | undefined, status: TViewCRUD) => void;
|
||||
|
||||
fetch: () => Promise<void>;
|
||||
create: (data: Partial<TView>) => Promise<void>;
|
||||
update: () => Promise<void>;
|
||||
remove: (viewId: string) => Promise<void>;
|
||||
duplicate: (viewId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
// view and view filter edit dropdowns
|
||||
|
@ -21,12 +21,13 @@ type TViewCreateEditForm = {
|
||||
viewType: TViewTypes;
|
||||
viewPageType: EViewPageType;
|
||||
viewOperations: TViewOperations;
|
||||
isLocalView: boolean;
|
||||
};
|
||||
|
||||
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewPageType, viewOperations } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewPageType, viewOperations, isLocalView } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView);
|
||||
const { getProjectById } = useProject();
|
||||
// states
|
||||
const [modalToggle, setModalToggle] = useState(false);
|
||||
@ -36,9 +37,9 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
const modalClose = useCallback(() => {
|
||||
setModalToggle(false);
|
||||
setTimeout(() => {
|
||||
viewOperations.localViewCreateEditClear(viewId);
|
||||
viewOperations.localViewCreateEdit(undefined, "CLEAR");
|
||||
}, 200);
|
||||
}, [viewId, setModalToggle, viewOperations]);
|
||||
}, [setModalToggle, viewOperations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewId) modalOpen();
|
||||
@ -46,16 +47,16 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
|
||||
const onContinue = async () => {
|
||||
setLoader(true);
|
||||
if (viewDetailStore?.is_create) {
|
||||
const payload = viewDetailStore?.filtersToUpdate;
|
||||
await viewOperations.create(payload);
|
||||
modalClose();
|
||||
} else {
|
||||
const payload = viewDetailStore?.filtersToUpdate;
|
||||
if (!payload) return;
|
||||
await viewOperations.update();
|
||||
modalClose();
|
||||
}
|
||||
// if (viewDetailStore?.id != "create") {
|
||||
// const payload = viewDetailStore?.filtersToUpdate;
|
||||
// await viewOperations.create(payload);
|
||||
// modalClose();
|
||||
// } else {
|
||||
// const payload = viewDetailStore?.filtersToUpdate;
|
||||
// if (!payload) return;
|
||||
// await viewOperations.update();
|
||||
// modalClose();
|
||||
// }
|
||||
setLoader(false);
|
||||
};
|
||||
|
||||
@ -131,6 +132,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
viewType={viewType}
|
||||
viewPageType={viewPageType}
|
||||
dropdownPlacement="right"
|
||||
isLocalView={isLocalView}
|
||||
>
|
||||
<div className="cursor-pointer relative rounded p-1.5 px-2 flex items-center gap-1 border border-custom-border-100 bg-custom-background-80">
|
||||
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
|
||||
@ -161,6 +163,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
propertyVisibleCount={undefined}
|
||||
isLocalView={isLocalView}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -169,7 +172,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onContinue} disabled={loader}>
|
||||
{loader ? `Saving...` : `${viewDetailStore?.is_create ? `Create` : `Update`} View`}
|
||||
{loader ? `Saving...` : `${viewDetailStore?.id === "create" ? `Create` : `Update`} View`}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
|
@ -3,18 +3,24 @@ import { observer } from "mobx-react-lite";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Copy, Eye, Globe2, Link2, Pencil, Trash } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// types
|
||||
import { TViewEditDropdownOptions, TViewOperations } from "../types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewEditDropdown = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
export const ViewEditDropdown: FC<TViewEditDropdown> = observer((props) => {
|
||||
const { viewId, viewOperations } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
// refs
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
// popper-js refs
|
||||
@ -46,7 +52,7 @@ export const ViewEditDropdown: FC<TViewEditDropdown> = observer((props) => {
|
||||
icon: Pencil,
|
||||
key: "rename",
|
||||
label: "Rename",
|
||||
onClick: () => viewOperations.localViewCreateEdit(viewId),
|
||||
onClick: () => viewOperations.localViewCreateEdit(viewId, "EDIT"),
|
||||
children: undefined,
|
||||
},
|
||||
{
|
||||
@ -96,6 +102,7 @@ export const ViewEditDropdown: FC<TViewEditDropdown> = observer((props) => {
|
||||
[viewOperations, viewId]
|
||||
);
|
||||
|
||||
if (viewDetailStore?.is_local_view) return <></>;
|
||||
return (
|
||||
<Menu as="div" className="relative flex-shrink-0" ref={dropdownRef}>
|
||||
<Menu.Button
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, Fragment, useEffect, useMemo, useState } from "react";
|
||||
import { FC, Fragment, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
@ -31,7 +31,7 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
||||
const handleViewTabsVisibility = () => {
|
||||
const tabContainer = document.getElementById("tab-container");
|
||||
const tabItemViewMore = document.getElementById("tab-item-view-more");
|
||||
const itemWidth = 124;
|
||||
const itemWidth = 122;
|
||||
if (!tabContainer || !tabItemViewMore) return;
|
||||
|
||||
const containerWidth = tabContainer.clientWidth;
|
||||
@ -49,14 +49,11 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
||||
return () => window.removeEventListener("resize", () => handleViewTabsVisibility());
|
||||
}, [viewStore?.viewIds]);
|
||||
|
||||
const viewIds = useMemo(() => {
|
||||
const ids = viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length) || [];
|
||||
if (!ids.includes(viewId)) {
|
||||
ids.pop();
|
||||
ids.push(viewId);
|
||||
}
|
||||
return ids;
|
||||
}, [viewId, viewStore, itemsToRenderViewsCount]);
|
||||
const viewIds = viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length) || [];
|
||||
if (!viewIds.includes(viewId)) {
|
||||
viewIds.pop();
|
||||
viewIds.push(viewId);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex justify-between px-5 gap-2">
|
||||
@ -102,7 +99,11 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 my-auto pb-1">
|
||||
<Button size="sm" prependIcon={<Plus />} onClick={() => viewOperations?.localViewCreateEdit(undefined)}>
|
||||
<Button
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => viewOperations?.localViewCreateEdit(undefined, "CREATE")}
|
||||
>
|
||||
New View
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -134,7 +134,7 @@ export const ViewDropdown: FC<TViewDropdown> = (props) => {
|
||||
|
||||
<div
|
||||
className="relative flex justify-center items-center gap-1 rounded p-1 py-1.5 transition-all border border-custom-border-200 bg-custom-background-90 hover:bg-custom-background-80 text-custom-text-300 hover:text-custom-text-200 cursor-pointer"
|
||||
onClick={() => viewOperations?.localViewCreateEdit(undefined)}
|
||||
onClick={() => viewOperations?.localViewCreateEdit(undefined, "CREATE")}
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
<div className="text-sm">New view</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
// types
|
||||
import { TViewTypes, TView } from "@plane/types";
|
||||
|
||||
@ -9,13 +8,20 @@ export const VIEW_TYPES: Record<TViewTypes, TViewTypes> = {
|
||||
PROJECT_PUBLIC_VIEWS: "PROJECT_PUBLIC_VIEWS",
|
||||
};
|
||||
|
||||
export type TViewCRUD = "CREATE" | "EDIT" | "SAVE_AS_NEW" | "CLEAR";
|
||||
|
||||
export const viewLocalPayload: Partial<TView> = {
|
||||
id: uuidV4(),
|
||||
id: "create",
|
||||
name: "",
|
||||
description: "",
|
||||
filters: undefined,
|
||||
display_filters: undefined,
|
||||
display_properties: undefined,
|
||||
is_local_view: false,
|
||||
is_create: true,
|
||||
};
|
||||
|
||||
export const generateViewStoreKey = (
|
||||
workspaceSlug: string,
|
||||
projectId: string | undefined,
|
||||
viewType: TViewTypes
|
||||
): string => `${workspaceSlug}_${projectId}_${viewType}`;
|
||||
|
@ -11,8 +11,9 @@ import { VIEW_TYPES } from "constants/view";
|
||||
export const useViewDetail = (
|
||||
workspaceSlug: string,
|
||||
projectId: string | undefined,
|
||||
viewId: string,
|
||||
viewType: TViewTypes | undefined
|
||||
viewId: string | undefined,
|
||||
viewType: TViewTypes | undefined,
|
||||
isEditable: boolean = false
|
||||
): TViewStore | undefined => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useViewDetail must be used within StoreProvider");
|
||||
@ -21,14 +22,18 @@ export const useViewDetail = (
|
||||
|
||||
switch (viewType) {
|
||||
case VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS:
|
||||
if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN;
|
||||
return context.view.workspacePrivateViewStore.viewById(viewId);
|
||||
case VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS:
|
||||
if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN;
|
||||
return context.view.workspacePublicViewStore.viewById(viewId);
|
||||
case VIEW_TYPES.PROJECT_PRIVATE_VIEWS:
|
||||
if (!projectId) return undefined;
|
||||
if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN;
|
||||
return context.view.projectPrivateViewStore.viewById(viewId);
|
||||
case VIEW_TYPES.PROJECT_PUBLIC_VIEWS:
|
||||
if (!projectId) return undefined;
|
||||
if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN;
|
||||
return context.view.projectPublicViewStore.viewById(viewId);
|
||||
default:
|
||||
return undefined;
|
||||
|
@ -8,7 +8,7 @@ import { TViewTypes } from "@plane/types";
|
||||
import { VIEW_TYPES } from "constants/view";
|
||||
|
||||
export const useView = (
|
||||
workspaceSlug: string,
|
||||
workspaceSlug: string | undefined,
|
||||
projectId: string | undefined,
|
||||
viewType: TViewTypes | undefined
|
||||
): ViewRootStore | undefined => {
|
||||
|
@ -36,8 +36,10 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => {
|
||||
? `PROJECT_VIEWS_${VIEW_TYPES.PROJECT_PUBLIC_VIEWS}_${workspaceSlug.toString()}_${projectId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
await viewStore?.fetch();
|
||||
console.log("viewStore", viewStore?.viewIds);
|
||||
if (workspaceSlug && projectId) {
|
||||
await viewStore?.fetch(workspaceSlug?.toString(), projectId?.toString());
|
||||
console.log("viewStore", viewStore?.viewIds);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -66,7 +68,7 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => {
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||
{viewStore?.loader === "init-loader" ? (
|
||||
{viewStore?.loader === "view-loader" ? (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
|
@ -36,8 +36,10 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => {
|
||||
? `PROJECT_VIEWS_${VIEW_TYPES.PROJECT_PUBLIC_VIEWS}_${workspaceSlug.toString()}_${projectId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
await viewStore?.fetch();
|
||||
console.log("viewStore", viewStore?.viewIds);
|
||||
if (workspaceSlug && projectId) {
|
||||
await viewStore?.fetch(workspaceSlug.toString(), projectId.toString());
|
||||
console.log("viewStore", viewStore?.viewIds);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -66,7 +68,7 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => {
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||
{viewStore?.loader === "init-loader" ? (
|
||||
{viewStore?.loader === "view-loader" ? (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
|
@ -5,10 +5,11 @@ import { CheckCircle } from "lucide-react";
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { GlobalViewRoot, ViewHeader } from "components/view";
|
||||
import { GlobalViewIssueLayoutRoot } from "components/issues";
|
||||
// types
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
// constants
|
||||
import { EViewPageType, VIEW_TYPES } from "constants/view";
|
||||
import { ELocalViews, EViewPageType, VIEW_TYPES } from "constants/view";
|
||||
|
||||
const WorkspacePrivateViewPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
@ -19,12 +20,12 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => {
|
||||
{
|
||||
key: VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS,
|
||||
title: "Private",
|
||||
href: `/${workspaceSlug}/views/private/assigned`,
|
||||
href: `/${workspaceSlug}/views/private/${ELocalViews.ASSIGNED}`,
|
||||
},
|
||||
{
|
||||
key: VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS,
|
||||
title: "Public",
|
||||
href: `/${workspaceSlug}/views/public/all-issues`,
|
||||
href: `/${workspaceSlug}/views/public/${ELocalViews.ALL_ISSUES}`,
|
||||
},
|
||||
],
|
||||
[workspaceSlug]
|
||||
@ -44,7 +45,9 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => {
|
||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 w-full">
|
||||
{/* content */}
|
||||
<GlobalViewRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
@ -56,9 +59,24 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-full overflow-hidden relative flex justify-center items-center text-sm text-custom-text-300">
|
||||
Issues render placeholder
|
||||
{/* issues */}
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
<GlobalViewIssueLayoutRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={undefined}
|
||||
viewId={viewId.toString()}
|
||||
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
|
||||
viewPageType={EViewPageType.ALL}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* TODO: once the functionality is done implement the empty states */}
|
||||
{/* <ViewEmptyStateRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={undefined}
|
||||
viewId={viewId.toString()}
|
||||
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
|
||||
></ViewEmptyStateRoot> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { CheckCircle } from "lucide-react";
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { GlobalViewRoot, ViewHeader } from "components/view";
|
||||
import { GlobalViewIssueLayoutRoot } from "components/issues";
|
||||
// types
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
// constants
|
||||
@ -44,7 +45,9 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => {
|
||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 w-full">
|
||||
{/* content */}
|
||||
<GlobalViewRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
@ -56,8 +59,15 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-full overflow-hidden relative flex justify-center items-center text-sm text-custom-text-300">
|
||||
Issues render placeholder
|
||||
{/* issues */}
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
<GlobalViewIssueLayoutRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={undefined}
|
||||
viewId={viewId.toString()}
|
||||
viewType={VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS}
|
||||
viewPageType={EViewPageType.ALL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -7,7 +7,8 @@ import { WorkspaceService } from "services/workspace.service";
|
||||
import { IssueService } from "services/issue";
|
||||
// types
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { TIssue, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types";
|
||||
import { TIssue, TLoader, TUnGroupedIssues, TViewDisplayFilters, ViewFlags } from "@plane/types";
|
||||
import { VIEW_TYPES, generateViewStoreKey } from "constants/view";
|
||||
|
||||
export interface IWorkspaceIssues {
|
||||
// observable
|
||||
@ -17,7 +18,7 @@ export interface IWorkspaceIssues {
|
||||
// computed
|
||||
groupedIssueIds: { dataViewId: string; issueIds: TUnGroupedIssues | undefined };
|
||||
// actions
|
||||
fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||
fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader, query?: any) => Promise<TIssue[]>;
|
||||
createIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -76,39 +77,52 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
|
||||
}
|
||||
|
||||
get groupedIssueIds() {
|
||||
const viewId = this.rootIssueStore.globalViewId;
|
||||
const workspaceSlug = this.rootIssueStore.workspaceSlug;
|
||||
if (!workspaceSlug || !viewId) return { dataViewId: "", issueIds: undefined };
|
||||
const { workspaceSlug, projectId, currentViewType, currentViewId } = this.rootIssueStore.rootStore.view;
|
||||
if (!workspaceSlug || !currentViewId || !currentViewType) return { dataViewId: "", issueIds: undefined };
|
||||
|
||||
const uniqueViewId = `${workspaceSlug}_${viewId}`;
|
||||
const viewRootKey = generateViewStoreKey(workspaceSlug, projectId, currentViewType);
|
||||
let displayFilters: TViewDisplayFilters | undefined = undefined;
|
||||
|
||||
const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.filters?.[viewId]?.displayFilters;
|
||||
if (!displayFilters) return { dataViewId: viewId, issueIds: undefined };
|
||||
if (currentViewType === VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS)
|
||||
displayFilters =
|
||||
this.rootIssueStore?.rootStore?.view?.workspacePrivateViewStore?.viewMap?.[viewRootKey]?.[currentViewId]
|
||||
?.appliedFilters?.display_filters;
|
||||
else if (currentViewType === VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS)
|
||||
displayFilters =
|
||||
this.rootIssueStore?.rootStore?.view?.workspacePublicViewStore?.viewMap?.[viewRootKey]?.[currentViewId]
|
||||
?.appliedFilters?.display_filters;
|
||||
|
||||
if (!displayFilters) return { dataViewId: currentViewId, issueIds: undefined };
|
||||
|
||||
const orderBy = displayFilters?.order_by;
|
||||
|
||||
const uniqueViewId = `${workspaceSlug}_${currentViewId}`;
|
||||
const viewIssueIds = this.issues[uniqueViewId];
|
||||
|
||||
if (!viewIssueIds) return { dataViewId: viewId, issueIds: undefined };
|
||||
if (!viewIssueIds) return { dataViewId: currentViewId, issueIds: undefined };
|
||||
|
||||
const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds);
|
||||
if (!_issues) return { dataViewId: viewId, issueIds: [] };
|
||||
if (!_issues) return { dataViewId: currentViewId, issueIds: [] };
|
||||
|
||||
let issueIds: TIssue | TUnGroupedIssues | undefined = undefined;
|
||||
|
||||
issueIds = this.unGroupedIssues(orderBy ?? "-created_at", _issues);
|
||||
|
||||
return { dataViewId: viewId, issueIds };
|
||||
return { dataViewId: currentViewId, issueIds };
|
||||
}
|
||||
|
||||
fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader = "init-loader") => {
|
||||
fetchIssues = async (
|
||||
workspaceSlug: string,
|
||||
viewId: string,
|
||||
loadType: TLoader = "init-loader",
|
||||
query: any = undefined
|
||||
) => {
|
||||
try {
|
||||
this.loader = loadType;
|
||||
|
||||
const uniqueViewId = `${workspaceSlug}_${viewId}`;
|
||||
|
||||
const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId);
|
||||
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
||||
const response = await this.workspaceService.getViewIssues(workspaceSlug, query || {});
|
||||
|
||||
runInAction(() => {
|
||||
set(
|
||||
|
@ -101,8 +101,10 @@ export class FiltersHelper {
|
||||
Object.keys(filteredParams).forEach((key) => {
|
||||
const _key = key as TViewFilterQueryParams;
|
||||
const _value: string | boolean | string[] | undefined = filteredParams[_key];
|
||||
if (_value != undefined && acceptableParamsByLayout.includes(_key))
|
||||
paramsObject[_key] = Array.isArray(_value) ? _value.join(",") : _value;
|
||||
if (_value != undefined && acceptableParamsByLayout.includes(_key)) {
|
||||
if (Array.isArray(_value)) _value.length > 0 && (paramsObject[_key] = _value.join(","));
|
||||
else paramsObject[_key] = _value;
|
||||
}
|
||||
});
|
||||
|
||||
if (paramsObject && !isEmpty(paramsObject)) {
|
||||
@ -110,7 +112,7 @@ export class FiltersHelper {
|
||||
.map((key) => {
|
||||
const _key = key as TViewFilterQueryParams;
|
||||
const _value: string | boolean | undefined = paramsObject[_key];
|
||||
if (!undefined) return `${_key}=${_value}`;
|
||||
if (_value) return `${_key}=${_value}`;
|
||||
})
|
||||
.join("&");
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { action, autorun, makeObservable, observable, runInAction } from "mobx";
|
||||
// stores
|
||||
import { ViewRootStore } from "./view-root.store";
|
||||
// services
|
||||
@ -13,14 +14,39 @@ import {
|
||||
import { RootStore } from "store/root.store";
|
||||
// constants
|
||||
import { EViewPageType, VIEW_TYPES } from "constants/view";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
export class GlobalViewRootStore {
|
||||
workspaceSlug: string | undefined = undefined;
|
||||
projectId: string | undefined = undefined;
|
||||
currentViewId: string | undefined = undefined;
|
||||
currentViewType: TViewTypes | undefined = undefined;
|
||||
currentUserId: string | undefined = undefined;
|
||||
|
||||
workspacePrivateViewStore: ViewRootStore;
|
||||
workspacePublicViewStore: ViewRootStore;
|
||||
projectPrivateViewStore: ViewRootStore;
|
||||
projectPublicViewStore: ViewRootStore;
|
||||
|
||||
constructor(private store: RootStore) {
|
||||
makeObservable(this, {
|
||||
workspaceSlug: observable.ref,
|
||||
projectId: observable.ref,
|
||||
currentViewId: observable.ref,
|
||||
currentViewType: observable.ref,
|
||||
currentUserId: observable.ref,
|
||||
// actions
|
||||
setWorkspaceSlug: action,
|
||||
setProjectId: action,
|
||||
setCurrentViewId: action,
|
||||
setCurrentViewType: action,
|
||||
setCurrentUserId: action,
|
||||
});
|
||||
|
||||
autorun(() => {
|
||||
this.currentUserId = store.user.currentUser?.id;
|
||||
});
|
||||
|
||||
const workspacePrivateDefaultViews: any[] = [
|
||||
{
|
||||
id: "assigned",
|
||||
@ -80,4 +106,11 @@ export class GlobalViewRootStore {
|
||||
VIEW_TYPES.PROJECT_PUBLIC_VIEWS
|
||||
);
|
||||
}
|
||||
|
||||
// helper actions
|
||||
setWorkspaceSlug = (workspaceSlug: string | undefined) => runInAction(() => (this.workspaceSlug = workspaceSlug));
|
||||
setProjectId = (projectId: string | undefined) => runInAction(() => (this.projectId = projectId));
|
||||
setCurrentViewId = (viewId: string | undefined) => runInAction(() => (this.currentViewId = viewId));
|
||||
setCurrentViewType = (viewType: TViewTypes | undefined) => runInAction(() => (this.currentViewType = viewType));
|
||||
setCurrentUserId = (userId: string | undefined) => runInAction(() => (this.currentUserId = userId));
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { computedFn } from "mobx-utils";
|
||||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import reverse from "lodash/reverse";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
// stores
|
||||
import { RootStore } from "store/root.store";
|
||||
import { ViewStore } from "./view.store";
|
||||
@ -10,30 +11,40 @@ import { ViewStore } from "./view.store";
|
||||
import { TUserViewService, TViewService } from "services/view/types";
|
||||
import { TView, TViewTypes } from "@plane/types";
|
||||
// constants
|
||||
import { EViewPageType } from "constants/view";
|
||||
import { EViewPageType, TViewCRUD, generateViewStoreKey, viewLocalPayload } from "constants/view";
|
||||
|
||||
export type TLoader = "init-loader" | "mutation-loader" | "submitting" | undefined;
|
||||
export type TLoader =
|
||||
| "view-loader"
|
||||
| "view-mutation-loader"
|
||||
| "view-detail-loader"
|
||||
| "create-submitting"
|
||||
| "delete-submitting"
|
||||
| "duplicate-submitting"
|
||||
| undefined;
|
||||
|
||||
type TViewRootStore = {
|
||||
// observables
|
||||
loader: TLoader;
|
||||
viewMap: Record<string, Record<string, Record<string, ViewStore>>>; // workspaceSlug/projectId, public/private, viewId -> ViewStore
|
||||
viewMapCEN: ViewStore | undefined; // view map Create, Edit, and save as New
|
||||
viewMap: Record<string, Record<string, ViewStore>>; // workspaceSlug/projectId/TViewType.toString(), viewId -> ViewStore
|
||||
// computed
|
||||
viewIds: string[];
|
||||
viewById: (viewId: string) => ViewStore | undefined;
|
||||
localView: () => ViewStore | undefined;
|
||||
// actions
|
||||
localViewCreate: (workspaceSlug: string, projectId: string | undefined, view: TView) => Promise<void>;
|
||||
fetch: (workspaceSlug: string, projectId: string | undefined, _loader?: TLoader) => Promise<void>;
|
||||
fetchById: (workspaceSlug: string, projectId: string | undefined, viewId: string) => Promise<void>;
|
||||
create: (workspaceSlug: string, projectId: string | undefined, view: Partial<TView>) => Promise<void>;
|
||||
remove: (workspaceSlug: string, projectId: string | undefined, viewId: string) => Promise<void>;
|
||||
duplicate: (workspaceSlug: string, projectId: string | undefined, viewId: string) => Promise<void>;
|
||||
remove: (viewId: string) => Promise<void>;
|
||||
localViewHandler: (viewId: string | undefined, status: TViewCRUD) => void;
|
||||
create: () => Promise<void>;
|
||||
duplicate: (viewId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export class ViewRootStore implements TViewRootStore {
|
||||
// observables
|
||||
loader: TLoader = "init-loader";
|
||||
viewMap: Record<string, Record<string, Record<string, ViewStore>>> = {};
|
||||
loader: TLoader = "view-loader";
|
||||
viewMapCEN: ViewStore | undefined = undefined;
|
||||
viewMap: Record<string, Record<string, ViewStore>> = {};
|
||||
|
||||
constructor(
|
||||
private store: RootStore,
|
||||
@ -46,11 +57,12 @@ export class ViewRootStore implements TViewRootStore {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
loader: observable.ref,
|
||||
viewMapCEN: observable,
|
||||
viewMap: observable,
|
||||
// computed
|
||||
viewIds: computed,
|
||||
// actions
|
||||
localViewCreate: action,
|
||||
localViewHandler: action,
|
||||
fetch: action,
|
||||
fetchById: action,
|
||||
create: action,
|
||||
@ -61,56 +73,46 @@ export class ViewRootStore implements TViewRootStore {
|
||||
|
||||
// computed
|
||||
get viewIds() {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return [];
|
||||
const { workspaceSlug, projectId, currentViewType } = this.store.view;
|
||||
if (!workspaceSlug || !currentViewType) return [];
|
||||
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
const views = this.viewMap?.[viewRootSlug]?.[this.viewType]
|
||||
? Object.values(this.viewMap?.[viewRootSlug]?.[this.viewType])
|
||||
: [];
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, currentViewType);
|
||||
const views = this.viewMap?.[viewRootSlug] ? Object.values(this.viewMap?.[viewRootSlug]) : [];
|
||||
|
||||
const localViews = views.filter((view) => view.is_local_view);
|
||||
let apiViews = views.filter((view) => !view.is_local_view && !view.is_create);
|
||||
let apiViews = views.filter((view) => !view.is_local_view);
|
||||
apiViews = reverse(sortBy(apiViews, "sort_order"));
|
||||
const _viewIds = [...localViews.map((view) => view.id), ...apiViews.map((view) => view.id)];
|
||||
return _viewIds as string[];
|
||||
}
|
||||
|
||||
viewById = computedFn((viewId: string) => {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return undefined;
|
||||
const { workspaceSlug, projectId, currentViewType } = this.store.view;
|
||||
if (!workspaceSlug || !currentViewType) return undefined;
|
||||
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
return this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId] || undefined;
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, currentViewType);
|
||||
return this.viewMap?.[viewRootSlug]?.[viewId] || undefined;
|
||||
});
|
||||
|
||||
localView = computedFn(() => this.viewMapCEN);
|
||||
|
||||
// actions
|
||||
localViewCreate = async (workspaceSlug: string, projectId: string | undefined, view: TView) => {
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
|
||||
runInAction(() => {
|
||||
if (view.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[viewRootSlug, this.viewType, view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
fetch = async (workspaceSlug: string, projectId: string | undefined, _loader: TLoader = "init-loader") => {
|
||||
fetch = async (workspaceSlug: string, projectId: string | undefined, _loader: TLoader = "view-loader") => {
|
||||
try {
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
runInAction(() => (this.loader = _loader));
|
||||
|
||||
this.loader = _loader;
|
||||
this.store.view.setWorkspaceSlug(workspaceSlug);
|
||||
this.store.view.setProjectId(projectId);
|
||||
this.store.view.setCurrentViewType(this.viewType);
|
||||
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType);
|
||||
if (this.defaultViews && this.defaultViews.length > 0)
|
||||
runInAction(() => {
|
||||
this.defaultViews?.forEach((view) => {
|
||||
if (view.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[viewRootSlug, this.viewType, view.id],
|
||||
[viewRootSlug, view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
});
|
||||
@ -118,100 +120,155 @@ export class ViewRootStore implements TViewRootStore {
|
||||
|
||||
const views = await this.service.fetch(workspaceSlug, projectId);
|
||||
if (!views) return;
|
||||
|
||||
runInAction(() => {
|
||||
views.forEach((view) => {
|
||||
if (view.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[viewRootSlug, this.viewType, view.id],
|
||||
[viewRootSlug, view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
});
|
||||
this.loader = undefined;
|
||||
});
|
||||
} catch {}
|
||||
} catch {
|
||||
runInAction(() => (this.loader = undefined));
|
||||
}
|
||||
};
|
||||
|
||||
fetchById = async (workspaceSlug: string, projectId: string | undefined, viewId: string) => {
|
||||
try {
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
runInAction(() => (this.loader = "view-detail-loader"));
|
||||
|
||||
this.store.view.setWorkspaceSlug(workspaceSlug);
|
||||
this.store.view.setProjectId(projectId);
|
||||
this.store.view.setCurrentViewId(viewId);
|
||||
this.store.view.setCurrentViewType(this.viewType);
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType);
|
||||
|
||||
const userView = await this.userService.fetch(workspaceSlug, projectId);
|
||||
if (!userView) return;
|
||||
|
||||
let view: TView | undefined = undefined;
|
||||
|
||||
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) {
|
||||
const currentView = { ...this.viewById(viewId) } as TView;
|
||||
if (!currentView) return;
|
||||
|
||||
view = currentView;
|
||||
view.filters = userView.filters;
|
||||
view.display_filters = userView.display_filters;
|
||||
view.display_properties = userView.display_properties;
|
||||
} else {
|
||||
const currentView = await this.service.fetchById(workspaceSlug, viewId, projectId);
|
||||
if (!currentView) return;
|
||||
|
||||
view = currentView;
|
||||
view?.display_filters && (view.display_filters = userView.display_filters);
|
||||
view?.display_properties && (view.display_properties = userView.display_properties);
|
||||
}
|
||||
|
||||
view?.display_filters && (view.display_filters = userView.display_filters);
|
||||
view?.display_properties && (view.display_properties = userView.display_properties);
|
||||
|
||||
if (!view) return;
|
||||
runInAction(() => {
|
||||
if (view?.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[viewRootSlug, this.viewType, view.id],
|
||||
[viewRootSlug, view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
});
|
||||
} catch {}
|
||||
|
||||
// fetching the issues
|
||||
const filterParams = this.viewMap?.[viewRootSlug]?.[viewId]?.appliedFiltersQueryParams?.params;
|
||||
this.store.issue.workspaceIssues.fetchIssues(workspaceSlug, viewId, "init-loader", filterParams);
|
||||
|
||||
runInAction(() => (this.loader = undefined));
|
||||
} catch {
|
||||
runInAction(() => (this.loader = undefined));
|
||||
}
|
||||
};
|
||||
|
||||
create = async (workspaceSlug: string, projectId: string | undefined, data: Partial<TView>) => {
|
||||
remove = async (viewId: string) => {
|
||||
try {
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
const { workspaceSlug, projectId, currentViewType } = this.store.view;
|
||||
if (!workspaceSlug || !currentViewType) return undefined;
|
||||
|
||||
const view = await this.service.create(workspaceSlug, data, projectId);
|
||||
runInAction(() => (this.loader = "delete-submitting"));
|
||||
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType);
|
||||
|
||||
await this.service.remove?.(workspaceSlug, viewId, projectId);
|
||||
|
||||
runInAction(() => {
|
||||
delete this.viewMap?.[viewRootSlug]?.[viewId];
|
||||
this.loader = undefined;
|
||||
});
|
||||
} catch {
|
||||
runInAction(() => (this.loader = undefined));
|
||||
}
|
||||
};
|
||||
|
||||
localViewHandler = (viewId: string | undefined, status: TViewCRUD) => {
|
||||
const { workspaceSlug, projectId, currentViewType } = this.store.view;
|
||||
if (!workspaceSlug || !currentViewType) return undefined;
|
||||
|
||||
if (status === "CLEAR") {
|
||||
runInAction(() => (this.viewMapCEN = undefined));
|
||||
return;
|
||||
}
|
||||
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, currentViewType);
|
||||
|
||||
let _view: Partial<TView> = {};
|
||||
if (status === "CREATE") _view = cloneDeep(viewLocalPayload);
|
||||
else if (status === "EDIT") {
|
||||
if (!viewId) return;
|
||||
_view = cloneDeep(this.viewMap?.[viewRootSlug]?.[viewId]);
|
||||
} else if (status === "SAVE_AS_NEW") {
|
||||
if (!viewId) return;
|
||||
_view = cloneDeep(this.viewMap?.[viewRootSlug]?.[viewId]);
|
||||
} else return;
|
||||
|
||||
runInAction(() => {
|
||||
if (_view.id)
|
||||
set(
|
||||
this,
|
||||
["viewMapCEN"],
|
||||
new ViewStore(this.store, _view as TView, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
create = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.viewMapCEN) return;
|
||||
|
||||
runInAction(() => (this.loader = "create-submitting"));
|
||||
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType);
|
||||
|
||||
const view = await this.service.create(workspaceSlug, this.viewMapCEN, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
if (view.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[viewRootSlug, this.viewType, view.id],
|
||||
[viewRootSlug, view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
this.viewMapCEN = undefined;
|
||||
this.loader = undefined;
|
||||
});
|
||||
|
||||
if (data.id) this.remove(workspaceSlug, projectId, data.id);
|
||||
} catch {}
|
||||
} catch {
|
||||
runInAction(() => (this.loader = undefined));
|
||||
}
|
||||
};
|
||||
|
||||
remove = async (workspaceSlug: string, projectId: string | undefined, viewId: string) => {
|
||||
duplicate = async (viewId: string) => {
|
||||
try {
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.service.duplicate) return;
|
||||
|
||||
if (
|
||||
this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId] != undefined &&
|
||||
!this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId]?.is_create
|
||||
)
|
||||
await this.service.remove?.(workspaceSlug, viewId, projectId);
|
||||
runInAction(() => (this.loader = "duplicate-submitting"));
|
||||
|
||||
runInAction(() => {
|
||||
delete this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId];
|
||||
});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
duplicate = async (workspaceSlug: string, projectId: string | undefined, viewId: string) => {
|
||||
try {
|
||||
if (!this.service.duplicate) return;
|
||||
|
||||
const viewRootSlug = projectId ? projectId : workspaceSlug;
|
||||
const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType);
|
||||
|
||||
const view = await this.service.duplicate(workspaceSlug, viewId, projectId);
|
||||
if (!view) return;
|
||||
@ -220,10 +277,13 @@ export class ViewRootStore implements TViewRootStore {
|
||||
if (view.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[viewRootSlug, this.viewType, view.id],
|
||||
[viewRootSlug, view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
this.loader = undefined;
|
||||
});
|
||||
} catch {}
|
||||
} catch {
|
||||
runInAction(() => (this.loader = undefined));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export type TViewStore = TView & {
|
||||
filtersToUpdate: TUpdateView;
|
||||
// computed
|
||||
appliedFilters: TViewFilterProps | undefined;
|
||||
appliedFiltersQueryParams: string | undefined;
|
||||
appliedFiltersQueryParams: { params: Object; query: string } | undefined;
|
||||
isFiltersApplied: boolean;
|
||||
isFiltersUpdateEnabled: boolean;
|
||||
// helper actions
|
||||
@ -45,7 +45,6 @@ export type TViewStore = TView & {
|
||||
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void;
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
|
||||
setIsEditable: (id_editable: boolean) => void;
|
||||
resetChanges: () => void;
|
||||
saveChanges: () => Promise<void>;
|
||||
// actions
|
||||
@ -77,8 +76,6 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
created_at: Date | undefined;
|
||||
updated_at: Date | undefined;
|
||||
is_local_view: boolean = false;
|
||||
is_create: boolean = false;
|
||||
is_editable: boolean = false;
|
||||
loader: TLoader = undefined;
|
||||
filtersToUpdate: TUpdateView;
|
||||
|
||||
@ -110,8 +107,6 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
this.created_at = _view.created_at;
|
||||
this.updated_at = _view.updated_at;
|
||||
this.is_local_view = _view.is_local_view;
|
||||
this.is_create = _view.is_create;
|
||||
this.is_editable = _view.is_editable;
|
||||
this.filtersToUpdate = {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
@ -142,8 +137,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
created_at: observable.ref,
|
||||
updated_at: observable.ref,
|
||||
is_local_view: observable.ref,
|
||||
is_create: observable.ref,
|
||||
is_editable: observable.ref,
|
||||
|
||||
loader: observable.ref,
|
||||
filtersToUpdate: observable,
|
||||
// computed
|
||||
@ -156,7 +150,6 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
setFilters: action,
|
||||
setDisplayFilters: action,
|
||||
setDisplayProperties: action,
|
||||
setIsEditable: action,
|
||||
resetChanges: action,
|
||||
saveChanges: action,
|
||||
// actions
|
||||
@ -197,8 +190,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
layout,
|
||||
EFilterTypes.FILTERS
|
||||
);
|
||||
|
||||
return this.computeAppliedFiltersQueryParameters(appliedFilters, requiredFilterProperties)?.query || undefined;
|
||||
return this.computeAppliedFiltersQueryParameters(appliedFilters, requiredFilterProperties) || undefined;
|
||||
}
|
||||
|
||||
get isFiltersApplied() {
|
||||
@ -247,6 +239,12 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
return concat(_values, filterValue);
|
||||
});
|
||||
});
|
||||
|
||||
const { workspaceSlug } = this.store.view;
|
||||
if (workspaceSlug && this.id) {
|
||||
const filterParams = this.appliedFiltersQueryParams?.params;
|
||||
this.store.issue.workspaceIssues.fetchIssues(workspaceSlug, this.id, "mutation", filterParams);
|
||||
}
|
||||
};
|
||||
|
||||
setDisplayFilters = async (display_filters: Partial<TViewDisplayFilters>) => {
|
||||
@ -288,12 +286,6 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
this.updateUserDisplayProperties({ [EFilterTypes.DISPLAY_PROPERTIES]: this.filtersToUpdate.display_properties });
|
||||
};
|
||||
|
||||
setIsEditable = (is_editable: boolean) => {
|
||||
runInAction(() => {
|
||||
this.is_editable = is_editable;
|
||||
});
|
||||
};
|
||||
|
||||
resetChanges = () => {
|
||||
runInAction(() => {
|
||||
const _view = cloneDeep(this);
|
||||
@ -310,11 +302,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
saveChanges = async () => {
|
||||
try {
|
||||
if (!this.id) return;
|
||||
|
||||
if (["all-issues", "assigned", "created", "subscribed"].includes(this.id)) {
|
||||
const payload = this.filtersToUpdate.filters;
|
||||
await this.updateUserFilters({ [EFilterTypes.FILTERS]: payload });
|
||||
} else await this.update(this.filtersToUpdate);
|
||||
console.log("coming here");
|
||||
await this.update(this.filtersToUpdate);
|
||||
} catch {
|
||||
Object.keys(this.filtersToUpdate).forEach((key) => {
|
||||
const _key = key as keyof TUpdateView;
|
||||
@ -326,13 +315,14 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
// actions
|
||||
update = async (viewData: TUpdateView) => {
|
||||
try {
|
||||
if (!this.workspace || !this.id) return;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.id) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.loader = "updating";
|
||||
});
|
||||
|
||||
const view = await this.service.update(this.workspace, this.id, viewData, this.project);
|
||||
const view = await this.service.update(workspaceSlug, this.id, viewData, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
@ -349,9 +339,10 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
lockView = async () => {
|
||||
try {
|
||||
if (!this.workspace || !this.id || !this.service.lock) return;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.id || !this.service.lock) return;
|
||||
|
||||
const view = await this.service.lock(this.workspace, this.id, this.project);
|
||||
const view = await this.service.lock(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
@ -364,9 +355,10 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
unlockView = async () => {
|
||||
try {
|
||||
if (!this.workspace || !this.id || !this.service.unlock) return;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.id || !this.service.unlock) return;
|
||||
|
||||
const view = await this.service.unlock(this.workspace, this.id, this.project);
|
||||
const view = await this.service.unlock(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
@ -379,9 +371,10 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
makeFavorite = async () => {
|
||||
try {
|
||||
if (!this.workspace || !this.id || !this.service.makeFavorite) return;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.id || !this.service.makeFavorite) return;
|
||||
|
||||
const view = await this.service.makeFavorite(this.workspace, this.id, this.project);
|
||||
const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
@ -394,9 +387,10 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
removeFavorite = async () => {
|
||||
try {
|
||||
if (!this.workspace || !this.id || !this.service.removeFavorite) return;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug || !this.id || !this.service.removeFavorite) return;
|
||||
|
||||
const view = await this.service.removeFavorite(this.workspace, this.id, this.project);
|
||||
const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
@ -410,7 +404,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
// updating the user specific filters, display filters, and display properties
|
||||
updateUserFilters = async (filters: { [EFilterTypes.FILTERS]: TViewFilters }) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const userView = await this.userService.update(workspaceSlug, filters, projectId);
|
||||
@ -428,7 +422,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
updateUserDisplayFilters = async (display_filters: { [EFilterTypes.DISPLAY_FILTERS]: TViewDisplayFilters }) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const userView = await this.userService.update(workspaceSlug, display_filters, projectId);
|
||||
@ -448,7 +442,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
[EFilterTypes.DISPLAY_PROPERTIES]: TViewDisplayProperties;
|
||||
}) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
const { workspaceSlug, projectId } = this.store.view;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const userView = await this.userService.update(workspaceSlug, display_properties, projectId);
|
||||
|
Loading…
Reference in New Issue
Block a user