mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: view compoennt and store updates
This commit is contained in:
parent
6325f97c8e
commit
cd81ec1002
16
packages/types/src/view/base.d.ts
vendored
16
packages/types/src/view/base.d.ts
vendored
@ -7,7 +7,6 @@ import {
|
||||
export type TViewTypes =
|
||||
| "WORKSPACE_YOUR_VIEWS"
|
||||
| "WORKSPACE_VIEWS"
|
||||
| "WORKSPACE_PROJECT_VIEWS"
|
||||
| "PROJECT_VIEWS"
|
||||
| "PROJECT_YOUR_VIEWS";
|
||||
|
||||
@ -29,17 +28,20 @@ export type TView = {
|
||||
name: string | undefined;
|
||||
description: string | undefined;
|
||||
query: string | undefined;
|
||||
filters: TViewFilters | undefined;
|
||||
display_filters: TViewDisplayFilters | undefined;
|
||||
display_properties: TViewDisplayProperties | undefined;
|
||||
filters: TViewFilters;
|
||||
display_filters: TViewDisplayFilters;
|
||||
display_properties: TViewDisplayProperties;
|
||||
access: TViewAccess | undefined;
|
||||
owned_by: string | undefined;
|
||||
sort_order: number | undefined;
|
||||
is_locked: boolean | undefined;
|
||||
is_pinned: boolean | undefined;
|
||||
is_favorite: boolean | undefined;
|
||||
is_locked: boolean;
|
||||
is_pinned: boolean;
|
||||
is_favorite: boolean;
|
||||
created_by: string | undefined;
|
||||
updated_by: string | undefined;
|
||||
created_at: Date | undefined;
|
||||
updated_at: Date | undefined;
|
||||
// local view variables
|
||||
is_local_view: boolean;
|
||||
is_create: boolean;
|
||||
};
|
||||
|
65
packages/types/src/view/filter.d.ts
vendored
65
packages/types/src/view/filter.d.ts
vendored
@ -5,10 +5,53 @@ export type TViewLayouts =
|
||||
| "spreadsheet"
|
||||
| "gantt";
|
||||
|
||||
export type TViewDisplayFiltersGrouped =
|
||||
| "project"
|
||||
| "state_detail.group"
|
||||
| "state"
|
||||
| "priority"
|
||||
| "labels"
|
||||
| "created_by"
|
||||
| "assignees"
|
||||
| "mentions"
|
||||
| "modules"
|
||||
| "cycles";
|
||||
|
||||
export type TViewDisplayFiltersOrderBy =
|
||||
| "sort_order"
|
||||
| "created_at"
|
||||
| "-created_at"
|
||||
| "updated_at"
|
||||
| "-updated_at"
|
||||
| "start_date"
|
||||
| "-start_date"
|
||||
| "target_date"
|
||||
| "-target_date"
|
||||
| "state__name"
|
||||
| "-state__name"
|
||||
| "priority"
|
||||
| "-priority"
|
||||
| "labels__name"
|
||||
| "-labels__name"
|
||||
| "assignees__first_name"
|
||||
| "-assignees__first_name"
|
||||
| "estimate_point"
|
||||
| "-estimate_point"
|
||||
| "link_count"
|
||||
| "-link_count"
|
||||
| "attachment_count"
|
||||
| "-attachment_count"
|
||||
| "sub_issues_count"
|
||||
| "-sub_issues_count";
|
||||
|
||||
export type TViewDisplayFiltersType = "active" | "backlog";
|
||||
|
||||
export type TViewCalendarLayouts = "month" | "week";
|
||||
|
||||
export type TViewFilters = {
|
||||
project: string[];
|
||||
module: string[];
|
||||
cycle: string[];
|
||||
priority: string[];
|
||||
state: string[];
|
||||
state_group: string[];
|
||||
@ -23,10 +66,10 @@ export type TViewFilters = {
|
||||
|
||||
export type TViewDisplayFilters = {
|
||||
layout: TViewLayouts;
|
||||
group_by: string | undefined;
|
||||
sub_group_by: string | undefined;
|
||||
order_by: string;
|
||||
type: string | undefined;
|
||||
group_by: TViewDisplayFiltersGrouped | undefined;
|
||||
sub_group_by: TViewDisplayFiltersGrouped | undefined;
|
||||
order_by: TViewDisplayFiltersOrderBy | string;
|
||||
type: TViewDisplayFiltersType | undefined;
|
||||
sub_issue: boolean;
|
||||
show_empty_groups: boolean;
|
||||
calendar: {
|
||||
@ -52,19 +95,21 @@ export type TViewDisplayProperties = {
|
||||
};
|
||||
|
||||
export type TViewFilterProps = {
|
||||
filters: TViewFilters | undefined;
|
||||
display_filters: TViewDisplayFilters | undefined;
|
||||
display_properties: TViewDisplayProperties | undefined;
|
||||
filters: TViewFilters;
|
||||
display_filters: TViewDisplayFilters;
|
||||
display_properties: TViewDisplayProperties;
|
||||
};
|
||||
|
||||
export type TViewFilterPartialProps = {
|
||||
filters: Partial<TViewFilters> | undefined;
|
||||
display_filters: Partial<TViewDisplayFilters> | undefined;
|
||||
display_properties: Partial<TViewDisplayProperties> | undefined;
|
||||
filters: Partial<TViewFilters>;
|
||||
display_filters: Partial<TViewDisplayFilters>;
|
||||
display_properties: Partial<TViewDisplayProperties>;
|
||||
};
|
||||
|
||||
export type TViewFilterQueryParams =
|
||||
| "project"
|
||||
| "module"
|
||||
| "cycle"
|
||||
| "priority"
|
||||
| "state"
|
||||
| "state_group"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, } from "react";
|
||||
import { FC, useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
// components
|
||||
import { IssuePeekOverview } from "components/issues";
|
||||
|
166
web/components/view/all-issues-root.tsx
Normal file
166
web/components/view/all-issues-root.tsx
Normal file
@ -0,0 +1,166 @@
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
// hooks
|
||||
import { useView, useViewDetail } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot, ViewLayoutRoot } from ".";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { VIEW_TYPES } from "constants/view";
|
||||
// types
|
||||
import { TViewOperations } from "./types";
|
||||
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
||||
|
||||
type TAllIssuesViewRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
};
|
||||
|
||||
export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId } = props;
|
||||
// states
|
||||
const [viewType, setViewType] = useState(VIEW_TYPES.WORKSPACE_VIEWS);
|
||||
const workspaceViewTabOptions = [
|
||||
{
|
||||
key: VIEW_TYPES.WORKSPACE_YOUR_VIEWS,
|
||||
title: "Your views",
|
||||
onClick: () => VIEW_TYPES.WORKSPACE_YOUR_VIEWS != viewType && setViewType(VIEW_TYPES.WORKSPACE_YOUR_VIEWS),
|
||||
},
|
||||
{
|
||||
key: VIEW_TYPES.WORKSPACE_VIEWS,
|
||||
title: "Workspace Views",
|
||||
onClick: () => VIEW_TYPES.WORKSPACE_VIEWS != viewType && setViewType(VIEW_TYPES.WORKSPACE_VIEWS),
|
||||
},
|
||||
];
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const viewOperations: TViewOperations = useMemo(
|
||||
() => ({
|
||||
localViewCreate: (data) => viewStore?.localViewCreate(data),
|
||||
clearLocalView: (viewId: string) => viewStore?.clearLocalView(viewId),
|
||||
setFilters: (filters: Partial<TViewFilters>) => viewDetailStore?.setFilters(filters),
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) =>
|
||||
viewDetailStore?.setDisplayFilters(display_filters),
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) =>
|
||||
viewDetailStore?.setDisplayProperties(display_properties),
|
||||
fetch: async () => await viewStore?.fetch(),
|
||||
create: async (data: Partial<TView>) => {
|
||||
try {
|
||||
await viewStore?.create(data);
|
||||
if (data.id) viewOperations.clearLocalView(data.id);
|
||||
} catch {
|
||||
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
[viewStore, viewDetailStore, setToastAlert]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceSlug && viewId && viewType && viewStore)
|
||||
viewStore?.fetch(viewStore?.viewIds.length > 0 ? "mutation-loader" : "init-loader");
|
||||
}, [workspaceSlug, viewId, viewType, viewStore]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
<div className="relative flex justify-between items-center gap-2 px-5 py-4">
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div className="flex-shrink-0 w-6 h-6 rounded relative flex justify-center items-center bg-custom-background-80">
|
||||
<CheckCircle size={12} />
|
||||
</div>
|
||||
<div className="font-medium">All Issues</div>
|
||||
</div>
|
||||
<div className="relative inline-flex items-center rounded border border-custom-border-300 bg-custom-background-80">
|
||||
{workspaceViewTabOptions.map((tab) => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`p-4 py-1.5 rounded text-sm transition-all cursor-pointer font-medium
|
||||
${
|
||||
viewType === tab.key
|
||||
? "text-custom-text-100 bg-custom-background-90"
|
||||
: "text-custom-text-200 bg-custom-background-80 hover:text-custom-text-100"
|
||||
}`}
|
||||
onClick={tab.onClick}
|
||||
>
|
||||
{tab.title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{viewStore?.loader && viewStore?.loader === "init-loader" ? (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ViewRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
|
||||
{/* <ViewFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewOperations={viewOperations}
|
||||
/> */}
|
||||
|
||||
<div className="p-5 border-b border-custom-border-200 relative flex gap-2">
|
||||
<div className="w-full">
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 h-full">
|
||||
<ViewLayoutRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 relative w-7 h-7 overflow-hidden border border-red-500 rounded flex justify-center items-center">
|
||||
Filters
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 relative w-7 h-7 overflow-hidden border border-red-500 rounded flex justify-center items-center">
|
||||
Display Filters
|
||||
</div>
|
||||
|
||||
{!viewDetailStore?.is_local_view && (
|
||||
<div className="flex-shrink-0 h-full">
|
||||
<ViewCreateEdit
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
>
|
||||
<div>Edit</div>
|
||||
</ViewCreateEdit>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,17 +1,49 @@
|
||||
import { FC } from "react";
|
||||
import { User, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// types
|
||||
import { TViewFilters, TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewFiltersItem = {
|
||||
type TViewAppliedFiltersItem = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
filterId: string;
|
||||
};
|
||||
|
||||
export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId } = props;
|
||||
export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, filterId } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
const removeFilterOption = () => {
|
||||
const filters = viewDetailStore?.appliedFilters?.filters;
|
||||
if (!filters) return;
|
||||
const filterValues = filters[filterKey];
|
||||
const updatedFilterValues = filterValues.filter((value) => value !== filterId);
|
||||
viewDetailStore?.setFilters({ [filterKey]: updatedFilterValues });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>ViewFiltersItem</div>
|
||||
<div
|
||||
key={`filter_value_${filterKey}_${filterId}`}
|
||||
className="border border-custom-border-200 rounded relative flex items-center gap-1 px-1 py-0.5"
|
||||
>
|
||||
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center overflow-hidden">
|
||||
<User size={12} />
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
{filterKey} - {filterId}
|
||||
</div>
|
||||
<div
|
||||
className="flex-shrink-0 w-3.5 h-3.5 relative flex justify-center items-center overflow-hidden rounded-full transition-all cursor-pointer bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200"
|
||||
onClick={removeFilterOption}
|
||||
>
|
||||
<X size={10} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -4,46 +4,56 @@ import isEmpty from "lodash/isEmpty";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// components
|
||||
import { ViewAppliedFiltersItem } from "./filter-item";
|
||||
// helpers
|
||||
import { generateTitle } from "./helper";
|
||||
// types
|
||||
import { TFilters } from "@plane/types";
|
||||
import { TViewFilters, TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewAppliedFilters = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
filterKey: keyof TFilters;
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
};
|
||||
|
||||
export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, filterKey } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey } = props;
|
||||
|
||||
const view = useViewDetail("WORKSPACE", workspaceSlug, projectId, viewId);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
const filterKeyValue =
|
||||
view?.appliedFilters?.filters && !isEmpty(view?.appliedFilters?.filters)
|
||||
? view?.appliedFilters?.filters?.[filterKey] || undefined
|
||||
viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters)
|
||||
? viewDetailStore?.appliedFilters?.filters?.[filterKey] || undefined
|
||||
: undefined;
|
||||
|
||||
if (!filterKeyValue || filterKeyValue.length <= 0) return <></>;
|
||||
return (
|
||||
<div key={filterKey} className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5">
|
||||
<div className="flex-shrink-0 text-xs">{generateTitle(filterKey)}</div>
|
||||
<div className="border border-red-500 relative flex items-center">
|
||||
{/* <div className="relative flex items-center">
|
||||
<div>Icon</div>
|
||||
<div>Title</div>
|
||||
<div>Close</div>
|
||||
</div>
|
||||
const clearFilter = () => viewDetailStore?.setFilters({ [filterKey]: [] });
|
||||
|
||||
<div>
|
||||
<div>Icon</div>
|
||||
<div>Title</div>
|
||||
<div>Close</div>
|
||||
</div> */}
|
||||
if (!filterKeyValue || filterKeyValue.length <= -1) return <></>;
|
||||
return (
|
||||
<div
|
||||
key={filterKey}
|
||||
className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 min-h-[32px]"
|
||||
>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-200">{generateTitle(filterKey)}</div>
|
||||
<div className="relative flex items-center gap-1 flex-wrap">
|
||||
{["1", "2", "3", "4"].map((filterId) => (
|
||||
<ViewAppliedFiltersItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
filterId={filterId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 rounded-full cursor-pointer transition-all bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200">
|
||||
<div
|
||||
className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 rounded-full cursor-pointer transition-all bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200"
|
||||
onClick={clearFilter}
|
||||
>
|
||||
<X size={10} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,45 +1,66 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// components
|
||||
import { ViewAppliedFilters } from "./filter";
|
||||
// types
|
||||
import { TFilters } from "@plane/types";
|
||||
import { TViewTypes, TViewFilters } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
|
||||
type TViewAppliedFiltersRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType } = props;
|
||||
// hooks
|
||||
const view = useViewDetail("WORKSPACE", workspaceSlug, projectId, viewId);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
const filterKeys =
|
||||
view?.appliedFilters && !isEmpty(view?.appliedFilters?.filters)
|
||||
? Object.keys(view?.appliedFilters?.filters)
|
||||
viewDetailStore?.appliedFilters && !isEmpty(viewDetailStore?.appliedFilters?.filters)
|
||||
? Object.keys(viewDetailStore?.appliedFilters?.filters)
|
||||
: undefined;
|
||||
|
||||
const clearAllFilters = () => {
|
||||
const clearedFilters: Partial<Record<keyof TViewFilters, string[]>> = {};
|
||||
filterKeys?.forEach((key) => {
|
||||
const _key = key as keyof TViewFilters;
|
||||
clearedFilters[_key] = [];
|
||||
});
|
||||
viewDetailStore?.setFilters(clearedFilters);
|
||||
};
|
||||
|
||||
if (!filterKeys) return <></>;
|
||||
return (
|
||||
<div className="relative flex items-center gap-2 flex-wrap border border-red-500 p-4">
|
||||
<div className="relative flex items-center gap-2 flex-wrap">
|
||||
{filterKeys.map((key) => {
|
||||
const filterKey = key as keyof TFilters;
|
||||
const filterKey = key as keyof TViewFilters;
|
||||
return (
|
||||
<ViewAppliedFilters
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 cursor-pointer transition-all hover:bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100 min-h-[32px]"
|
||||
onClick={clearAllFilters}
|
||||
>
|
||||
<div className="text-xs">Clear All</div>
|
||||
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4">
|
||||
<X size={10} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,10 +1,14 @@
|
||||
export * from "./root";
|
||||
export * from "./all-issues-root";
|
||||
|
||||
// views
|
||||
export * from "./views/root";
|
||||
export * from "./views/view-item";
|
||||
export * from "./views/create-edit";
|
||||
export * from "./views/create-edit-form";
|
||||
|
||||
// layouts
|
||||
export * from "./layout";
|
||||
|
||||
// view filters
|
||||
export * from "./filters/root";
|
||||
|
||||
|
54
web/components/view/layout.tsx
Normal file
54
web/components/view/layout.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { LucideIcon, List, Kanban, Calendar, Sheet, GanttChartSquare } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "./types";
|
||||
|
||||
type TViewLayoutRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
const LAYOUTS_DATA: { key: string; title: string; icon: LucideIcon }[] = [
|
||||
{ key: "list", title: "List Layout", icon: List },
|
||||
{ key: "kanban", title: "Kanban Layout", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar Layout", icon: Calendar },
|
||||
{ key: "spreadsheet", title: "Spreadsheet Layout", icon: Sheet },
|
||||
{ key: "gantt", title: "Gantt Chart layout", icon: GanttChartSquare },
|
||||
];
|
||||
|
||||
export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
return (
|
||||
<div className="relative flex gap-0.5 items-center bg-custom-background-80 rounded-md p-1">
|
||||
{LAYOUTS_DATA.map((layout) => (
|
||||
<Tooltip tooltipContent={layout.title} position="bottom">
|
||||
<div
|
||||
key={layout.key}
|
||||
className={`relative h-[24px] w-7 flex justify-center items-center overflow-hidden rounded transition-all cursor-pointer
|
||||
${
|
||||
viewDetailStore?.filtersToUpdate?.display_filters?.layout === layout.key
|
||||
? `bg-custom-background-100 shadow-custom-shadow-2xs`
|
||||
: `hover:bg-custom-background-100`
|
||||
}
|
||||
`}
|
||||
onClick={() => viewOperations.setDisplayFilters({ layout: layout.key })}
|
||||
>
|
||||
<layout.icon size={12} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,77 +0,0 @@
|
||||
import { FC, ReactNode, useEffect, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useView } from "hooks/store/use-view";
|
||||
// components
|
||||
import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot } from "./";
|
||||
// types
|
||||
import { TViewOperations } from "./types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
type TWorkspaceViewRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string | undefined;
|
||||
viewType: TViewTypes;
|
||||
};
|
||||
|
||||
export const WorkspaceViewRoot: FC<TWorkspaceViewRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType } = props;
|
||||
// hooks
|
||||
const views = useView(workspaceSlug, projectId, viewType);
|
||||
|
||||
const viewOperations: TViewOperations = useMemo(
|
||||
() => ({
|
||||
create: async (data) => {
|
||||
await views?.create(data);
|
||||
},
|
||||
fetch: async () => {
|
||||
await views?.fetch();
|
||||
},
|
||||
}),
|
||||
[views]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceSlug && viewId && viewOperations) viewOperations.fetch();
|
||||
}, [workspaceSlug, viewId, viewOperations]);
|
||||
|
||||
console.log("views?.viewMap", Object.keys(views?.viewMap).length);
|
||||
|
||||
Object.keys(views?.viewMap).map((viewId) => {
|
||||
console.log(views?.viewMap?.[viewId]?.access);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full border border-red-500">
|
||||
<ViewCreateEdit
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
|
||||
{/* <ViewRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/> */}
|
||||
|
||||
{/* <ViewFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={"61d6b507-ae5c-45d6-b169-da7162f016a0"}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={"61d6b507-ae5c-45d6-b169-da7162f016a0"}
|
||||
viewOperations={viewOperations}
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
});
|
9
web/components/view/types.d.ts
vendored
9
web/components/view/types.d.ts
vendored
@ -1,6 +1,11 @@
|
||||
import { TView } from "@plane/types";
|
||||
|
||||
export type TViewOperations = {
|
||||
create: (data: Partial<TView>) => void;
|
||||
fetch: () => void;
|
||||
localViewCreate: (data: TView) => void;
|
||||
clearLocalView: (viewId: string) => void;
|
||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
fetch: () => Promise<void>;
|
||||
create: (data: Partial<TView>) => Promise<void>;
|
||||
};
|
||||
|
@ -1,23 +1,40 @@
|
||||
import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Trash2, Plus, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// components
|
||||
import { ViewAppliedFiltersRoot } from "../";
|
||||
// ui
|
||||
import { Input, Button } from "@plane/ui";
|
||||
// types
|
||||
import { TView, TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
|
||||
type TViewCreateEditForm = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
modalToggle: boolean;
|
||||
|
||||
handleModalClose: () => void;
|
||||
viewOperations?: TViewOperations;
|
||||
onSubmit: (viewData: Partial<TView>) => void;
|
||||
};
|
||||
|
||||
export const ViewCreateEditForm: FC<TViewCreateEditForm> = (props) => {
|
||||
const { modalToggle, handleModalClose, viewOperations } = props;
|
||||
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, modalToggle, handleModalClose, onSubmit } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
const createView = () => {
|
||||
viewOperations?.create({ name: "create" });
|
||||
const onContinue = async () => {
|
||||
const payload: Partial<TView> = {
|
||||
id: viewDetailStore?.id,
|
||||
name: viewDetailStore?.name,
|
||||
filters: viewDetailStore?.filters,
|
||||
};
|
||||
onSubmit(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -48,23 +65,24 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = (props) => {
|
||||
>
|
||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem] py-5 border-[0.1px] border-custom-border-100">
|
||||
<div className="p-3 px-5 relative flex items-center gap-2">
|
||||
<div className="relative rounded p-1.5 px-2 flex items-center gap-1 border border-custom-border-100 bg-custom-background-80">
|
||||
{/* <div className="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">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<div className="text-xs uppercase">Project Identifier</div>
|
||||
</div>
|
||||
<div className="">Create|Edit View</div>
|
||||
</div> */}
|
||||
<div className="">Create View</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 px-5">
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
// value={value}
|
||||
// onChange={onChange}
|
||||
// hasError={Boolean(errors.email)}
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
value={viewDetailStore?.name || ""}
|
||||
onChange={(e) => {
|
||||
viewDetailStore?.setName(e.target.value);
|
||||
}}
|
||||
placeholder="What do you want to call this view?"
|
||||
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||
autoFocus
|
||||
@ -86,13 +104,23 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 px-5 relative bg-custom-background-80">Applied Filters with each dropdown</div>
|
||||
<div className="p-3 px-5 relative bg-custom-background-80">
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-3 px-5 relative flex justify-end items-center gap-2">
|
||||
<Button variant="neutral-primary" onClick={handleModalClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary">Create View</Button>
|
||||
<Button variant="primary" onClick={onContinue}>
|
||||
Create View
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
@ -101,4 +129,4 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = (props) => {
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { FC, useState } from "react";
|
||||
import { FC, ReactNode, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { ViewCreateEditForm } from "./create-edit-form";
|
||||
// constants
|
||||
import { viewLocalPayload } from "constants/view";
|
||||
// types
|
||||
import { TViewOperations } from "../types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
import { TView, TViewFilters, TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewCreateEdit = {
|
||||
workspaceSlug: string;
|
||||
@ -14,29 +17,72 @@ type TViewCreateEdit = {
|
||||
viewId: string | undefined;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const ViewCreateEdit: FC<TViewCreateEdit> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewOperations } = props;
|
||||
export const ViewCreateEdit: FC<TViewCreateEdit> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, children } = props;
|
||||
// states
|
||||
const [currentViewId, setCurrentViewId] = useState<string>();
|
||||
const [currentFilters, setCurrentFilters] = useState<Partial<TViewFilters>>({});
|
||||
const [modalToggle, setModalToggle] = useState(false);
|
||||
|
||||
const handleModalOpen = () => setModalToggle(true);
|
||||
const handleModalClose = () => setModalToggle(false);
|
||||
const handleModalOpen = () => {
|
||||
if (viewId === undefined) {
|
||||
const viewPayload = viewLocalPayload;
|
||||
setCurrentViewId(viewPayload.id);
|
||||
viewOperations?.localViewCreate(viewPayload as TView);
|
||||
} else {
|
||||
setCurrentViewId(viewId);
|
||||
}
|
||||
setModalToggle(true);
|
||||
};
|
||||
|
||||
const createView = () => {
|
||||
viewOperations?.create({ name: "create" });
|
||||
const handleModalClose = () => {
|
||||
if (viewId === undefined) {
|
||||
if (currentViewId) viewOperations?.clearLocalView(currentViewId);
|
||||
} else {
|
||||
}
|
||||
setModalToggle(false);
|
||||
setCurrentViewId(undefined);
|
||||
setCurrentFilters({});
|
||||
};
|
||||
|
||||
const onSubmit = async (viewData: Partial<TView>) => {
|
||||
if (!viewData?.name) return;
|
||||
try {
|
||||
await viewOperations.create(viewData);
|
||||
handleModalClose();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewCreateEditForm modalToggle={modalToggle} handleModalClose={handleModalClose} />
|
||||
<div className="border border-red-500 p-5">
|
||||
<Button size="sm" className="flex justify-center items-center" onClick={createView}>
|
||||
<Plus size={12} />
|
||||
<span>New View</span>
|
||||
</Button>
|
||||
{currentViewId && (
|
||||
<ViewCreateEditForm
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={currentViewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
onSubmit={onSubmit}
|
||||
modalToggle={modalToggle}
|
||||
handleModalClose={handleModalClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="inline-block" onClick={handleModalOpen}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<Button size="sm" className="flex justify-center items-center">
|
||||
<Plus size={12} />
|
||||
<span>New View</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { FC } from "react";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useView } from "hooks/store";
|
||||
// components
|
||||
import { ViewItem, ViewCreateEdit } from "../";
|
||||
// types
|
||||
import { TViewOperations } from "../types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
@ -12,25 +16,36 @@ type TViewRoot = {
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
export const ViewRoot: FC<TViewRoot> = (props) => {
|
||||
const {} = props;
|
||||
export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
|
||||
return (
|
||||
<div className="border border-red-500 relative flex items-center gap-2">
|
||||
{/* header */}
|
||||
<div className="">Workspace Views</div>
|
||||
{/* divider */}
|
||||
<div className="relative w-[50px] h-[50px] flex justify-center items-center">
|
||||
<div className="absolute top-0 bottom-0 border border-red-500 w-[0.5px]" />
|
||||
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center rounded-full bg-red-500">
|
||||
<ChevronRight size={12} />
|
||||
<div className="border-b border-custom-border-100 relative flex px-5 gap-2">
|
||||
{viewStore?.viewIds && viewStore?.viewIds.length > 0 && (
|
||||
<div key={`views_list_${viewId}`} className="relative flex items-center w-full overflow-x-auto">
|
||||
{viewStore?.viewIds.map((_viewId) => (
|
||||
<ViewItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewItemId={_viewId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* views content */}
|
||||
<div className=" relative flex items-center">
|
||||
<div>Icon</div>
|
||||
<div>Title</div>
|
||||
)}
|
||||
|
||||
<div className="flex-shrink-0 my-auto pb-1">
|
||||
<ViewCreateEdit
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={undefined}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
46
web/components/view/views/view-item.tsx
Normal file
46
web/components/view/views/view-item.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { FC } from "react";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useView } from "hooks/store";
|
||||
// ui
|
||||
import { PhotoFilterIcon } from "@plane/ui";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewItem = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string | undefined;
|
||||
viewType: TViewTypes;
|
||||
viewItemId: string;
|
||||
};
|
||||
|
||||
export const ViewItem: FC<TViewItem> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewItemId } = props;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
|
||||
const view = viewStore?.viewById(viewItemId);
|
||||
|
||||
if (!view) return <></>;
|
||||
return (
|
||||
<div key={viewItemId} className="space-y-0.5 relative h-full flex flex-col justify-between">
|
||||
<Link
|
||||
href={`/${workspaceSlug}/workspace-views/${viewItemId}`}
|
||||
className={`cursor-pointer relative p-2 px-2.5 flex justify-center items-center gap-1.5 rounded transition-all hover:bg-custom-background-80
|
||||
${viewItemId === viewId ? `text-custom-primary-100 bg-custom-primary-100/10` : `border-transparent`}
|
||||
`}
|
||||
onClick={(e) => viewItemId === viewId && e.preventDefault()}
|
||||
>
|
||||
<div className="flex-shrink-0 bg-custom-background-80 rounded-sm relative w-5 h-5 flex justify-center items-center overflow-hidden">
|
||||
<PhotoFilterIcon className="w-3 h-3" />
|
||||
</div>
|
||||
<div className="w-full max-w-[80px] inline-block text-sm line-clamp-1 truncate overflow-hidden font-medium">
|
||||
{view?.name}
|
||||
</div>
|
||||
</Link>
|
||||
<div className={`border-b-2 ${viewItemId === viewId ? `border-custom-primary-100` : `border-transparent`}`} />
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,47 +0,0 @@
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
export const VIEW_TYPES: Record<TViewTypes, TViewTypes> = {
|
||||
WORKSPACE_YOUR_VIEWS: "WORKSPACE_YOUR_VIEWS",
|
||||
WORKSPACE_VIEWS: "WORKSPACE_VIEWS",
|
||||
WORKSPACE_PROJECT_VIEWS: "WORKSPACE_PROJECT_VIEWS",
|
||||
PROJECT_VIEWS: "PROJECT_VIEWS",
|
||||
PROJECT_YOUR_VIEWS: "PROJECT_YOUR_VIEWS",
|
||||
};
|
||||
|
||||
export const VIEW_DEFAULT_FILTER_PARAMETERS = {
|
||||
filters: {
|
||||
default: [
|
||||
"project",
|
||||
"priority",
|
||||
"state",
|
||||
"state_group",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"subscriber",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
},
|
||||
display_filters: {
|
||||
default: ["layout", "group_by", "sub_group_by", "order_by", "type", "sub_issue", "show_empty_groups", "calendar"],
|
||||
},
|
||||
display_properties: {
|
||||
default: [
|
||||
"assignee",
|
||||
"start_date",
|
||||
"due_date",
|
||||
"labels",
|
||||
"key",
|
||||
"priority",
|
||||
"state",
|
||||
"sub_issue_count",
|
||||
"link",
|
||||
"attachment_count",
|
||||
"estimate",
|
||||
"created_on",
|
||||
"updated_on",
|
||||
],
|
||||
},
|
||||
};
|
218
web/constants/view/filters.ts
Normal file
218
web/constants/view/filters.ts
Normal file
@ -0,0 +1,218 @@
|
||||
// types
|
||||
import { TViewFilters, TViewDisplayFilters, TViewLayouts } from "@plane/types";
|
||||
|
||||
type TViewLayoutFilterProperties = {
|
||||
filters: Partial<keyof TViewFilters>[];
|
||||
display_filters: Partial<keyof TViewDisplayFilters>[];
|
||||
extra_options: ("sub_issue" | "show_empty_groups")[];
|
||||
display_properties: boolean;
|
||||
};
|
||||
|
||||
type TViewLayoutFilters = {
|
||||
list: TViewLayoutFilterProperties;
|
||||
kanban: TViewLayoutFilterProperties;
|
||||
calendar: TViewLayoutFilterProperties;
|
||||
spreadsheet: TViewLayoutFilterProperties;
|
||||
gantt: TViewLayoutFilterProperties;
|
||||
};
|
||||
|
||||
type TFilterPermissions = {
|
||||
all: Omit<TViewLayoutFilters, "list" | "kanban" | "calendar" | "gantt"> & {
|
||||
layouts: Omit<TViewLayouts, "list" | "kanban" | "calendar" | "gantt">[];
|
||||
};
|
||||
profile: Omit<TViewLayoutFilters, "spreadsheet" | "calendar" | "gantt"> & {
|
||||
layouts: Omit<TViewLayouts, "spreadsheet" | "calendar" | "gantt">[];
|
||||
};
|
||||
project: TViewLayoutFilters & {
|
||||
layouts: TViewLayouts[];
|
||||
};
|
||||
archived: Omit<TViewLayoutFilters, "kanban" | "spreadsheet" | "calendar" | "gantt"> & {
|
||||
layouts: Omit<TViewLayouts, "kanban" | "spreadsheet" | "calendar" | "gantt">[];
|
||||
};
|
||||
draft: Omit<TViewLayoutFilters, "spreadsheet" | "calendar" | "gantt"> & {
|
||||
layouts: Omit<TViewLayouts, "kanban" | "spreadsheet" | "calendar" | "gantt">[];
|
||||
};
|
||||
};
|
||||
|
||||
export const ALL_FILTER_PERMISSIONS: TFilterPermissions["all"] = {
|
||||
layouts: ["spreadsheet"],
|
||||
spreadsheet: {
|
||||
filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"],
|
||||
display_filters: ["type"],
|
||||
extra_options: [],
|
||||
display_properties: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const PROFILE_FILTER_PERMISSIONS: TFilterPermissions["profile"] = {
|
||||
layouts: ["list", "kanban"],
|
||||
list: {
|
||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||
display_filters: ["group_by", "order_by", "type"],
|
||||
extra_options: [],
|
||||
display_properties: true,
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||
display_filters: ["group_by", "order_by", "type"],
|
||||
extra_options: [],
|
||||
display_properties: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
|
||||
layouts: ["list", "kanban", "spreadsheet", "calendar", "gantt"],
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["group_by", "order_by", "type"],
|
||||
extra_options: ["sub_issue", "show_empty_groups"],
|
||||
display_properties: true,
|
||||
},
|
||||
kanban: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["group_by", "sub_group_by", "order_by", "type"],
|
||||
extra_options: ["sub_issue", "show_empty_groups"],
|
||||
display_properties: true,
|
||||
},
|
||||
calendar: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["type"],
|
||||
extra_options: ["sub_issue"],
|
||||
display_properties: true,
|
||||
},
|
||||
spreadsheet: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["order_by", "type"],
|
||||
extra_options: [],
|
||||
display_properties: true,
|
||||
},
|
||||
|
||||
gantt: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["order_by", "type"],
|
||||
extra_options: ["sub_issue"],
|
||||
display_properties: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const ARCHIVED_FILTER_PERMISSIONS: TFilterPermissions["archived"] = {
|
||||
layouts: ["list"],
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["group_by", "order_by"],
|
||||
extra_options: [],
|
||||
display_properties: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const DRAFT_FILTER_PERMISSIONS: TFilterPermissions["draft"] = {
|
||||
layouts: ["list", "kanban"],
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["group_by", "order_by", "type"],
|
||||
extra_options: ["sub_issue", "show_empty_groups"],
|
||||
display_properties: true,
|
||||
},
|
||||
kanban: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"module",
|
||||
"cycle",
|
||||
],
|
||||
display_filters: ["group_by", "sub_group_by", "order_by", "type"],
|
||||
extra_options: ["sub_issue", "show_empty_groups"],
|
||||
display_properties: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const VIEW_DEFAULT_FILTER_PARAMETERS: TFilterPermissions = {
|
||||
all: ALL_FILTER_PERMISSIONS,
|
||||
profile: PROFILE_FILTER_PERMISSIONS,
|
||||
project: PROJECT_FILTER_PERMISSIONS,
|
||||
archived: ARCHIVED_FILTER_PERMISSIONS,
|
||||
draft: DRAFT_FILTER_PERMISSIONS,
|
||||
};
|
2
web/constants/view/index.ts
Normal file
2
web/constants/view/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./root";
|
||||
export * from "./filters";
|
21
web/constants/view/root.ts
Normal file
21
web/constants/view/root.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
// types
|
||||
import { TViewTypes, TView } from "@plane/types";
|
||||
|
||||
export const VIEW_TYPES: Record<TViewTypes, TViewTypes> = {
|
||||
WORKSPACE_YOUR_VIEWS: "WORKSPACE_YOUR_VIEWS",
|
||||
WORKSPACE_VIEWS: "WORKSPACE_VIEWS",
|
||||
PROJECT_VIEWS: "PROJECT_VIEWS",
|
||||
PROJECT_YOUR_VIEWS: "PROJECT_YOUR_VIEWS",
|
||||
};
|
||||
|
||||
export const viewLocalPayload: Partial<TView> = {
|
||||
id: uuidV4(),
|
||||
name: "",
|
||||
description: "",
|
||||
filters: {},
|
||||
display_filters: {},
|
||||
display_properties: {},
|
||||
is_local_view: false,
|
||||
is_create: true,
|
||||
};
|
@ -15,17 +15,13 @@ export const useViewDetail = (
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useViewDetail must be used within StoreProvider");
|
||||
|
||||
if (!workspaceSlug) throw new Error("useViewDetail hook must require workspaceSlug");
|
||||
|
||||
if (!viewId) throw new Error("useViewDetail hook must require viewId");
|
||||
if (!workspaceSlug || !viewId) return undefined;
|
||||
|
||||
switch (viewType) {
|
||||
case "WORKSPACE_YOUR_VIEWS":
|
||||
return context.view.workspaceViewStore.viewById(viewId);
|
||||
return context.view.workspaceViewMeStore.viewById(viewId);
|
||||
case "WORKSPACE_VIEWS":
|
||||
return context.view.workspaceViewMeStore.viewById(viewId);
|
||||
case "WORKSPACE_PROJECT_VIEWS":
|
||||
return context.view.workspaceViewMeStore.viewById(viewId);
|
||||
return context.view.workspaceViewStore.viewById(viewId);
|
||||
case "PROJECT_YOUR_VIEWS":
|
||||
if (!projectId) throw new Error("useView hook must require projectId");
|
||||
return context.view.projectViewMeStore.viewById(viewId);
|
||||
@ -33,6 +29,6 @@ export const useViewDetail = (
|
||||
if (!projectId) throw new Error("useView hook must require projectId");
|
||||
return context.view.projectViewStore.viewById(viewId);
|
||||
default:
|
||||
throw new Error("useView hook must require viewType");
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { useContext } from "react";
|
||||
// mobx store
|
||||
import { StoreContext } from "contexts/store-context";
|
||||
// types
|
||||
import { ViewRoot } from "store/view/view-root.store";
|
||||
import { ViewRootStore } from "store/view/view-root.store";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
@ -10,19 +10,17 @@ export const useView = (
|
||||
workspaceSlug: string,
|
||||
projectId: string | undefined,
|
||||
viewType: TViewTypes | undefined
|
||||
): ViewRoot => {
|
||||
): ViewRootStore | undefined => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useView must be used within StoreProvider");
|
||||
|
||||
if (!workspaceSlug) throw new Error("useView hook must require workspaceSlug");
|
||||
if (!workspaceSlug || !viewType) return undefined;
|
||||
|
||||
switch (viewType) {
|
||||
case "WORKSPACE_YOUR_VIEWS":
|
||||
return context.view.workspaceViewStore;
|
||||
return context.view.workspaceViewMeStore;
|
||||
case "WORKSPACE_VIEWS":
|
||||
return context.view.workspaceViewMeStore;
|
||||
case "WORKSPACE_PROJECT_VIEWS":
|
||||
return context.view.workspaceViewMeStore;
|
||||
return context.view.workspaceViewStore;
|
||||
case "PROJECT_YOUR_VIEWS":
|
||||
if (!projectId) throw new Error("useView hook must require projectId");
|
||||
return context.view.projectViewMeStore;
|
||||
@ -30,6 +28,6 @@ export const useView = (
|
||||
if (!projectId) throw new Error("useView hook must require projectId");
|
||||
return context.view.projectViewStore;
|
||||
default:
|
||||
throw new Error("useView hook must require viewType");
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
@ -56,6 +56,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
||||
>
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
</PostHogProvider>
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
</CrispWrapper>
|
||||
</StoreWrapper>
|
||||
</InstanceLayout>
|
||||
|
@ -1,24 +1,34 @@
|
||||
import { ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { GlobalViewsHeader } from "components/workspace";
|
||||
import { AllIssueLayoutRoot } from "components/issues";
|
||||
import { GlobalIssuesHeader } from "components/headers";
|
||||
// import { GlobalViewsHeader } from "components/workspace";
|
||||
// import { AllIssueLayoutRoot } from "components/issues";
|
||||
// import { GlobalIssuesHeader } from "components/headers";
|
||||
import { AllIssuesViewRoot } from "components/view";
|
||||
// types
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
|
||||
const GlobalViewIssuesPage: NextPageWithLayout = () => (
|
||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
||||
<GlobalViewsHeader />
|
||||
<AllIssueLayoutRoot />
|
||||
const GlobalViewIssuesPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, globalViewId: viewId } = router.query;
|
||||
|
||||
if (!workspaceSlug || !viewId) return <></>;
|
||||
return (
|
||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
||||
<AllIssuesViewRoot workspaceSlug={workspaceSlug.toString()} projectId={undefined} viewId={viewId.toString()} />
|
||||
{/* <GlobalViewsHeader />
|
||||
<AllIssueLayoutRoot /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;
|
||||
// return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;
|
||||
return <AppLayout header={<></>}>{page}</AppLayout>;
|
||||
};
|
||||
|
||||
export default GlobalViewIssuesPage;
|
||||
|
@ -11,7 +11,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService {
|
||||
}
|
||||
|
||||
async fetch(workspaceSlug: string): Promise<TView[]> {
|
||||
return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/?type=workspace`)
|
||||
return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
|
@ -11,17 +11,19 @@ import {
|
||||
export class FiltersHelper {
|
||||
// computed filters
|
||||
computedFilters = (filters: TViewFilters, defaultValues?: Partial<TViewFilters>): TViewFilters => ({
|
||||
project: filters?.project || defaultValues?.project || [],
|
||||
priority: filters?.priority || defaultValues?.priority || [],
|
||||
state: filters?.state || defaultValues?.state || [],
|
||||
state_group: filters?.state_group || defaultValues?.state_group || [],
|
||||
assignees: filters?.assignees || defaultValues?.assignees || [],
|
||||
mentions: filters?.mentions || defaultValues?.mentions || [],
|
||||
subscriber: filters?.subscriber || defaultValues?.subscriber || [],
|
||||
created_by: filters?.created_by || defaultValues?.created_by || [],
|
||||
labels: filters?.labels || defaultValues?.labels || [],
|
||||
start_date: filters?.start_date || defaultValues?.start_date || [],
|
||||
target_date: filters?.target_date || defaultValues?.target_date || [],
|
||||
project: defaultValues?.project || filters?.project || [],
|
||||
module: defaultValues?.module || filters?.module || [],
|
||||
cycle: defaultValues?.cycle || filters?.cycle || [],
|
||||
priority: defaultValues?.priority || filters?.priority || [],
|
||||
state: defaultValues?.state || filters?.state || [],
|
||||
state_group: defaultValues?.state_group || filters?.state_group || [],
|
||||
assignees: defaultValues?.assignees || filters?.assignees || [],
|
||||
mentions: defaultValues?.mentions || filters?.mentions || [],
|
||||
subscriber: defaultValues?.subscriber || filters?.subscriber || [],
|
||||
created_by: defaultValues?.created_by || filters?.created_by || [],
|
||||
labels: defaultValues?.labels || filters?.labels || [],
|
||||
start_date: defaultValues?.start_date || filters?.start_date || [],
|
||||
target_date: defaultValues?.target_date || filters?.target_date || [],
|
||||
});
|
||||
|
||||
// computed display filters
|
||||
@ -29,16 +31,16 @@ export class FiltersHelper {
|
||||
displayFilters: TViewDisplayFilters,
|
||||
defaultValues?: Partial<TViewDisplayFilters>
|
||||
): TViewDisplayFilters => ({
|
||||
layout: displayFilters?.layout || defaultValues?.layout || "list",
|
||||
group_by: displayFilters?.group_by || defaultValues?.group_by || "none",
|
||||
sub_group_by: displayFilters?.sub_group_by || defaultValues?.sub_group_by || undefined,
|
||||
order_by: displayFilters?.order_by || defaultValues?.order_by || "sort_order",
|
||||
type: displayFilters?.type || defaultValues?.type || undefined,
|
||||
sub_issue: displayFilters?.sub_issue || defaultValues?.sub_issue || false,
|
||||
show_empty_groups: displayFilters?.show_empty_groups || defaultValues?.show_empty_groups || false,
|
||||
layout: defaultValues?.layout || displayFilters?.layout || "list",
|
||||
group_by: defaultValues?.group_by || displayFilters?.group_by || undefined,
|
||||
sub_group_by: defaultValues?.sub_group_by || displayFilters?.sub_group_by || undefined,
|
||||
order_by: defaultValues?.order_by || displayFilters?.order_by || "sort_order",
|
||||
type: defaultValues?.type || displayFilters?.type || undefined,
|
||||
sub_issue: defaultValues?.sub_issue || displayFilters?.sub_issue || false,
|
||||
show_empty_groups: defaultValues?.show_empty_groups || displayFilters?.show_empty_groups || false,
|
||||
calendar: {
|
||||
show_weekends: displayFilters?.calendar?.show_weekends || defaultValues?.calendar?.show_weekends || false,
|
||||
layout: displayFilters?.calendar?.layout || defaultValues?.calendar?.layout || "month",
|
||||
show_weekends: defaultValues?.calendar?.show_weekends || displayFilters?.calendar?.show_weekends || false,
|
||||
layout: defaultValues?.calendar?.layout || displayFilters?.calendar?.layout || "month",
|
||||
},
|
||||
});
|
||||
|
||||
@ -47,19 +49,19 @@ export class FiltersHelper {
|
||||
displayProperties: TViewDisplayProperties,
|
||||
defaultValues?: Partial<TViewDisplayProperties>
|
||||
): TViewDisplayProperties => ({
|
||||
assignee: displayProperties?.assignee || defaultValues?.assignee || true,
|
||||
start_date: displayProperties?.start_date || defaultValues?.start_date || true,
|
||||
due_date: displayProperties?.due_date || defaultValues?.due_date || true,
|
||||
labels: displayProperties?.labels || defaultValues?.labels || true,
|
||||
priority: displayProperties?.priority || defaultValues?.priority || true,
|
||||
state: displayProperties?.state || defaultValues?.state || true,
|
||||
sub_issue_count: displayProperties?.sub_issue_count || defaultValues?.sub_issue_count || true,
|
||||
attachment_count: displayProperties?.attachment_count || defaultValues?.attachment_count || true,
|
||||
link: displayProperties?.link || defaultValues?.link || true,
|
||||
estimate: displayProperties?.estimate || defaultValues?.estimate || true,
|
||||
key: displayProperties?.key || defaultValues?.key || true,
|
||||
created_on: displayProperties?.created_on || defaultValues?.created_on || true,
|
||||
updated_on: displayProperties?.updated_on || defaultValues?.updated_on || true,
|
||||
assignee: defaultValues?.assignee || displayProperties?.assignee || true,
|
||||
start_date: defaultValues?.start_date || displayProperties?.start_date || true,
|
||||
due_date: defaultValues?.due_date || displayProperties?.due_date || true,
|
||||
labels: defaultValues?.labels || displayProperties?.labels || true,
|
||||
priority: defaultValues?.priority || displayProperties?.priority || true,
|
||||
state: defaultValues?.state || displayProperties?.state || true,
|
||||
sub_issue_count: defaultValues?.sub_issue_count || displayProperties?.sub_issue_count || true,
|
||||
attachment_count: defaultValues?.attachment_count || displayProperties?.attachment_count || true,
|
||||
link: defaultValues?.link || displayProperties?.link || true,
|
||||
estimate: defaultValues?.estimate || displayProperties?.estimate || true,
|
||||
key: defaultValues?.key || displayProperties?.key || true,
|
||||
created_on: defaultValues?.created_on || displayProperties?.created_on || true,
|
||||
updated_on: defaultValues?.updated_on || displayProperties?.updated_on || true,
|
||||
});
|
||||
|
||||
// compute filters and display_filters issue query parameters
|
||||
|
@ -1,5 +1,4 @@
|
||||
// services
|
||||
|
||||
import {
|
||||
WorkspaceViewService,
|
||||
WorkspaceMeViewService,
|
||||
@ -21,7 +20,6 @@ export class GlobalViewRootStore {
|
||||
// views root
|
||||
workspaceViewMeStore: ViewRootStore;
|
||||
workspaceViewStore: ViewRootStore;
|
||||
workspaceViewProjectStore: ViewRootStore;
|
||||
projectViewStore: ViewRootStore;
|
||||
projectViewMeStore: ViewRootStore;
|
||||
|
||||
@ -32,10 +30,41 @@ export class GlobalViewRootStore {
|
||||
cycleUserViewStore?: userViewRootStore;
|
||||
|
||||
constructor(private store: RootStore) {
|
||||
// views root
|
||||
const defaultViews: any[] = [
|
||||
{
|
||||
id: "all-issues",
|
||||
name: "All Issues",
|
||||
filters: {},
|
||||
is_local_view: true,
|
||||
},
|
||||
{
|
||||
id: "assigned",
|
||||
name: "Assigned",
|
||||
filters: {
|
||||
assignees: store.user?.currentUser?.id ? [store.user?.currentUser?.id] : [],
|
||||
},
|
||||
is_local_view: true,
|
||||
},
|
||||
{
|
||||
id: "created",
|
||||
name: "Created",
|
||||
filters: {
|
||||
created_by: store.user?.currentUser?.id ? [store.user?.currentUser?.id] : [],
|
||||
},
|
||||
is_local_view: true,
|
||||
},
|
||||
{
|
||||
id: "subscribed",
|
||||
name: "Subscribed",
|
||||
filters: {
|
||||
subscriber: store.user?.currentUser?.id ? [store.user?.currentUser?.id] : [],
|
||||
},
|
||||
is_local_view: true,
|
||||
},
|
||||
];
|
||||
|
||||
this.workspaceViewMeStore = new ViewRootStore(this.store, new WorkspaceMeViewService());
|
||||
this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService());
|
||||
this.workspaceViewProjectStore = new ViewRootStore(this.store, new WorkspaceMeViewService());
|
||||
this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), defaultViews);
|
||||
this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService());
|
||||
this.projectViewMeStore = new ViewRootStore(this.store, new ProjectViewMeService());
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import set from "lodash/set";
|
||||
// stores
|
||||
import { RootStore } from "store/root.store";
|
||||
import { UserViewStore } from "./view.store";
|
||||
// types
|
||||
import { TUserViewService } from "services/view/types";
|
||||
|
@ -7,33 +7,41 @@ import { ViewStore } from "./view.store";
|
||||
import { TViewService } from "services/view/types";
|
||||
import { TView } from "@plane/types";
|
||||
|
||||
export type TLoader = "" | undefined;
|
||||
export type TLoader = "init-loader" | "mutation-loader" | "submitting" | undefined;
|
||||
|
||||
type TViewRootStore = {
|
||||
// observables
|
||||
loader: TLoader;
|
||||
viewMap: Record<string, ViewStore>;
|
||||
// computed
|
||||
viewIds: string[];
|
||||
// helper actions
|
||||
viewById: (viewId: string) => ViewStore | undefined;
|
||||
// actions
|
||||
fetch: () => Promise<void>;
|
||||
fetch: (_loader?: TLoader) => Promise<void>;
|
||||
localViewCreate: (view: TView) => Promise<void>;
|
||||
clearLocalView: (viewId: string) => Promise<void>;
|
||||
create: (view: Partial<TView>) => Promise<void>;
|
||||
remove: (viewId: string) => Promise<void>;
|
||||
duplicate: (viewId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export class ViewRootStore implements TViewRootStore {
|
||||
// observables
|
||||
loader: TLoader = "init-loader";
|
||||
viewMap: Record<string, ViewStore> = {};
|
||||
|
||||
constructor(private store: RootStore, private service: TViewService) {
|
||||
constructor(private store: RootStore, private service: TViewService, private defaultViews: TView[] = []) {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
viewMap: observable.ref,
|
||||
loader: observable.ref,
|
||||
viewMap: observable,
|
||||
// computed
|
||||
viewIds: computed,
|
||||
// actions
|
||||
fetch: action,
|
||||
localViewCreate: action,
|
||||
clearLocalView: action,
|
||||
create: action,
|
||||
remove: action,
|
||||
duplicate: action,
|
||||
@ -42,17 +50,26 @@ export class ViewRootStore implements TViewRootStore {
|
||||
|
||||
// computed
|
||||
get viewIds() {
|
||||
return Object.keys(this.viewMap);
|
||||
const views = Object.values(this.viewMap);
|
||||
return views.filter((view) => !view?.is_create).map((view) => view.id) as string[];
|
||||
}
|
||||
|
||||
// helper actions
|
||||
viewById = (viewId: string) => this.viewMap?.[viewId] || undefined;
|
||||
|
||||
// actions
|
||||
fetch = async () => {
|
||||
fetch = async (_loader: TLoader = "init-loader") => {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
runInAction(() => {
|
||||
if (this.defaultViews && this.defaultViews.length > 0)
|
||||
this.defaultViews?.forEach((view) => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
});
|
||||
|
||||
this.loader = _loader;
|
||||
const views = await this.service.fetch(workspaceSlug, projectId);
|
||||
if (!views) return;
|
||||
|
||||
@ -60,6 +77,19 @@ export class ViewRootStore implements TViewRootStore {
|
||||
views.forEach((view) => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
this.loader = undefined;
|
||||
});
|
||||
};
|
||||
|
||||
localViewCreate = async (view: TView) => {
|
||||
runInAction(() => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
};
|
||||
|
||||
clearLocalView = async (viewId: string) => {
|
||||
runInAction(() => {
|
||||
if (viewId) delete this.viewMap[viewId];
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -26,9 +26,10 @@ export type TViewStore = TView & {
|
||||
appliedFilters: TViewFilterProps | undefined;
|
||||
appliedFiltersQueryParams: string | undefined;
|
||||
// helper actions
|
||||
updateFilters: (filters: Partial<TViewFilters>) => void;
|
||||
updateDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
updateDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
setName: (name: string) => void;
|
||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
resetFilterChanges: () => void;
|
||||
saveFilterChanges: () => void;
|
||||
// actions
|
||||
@ -46,19 +47,22 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
name: string | undefined;
|
||||
description: string | undefined;
|
||||
query: string | undefined;
|
||||
filters: TViewFilters | undefined;
|
||||
display_filters: TViewDisplayFilters | undefined;
|
||||
display_properties: TViewDisplayProperties | undefined;
|
||||
filters: TViewFilters;
|
||||
display_filters: TViewDisplayFilters;
|
||||
display_properties: TViewDisplayProperties;
|
||||
access: TViewAccess | undefined;
|
||||
owned_by: string | undefined;
|
||||
sort_order: number | undefined;
|
||||
is_locked: boolean | undefined;
|
||||
is_pinned: boolean | undefined;
|
||||
is_favorite: boolean | undefined;
|
||||
is_locked: boolean = false;
|
||||
is_pinned: boolean = false;
|
||||
is_favorite: boolean = false;
|
||||
created_by: string | undefined;
|
||||
updated_by: string | undefined;
|
||||
created_at: Date | undefined;
|
||||
updated_at: Date | undefined;
|
||||
// local variables
|
||||
is_local_view: boolean = false;
|
||||
is_create: boolean = false;
|
||||
|
||||
loader: TLoader = undefined;
|
||||
filtersToUpdate: TViewFilterPartialProps = {
|
||||
@ -75,11 +79,9 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
this.name = _view.name;
|
||||
this.description = _view.description;
|
||||
this.query = _view.query;
|
||||
this.filters = _view.filters ? this.computedFilters(_view.filters) : undefined;
|
||||
this.display_filters = _view.display_filters ? this.computedDisplayFilters(_view.display_filters) : undefined;
|
||||
this.display_properties = _view.display_properties
|
||||
? this.computedDisplayProperties(_view.display_properties)
|
||||
: undefined;
|
||||
this.filters = this.computedFilters(_view.filters);
|
||||
this.display_filters = this.computedDisplayFilters(_view.display_filters);
|
||||
this.display_properties = this.computedDisplayProperties(_view.display_properties);
|
||||
this.access = _view.access;
|
||||
this.owned_by = _view.owned_by;
|
||||
this.sort_order = _view.sort_order;
|
||||
@ -90,18 +92,42 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
this.updated_by = _view.updated_by;
|
||||
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;
|
||||
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
loader: observable,
|
||||
filtersToUpdate: observable.ref,
|
||||
id: observable.ref,
|
||||
workspace: observable.ref,
|
||||
project: observable.ref,
|
||||
name: observable.ref,
|
||||
description: observable.ref,
|
||||
query: observable.ref,
|
||||
filters: observable,
|
||||
display_filters: observable,
|
||||
display_properties: observable,
|
||||
access: observable.ref,
|
||||
owned_by: observable.ref,
|
||||
sort_order: observable.ref,
|
||||
is_locked: observable.ref,
|
||||
is_pinned: observable.ref,
|
||||
is_favorite: observable.ref,
|
||||
created_by: observable.ref,
|
||||
updated_by: observable.ref,
|
||||
created_at: observable.ref,
|
||||
updated_at: observable.ref,
|
||||
is_local_view: observable.ref,
|
||||
is_create: observable.ref,
|
||||
loader: observable.ref,
|
||||
filtersToUpdate: observable,
|
||||
// computed
|
||||
appliedFilters: computed,
|
||||
appliedFiltersQueryParams: computed,
|
||||
// helper actions
|
||||
updateFilters: action,
|
||||
updateDisplayFilters: action,
|
||||
updateDisplayProperties: action,
|
||||
setName: action,
|
||||
setFilters: action,
|
||||
setDisplayFilters: action,
|
||||
setDisplayProperties: action,
|
||||
resetFilterChanges: action,
|
||||
saveFilterChanges: action,
|
||||
// actions
|
||||
@ -114,13 +140,12 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
// computed
|
||||
get appliedFilters() {
|
||||
return {
|
||||
filters: this.filters ? this.computedFilters(this.filters, this.filtersToUpdate.filters) : undefined,
|
||||
display_filters: this.display_filters
|
||||
? this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters)
|
||||
: undefined,
|
||||
display_properties: this.display_properties
|
||||
? this.computedDisplayProperties(this.display_properties, this.filtersToUpdate.display_properties)
|
||||
: undefined,
|
||||
filters: this.computedFilters(this.filters, this.filtersToUpdate.filters),
|
||||
display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters),
|
||||
display_properties: this.computedDisplayProperties(
|
||||
this.display_properties,
|
||||
this.filtersToUpdate.display_properties
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -131,14 +156,23 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
}
|
||||
|
||||
// helper actions
|
||||
updateFilters = (filters: Partial<TViewFilters>) => {
|
||||
setName = (name: string) => {
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
this.filtersToUpdate.filters = filters;
|
||||
this.name = name;
|
||||
});
|
||||
};
|
||||
|
||||
updateDisplayFilters = async (display_filters: Partial<TViewDisplayFilters>) => {
|
||||
setFilters = (filters: Partial<TViewFilters>) => {
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
Object.keys(filters).forEach((key) => {
|
||||
const _key = key as keyof TViewFilters;
|
||||
set(this.filtersToUpdate, ["filters", _key], filters[_key]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
setDisplayFilters = async (display_filters: Partial<TViewDisplayFilters>) => {
|
||||
const appliedFilters = this.appliedFilters;
|
||||
|
||||
const layout = appliedFilters?.display_filters?.layout;
|
||||
@ -146,7 +180,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
const group_by = appliedFilters?.display_filters?.group_by;
|
||||
const sub_issue = appliedFilters?.display_filters?.sub_issue;
|
||||
|
||||
if (group_by === undefined) display_filters.sub_group_by = undefined;
|
||||
if (group_by === undefined && display_filters.sub_group_by) display_filters.sub_group_by = undefined;
|
||||
if (layout === "kanban") {
|
||||
if (sub_group_by === group_by) display_filters.group_by = undefined;
|
||||
if (group_by === null) display_filters.group_by = "state";
|
||||
@ -155,14 +189,22 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
this.filtersToUpdate.display_filters = display_filters;
|
||||
Object.keys(display_filters).forEach((key) => {
|
||||
const _key = key as keyof TViewDisplayFilters;
|
||||
set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]);
|
||||
});
|
||||
});
|
||||
|
||||
console.log("this.filtersToUpdate", this.filtersToUpdate?.display_filters?.layout);
|
||||
};
|
||||
|
||||
updateDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
||||
setDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
this.filtersToUpdate.display_properties = display_properties;
|
||||
Object.keys(display_properties).forEach((key) => {
|
||||
const _key = key as keyof TViewDisplayProperties;
|
||||
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user