chore: add empty state for list and spreadsheet layouts (#2531)

* chore: add empty state for list and spreadsheet layouts

* fix: build errors
This commit is contained in:
Aaryan Khandelwal 2023-10-30 20:09:04 +05:30 committed by GitHub
parent 8eaac60aa5
commit 050406b8a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 417 additions and 105 deletions

View File

@ -0,0 +1,25 @@
import { PlusIcon } from "lucide-react";
// components
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
export const CycleEmptyState: React.FC = () => (
<div className="h-full w-full grid place-items-center">
<EmptyState
title="Cycle issues will appear here"
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
primaryButton={{
text: "New issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
},
}}
/>
</div>
);

View File

@ -0,0 +1,25 @@
import { PlusIcon } from "lucide-react";
// components
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
export const GlobalViewEmptyState: React.FC = () => (
<div className="h-full w-full grid place-items-center">
<EmptyState
title="View issues will appear here"
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
primaryButton={{
text: "New issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
},
}}
/>
</div>
);

View File

@ -0,0 +1,5 @@
export * from "./cycle";
export * from "./global-view";
export * from "./module";
export * from "./project-view";
export * from "./project";

View File

@ -0,0 +1,25 @@
import { PlusIcon } from "lucide-react";
// components
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
export const ModuleEmptyState: React.FC = () => (
<div className="h-full w-full grid place-items-center">
<EmptyState
title="Module issues will appear here"
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
primaryButton={{
text: "New issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
},
}}
/>
</div>
);

View File

@ -0,0 +1,25 @@
import { PlusIcon } from "lucide-react";
// components
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
export const ProjectViewEmptyState: React.FC = () => (
<div className="h-full w-full grid place-items-center">
<EmptyState
title="View issues will appear here"
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
primaryButton={{
text: "New issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
},
}}
/>
</div>
);

View File

@ -0,0 +1,25 @@
import { PlusIcon } from "lucide-react";
// components
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
export const ProjectEmptyState: React.FC = () => (
<div className="h-full w-full grid place-items-center">
<EmptyState
title="Project issues will appear here"
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
primaryButton={{
text: "New issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
},
}}
/>
</div>
);

View File

@ -1,5 +1,6 @@
// filters
export * from "./filters";
export * from "./empty-states";
export * from "./quick-action-dropdowns";
// layouts

View File

@ -1,4 +1,5 @@
export * from "./roots";
export * from "./block";
export * from "./roots";
export * from "./blocks-list";
export * from "./inline-create-issue-form";

View File

