Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion web/components/core/filters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from "./date-filter-modal";
export * from "./date-filter-select";
export * from "./filters-list";
export * from "./workspace-filters-list";
export * from "./issues-view-filter";
111 changes: 111 additions & 0 deletions web/components/headers/cycle-issues.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useCallback } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";

// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";

export const CycleIssuesHeader: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;

const {
issueFilter: issueFilterStore,
cycleIssueFilter: cycleIssueFilterStore,
project: projectStore,
} = useMobxStore();

const activeLayout = issueFilterStore.userDisplayFilters.layout;

const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;

issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
display_filters: {
layout,
},
});
},
[issueFilterStore, projectId, workspaceSlug]
);

const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId || !cycleId) return;

const newValues = cycleIssueFilterStore.cycleFilters?.[key] ?? [];

if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (cycleIssueFilterStore.cycleFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}

cycleIssueFilterStore.updateCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), {
[key]: newValues,
});
},
[cycleId, cycleIssueFilterStore, projectId, workspaceSlug]
);

const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;

issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
display_filters: {
...updatedDisplayFilter,
},
});
},
[issueFilterStore, projectId, workspaceSlug]
);

const handleDisplayPropertiesUpdate = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;

issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property);
},
[issueFilterStore, projectId, workspaceSlug]
);

return (
<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}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>
<FiltersDropdown title="View">
<DisplayFiltersSelection
displayFilters={issueFilterStore.userDisplayFilters}
displayProperties={issueFilterStore.userDisplayProperties}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
/>
</FiltersDropdown>
</div>
);
});
1 change: 1 addition & 0 deletions web/components/headers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./cycle-issues";
export * from "./global-issues";
export * from "./module-issues";
export * from "./project-issues";
Expand Down
39 changes: 39 additions & 0 deletions web/components/issues/issue-layouts/calendar/cycle-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { observer } from "mobx-react-lite";
import { DragDropContext, DropResult } from "@hello-pangea/dnd";

// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { CalendarChart } from "components/issues";
// types
import { IIssueGroupedStructure } from "store/issue";

export const CycleCalendarLayout: React.FC = observer(() => {
const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();

// TODO: add drag and drop functionality
const onDragEnd = (result: DropResult) => {
if (!result) return;

// return if not dropped on the correct place
if (!result.destination) return;

// return if dropped on the same date
if (result.destination.droppableId === result.source.droppableId) return;

// issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
};

const issues = cycleIssueStore.getIssues;

return (
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
<DragDropContext onDragEnd={onDragEnd}>
<CalendarChart
issues={issues as IIssueGroupedStructure | null}
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
/>
</DragDropContext>
</div>
);
});
1 change: 1 addition & 0 deletions web/components/issues/issue-layouts/calendar/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./dropdowns";
export * from "./calendar";
export * from "./cycle-root";
export * from "./types.d";
export * from "./day-tile";
export * from "./header";
Expand Down
4 changes: 2 additions & 2 deletions web/components/issues/issue-layouts/calendar/module-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { CalendarChart } from "components/issues";
import { IIssueGroupedStructure } from "store/issue";

export const ModuleCalendarLayout: React.FC = observer(() => {
const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();

// TODO: add drag and drop functionality
const onDragEnd = (result: DropResult) => {
Expand All @@ -24,7 +24,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
// issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
};

const issues = moduleStore.getIssues;
const issues = moduleIssueStore.getIssues;

return (
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
Expand Down
20 changes: 18 additions & 2 deletions web/components/issues/issue-layouts/cycle-layout-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import useSWR from "swr";
// mobx react lite
import { observer } from "mobx-react-lite";
// components
import { CycleKanBanLayout, CycleListLayout } from "components/issues";
import {
CycleCalendarLayout,
CycleGanttLayout,
CycleKanBanLayout,
CycleListLayout,
CycleSpreadsheetLayout,
} from "components/issues";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";

Expand Down Expand Up @@ -46,7 +52,17 @@ export const CycleLayoutRoot: React.FC = observer(() => {

return (
<div className="w-full h-full">
{activeLayout === "list" ? <CycleListLayout /> : activeLayout === "kanban" ? <CycleKanBanLayout /> : null}
{activeLayout === "list" ? (
<CycleListLayout />
) : activeLayout === "kanban" ? (
<CycleKanBanLayout />
) : activeLayout === "calendar" ? (
<CycleCalendarLayout />
) : activeLayout === "gantt_chart" ? (
<CycleGanttLayout />
) : activeLayout === "spreadsheet" ? (
<CycleSpreadsheetLayout />
) : null}
</div>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
} = useMobxStore();

const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined;
const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined;

// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(storedFilters ?? {}).forEach(([key, value]) => {
if (!value) return;

if (Array.isArray(value) && value.length === 0) return;

appliedFilters[key as keyof IIssueFilterOptions] = value;
});

const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!globalViewId) return;
Expand Down Expand Up @@ -72,15 +83,16 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
});
};

const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined;

// update stored filters when view details are fetched
useEffect(() => {
if (!globalViewId || !viewDetails) return;

if (!globalViewFiltersStore.storedFilters[globalViewId.toString()])
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {});
}, [globalViewId, globalViewFiltersStore, storedFilters, viewDetails]);
}, [globalViewId, globalViewFiltersStore, viewDetails]);

// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;

return (
<div className="flex items-start justify-between gap-4 p-4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
} = useMobxStore();

const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] ?? {} : {};
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;

// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(storedFilters).forEach(([key, value]) => {
Object.entries(storedFilters ?? {}).forEach(([key, value]) => {
if (!value) return;

if (Array.isArray(value) && value.length === 0) return;
Expand Down Expand Up @@ -60,7 +60,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !viewId) return;

const newFilters: IIssueFilterOptions = {};
Object.keys(storedFilters).forEach((key) => {
Object.keys(storedFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});

Expand Down
55 changes: 55 additions & 0 deletions web/components/issues/issue-layouts/gantt/cycle-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";

// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues";
// types
import { IIssueUnGroupedStructure } from "store/issue";

export const CycleGanttLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;

const { projectDetails } = useProjectDetails();

const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();

const appliedDisplayFilters = issueFilterStore.userDisplayFilters;

const issues = cycleIssueStore.getIssues;

const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;

return (
<>
<IssuePeekOverview
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug?.toString() ?? ""}
readOnly={!isAllowed}
/>
<div className="w-full h-full">
<GanttChartRoot
border={false}
title="Issues"
loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
blockUpdateHandler={(block, payload) => {
// TODO: update mutation logic
// updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
}}
BlockRender={IssueGanttBlock}
SidebarBlockRender={IssueGanttSidebarBlock}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
/>
</div>
</>
);
});
1 change: 1 addition & 0 deletions web/components/issues/issue-layouts/gantt/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./blocks";
export * from "./cycle-root";
export * from "./module-root";
export * from "./project-view-root";
export * from "./root";
4 changes: 2 additions & 2 deletions web/components/issues/issue-layouts/gantt/module-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export const ModuleGanttLayout: React.FC = observer(() => {

const { projectDetails } = useProjectDetails();

const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();

const appliedDisplayFilters = issueFilterStore.userDisplayFilters;

const issues = moduleStore.getIssues;
const issues = moduleIssueStore.getIssues;

const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;

Expand Down
Loading