chore: implemented CRUD operations in all the layouts (#2505)

* chore: basic crud operations added to the list view

* refactor: cycle details page

* refactor: module details page

* chore: added quick actions to kanban issue block

* chore: implement quick actions in calendar layout

* fix: custom menu component

* chore: separate quick action dropdowns implemented

* style: loader for calendar

* fix: build errors
This commit is contained in:
Aaryan Khandelwal
2023-10-20 17:07:46 +05:30
committed by GitHub
parent 9bddd2eb67
commit d78b4dccf3
76 changed files with 2336 additions and 1153 deletions

View File

@@ -1,16 +1,20 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useLocalStorage from "hooks/use-local-storage";
// components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics";
// ui
import { Button } from "@plane/ui";
import { Breadcrumbs, Button, CustomMenu } from "@plane/ui";
// icons
import { Plus } from "lucide-react";
import { ArrowRight, ContrastIcon, Plus } from "lucide-react";
// helpers
import { truncateText } from "helpers/string.helper";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants
@@ -28,9 +32,15 @@ export const CycleIssuesHeader: React.FC = observer(() => {
cycleIssueFilter: cycleIssueFilterStore,
project: projectStore,
} = useMobxStore();
const activeLayout = issueFilterStore.userDisplayFilters.layout;
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
const toggleSidebar = () => {
setValue(`${!isSidebarCollapsed}`);
};
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
@@ -88,6 +98,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
[issueFilterStore, projectId, workspaceSlug]
);
const cyclesList = projectId ? cycleStore.cycles[projectId.toString()] : undefined;
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
return (
@@ -97,50 +108,91 @@ export const CycleIssuesHeader: React.FC = observer(() => {
onClose={() => setAnalyticsModal(false)}
cycleDetails={cycleDetails ?? undefined}
/>
<div className="flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters">
<FilterSelection
filters={cycleIssueFilterStore.cycleFilters}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs onBack={() => router.back()}>
<Breadcrumbs.BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(cycleDetails?.project_detail.name ?? "", 32)} Cycles`}</p>
</a>
</Link>
}
/>
</Breadcrumbs>
<CustomMenu
label={
<>
<ContrastIcon className="h-3 w-3" />
{cycleDetails?.name && truncateText(cycleDetails.name, 40)}
</>
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined}
className="ml-1.5 flex-shrink-0"
width="auto"
>
{cyclesList?.map((cycle) => (
<CustomMenu.MenuItem
key={cycle.id}
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`)}
>
{truncateText(cycle.name, 40)}
</CustomMenu.MenuItem>
))}
</CustomMenu>
</div>
<div className="flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
</FiltersDropdown>
<FiltersDropdown title="Display">
<DisplayFiltersSelection
displayFilters={issueFilterStore.userDisplayFilters}
displayProperties={issueFilterStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
/>
</FiltersDropdown>
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics
</Button>
<Button
onClick={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
size="sm"
prependIcon={<Plus />}
>
Add Issue
</Button>
<FiltersDropdown title="Filters">
<FilterSelection
filters={cycleIssueFilterStore.cycleFilters}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>
<FiltersDropdown title="Display">
<DisplayFiltersSelection
displayFilters={issueFilterStore.userDisplayFilters}
displayProperties={issueFilterStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
/>
</FiltersDropdown>
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics
</Button>
<Button
onClick={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
size="sm"
prependIcon={<Plus />}
>
Add Issue
</Button>
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80"
onClick={toggleSidebar}
>
<ArrowRight className={`h-4 w-4 duration-300 ${isSidebarCollapsed ? "-rotate-180" : ""}`} />
</button>
</div>
</div>
</>
);

View File

@@ -1,16 +1,20 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useLocalStorage from "hooks/use-local-storage";
// components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics";
// ui
import { Button } from "@plane/ui";
import { Breadcrumbs, Button, CustomMenu } from "@plane/ui";
// icons
import { Plus } from "lucide-react";
import { ArrowRight, ContrastIcon, Plus } from "lucide-react";
// helpers
import { truncateText } from "helpers/string.helper";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants
@@ -28,9 +32,15 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
moduleFilter: moduleFilterStore,
project: projectStore,
} = useMobxStore();
const activeLayout = issueFilterStore.userDisplayFilters.layout;
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
const toggleSidebar = () => {
setValue(`${!isSidebarCollapsed}`);
};
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
@@ -88,6 +98,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
[issueFilterStore, projectId, workspaceSlug]
);
const modulesList = projectId ? moduleStore.modules[projectId.toString()] : undefined;
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
return (
@@ -97,50 +108,94 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
onClose={() => setAnalyticsModal(false)}
moduleDetails={moduleDetails ?? undefined}
/>
<div className="flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters">
<FilterSelection
filters={moduleFilterStore.moduleFilters}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs onBack={() => router.back()}>
<Breadcrumbs.BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectId}/modules`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(
moduleDetails?.project_detail.name ?? "",
32
)} Modules`}</p>
</a>
</Link>
}
/>
</Breadcrumbs>
<CustomMenu
label={
<>
<ContrastIcon className="h-3 w-3" />
{moduleDetails?.name && truncateText(moduleDetails.name, 40)}
</>
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined}
className="ml-1.5 flex-shrink-0"
width="auto"
>
{modulesList?.map((module) => (
<CustomMenu.MenuItem
key={module.id}
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/modules/${module.id}`)}
>
{truncateText(module.name, 40)}
</CustomMenu.MenuItem>
))}
</CustomMenu>
</div>
<div className="flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
</FiltersDropdown>
<FiltersDropdown title="Display">
<DisplayFiltersSelection
displayFilters={issueFilterStore.userDisplayFilters}
displayProperties={issueFilterStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
/>
</FiltersDropdown>
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics
</Button>
<Button
onClick={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
size="sm"
prependIcon={<Plus />}
>
Add Issue
</Button>
<FiltersDropdown title="Filters">
<FilterSelection
filters={moduleFilterStore.moduleFilters}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>
<FiltersDropdown title="Display">
<DisplayFiltersSelection
displayFilters={issueFilterStore.userDisplayFilters}
displayProperties={issueFilterStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
/>
</FiltersDropdown>
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics
</Button>
<Button
onClick={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
size="sm"
prependIcon={<Plus />}
>
Add Issue
</Button>
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80"
onClick={toggleSidebar}
>
<ArrowRight className={`h-4 w-4 duration-300 ${isSidebarCollapsed ? "-rotate-180" : ""}`} />
</button>
</div>
</div>
</>
);