@ -68,7 +68,7 @@ export const ModuleListLayout: React.FC = observer(() => {
: null;
return (
<div className={`relative w-full h-full bg-custom-background-90`}>
<div className="relative w-full h-full bg-custom-background-90">
<List
issues={issues}
group_by={group_by}

View File

@ -8,6 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import {
CycleAppliedFiltersRoot,
CycleCalendarLayout,
CycleEmptyState,
CycleGanttLayout,
CycleKanBanLayout,
CycleListLayout,
@ -50,12 +51,17 @@ export const CycleLayoutRoot: React.FC = observer(() => {
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date)
: "draft";
const issueCount = cycleIssueStore.getIssuesCount;
return (
<>
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
<div className="relative w-full h-full flex flex-col overflow-hidden">
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
<CycleAppliedFiltersRoot />
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
<CycleEmptyState />
) : (
<div className="w-full h-full overflow-auto">
{activeLayout === "list" ? (
<CycleListLayout />
@ -69,6 +75,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
<CycleSpreadsheetLayout />
) : null}
</div>
)}
</div>
</>
);

View File

@ -5,7 +5,7 @@ import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { GlobalViewsAppliedFiltersRoot, SpreadsheetView } from "components/issues";
import { GlobalViewEmptyState, GlobalViewsAppliedFiltersRoot, SpreadsheetView } from "components/issues";
// types
import { IIssue, IIssueDisplayFilterOptions, TStaticViewTypes } from "types";
@ -81,6 +81,9 @@ export const GlobalViewLayoutRoot: React.FC<Props> = observer((props) => {
return (
<div className="relative w-full h-full flex flex-col overflow-hidden">
<GlobalViewsAppliedFiltersRoot />
{issues?.length === 0 ? (
<GlobalViewEmptyState />
) : (
<div className="h-full w-full overflow-auto">
<SpreadsheetView
displayProperties={workspaceFilterStore.workspaceDisplayProperties}
@ -94,6 +97,7 @@ export const GlobalViewLayoutRoot: React.FC<Props> = observer((props) => {
disableUserActions={false}
/>
</div>
)}
</div>
);
});

View File

@ -9,6 +9,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import {
ModuleAppliedFiltersRoot,
ModuleCalendarLayout,
ModuleEmptyState,
ModuleGanttLayout,
ModuleKanBanLayout,
ModuleListLayout,
@ -46,9 +47,14 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
const activeLayout = issueFilterStore.userDisplayFilters.layout;
const issueCount = moduleIssueStore.getIssuesCount;
return (
<div className="relative w-full h-full flex flex-col overflow-hidden">
<ModuleAppliedFiltersRoot />
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
<ModuleEmptyState />
) : (
<div className="h-full w-full overflow-auto">
{activeLayout === "list" ? (
<ModuleListLayout />
@ -62,6 +68,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
<ModuleSpreadsheetLayout />
) : null}
</div>
)}
</div>
);
});

View File

@ -12,6 +12,7 @@ import {
KanBanLayout,
ProjectAppliedFiltersRoot,
ProjectSpreadsheetLayout,
ProjectEmptyState,
} from "components/issues";
export const ProjectLayoutRoot: React.FC = observer(() => {
@ -30,9 +31,14 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
const activeLayout = issueFilterStore.userDisplayFilters.layout;
const issueCount = issueStore.getIssuesCount;
return (
<div className="relative w-full h-full flex flex-col overflow-hidden">
<ProjectAppliedFiltersRoot />
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
<ProjectEmptyState />
) : (
<div className="w-full h-full overflow-auto">
{activeLayout === "list" ? (
<ListLayout />
@ -46,6 +52,7 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
<ProjectSpreadsheetLayout />
) : null}
</div>
)}
</div>
);
});

View File

@ -11,6 +11,7 @@ import {
ModuleListLayout,
ProjectViewAppliedFiltersRoot,
ProjectViewCalendarLayout,
ProjectViewEmptyState,
ProjectViewGanttLayout,
ProjectViewSpreadsheetLayout,
} from "components/issues";
@ -48,9 +49,14 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
const activeLayout = issueFilterStore.userDisplayFilters.layout;
const issueCount = projectViewIssuesStore.getIssuesCount;
return (
<div className="relative h-full w-full flex flex-col overflow-hidden">
<ProjectViewAppliedFiltersRoot />
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
<ProjectViewEmptyState />
) : (
<div className="h-full w-full overflow-y-auto">
{activeLayout === "list" ? (
<ModuleListLayout />
@ -64,6 +70,7 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
<ProjectViewSpreadsheetLayout />
) : null}
</div>
)}
</div>
);
});

View File

@ -6,15 +6,12 @@ import { IIssue } from "types";
// services
import { IssueService } from "services/issue";
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
export type IIssueGroupWithSubGroupsStructure = {
[group_id: string]: {
[sub_group_id: string]: IIssue[];
};
};
export type IIssueUnGroupedStructure = IIssue[];
import {
IIssueGroupWithSubGroupsStructure,
IIssueGroupedStructure,
IIssueType,
IIssueUnGroupedStructure,
} from "store/issue";
export interface IArchivedIssueStore {
loader: boolean;

View File

@ -9,15 +9,12 @@ import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
// types
import { IIssue } from "types";
import { IBlockUpdateData } from "components/gantt-chart";
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
export type IIssueGroupWithSubGroupsStructure = {
[group_id: string]: {
[sub_group_id: string]: IIssue[];
};
};
export type IIssueUnGroupedStructure = IIssue[];
import {
IIssueGroupWithSubGroupsStructure,
IIssueGroupedStructure,
IIssueType,
IIssueUnGroupedStructure,
} from "store/issue";
export interface ICycleIssueStore {
loader: boolean;
@ -33,6 +30,7 @@ export interface ICycleIssueStore {
// computed
getIssueType: IIssueType | null;
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
getIssuesCount: number;
// action
fetchIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
@ -73,6 +71,7 @@ export class CycleIssueStore implements ICycleIssueStore {
// computed
getIssueType: computed,
getIssues: computed,
getIssuesCount: computed,
// actions
fetchIssues: action,
updateIssueStructure: action,
@ -130,6 +129,44 @@ export class CycleIssueStore implements ICycleIssueStore {
return this.issues?.[cycleId]?.[issueType] || null;
}
get getIssuesCount() {
const issueType = this.getIssueType;
let issuesCount = 0;
if (issueType === "grouped") {
const issues = this.getIssues as IIssueGroupedStructure;
if (!issues) return 0;
Object.keys(issues).map((group_id) => {
issuesCount += issues[group_id].length;
});
}
if (issueType === "groupWithSubGroups") {
const issues = this.getIssues as IIssueGroupWithSubGroupsStructure;
if (!issues) return 0;
Object.keys(issues).map((sub_group_id) => {
Object.keys(issues[sub_group_id]).map((group_id) => {
issuesCount += issues[sub_group_id][group_id].length;
});
});
}
if (issueType === "ungrouped") {
const issues = this.getIssues as IIssueUnGroupedStructure;
if (!issues) return 0;
issuesCount = issues.length;
}
return issuesCount;
}
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
const cycleId: string | null = this.rootStore?.cycle?.cycleId || null;
const issueType = this.getIssueType;

View File

@ -1,7 +1,7 @@
import { action, makeObservable, runInAction } from "mobx";
// types
import { RootStore } from "../root";
import { IIssueType } from "./cycle_issue.store";
import { IIssueType } from "store/issue";
export interface ICycleIssueCalendarViewStore {
// actions

View File

@ -6,15 +6,12 @@ import { IIssue } from "types";
// services
import { IssueService } from "services/issue";
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
export type IIssueGroupWithSubGroupsStructure = {
[group_id: string]: {
[sub_group_id: string]: IIssue[];
};
};
export type IIssueUnGroupedStructure = IIssue[];
import {
IIssueGroupWithSubGroupsStructure,
IIssueGroupedStructure,
IIssueType,
IIssueUnGroupedStructure,
} from "store/issue";
export interface IDraftIssueStore {
loader: boolean;

View File

@ -31,6 +31,7 @@ export interface IIssueStore {
// computed
getIssueType: IIssueType | null;
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
getIssuesCount: number;
// action
fetchIssues: (workspaceSlug: string, projectId: string) => Promise<any>;
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
@ -68,6 +69,7 @@ export class IssueStore implements IIssueStore {
// computed
getIssueType: computed,
getIssues: computed,
getIssuesCount: computed,
// actions
fetchIssues: action,
updateIssueStructure: action,
@ -120,6 +122,44 @@ export class IssueStore implements IIssueStore {
return this.issues?.[projectId]?.[issueType] || null;
}
get getIssuesCount() {
const issueType = this.getIssueType;
let issuesCount = 0;
if (issueType === "grouped") {
const issues = this.getIssues as IIssueGroupedStructure;
if (!issues) return 0;
Object.keys(issues).map((group_id) => {
issuesCount += issues[group_id].length;
});
}
if (issueType === "groupWithSubGroups") {
const issues = this.getIssues as IIssueGroupWithSubGroupsStructure;
if (!issues) return 0;
Object.keys(issues).map((sub_group_id) => {
Object.keys(issues[sub_group_id]).map((group_id) => {
issuesCount += issues[sub_group_id][group_id].length;
});
});
}
if (issueType === "ungrouped") {
const issues = this.getIssues as IIssueUnGroupedStructure;
if (!issues) return 0;
issuesCount = issues.length;
}
return issuesCount;
}
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
const projectId: string | null = issue?.project;
const issueType = this.getIssueType;

View File

@ -8,15 +8,12 @@ import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
// types
import { IIssue } from "types";
import { IBlockUpdateData } from "components/gantt-chart";
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
export type IIssueGroupWithSubGroupsStructure = {
[group_id: string]: {
[sub_group_id: string]: IIssue[];
};
};
export type IIssueUnGroupedStructure = IIssue[];
import {
IIssueGroupWithSubGroupsStructure,
IIssueGroupedStructure,
IIssueType,
IIssueUnGroupedStructure,
} from "store/issue";
export interface IModuleIssueStore {
loader: boolean;
@ -32,6 +29,7 @@ export interface IModuleIssueStore {
// computed
getIssueType: IIssueType | null;
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
getIssuesCount: number;
// action
fetchIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<any>;
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
@ -76,6 +74,7 @@ export class ModuleIssueStore implements IModuleIssueStore {
// computed
getIssueType: computed,
getIssues: computed,
getIssuesCount: computed,
// actions
fetchIssues: action,
updateIssueStructure: action,
@ -132,6 +131,44 @@ export class ModuleIssueStore implements IModuleIssueStore {
return this.issues?.[moduleId]?.[issueType] || null;
}
get getIssuesCount() {
const issueType = this.getIssueType;
let issuesCount = 0;
if (issueType === "grouped") {
const issues = this.getIssues as IIssueGroupedStructure;
if (!issues) return 0;
Object.keys(issues).map((group_id) => {
issuesCount += issues[group_id].length;
});
}
if (issueType === "groupWithSubGroups") {
const issues = this.getIssues as IIssueGroupWithSubGroupsStructure;
if (!issues) return 0;
Object.keys(issues).map((sub_group_id) => {
Object.keys(issues[sub_group_id]).map((group_id) => {
issuesCount += issues[sub_group_id][group_id].length;
});
});
}
if (issueType === "ungrouped") {
const issues = this.getIssues as IIssueUnGroupedStructure;
if (!issues) return 0;
issuesCount = issues.length;
}
return issuesCount;
}
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
const moduleId: string | null = this.rootStore?.module?.moduleId;
const issueType = this.getIssueType;

View File

@ -1,7 +1,7 @@
import { action, makeObservable, runInAction } from "mobx";
// types
import { RootStore } from "../root";
import { IIssueType } from "./module_issue.store";
import { IIssueType } from "store/issue";
export interface IModuleIssueCalendarViewStore {
// actions

View File

@ -45,6 +45,7 @@ export interface IProjectViewIssuesStore {
// computed
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
getIssuesCount: number;
getIssueType: IIssueType | null;
}
@ -86,6 +87,7 @@ export class ProjectViewIssuesStore implements IProjectViewIssuesStore {
// computed
getIssueType: computed,
getIssues: computed,
getIssuesCount: computed,
});
this.rootStore = _rootStore;
@ -147,6 +149,44 @@ export class ProjectViewIssuesStore implements IProjectViewIssuesStore {
return this.viewIssues?.[viewId]?.[issueType] || null;
}
get getIssuesCount() {
const issueType = this.rootStore.issue.getIssueType;
let issuesCount = 0;
if (issueType === "grouped") {
const issues = this.getIssues as IIssueGroupedStructure;
if (!issues) return 0;
Object.keys(issues).map((group_id) => {
issuesCount += issues[group_id].length;
});
}
if (issueType === "groupWithSubGroups") {
const issues = this.getIssues as IIssueGroupWithSubGroupsStructure;
if (!issues) return 0;
Object.keys(issues).map((sub_group_id) => {
Object.keys(issues[sub_group_id]).map((group_id) => {
issuesCount += issues[sub_group_id][group_id].length;
});
});
}
if (issueType === "ungrouped") {
const issues = this.getIssues as IIssueUnGroupedStructure;
if (!issues) return 0;
issuesCount = issues.length;
}
return issuesCount;
}
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
const viewId: string | null = this.rootStore.projectViews.viewId;
const issueType = this.rootStore.issue.getIssueType;