forked from github/plane
chore: cycles, modules store integration, list and kanban layouts and updated kanban logic (#2399)
* chore: cycle, cycle-issue, cycle-filters, cycle-kanban, cycle layout setup * chore: cycles kanban and list view store * chore: cycles, modules kanban and list, kanban view store
This commit is contained in:
parent
13874f2357
commit
e28919a964
@ -32,11 +32,7 @@ import {
|
||||
import { ExclamationIcon } from "components/icons";
|
||||
// helpers
|
||||
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
|
||||
import {
|
||||
isDateGreaterThanToday,
|
||||
renderDateFormat,
|
||||
renderShortDateWithYearFormat,
|
||||
} from "helpers/date-time.helper";
|
||||
import { isDateGreaterThanToday, renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { ICurrentUserResponse, ICycle } from "types";
|
||||
// fetch-keys
|
||||
@ -50,13 +46,7 @@ type Props = {
|
||||
user: ICurrentUserResponse | undefined;
|
||||
};
|
||||
|
||||
export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
cycle,
|
||||
isOpen,
|
||||
cycleStatus,
|
||||
isCompleted,
|
||||
user,
|
||||
}) => {
|
||||
export const CycleDetailsSidebar: React.FC<Props> = ({ cycle, isOpen, cycleStatus, isCompleted, user }) => {
|
||||
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@ -76,11 +66,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
const submitChanges = (data: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
|
||||
mutate<ICycle>(
|
||||
CYCLE_DETAILS(cycleId as string),
|
||||
(prevData) => ({ ...(prevData as ICycle), ...data }),
|
||||
false
|
||||
);
|
||||
mutate<ICycle>(CYCLE_DETAILS(cycleId as string), (prevData) => ({ ...(prevData as ICycle), ...data }), false);
|
||||
|
||||
cyclesService
|
||||
.patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data, user)
|
||||
@ -89,8 +75,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
};
|
||||
|
||||
const handleCopyText = () => {
|
||||
const originURL =
|
||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||
|
||||
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`)
|
||||
.then(() => {
|
||||
@ -116,11 +101,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
|
||||
const dateChecker = async (payload: any) => {
|
||||
try {
|
||||
const res = await cyclesService.cycleDateCheck(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
payload
|
||||
);
|
||||
const res = await cyclesService.cycleDateCheck(workspaceSlug as string, projectId as string, payload);
|
||||
return res.status;
|
||||
} catch (err) {
|
||||
return false;
|
||||
@ -277,20 +258,13 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
const isStartValid = new Date(`${cycle?.start_date}`) <= new Date();
|
||||
const isEndValid = new Date(`${cycle?.end_date}`) >= new Date(`${cycle?.start_date}`);
|
||||
|
||||
const progressPercentage = cycle
|
||||
? Math.round((cycle.completed_issues / cycle.total_issues) * 100)
|
||||
: null;
|
||||
const progressPercentage = cycle ? Math.round((cycle.completed_issues / cycle.total_issues) * 100) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteCycleModal
|
||||
isOpen={cycleDeleteModal}
|
||||
setIsOpen={setCycleDeleteModal}
|
||||
data={cycle}
|
||||
user={user}
|
||||
/>
|
||||
<DeleteCycleModal isOpen={cycleDeleteModal} setIsOpen={setCycleDeleteModal} data={cycle} user={user} />
|
||||
<div
|
||||
className={`fixed top-[66px] ${
|
||||
className={`fixed top-[66px] z-20 ${
|
||||
isOpen ? "right-0" : "-right-[24rem]"
|
||||
} h-full w-[24rem] overflow-y-auto border-l border-custom-border-200 bg-custom-sidebar-background-100 pt-5 pb-10 duration-300`}
|
||||
>
|
||||
@ -316,9 +290,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
<CalendarDaysIcon className="h-3 w-3" />
|
||||
<span>
|
||||
{renderShortDateWithYearFormat(
|
||||
new Date(
|
||||
`${watch("start_date") ? watch("start_date") : cycle?.start_date}`
|
||||
),
|
||||
new Date(`${watch("start_date") ? watch("start_date") : cycle?.start_date}`),
|
||||
"Start date"
|
||||
)}
|
||||
</span>
|
||||
@ -367,9 +339,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
|
||||
<span>
|
||||
{renderShortDateWithYearFormat(
|
||||
new Date(
|
||||
`${watch("end_date") ? watch("end_date") : cycle?.end_date}`
|
||||
),
|
||||
new Date(`${watch("end_date") ? watch("end_date") : cycle?.end_date}`),
|
||||
"End date"
|
||||
)}
|
||||
</span>
|
||||
@ -409,9 +379,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2">
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
<div className="max-w-[300px]">
|
||||
<h4 className="text-xl font-semibold text-custom-text-100 break-words w-full">
|
||||
{cycle.name}
|
||||
</h4>
|
||||
<h4 className="text-xl font-semibold text-custom-text-100 break-words w-full">{cycle.name}</h4>
|
||||
</div>
|
||||
<CustomMenu width="lg" ellipsis>
|
||||
{!isCompleted && (
|
||||
@ -480,9 +448,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
<div className="flex w-full flex-col items-center justify-start gap-2 border-t border-custom-border-200 p-6">
|
||||
<Disclosure defaultOpen>
|
||||
{({ open }) => (
|
||||
<div
|
||||
className={`relative flex h-full w-full flex-col ${open ? "" : "flex-row"}`}
|
||||
>
|
||||
<div className={`relative flex h-full w-full flex-col ${open ? "" : "flex-row"}`}>
|
||||
<div className="flex w-full items-center justify-between gap-2 ">
|
||||
<div className="flex items-center justify-start gap-2 text-sm">
|
||||
<span className="font-medium text-custom-text-200">Progress</span>
|
||||
@ -503,11 +469,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
</Disclosure.Button>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<ExclamationIcon
|
||||
height={14}
|
||||
width={14}
|
||||
className="fill-current text-custom-text-200"
|
||||
/>
|
||||
<ExclamationIcon height={14} width={14} className="fill-current text-custom-text-200" />
|
||||
<span className="text-xs italic text-custom-text-200">
|
||||
{cycleStatus === "upcoming"
|
||||
? "Cycle is yet to start."
|
||||
@ -527,8 +489,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
</span>
|
||||
<span>
|
||||
Pending Issues -{" "}
|
||||
{cycle.total_issues -
|
||||
(cycle.completed_issues + cycle.cancelled_issues)}
|
||||
{cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -564,9 +525,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
<div className="flex w-full flex-col items-center justify-start gap-2 border-t border-custom-border-200 p-6">
|
||||
<Disclosure defaultOpen>
|
||||
{({ open }) => (
|
||||
<div
|
||||
className={`relative flex h-full w-full flex-col ${open ? "" : "flex-row"}`}
|
||||
>
|
||||
<div className={`relative flex h-full w-full flex-col ${open ? "" : "flex-row"}`}>
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-start gap-2 text-sm">
|
||||
<span className="font-medium text-custom-text-200">Other Information</span>
|
||||
@ -581,11 +540,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
||||
</Disclosure.Button>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<ExclamationIcon
|
||||
height={14}
|
||||
width={14}
|
||||
className="fill-current text-custom-text-200"
|
||||
/>
|
||||
<ExclamationIcon height={14} width={14} className="fill-current text-custom-text-200" />
|
||||
<span className="text-xs italic text-custom-text-200">
|
||||
No issues found. Please add issue.
|
||||
</span>
|
||||
|
53
web/components/issues/issue-layouts/cycle-layout-root.tsx
Normal file
53
web/components/issues/issue-layouts/cycle-layout-root.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
// next imports
|
||||
import { useRouter } from "next/router";
|
||||
// swr
|
||||
import useSWR from "swr";
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { CycleListLayout } from "./list/cycle-root";
|
||||
import { CycleKanBanLayout } from "./kanban/cycle-root";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const CycleLayoutRoot: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
cycleId: string;
|
||||
};
|
||||
|
||||
const {
|
||||
project: projectStore,
|
||||
issueFilter: issueFilterStore,
|
||||
cycleIssue: cycleIssueStore,
|
||||
cycleIssueFilter: cycleIssueFilterStore,
|
||||
} = useMobxStore();
|
||||
|
||||
useSWR(workspaceSlug && projectId && cycleId ? `CYCLE_ISSUES` : null, async () => {
|
||||
if (workspaceSlug && projectId && cycleId) {
|
||||
// fetching the project display filters and display properties
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId);
|
||||
// fetching the cycle filters
|
||||
await cycleIssueFilterStore.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
||||
|
||||
// fetching the project state, labels and members
|
||||
await projectStore.fetchProjectStates(workspaceSlug, projectId);
|
||||
await projectStore.fetchProjectLabels(workspaceSlug, projectId);
|
||||
await projectStore.fetchProjectMembers(workspaceSlug, projectId);
|
||||
|
||||
// fetching the cycle issues
|
||||
await cycleIssueStore.fetchIssues(workspaceSlug, projectId, cycleId);
|
||||
}
|
||||
});
|
||||
|
||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
{activeLayout === "list" ? <CycleListLayout /> : activeLayout === "kanban" ? <CycleKanBanLayout /> : null}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,7 +1,15 @@
|
||||
// filters
|
||||
export * from "./filters";
|
||||
|
||||
// layouts
|
||||
export * from "./list";
|
||||
export * from "./calendar";
|
||||
export * from "./filters";
|
||||
export * from "./gantt";
|
||||
export * from "./kanban";
|
||||
export * from "./spreadsheet";
|
||||
|
||||
// cycle root layout
|
||||
export * from "./cycle-layout-root";
|
||||
|
||||
// module root layout
|
||||
export * from "./module-all-layouts";
|
||||
|
85
web/components/issues/issue-layouts/kanban/cycle-root.tsx
Normal file
85
web/components/issues/issue-layouts/kanban/cycle-root.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import React from "react";
|
||||
// react beautiful dnd
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { KanBan } from "./default";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface ICycleKanBanLayout {}
|
||||
|
||||
export const CycleKanBanLayout: React.FC = observer(() => {
|
||||
const {
|
||||
cycleIssue: cycleIssueStore,
|
||||
issueFilter: issueFilterStore,
|
||||
cycleIssueKanBanView: cycleIssueKanBanViewStore,
|
||||
}: RootStore = useMobxStore();
|
||||
|
||||
const issues = cycleIssueStore?.getIssues;
|
||||
|
||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
result.destination &&
|
||||
result.source &&
|
||||
result.destination.droppableId === result.source.droppableId &&
|
||||
result.destination.index === result.source.index
|
||||
)
|
||||
return;
|
||||
|
||||
currentKanBanView === "default"
|
||||
? cycleIssueKanBanViewStore?.handleDragDrop(result.source, result.destination)
|
||||
: cycleIssueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
||||
};
|
||||
|
||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => {
|
||||
cycleIssueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||
};
|
||||
|
||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||
cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -22,6 +22,8 @@ export interface IGroupByKanBan {
|
||||
isDragDisabled: boolean;
|
||||
handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void;
|
||||
display_properties: any;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
const GroupByKanBan: React.FC<IGroupByKanBan> = observer(
|
||||
@ -35,11 +37,11 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer(
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
display_properties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
}) => {
|
||||
const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
|
||||
|
||||
const verticalAlignPosition = (_list: any) =>
|
||||
issueKanBanViewStore.kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string);
|
||||
kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full flex">
|
||||
@ -54,43 +56,47 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer(
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={issues?.[getValueFromObject(_list, listKey) as string]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!verticalAlignPosition(_list) && (
|
||||
<div className="w-full min-h-[150px] h-full">
|
||||
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`w-full h-full relative transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issues ? (
|
||||
<IssueBlock
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={getValueFromObject(_list, listKey) as string}
|
||||
issues={issues[getValueFromObject(_list, listKey) as string]}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
/>
|
||||
) : (
|
||||
isDragDisabled && (
|
||||
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`min-h-[150px] h-full ${
|
||||
verticalAlignPosition(_list) ? `w-[0px] overflow-hidden` : `w-full transition-all`
|
||||
}`}
|
||||
>
|
||||
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`w-full h-full relative transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issues ? (
|
||||
<IssueBlock
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={getValueFromObject(_list, listKey) as string}
|
||||
issues={issues[getValueFromObject(_list, listKey) as string]}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
/>
|
||||
) : (
|
||||
isDragDisabled && (
|
||||
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -106,10 +112,21 @@ export interface IKanBan {
|
||||
handleDragDrop?: (result: any) => void | undefined;
|
||||
handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void;
|
||||
display_properties: any;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const KanBan: React.FC<IKanBan> = observer(
|
||||
({ issues, sub_group_by, group_by, sub_group_id = "null", handleIssues, display_properties }) => {
|
||||
({
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
sub_group_id = "null",
|
||||
handleIssues,
|
||||
display_properties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
}) => {
|
||||
const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
@ -125,6 +142,8 @@ export const KanBan: React.FC<IKanBan> = observer(
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -139,6 +158,8 @@ export const KanBan: React.FC<IKanBan> = observer(
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -153,6 +174,8 @@ export const KanBan: React.FC<IKanBan> = observer(
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -167,6 +190,8 @@ export const KanBan: React.FC<IKanBan> = observer(
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -181,6 +206,8 @@ export const KanBan: React.FC<IKanBan> = observer(
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -195,6 +222,8 @@ export const KanBan: React.FC<IKanBan> = observer(
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -14,12 +14,14 @@ export interface IAssigneesHeader {
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const Icon = ({ user }: any) => <Avatar user={user} height="22px" width="22px" fontSize="12px" />;
|
||||
|
||||
export const AssigneesHeader: React.FC<IAssigneesHeader> = observer(
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count }) => {
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null;
|
||||
@ -33,6 +35,8 @@ export const AssigneesHeader: React.FC<IAssigneesHeader> = observer(
|
||||
icon={<Icon user={assignee?.member} />}
|
||||
title={assignee?.member?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
@ -42,6 +46,8 @@ export const AssigneesHeader: React.FC<IAssigneesHeader> = observer(
|
||||
icon={<Icon user={assignee?.member} />}
|
||||
title={assignee?.member?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -14,10 +14,12 @@ export interface ICreatedByHeader {
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const CreatedByHeader: React.FC<ICreatedByHeader> = observer(
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count }) => {
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null;
|
||||
@ -31,6 +33,8 @@ export const CreatedByHeader: React.FC<ICreatedByHeader> = observer(
|
||||
icon={<Icon user={createdBy?.member} />}
|
||||
title={createdBy?.member?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
@ -40,6 +44,8 @@ export const CreatedByHeader: React.FC<ICreatedByHeader> = observer(
|
||||
icon={<Icon user={createdBy?.member} />}
|
||||
title={createdBy?.member?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -3,9 +3,6 @@ import React from "react";
|
||||
import { Plus, Minimize2, Maximize2, Circle } from "lucide-react";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
interface IHeaderGroupByCard {
|
||||
sub_group_by: string | null;
|
||||
@ -14,13 +11,13 @@ interface IHeaderGroupByCard {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const HeaderGroupByCard = observer(
|
||||
({ sub_group_by, group_by, column_id, icon, title, count }: IHeaderGroupByCard) => {
|
||||
const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
|
||||
|
||||
const verticalAlignPosition = issueKanBanViewStore.kanBanToggle?.groupByHeaderMinMax.includes(column_id);
|
||||
({ sub_group_by, group_by, column_id, icon, title, count, kanBanToggle, handleKanBanToggle }: IHeaderGroupByCard) => {
|
||||
const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -46,7 +43,7 @@ export const HeaderGroupByCard = observer(
|
||||
{sub_group_by === null && (
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
|
||||
onClick={() => issueKanBanViewStore?.handleKanBanToggle("groupByHeaderMinMax", column_id)}
|
||||
onClick={() => handleKanBanToggle("groupByHeaderMinMax", column_id)}
|
||||
>
|
||||
{verticalAlignPosition ? (
|
||||
<Maximize2 width={14} strokeWidth={2} />
|
||||
|
@ -16,10 +16,12 @@ export interface IKanBanGroupByHeaderRoot {
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
|
||||
({ column_id, sub_group_by, group_by, issues_count }) => (
|
||||
({ column_id, sub_group_by, group_by, issues_count, kanBanToggle, handleKanBanToggle }) => (
|
||||
<>
|
||||
{group_by && group_by === "state" && (
|
||||
<StateHeader
|
||||
@ -28,6 +30,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
@ -37,6 +41,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "priority" && (
|
||||
@ -46,6 +52,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "labels" && (
|
||||
@ -55,6 +63,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "assignees" && (
|
||||
@ -64,6 +74,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "created_by" && (
|
||||
@ -73,6 +85,8 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -13,6 +13,8 @@ export interface ILabelHeader {
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
const Icon = ({ color }: any) => (
|
||||
@ -20,7 +22,7 @@ const Icon = ({ color }: any) => (
|
||||
);
|
||||
|
||||
export const LabelHeader: React.FC<ILabelHeader> = observer(
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count }) => {
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null;
|
||||
@ -34,6 +36,8 @@ export const LabelHeader: React.FC<ILabelHeader> = observer(
|
||||
icon={<Icon color={label?.color} />}
|
||||
title={label?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
@ -43,6 +47,8 @@ export const LabelHeader: React.FC<ILabelHeader> = observer(
|
||||
icon={<Icon />}
|
||||
title={label?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -8,13 +8,14 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
// constants
|
||||
import { issuePriorityByKey } from "constants/issue";
|
||||
|
||||
|
||||
export interface IPriorityHeader {
|
||||
column_id: string;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
const Icon = ({ priority }: any) => (
|
||||
@ -44,7 +45,7 @@ const Icon = ({ priority }: any) => (
|
||||
);
|
||||
|
||||
export const PriorityHeader: React.FC<IPriorityHeader> = observer(
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count }) => {
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => {
|
||||
const priority = column_id && issuePriorityByKey(column_id);
|
||||
|
||||
return (
|
||||
@ -56,6 +57,8 @@ export const PriorityHeader: React.FC<IPriorityHeader> = observer(
|
||||
icon={<Icon priority={priority?.key} />}
|
||||
title={priority?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
@ -65,6 +68,8 @@ export const PriorityHeader: React.FC<IPriorityHeader> = observer(
|
||||
icon={<Icon priority={priority?.key} />}
|
||||
title={priority?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -13,6 +13,8 @@ export interface IStateGroupHeader {
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
||||
@ -22,7 +24,7 @@ export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) =>
|
||||
);
|
||||
|
||||
export const StateGroupHeader: React.FC<IStateGroupHeader> = observer(
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count }) => {
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => {
|
||||
const stateGroup = column_id && issueStateGroupByKey(column_id);
|
||||
|
||||
console.log("stateGroup", stateGroup);
|
||||
@ -36,6 +38,8 @@ export const StateGroupHeader: React.FC<IStateGroupHeader> = observer(
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
@ -45,6 +49,8 @@ export const StateGroupHeader: React.FC<IStateGroupHeader> = observer(
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -14,10 +14,12 @@ export interface IStateHeader {
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const StateHeader: React.FC<IStateHeader> = observer(
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count }) => {
|
||||
({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null;
|
||||
@ -31,6 +33,8 @@ export const StateHeader: React.FC<IStateHeader> = observer(
|
||||
icon={<Icon stateGroup={state?.group} color={state?.color} />}
|
||||
title={state?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
@ -40,6 +44,8 @@ export const StateHeader: React.FC<IStateHeader> = observer(
|
||||
icon={<Icon stateGroup={state?.group} color={state?.color} />}
|
||||
title={state?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -3,27 +3,24 @@ import React from "react";
|
||||
import { Circle, ChevronDown, ChevronUp } from "lucide-react";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
interface IHeaderSubGroupByCard {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
count: number;
|
||||
column_id: string;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }: IHeaderSubGroupByCard) => {
|
||||
const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
export const HeaderSubGroupByCard = observer(
|
||||
({ icon, title, count, column_id, kanBanToggle, handleKanBanToggle }: IHeaderSubGroupByCard) => (
|
||||
<div className={`flex-shrink-0 relative flex gap-2 rounded-sm flex-row items-center w-full p-1.5`}>
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
|
||||
onClick={() => issueKanBanViewStore?.handleKanBanToggle("subgroupByIssuesVisibility", column_id)}
|
||||
onClick={() => handleKanBanToggle("subgroupByIssuesVisibility", column_id)}
|
||||
>
|
||||
{issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? (
|
||||
{kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? (
|
||||
<ChevronDown width={14} strokeWidth={2} />
|
||||
) : (
|
||||
<ChevronUp width={14} strokeWidth={2} />
|
||||
@ -39,5 +36,5 @@ export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }:
|
||||
<div className="pl-2 text-sm font-medium text-custom-text-300">{count || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
|
@ -13,10 +13,12 @@ export interface IKanBanSubGroupByHeaderRoot {
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer(
|
||||
({ column_id, sub_group_by, group_by, issues_count }) => (
|
||||
({ column_id, sub_group_by, group_by, issues_count, kanBanToggle, handleKanBanToggle }) => (
|
||||
<>
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<StateHeader
|
||||
@ -25,6 +27,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||
@ -34,6 +38,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "priority" && (
|
||||
@ -43,6 +49,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "labels" && (
|
||||
@ -52,6 +60,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "assignees" && (
|
||||
@ -61,6 +71,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "created_by" && (
|
||||
@ -70,6 +82,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
77
web/components/issues/issue-layouts/kanban/module-root.tsx
Normal file
77
web/components/issues/issue-layouts/kanban/module-root.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
// react beautiful dnd
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { KanBan } from "./default";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IModuleKanBanLayout {}
|
||||
|
||||
export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
const {
|
||||
issue: issueStore,
|
||||
issueFilter: issueFilterStore,
|
||||
issueKanBanView: issueKanBanViewStore,
|
||||
}: RootStore = useMobxStore();
|
||||
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
result.destination &&
|
||||
result.source &&
|
||||
result.destination.droppableId === result.source.droppableId &&
|
||||
result.destination.index === result.source.index
|
||||
)
|
||||
return;
|
||||
|
||||
currentKanBanView === "default"
|
||||
? issueKanBanViewStore?.handleDragDrop(result.source, result.destination)
|
||||
: issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
||||
};
|
||||
|
||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => {
|
||||
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -51,6 +51,10 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||
};
|
||||
|
||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||
issueKanBanViewStore.handleKanBanToggle(toggle, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
@ -61,6 +65,8 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
@ -69,6 +75,8 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
|
@ -17,6 +17,8 @@ interface ISubGroupSwimlaneHeader {
|
||||
group_by: string | null;
|
||||
list: any;
|
||||
listKey: string;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
issues,
|
||||
@ -24,6 +26,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
group_by,
|
||||
list,
|
||||
listKey,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
}) => {
|
||||
const calculateIssueCount = (column_id: string) => {
|
||||
let issueCount = 0;
|
||||
@ -45,6 +49,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@ -56,11 +62,21 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
issues: any;
|
||||
handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void;
|
||||
display_properties: any;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer(
|
||||
({ issues, sub_group_by, group_by, list, listKey, handleIssues, display_properties }) => {
|
||||
const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore();
|
||||
|
||||
({
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
list,
|
||||
listKey,
|
||||
handleIssues,
|
||||
display_properties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
}) => {
|
||||
const calculateIssueCount = (column_id: string) => {
|
||||
let issueCount = 0;
|
||||
issues?.[column_id] &&
|
||||
@ -83,13 +99,13 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer(
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-b border-custom-border-400 border-dashed" />
|
||||
</div>
|
||||
{!issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes(
|
||||
getValueFromObject(_list, listKey) as string
|
||||
) && (
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
|
||||
<div className="relative">
|
||||
<KanBan
|
||||
issues={issues?.[getValueFromObject(_list, listKey) as string]}
|
||||
@ -98,6 +114,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer(
|
||||
sub_group_id={getValueFromObject(_list, listKey) as string}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -114,10 +132,12 @@ export interface IKanBanSwimLanes {
|
||||
group_by: string | null;
|
||||
handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void;
|
||||
display_properties: any;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
}
|
||||
|
||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
({ issues, sub_group_by, group_by, handleIssues, display_properties }) => {
|
||||
({ issues, sub_group_by, group_by, handleIssues, display_properties, kanBanToggle, handleKanBanToggle }) => {
|
||||
const { project: projectStore }: RootStore = useMobxStore();
|
||||
|
||||
return (
|
||||
@ -130,6 +150,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
group_by={group_by}
|
||||
list={projectStore?.projectStates}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -140,6 +162,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
group_by={group_by}
|
||||
list={ISSUE_STATE_GROUPS}
|
||||
listKey={`key`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -150,6 +174,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
group_by={group_by}
|
||||
list={ISSUE_PRIORITIES}
|
||||
listKey={`key`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -160,6 +186,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
group_by={group_by}
|
||||
list={projectStore?.projectLabels}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -170,6 +198,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
group_by={group_by}
|
||||
list={projectStore?.projectMembers}
|
||||
listKey={`member.id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -180,6 +210,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
group_by={group_by}
|
||||
list={projectStore?.projectMembers}
|
||||
listKey={`member.id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -193,6 +225,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -205,6 +239,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -217,6 +253,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -229,6 +267,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -241,6 +281,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
listKey={`member.id`}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -253,6 +295,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer(
|
||||
listKey={`member.id`}
|
||||
handleIssues={handleIssues}
|
||||
display_properties={display_properties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
77
web/components/issues/issue-layouts/kanban/view-root.tsx
Normal file
77
web/components/issues/issue-layouts/kanban/view-root.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
// react beautiful dnd
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { KanBan } from "./default";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IViewKanBanLayout {}
|
||||
|
||||
export const ViewKanBanLayout: React.FC = observer(() => {
|
||||
const {
|
||||
issue: issueStore,
|
||||
issueFilter: issueFilterStore,
|
||||
issueKanBanView: issueKanBanViewStore,
|
||||
}: RootStore = useMobxStore();
|
||||
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
result.destination &&
|
||||
result.source &&
|
||||
result.destination.droppableId === result.source.droppableId &&
|
||||
result.destination.index === result.source.index
|
||||
)
|
||||
return;
|
||||
|
||||
currentKanBanView === "default"
|
||||
? issueKanBanViewStore?.handleDragDrop(result.source, result.destination)
|
||||
: issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
||||
};
|
||||
|
||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => {
|
||||
issueStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
});
|
30
web/components/issues/issue-layouts/list/cycle-root.tsx
Normal file
30
web/components/issues/issue-layouts/list/cycle-root.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { List } from "./default";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface ICycleListLayout {}
|
||||
|
||||
export const CycleListLayout: React.FC = observer(() => {
|
||||
const { issueFilter: issueFilterStore, cycleIssue: cycleIssueStore }: RootStore = useMobxStore();
|
||||
|
||||
const issues = cycleIssueStore?.getIssues;
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const updateIssue = (group_by: string | null, issue: any) => {
|
||||
cycleIssueStore.updateIssueStructure(group_by, null, issue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-full bg-custom-background-90`}>
|
||||
<List issues={issues} group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} />
|
||||
</div>
|
||||
);
|
||||
});
|
30
web/components/issues/issue-layouts/list/module-root.tsx
Normal file
30
web/components/issues/issue-layouts/list/module-root.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { List } from "./default";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IModuleListLayout {}
|
||||
|
||||
export const ModuleListLayout: React.FC = observer(() => {
|
||||
const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const updateIssue = (group_by: string | null, issue: any) => {
|
||||
issueStore.updateIssueStructure(group_by, null, issue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-full bg-custom-background-90`}>
|
||||
<List issues={issues} group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} />
|
||||
</div>
|
||||
);
|
||||
});
|
30
web/components/issues/issue-layouts/list/view-root.tsx
Normal file
30
web/components/issues/issue-layouts/list/view-root.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { List } from "./default";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IViewListLayout {}
|
||||
|
||||
export const ViewListLayout: React.FC = observer(() => {
|
||||
const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore();
|
||||
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const updateIssue = (group_by: string | null, issue: any) => {
|
||||
issueStore.updateIssueStructure(group_by, null, issue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-full bg-custom-background-90`}>
|
||||
<List issues={issues} group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} />
|
||||
</div>
|
||||
);
|
||||
});
|
19
web/constants/kanban-helpers.ts
Normal file
19
web/constants/kanban-helpers.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export const sortArrayByDate = (_array: any[], _key: string): any[] => {
|
||||
console.log("date sorting");
|
||||
// return _array.sort((a, b) => {
|
||||
// const x = new Date(a[_key]);
|
||||
// const y = new Date(b[_key]);
|
||||
// return x > y ? -1 : x < y ? 1 : 0;
|
||||
// });
|
||||
return _array;
|
||||
};
|
||||
|
||||
export const sortArrayByPriority = (_array: any[], _key: string): any[] => {
|
||||
console.log("priority sorting");
|
||||
// return _array.sort((a, b) => {
|
||||
// const x = new Date(a[_key]);
|
||||
// const y = new Date(b[_key]);
|
||||
// return x > y ? -1 : x < y ? 1 : 0;
|
||||
// });
|
||||
return _array;
|
||||
};
|
@ -100,6 +100,7 @@
|
||||
"prettier": "^2.8.7",
|
||||
"tailwind-config-custom": "*",
|
||||
"tsconfig": "*",
|
||||
"@plane/ui": "*",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@ -13,6 +13,7 @@ import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||
// components
|
||||
import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "components/core";
|
||||
import { CycleDetailsSidebar, TransferIssues, TransferIssuesModal } from "components/cycles";
|
||||
import { CycleLayoutRoot } from "components/issues/issue-layouts";
|
||||
// services
|
||||
import issuesService from "services/issue.service";
|
||||
import cycleServices from "services/cycles.service";
|
||||
@ -163,19 +164,17 @@ const SingleCycle: React.FC = () => {
|
||||
) : (
|
||||
<>
|
||||
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
|
||||
|
||||
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
|
||||
|
||||
<div
|
||||
className={`h-full flex flex-col ${cycleSidebar ? "mr-[24rem]" : ""} ${
|
||||
className={`relative w-full h-full flex flex-col overflow-auto ${cycleSidebar ? "mr-[24rem]" : ""} ${
|
||||
analyticsModal ? "mr-[50%]" : ""
|
||||
} duration-300`}
|
||||
>
|
||||
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
|
||||
<div className="relative overflow-y-auto w-full h-full">
|
||||
<IssuesView
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
disableUserActions={cycleStatus === "completed" ?? false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CycleLayoutRoot />
|
||||
</div>
|
||||
<CycleDetailsSidebar
|
||||
cycleStatus={cycleStatus}
|
||||
|
192
web/store/cycle_issue.ts
Normal file
192
web/store/cycle_issue.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// store
|
||||
import { RootStore } from "./root";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// services
|
||||
import { CycleService } from "services/cycles.service";
|
||||
// constants
|
||||
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[];
|
||||
|
||||
export interface ICycleIssueStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
// issues
|
||||
issues: {
|
||||
[cycleId: string]: {
|
||||
grouped: IIssueGroupedStructure;
|
||||
groupWithSubGroups: IIssueGroupWithSubGroupsStructure;
|
||||
ungrouped: IIssueUnGroupedStructure;
|
||||
};
|
||||
};
|
||||
// computed
|
||||
getIssueType: IIssueType | null;
|
||||
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
|
||||
// action
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
||||
}
|
||||
|
||||
class CycleIssueStore implements ICycleIssueStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
issues: {
|
||||
[cycle_id: string]: {
|
||||
grouped: {
|
||||
[group_id: string]: IIssue[];
|
||||
};
|
||||
groupWithSubGroups: {
|
||||
[group_id: string]: {
|
||||
[sub_group_id: string]: IIssue[];
|
||||
};
|
||||
};
|
||||
ungrouped: IIssue[];
|
||||
};
|
||||
} = {};
|
||||
// service
|
||||
cycleService;
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
loader: observable.ref,
|
||||
error: observable.ref,
|
||||
issues: observable.ref,
|
||||
// computed
|
||||
getIssueType: computed,
|
||||
getIssues: computed,
|
||||
// actions
|
||||
fetchIssues: action,
|
||||
updateIssueStructure: action,
|
||||
});
|
||||
this.rootStore = _rootStore;
|
||||
this.cycleService = new CycleService();
|
||||
}
|
||||
|
||||
get getIssueType() {
|
||||
const groupedLayouts = ["kanban", "list", "calendar"];
|
||||
const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
|
||||
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
if (!issueLayout) return null;
|
||||
|
||||
const _issueState = groupedLayouts.includes(issueLayout)
|
||||
? issueSubGroup
|
||||
? "groupWithSubGroups"
|
||||
: "grouped"
|
||||
: ungroupedLayouts.includes(issueLayout)
|
||||
? "ungrouped"
|
||||
: null;
|
||||
|
||||
return _issueState || null;
|
||||
}
|
||||
|
||||
get getIssues() {
|
||||
const cycleId: string | null = this.rootStore?.cycle?.cycleId;
|
||||
const issueType = this.getIssueType;
|
||||
if (!cycleId || !issueType) return null;
|
||||
|
||||
return this.issues?.[cycleId]?.[issueType] || null;
|
||||
}
|
||||
|
||||
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;
|
||||
if (!cycleId || !issueType) return null;
|
||||
|
||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
||||
this.getIssues;
|
||||
if (!issues) return null;
|
||||
|
||||
if (issueType === "grouped" && group_id) {
|
||||
issues = issues as IIssueGroupedStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
||||
};
|
||||
}
|
||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[sub_group_id]: {
|
||||
...issues[sub_group_id],
|
||||
[group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (issueType === "ungrouped") {
|
||||
issues = issues as IIssueUnGroupedStructure;
|
||||
issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i));
|
||||
}
|
||||
|
||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
||||
if (orderBy === "-created_at") {
|
||||
issues = sortArrayByDate(issues as any, "created_at");
|
||||
}
|
||||
if (orderBy === "-updated_at") {
|
||||
issues = sortArrayByDate(issues as any, "updated_at");
|
||||
}
|
||||
if (orderBy === "start_date") {
|
||||
issues = sortArrayByDate(issues as any, "updated_at");
|
||||
}
|
||||
if (orderBy === "priority") {
|
||||
issues = sortArrayByPriority(issues as any, "priority");
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.issues = { ...this.issues, [cycleId]: { ...this.issues[cycleId], [issueType]: issues } };
|
||||
});
|
||||
};
|
||||
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
this.error = null;
|
||||
|
||||
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
|
||||
this.rootStore.project.setProjectId(projectId);
|
||||
this.rootStore.cycle.setCycleId(cycleId);
|
||||
|
||||
const params = this.rootStore?.cycleIssueFilter?.appliedFilters;
|
||||
const issueResponse = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params);
|
||||
|
||||
const issueType = this.getIssueType;
|
||||
if (issueType != null) {
|
||||
const _issues = {
|
||||
...this.issues,
|
||||
[cycleId]: {
|
||||
...this.issues[cycleId],
|
||||
[issueType]: issueResponse,
|
||||
},
|
||||
};
|
||||
runInAction(() => {
|
||||
this.issues = _issues;
|
||||
this.loader = false;
|
||||
this.error = null;
|
||||
});
|
||||
}
|
||||
|
||||
return issueResponse;
|
||||
} catch (error) {
|
||||
console.error("Error: Fetching error in issues", error);
|
||||
this.loader = false;
|
||||
this.error = error;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CycleIssueStore;
|
156
web/store/cycle_issue_filters.ts
Normal file
156
web/store/cycle_issue_filters.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// services
|
||||
import { CycleService } from "services/cycles.service";
|
||||
// helpers
|
||||
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||
// types
|
||||
import { RootStore } from "./root";
|
||||
import { IIssueFilterOptions, TIssueParams } from "types";
|
||||
|
||||
export interface ICycleIssueFilterStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
cycleFilters: IIssueFilterOptions;
|
||||
|
||||
// action
|
||||
fetchCycleFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||
updateCycleFilters: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
filterToUpdate: Partial<IIssueFilterOptions>
|
||||
) => Promise<void>;
|
||||
|
||||
// computed
|
||||
appliedFilters: TIssueParams[] | null;
|
||||
}
|
||||
|
||||
class CycleIssueFilterStore implements ICycleIssueFilterStore {
|
||||
// observables
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
cycleFilters: IIssueFilterOptions = {};
|
||||
// root store
|
||||
rootStore;
|
||||
// services
|
||||
cycleService;
|
||||
|
||||
constructor(_rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
loader: observable.ref,
|
||||
error: observable.ref,
|
||||
cycleFilters: observable.ref,
|
||||
// computed
|
||||
appliedFilters: computed,
|
||||
// actions
|
||||
fetchCycleFilters: action,
|
||||
updateCycleFilters: action,
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
this.cycleService = new CycleService();
|
||||
}
|
||||
|
||||
computedFilter = (filters: any, filteredParams: any) => {
|
||||
const computedFilters: any = {};
|
||||
Object.keys(filters).map((key) => {
|
||||
if (filters[key] != undefined && filteredParams.includes(key))
|
||||
computedFilters[key] =
|
||||
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
|
||||
});
|
||||
|
||||
return computedFilters;
|
||||
};
|
||||
|
||||
get appliedFilters(): TIssueParams[] | null {
|
||||
const userDisplayFilters = this.rootStore?.issueFilter?.userDisplayFilters;
|
||||
|
||||
console.log("userDisplayFilters", userDisplayFilters);
|
||||
console.log("this.cycleFilters", this.cycleFilters);
|
||||
|
||||
if (!this.cycleFilters || !userDisplayFilters) return null;
|
||||
|
||||
let filteredRouteParams: any = {
|
||||
priority: this.cycleFilters?.priority || undefined,
|
||||
state_group: this.cycleFilters?.state_group || undefined,
|
||||
state: this.cycleFilters?.state || undefined,
|
||||
assignees: this.cycleFilters?.assignees || undefined,
|
||||
created_by: this.cycleFilters?.created_by || undefined,
|
||||
labels: this.cycleFilters?.labels || undefined,
|
||||
start_date: this.cycleFilters?.start_date || undefined,
|
||||
target_date: this.cycleFilters?.target_date || undefined,
|
||||
group_by: userDisplayFilters?.group_by || "state",
|
||||
order_by: userDisplayFilters?.order_by || "-created_at",
|
||||
sub_group_by: userDisplayFilters?.sub_group_by || undefined,
|
||||
type: userDisplayFilters?.type || undefined,
|
||||
sub_issue: userDisplayFilters?.sub_issue || true,
|
||||
show_empty_groups: userDisplayFilters?.show_empty_groups || true,
|
||||
start_target_date: userDisplayFilters?.start_target_date || true,
|
||||
};
|
||||
|
||||
console.log("----");
|
||||
console.log("filteredRouteParams", filteredRouteParams);
|
||||
const filteredParams = handleIssueQueryParamsByLayout(userDisplayFilters.layout, "issues");
|
||||
console.log("filteredParams", filteredParams);
|
||||
console.log("----");
|
||||
|
||||
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||
|
||||
if (userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date";
|
||||
if (userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
|
||||
return filteredRouteParams;
|
||||
}
|
||||
|
||||
fetchCycleFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
const cycleResponse = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
runInAction(() => {
|
||||
this.cycleFilters = cycleResponse?.view_props?.filters || {};
|
||||
});
|
||||
} catch (error) {
|
||||
runInAction(() => {
|
||||
this.error = error;
|
||||
});
|
||||
|
||||
console.log("Failed to fetch user filters in issue filter store", error);
|
||||
}
|
||||
};
|
||||
|
||||
updateCycleFilters = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
properties: Partial<IIssueFilterOptions>
|
||||
) => {
|
||||
const newProperties = {
|
||||
...this.cycleFilters,
|
||||
...properties,
|
||||
};
|
||||
|
||||
try {
|
||||
runInAction(() => {
|
||||
this.cycleFilters = newProperties;
|
||||
});
|
||||
|
||||
const payload = {
|
||||
view_props: {
|
||||
filters: newProperties,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, payload, undefined);
|
||||
} catch (error) {
|
||||
this.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
||||
|
||||
runInAction(() => {
|
||||
this.error = error;
|
||||
});
|
||||
|
||||
console.log("Failed to update user filters in issue filter store", error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CycleIssueFilterStore;
|
404
web/store/cycle_issue_kanban_view.ts
Normal file
404
web/store/cycle_issue_kanban_view.ts
Normal file
@ -0,0 +1,404 @@
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
// types
|
||||
import { RootStore } from "./root";
|
||||
import { IIssueType } from "./issue";
|
||||
|
||||
export interface ICycleIssueKanBanViewStore {
|
||||
kanBanToggle: {
|
||||
groupByHeaderMinMax: string[];
|
||||
subgroupByIssuesVisibility: string[];
|
||||
};
|
||||
// computed
|
||||
canUserDragDrop: boolean;
|
||||
canUserDragDropVertically: boolean;
|
||||
canUserDragDropHorizontally: boolean;
|
||||
// actions
|
||||
handleKanBanToggle: (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => void;
|
||||
handleSwimlaneDragDrop: (source: any, destination: any) => void;
|
||||
handleDragDrop: (source: any, destination: any) => void;
|
||||
}
|
||||
|
||||
class CycleIssueKanBanViewStore implements ICycleIssueKanBanViewStore {
|
||||
kanBanToggle: {
|
||||
groupByHeaderMinMax: string[];
|
||||
subgroupByIssuesVisibility: string[];
|
||||
} = { groupByHeaderMinMax: [], subgroupByIssuesVisibility: [] };
|
||||
// root store
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
kanBanToggle: observable,
|
||||
// computed
|
||||
canUserDragDrop: computed,
|
||||
canUserDragDropVertically: computed,
|
||||
canUserDragDropHorizontally: computed,
|
||||
|
||||
// actions
|
||||
handleKanBanToggle: action,
|
||||
handleSwimlaneDragDrop: action,
|
||||
handleDragDrop: action,
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
}
|
||||
|
||||
get canUserDragDrop() {
|
||||
if (
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.order_by &&
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" &&
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by)
|
||||
) {
|
||||
if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true;
|
||||
if (
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by)
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get canUserDragDropVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get canUserDragDropHorizontally() {
|
||||
return false;
|
||||
}
|
||||
|
||||
handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||
this.kanBanToggle = {
|
||||
...this.kanBanToggle,
|
||||
[toggle]: this.kanBanToggle[toggle].includes(value)
|
||||
? this.kanBanToggle[toggle].filter((v) => v !== value)
|
||||
: [...this.kanBanToggle[toggle], value],
|
||||
};
|
||||
};
|
||||
|
||||
handleSwimlaneDragDrop = async (source: any, destination: any) => {
|
||||
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||
const projectId = this.rootStore?.project?.projectId;
|
||||
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const currentIssues: any = this.rootStore.cycleIssue?.getIssues;
|
||||
|
||||
const sortOrderDefaultValue = 65535;
|
||||
|
||||
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) {
|
||||
// update issue payload
|
||||
let updateIssue: any = {
|
||||
workspaceSlug: workspaceSlug,
|
||||
projectId: projectId,
|
||||
};
|
||||
|
||||
// source, destination group and sub group id
|
||||
let droppableSourceColumnId = source.droppableId;
|
||||
droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null;
|
||||
let droppableDestinationColumnId = destination.droppableId;
|
||||
droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null;
|
||||
if (!droppableSourceColumnId || !droppableDestinationColumnId) return null;
|
||||
|
||||
const source_group_id: string = droppableSourceColumnId[0];
|
||||
const source_sub_group_id: string = droppableSourceColumnId[1] === "null" ? null : droppableSourceColumnId[1];
|
||||
|
||||
const destination_group_id: string = droppableDestinationColumnId[0];
|
||||
const destination_sub_group_id: string =
|
||||
droppableDestinationColumnId[1] === "null" ? null : droppableDestinationColumnId[1];
|
||||
|
||||
if (source_sub_group_id === destination_sub_group_id) {
|
||||
if (source_group_id === destination_group_id) {
|
||||
const _issues = currentIssues[source_sub_group_id][source_group_id];
|
||||
|
||||
// update the sort order
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _issues.length - 1) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _issues.splice(source.index, 1);
|
||||
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_sub_group_id][source_group_id] = _issues;
|
||||
}
|
||||
|
||||
if (source_group_id != destination_group_id) {
|
||||
const _sourceIssues = currentIssues[source_sub_group_id][source_group_id];
|
||||
let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || [];
|
||||
|
||||
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _destinationIssues.length) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order:
|
||||
(_destinationIssues[destination.index - 1].sort_order +
|
||||
_destinationIssues[destination.index].sort_order) /
|
||||
2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
// updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
// updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
|
||||
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||
}
|
||||
}
|
||||
|
||||
if (source_sub_group_id != destination_sub_group_id) {
|
||||
const _sourceIssues = currentIssues[source_sub_group_id][source_group_id];
|
||||
let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || [];
|
||||
|
||||
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _destinationIssues.length) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order:
|
||||
(_destinationIssues[destination.index - 1].sort_order +
|
||||
_destinationIssues[destination.index].sort_order) /
|
||||
2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
// updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
// updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
}
|
||||
|
||||
const reorderedIssues = {
|
||||
...this.rootStore?.cycleIssue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId],
|
||||
[issueType]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType],
|
||||
[issueType]: currentIssues,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.rootStore.cycleIssue.issues = { ...reorderedIssues };
|
||||
});
|
||||
|
||||
// console.log("updateIssue", updateIssue);
|
||||
|
||||
// this.rootStore.issueDetail?.updateIssue(
|
||||
// updateIssue.workspaceSlug,
|
||||
// updateIssue.projectId,
|
||||
// updateIssue.issueId,
|
||||
// updateIssue,
|
||||
// undefined
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
handleDragDrop = async (source: any, destination: any) => {
|
||||
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||
const projectId = this.rootStore?.project?.projectId;
|
||||
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const currentIssues: any = this.rootStore.cycleIssue?.getIssues;
|
||||
|
||||
const sortOrderDefaultValue = 65535;
|
||||
|
||||
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) {
|
||||
// update issue payload
|
||||
let updateIssue: any = {
|
||||
workspaceSlug: workspaceSlug,
|
||||
projectId: projectId,
|
||||
};
|
||||
|
||||
// source, destination group and sub group id
|
||||
let droppableSourceColumnId = source.droppableId;
|
||||
droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null;
|
||||
let droppableDestinationColumnId = destination.droppableId;
|
||||
droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null;
|
||||
if (!droppableSourceColumnId || !droppableDestinationColumnId) return null;
|
||||
|
||||
const source_group_id: string = droppableSourceColumnId[0];
|
||||
const destination_group_id: string = droppableDestinationColumnId[0];
|
||||
|
||||
if (this.canUserDragDrop) {
|
||||
// vertical
|
||||
if (source_group_id === destination_group_id) {
|
||||
const _issues = currentIssues[source_group_id];
|
||||
|
||||
// update the sort order
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _issues.length - 1) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _issues.splice(source.index, 1);
|
||||
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_group_id] = _issues;
|
||||
}
|
||||
|
||||
// horizontal
|
||||
if (source_group_id != destination_group_id) {
|
||||
const _sourceIssues = currentIssues[source_group_id];
|
||||
let _destinationIssues = currentIssues[destination_group_id] || [];
|
||||
|
||||
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _destinationIssues.length) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order:
|
||||
(_destinationIssues[destination.index - 1].sort_order +
|
||||
_destinationIssues[destination.index].sort_order) /
|
||||
2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
currentIssues[source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_group_id] = _destinationIssues;
|
||||
}
|
||||
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
}
|
||||
|
||||
// user can drag the issues only vertically
|
||||
if (this.canUserDragDropVertically && destination_group_id === destination_group_id) {
|
||||
}
|
||||
|
||||
// user can drag the issues only horizontally
|
||||
if (this.canUserDragDropHorizontally && destination_group_id != destination_group_id) {
|
||||
}
|
||||
|
||||
const reorderedIssues = {
|
||||
...this.rootStore?.cycleIssue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId],
|
||||
[issueType]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType],
|
||||
[issueType]: currentIssues,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.rootStore.cycleIssue.issues = { ...reorderedIssues };
|
||||
});
|
||||
|
||||
this.rootStore.issueDetail?.updateIssue(
|
||||
updateIssue.workspaceSlug,
|
||||
updateIssue.projectId,
|
||||
updateIssue.issueId,
|
||||
updateIssue,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CycleIssueKanBanViewStore;
|
@ -11,20 +11,28 @@ export interface ICycleStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
|
||||
cycleId: string | null;
|
||||
cycles: {
|
||||
[project_id: string]: ICycle[];
|
||||
};
|
||||
|
||||
cycle_details: {
|
||||
[cycle_id: string]: ICycle;
|
||||
};
|
||||
|
||||
// computed
|
||||
getCycleById: (cycleId: string) => ICycle | null;
|
||||
|
||||
// actions
|
||||
setCycleId: (cycleId: string) => void;
|
||||
|
||||
fetchCycles: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
params: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete"
|
||||
) => Promise<void>;
|
||||
fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
|
||||
createCycle: (workspaceSlug: string, projectId: string, data: any) => Promise<ICycle>;
|
||||
updateCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: any) => Promise<ICycle>;
|
||||
|
||||
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||
@ -34,6 +42,7 @@ class CycleStore implements ICycleStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
|
||||
cycleId: string | null = null;
|
||||
cycles: {
|
||||
[project_id: string]: ICycle[];
|
||||
} = {};
|
||||
@ -54,12 +63,19 @@ class CycleStore implements ICycleStore {
|
||||
loader: observable,
|
||||
error: observable.ref,
|
||||
|
||||
cycleId: observable,
|
||||
cycles: observable.ref,
|
||||
cycle_details: observable.ref,
|
||||
|
||||
// computed
|
||||
projectCycles: computed,
|
||||
|
||||
// actions
|
||||
setCycleId: action,
|
||||
getCycleById: action,
|
||||
|
||||
fetchCycles: action,
|
||||
fetchCycleWithId: action,
|
||||
createCycle: action,
|
||||
|
||||
addCycleToFavorites: action,
|
||||
@ -78,7 +94,13 @@ class CycleStore implements ICycleStore {
|
||||
return this.cycles[this.rootStore.project.projectId] || null;
|
||||
}
|
||||
|
||||
getCycleById = (cycleId: string) => this.cycle_details[cycleId] || null;
|
||||
|
||||
// actions
|
||||
setCycleId = (cycleId: string) => {
|
||||
this.cycleId = cycleId;
|
||||
};
|
||||
|
||||
fetchCycles = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -105,6 +127,23 @@ class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
fetchCycleWithId = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
const response = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
|
||||
runInAction(() => {
|
||||
this.cycle_details = {
|
||||
...this.cycle_details,
|
||||
[response?.id]: response,
|
||||
};
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to fetch cycle detail from cycle store");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
createCycle = async (workspaceSlug: string, projectId: string, data: any) => {
|
||||
try {
|
||||
const response = await this.cycleService.createCycle(
|
||||
@ -128,6 +167,34 @@ class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
updateCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: any) => {
|
||||
try {
|
||||
const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, data, undefined);
|
||||
|
||||
const _cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
||||
if (cycle.id === cycleId) return { ...cycle, ...response };
|
||||
return cycle;
|
||||
}),
|
||||
};
|
||||
const _cycleDetails = {
|
||||
...this.cycle_details,
|
||||
[cycleId]: { ...this.cycle_details[cycleId], ...response },
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.cycles = _cycles;
|
||||
this.cycle_details = _cycleDetails;
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to update cycle from cycle store");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
runInAction(() => {
|
||||
|
@ -5,6 +5,7 @@ import { RootStore } from "./root";
|
||||
import { IIssue } from "types";
|
||||
// services
|
||||
import { IssueService } from "services/issue.service";
|
||||
import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers";
|
||||
|
||||
export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped";
|
||||
export type IIssueGroupedStructure = { [group_id: string]: IIssue[] };
|
||||
@ -129,7 +130,19 @@ class IssueStore implements IIssueStore {
|
||||
issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i));
|
||||
}
|
||||
|
||||
// reorder issues based on the issue update
|
||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
||||
if (orderBy === "-created_at") {
|
||||
issues = sortArrayByDate(issues as any, "created_at");
|
||||
}
|
||||
if (orderBy === "-updated_at") {
|
||||
issues = sortArrayByDate(issues as any, "updated_at");
|
||||
}
|
||||
if (orderBy === "start_date") {
|
||||
issues = sortArrayByDate(issues as any, "updated_at");
|
||||
}
|
||||
if (orderBy === "priority") {
|
||||
issues = sortArrayByPriority(issues as any, "priority");
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } };
|
||||
|
@ -50,7 +50,7 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by)
|
||||
) {
|
||||
if (this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by === null) return true;
|
||||
if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true;
|
||||
if (
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by)
|
||||
@ -166,20 +166,30 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
};
|
||||
}
|
||||
|
||||
let issueStatePriority = {};
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") {
|
||||
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
issueStatePriority = { ...issueStatePriority, state: destination_group_id };
|
||||
}
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") {
|
||||
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
issueStatePriority = { ...issueStatePriority, priority: destination_group_id };
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
...issueStatePriority,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
else
|
||||
_destinationIssues = [
|
||||
..._destinationIssues,
|
||||
{ ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority },
|
||||
];
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
// updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
// updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
|
||||
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||
}
|
||||
@ -216,22 +226,51 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
};
|
||||
}
|
||||
|
||||
let issueStatePriority = {};
|
||||
if (source_group_id === destination_group_id) {
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "state") {
|
||||
updateIssue = { ...updateIssue, state: destination_sub_group_id };
|
||||
issueStatePriority = { ...issueStatePriority, state: destination_sub_group_id };
|
||||
}
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "priority") {
|
||||
updateIssue = { ...updateIssue, priority: destination_sub_group_id };
|
||||
issueStatePriority = { ...issueStatePriority, priority: destination_sub_group_id };
|
||||
}
|
||||
} else {
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "state") {
|
||||
updateIssue = { ...updateIssue, state: destination_sub_group_id, priority: destination_group_id };
|
||||
issueStatePriority = {
|
||||
...issueStatePriority,
|
||||
state: destination_sub_group_id,
|
||||
priority: destination_group_id,
|
||||
};
|
||||
}
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "priority") {
|
||||
updateIssue = { ...updateIssue, state: destination_group_id, priority: destination_sub_group_id };
|
||||
issueStatePriority = {
|
||||
...issueStatePriority,
|
||||
state: destination_group_id,
|
||||
priority: destination_sub_group_id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
...issueStatePriority,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
else
|
||||
_destinationIssues = [
|
||||
..._destinationIssues,
|
||||
{ ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority },
|
||||
];
|
||||
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
// updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
// updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
}
|
||||
|
||||
const reorderedIssues = {
|
||||
@ -249,15 +288,13 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
this.rootStore.issue.issues = { ...reorderedIssues };
|
||||
});
|
||||
|
||||
// console.log("updateIssue", updateIssue);
|
||||
|
||||
// this.rootStore.issueDetail?.updateIssue(
|
||||
// updateIssue.workspaceSlug,
|
||||
// updateIssue.projectId,
|
||||
// updateIssue.issueId,
|
||||
// updateIssue,
|
||||
// undefined
|
||||
// );
|
||||
this.rootStore.issueDetail?.updateIssue(
|
||||
updateIssue.workspaceSlug,
|
||||
updateIssue.projectId,
|
||||
updateIssue.issueId,
|
||||
updateIssue,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -348,23 +385,33 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
};
|
||||
}
|
||||
|
||||
let issueStatePriority = {};
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") {
|
||||
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
issueStatePriority = { ...issueStatePriority, state: destination_group_id };
|
||||
}
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") {
|
||||
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
issueStatePriority = { ...issueStatePriority, priority: destination_group_id };
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
...issueStatePriority,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
else
|
||||
_destinationIssues = [
|
||||
..._destinationIssues,
|
||||
{ ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority },
|
||||
];
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
currentIssues[source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_group_id] = _destinationIssues;
|
||||
}
|
||||
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
}
|
||||
|
||||
// user can drag the issues only vertically
|
||||
|
197
web/store/module_issue.ts
Normal file
197
web/store/module_issue.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// store
|
||||
import { RootStore } from "./root";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// services
|
||||
import { ModuleService } from "services/modules.service";
|
||||
// helpers
|
||||
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[];
|
||||
|
||||
export interface IModuleIssueStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
// issues
|
||||
issues: {
|
||||
[module_id: string]: {
|
||||
grouped: IIssueGroupedStructure;
|
||||
groupWithSubGroups: IIssueGroupWithSubGroupsStructure;
|
||||
ungrouped: IIssueUnGroupedStructure;
|
||||
};
|
||||
};
|
||||
// computed
|
||||
getIssueType: IIssueType | null;
|
||||
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
|
||||
// action
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<any>;
|
||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
||||
}
|
||||
|
||||
class ModuleIssueStore implements IModuleIssueStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
issues: {
|
||||
[module_id: string]: {
|
||||
grouped: {
|
||||
[group_id: string]: IIssue[];
|
||||
};
|
||||
groupWithSubGroups: {
|
||||
[group_id: string]: {
|
||||
[sub_group_id: string]: IIssue[];
|
||||
};
|
||||
};
|
||||
ungrouped: IIssue[];
|
||||
};
|
||||
} = {};
|
||||
// service
|
||||
moduleService;
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
loader: observable.ref,
|
||||
error: observable.ref,
|
||||
issues: observable.ref,
|
||||
// computed
|
||||
getIssueType: computed,
|
||||
getIssues: computed,
|
||||
// actions
|
||||
fetchIssues: action,
|
||||
updateIssueStructure: action,
|
||||
});
|
||||
this.rootStore = _rootStore;
|
||||
this.moduleService = new ModuleService();
|
||||
}
|
||||
|
||||
get getIssueType() {
|
||||
const groupedLayouts = ["kanban", "list", "calendar"];
|
||||
const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
|
||||
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
|
||||
|
||||
if (!issueLayout) return null;
|
||||
|
||||
const _issueState = groupedLayouts.includes(issueLayout)
|
||||
? issueSubGroup
|
||||
? "groupWithSubGroups"
|
||||
: "grouped"
|
||||
: ungroupedLayouts.includes(issueLayout)
|
||||
? "ungrouped"
|
||||
: null;
|
||||
|
||||
return _issueState || null;
|
||||
}
|
||||
|
||||
get getIssues() {
|
||||
const moduleId: string | null = this.rootStore?.module?.moduleId;
|
||||
const issueType = this.getIssueType;
|
||||
if (!moduleId || !issueType) return null;
|
||||
|
||||
return this.issues?.[moduleId]?.[issueType] || null;
|
||||
}
|
||||
|
||||
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;
|
||||
if (!moduleId || !issueType) return null;
|
||||
|
||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
||||
this.getIssues;
|
||||
if (!issues) return null;
|
||||
|
||||
if (issueType === "grouped" && group_id) {
|
||||
issues = issues as IIssueGroupedStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
||||
};
|
||||
}
|
||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[sub_group_id]: {
|
||||
...issues[sub_group_id],
|
||||
[group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (issueType === "ungrouped") {
|
||||
issues = issues as IIssueUnGroupedStructure;
|
||||
issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i));
|
||||
}
|
||||
|
||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
||||
if (orderBy === "-created_at") {
|
||||
issues = sortArrayByDate(issues as any, "created_at");
|
||||
}
|
||||
if (orderBy === "-updated_at") {
|
||||
issues = sortArrayByDate(issues as any, "updated_at");
|
||||
}
|
||||
if (orderBy === "start_date") {
|
||||
issues = sortArrayByDate(issues as any, "updated_at");
|
||||
}
|
||||
if (orderBy === "priority") {
|
||||
issues = sortArrayByPriority(issues as any, "priority");
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.issues = { ...this.issues, [moduleId]: { ...this.issues[moduleId], [issueType]: issues } };
|
||||
});
|
||||
};
|
||||
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
this.error = null;
|
||||
|
||||
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
|
||||
this.rootStore.project.setProjectId(projectId);
|
||||
this.rootStore.module.setModuleId(moduleId);
|
||||
|
||||
const params = this.rootStore?.cycleIssueFilter?.appliedFilters;
|
||||
const issueResponse = await this.moduleService.getModuleIssuesWithParams(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
moduleId,
|
||||
params
|
||||
);
|
||||
|
||||
const issueType = this.getIssueType;
|
||||
if (issueType != null) {
|
||||
const _issues = {
|
||||
...this.issues,
|
||||
[moduleId]: {
|
||||
...this.issues[moduleId],
|
||||
[issueType]: issueResponse,
|
||||
},
|
||||
};
|
||||
runInAction(() => {
|
||||
this.issues = _issues;
|
||||
this.loader = false;
|
||||
this.error = null;
|
||||
});
|
||||
}
|
||||
|
||||
return issueResponse;
|
||||
} catch (error) {
|
||||
console.error("Error: Fetching error in issues", error);
|
||||
this.loader = false;
|
||||
this.error = error;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ModuleIssueStore;
|
404
web/store/module_issue_kanban_view.ts
Normal file
404
web/store/module_issue_kanban_view.ts
Normal file
@ -0,0 +1,404 @@
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
// types
|
||||
import { RootStore } from "./root";
|
||||
import { IIssueType } from "./issue";
|
||||
|
||||
export interface IModuleIssueKanBanViewStore {
|
||||
kanBanToggle: {
|
||||
groupByHeaderMinMax: string[];
|
||||
subgroupByIssuesVisibility: string[];
|
||||
};
|
||||
// computed
|
||||
canUserDragDrop: boolean;
|
||||
canUserDragDropVertically: boolean;
|
||||
canUserDragDropHorizontally: boolean;
|
||||
// actions
|
||||
handleKanBanToggle: (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => void;
|
||||
handleSwimlaneDragDrop: (source: any, destination: any) => void;
|
||||
handleDragDrop: (source: any, destination: any) => void;
|
||||
}
|
||||
|
||||
class ModuleIssueKanBanViewStore implements IModuleIssueKanBanViewStore {
|
||||
kanBanToggle: {
|
||||
groupByHeaderMinMax: string[];
|
||||
subgroupByIssuesVisibility: string[];
|
||||
} = { groupByHeaderMinMax: [], subgroupByIssuesVisibility: [] };
|
||||
// root store
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
kanBanToggle: observable,
|
||||
// computed
|
||||
canUserDragDrop: computed,
|
||||
canUserDragDropVertically: computed,
|
||||
canUserDragDropHorizontally: computed,
|
||||
|
||||
// actions
|
||||
handleKanBanToggle: action,
|
||||
handleSwimlaneDragDrop: action,
|
||||
handleDragDrop: action,
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
}
|
||||
|
||||
get canUserDragDrop() {
|
||||
if (
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.order_by &&
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" &&
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by)
|
||||
) {
|
||||
if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true;
|
||||
if (
|
||||
this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by &&
|
||||
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by)
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get canUserDragDropVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get canUserDragDropHorizontally() {
|
||||
return false;
|
||||
}
|
||||
|
||||
handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||
this.kanBanToggle = {
|
||||
...this.kanBanToggle,
|
||||
[toggle]: this.kanBanToggle[toggle].includes(value)
|
||||
? this.kanBanToggle[toggle].filter((v) => v !== value)
|
||||
: [...this.kanBanToggle[toggle], value],
|
||||
};
|
||||
};
|
||||
|
||||
handleSwimlaneDragDrop = async (source: any, destination: any) => {
|
||||
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||
const projectId = this.rootStore?.project?.projectId;
|
||||
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const currentIssues: any = this.rootStore.moduleIssue?.getIssues;
|
||||
|
||||
const sortOrderDefaultValue = 65535;
|
||||
|
||||
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) {
|
||||
// update issue payload
|
||||
let updateIssue: any = {
|
||||
workspaceSlug: workspaceSlug,
|
||||
projectId: projectId,
|
||||
};
|
||||
|
||||
// source, destination group and sub group id
|
||||
let droppableSourceColumnId = source.droppableId;
|
||||
droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null;
|
||||
let droppableDestinationColumnId = destination.droppableId;
|
||||
droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null;
|
||||
if (!droppableSourceColumnId || !droppableDestinationColumnId) return null;
|
||||
|
||||
const source_group_id: string = droppableSourceColumnId[0];
|
||||
const source_sub_group_id: string = droppableSourceColumnId[1] === "null" ? null : droppableSourceColumnId[1];
|
||||
|
||||
const destination_group_id: string = droppableDestinationColumnId[0];
|
||||
const destination_sub_group_id: string =
|
||||
droppableDestinationColumnId[1] === "null" ? null : droppableDestinationColumnId[1];
|
||||
|
||||
if (source_sub_group_id === destination_sub_group_id) {
|
||||
if (source_group_id === destination_group_id) {
|
||||
const _issues = currentIssues[source_sub_group_id][source_group_id];
|
||||
|
||||
// update the sort order
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _issues.length - 1) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _issues.splice(source.index, 1);
|
||||
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_sub_group_id][source_group_id] = _issues;
|
||||
}
|
||||
|
||||
if (source_group_id != destination_group_id) {
|
||||
const _sourceIssues = currentIssues[source_sub_group_id][source_group_id];
|
||||
let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || [];
|
||||
|
||||
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _destinationIssues.length) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order:
|
||||
(_destinationIssues[destination.index - 1].sort_order +
|
||||
_destinationIssues[destination.index].sort_order) /
|
||||
2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
// updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
// updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
|
||||
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||
}
|
||||
}
|
||||
|
||||
if (source_sub_group_id != destination_sub_group_id) {
|
||||
const _sourceIssues = currentIssues[source_sub_group_id][source_group_id];
|
||||
let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || [];
|
||||
|
||||
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _destinationIssues.length) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order:
|
||||
(_destinationIssues[destination.index - 1].sort_order +
|
||||
_destinationIssues[destination.index].sort_order) /
|
||||
2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
// updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
// if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
// updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
}
|
||||
|
||||
const reorderedIssues = {
|
||||
...this.rootStore?.cycleIssue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId],
|
||||
[issueType]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType],
|
||||
[issueType]: currentIssues,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.rootStore.moduleIssue.issues = { ...reorderedIssues };
|
||||
});
|
||||
|
||||
// console.log("updateIssue", updateIssue);
|
||||
|
||||
// this.rootStore.issueDetail?.updateIssue(
|
||||
// updateIssue.workspaceSlug,
|
||||
// updateIssue.projectId,
|
||||
// updateIssue.issueId,
|
||||
// updateIssue,
|
||||
// undefined
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
handleDragDrop = async (source: any, destination: any) => {
|
||||
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||
const projectId = this.rootStore?.project?.projectId;
|
||||
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||
const currentIssues: any = this.rootStore.moduleIssue?.getIssues;
|
||||
|
||||
const sortOrderDefaultValue = 65535;
|
||||
|
||||
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) {
|
||||
// update issue payload
|
||||
let updateIssue: any = {
|
||||
workspaceSlug: workspaceSlug,
|
||||
projectId: projectId,
|
||||
};
|
||||
|
||||
// source, destination group and sub group id
|
||||
let droppableSourceColumnId = source.droppableId;
|
||||
droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null;
|
||||
let droppableDestinationColumnId = destination.droppableId;
|
||||
droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null;
|
||||
if (!droppableSourceColumnId || !droppableDestinationColumnId) return null;
|
||||
|
||||
const source_group_id: string = droppableSourceColumnId[0];
|
||||
const destination_group_id: string = droppableDestinationColumnId[0];
|
||||
|
||||
if (this.canUserDragDrop) {
|
||||
// vertical
|
||||
if (source_group_id === destination_group_id) {
|
||||
const _issues = currentIssues[source_group_id];
|
||||
|
||||
// update the sort order
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _issues.length - 1) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _issues.splice(source.index, 1);
|
||||
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
currentIssues[source_group_id] = _issues;
|
||||
}
|
||||
|
||||
// horizontal
|
||||
if (source_group_id != destination_group_id) {
|
||||
const _sourceIssues = currentIssues[source_group_id];
|
||||
let _destinationIssues = currentIssues[destination_group_id] || [];
|
||||
|
||||
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||
if (destination.index === 0) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destination.index === _destinationIssues.length) {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order:
|
||||
(_destinationIssues[destination.index - 1].sort_order +
|
||||
_destinationIssues[destination.index].sort_order) /
|
||||
2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||
if (_destinationIssues && _destinationIssues.length > 0)
|
||||
_destinationIssues.splice(destination.index, 0, {
|
||||
...removed,
|
||||
sort_order: updateIssue.sort_order,
|
||||
});
|
||||
else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }];
|
||||
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||
|
||||
currentIssues[source_group_id] = _sourceIssues;
|
||||
currentIssues[destination_group_id] = _destinationIssues;
|
||||
}
|
||||
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state")
|
||||
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority")
|
||||
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||
}
|
||||
|
||||
// user can drag the issues only vertically
|
||||
if (this.canUserDragDropVertically && destination_group_id === destination_group_id) {
|
||||
}
|
||||
|
||||
// user can drag the issues only horizontally
|
||||
if (this.canUserDragDropHorizontally && destination_group_id != destination_group_id) {
|
||||
}
|
||||
|
||||
const reorderedIssues = {
|
||||
...this.rootStore?.cycleIssue.issues,
|
||||
[projectId]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId],
|
||||
[issueType]: {
|
||||
...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType],
|
||||
[issueType]: currentIssues,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.rootStore.moduleIssue.issues = { ...reorderedIssues };
|
||||
});
|
||||
|
||||
this.rootStore.issueDetail?.updateIssue(
|
||||
updateIssue.workspaceSlug,
|
||||
updateIssue.projectId,
|
||||
updateIssue.issueId,
|
||||
updateIssue,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ModuleIssueKanBanViewStore;
|
@ -9,13 +9,18 @@ import DraftIssuesStore from "./issue_draft";
|
||||
import WorkspaceStore, { IWorkspaceStore } from "./workspace";
|
||||
import ProjectStore, { IProjectStore } from "./project";
|
||||
import ModuleStore, { IModuleStore } from "./modules";
|
||||
import ModuleIssueStore, { IModuleIssueStore } from "./module_issue";
|
||||
import ModuleFilterStore, { IModuleFilterStore } from "./module_filters";
|
||||
import ModuleIssueKanBanViewStore, { IModuleIssueKanBanViewStore } from "./module_issue_kanban_view";
|
||||
import CycleStore, { ICycleStore } from "./cycles";
|
||||
import CycleIssueStore, { ICycleIssueStore } from "./cycle_issue";
|
||||
import CycleIssueFilterStore, { ICycleIssueFilterStore } from "./cycle_issue_filters";
|
||||
import CycleIssueKanBanViewStore, { ICycleIssueKanBanViewStore } from "./cycle_issue_kanban_view";
|
||||
import ViewStore, { IViewStore } from "./views";
|
||||
import IssueFilterStore, { IIssueFilterStore } from "./issue_filters";
|
||||
import IssueViewDetailStore from "./issue_detail";
|
||||
import IssueKanBanViewStore from "./kanban_view";
|
||||
import CalendarStore, { ICalendarStore } from "./calendar";
|
||||
import ModuleFilterStore, { IModuleFilterStore } from "./module_filters";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
|
||||
@ -27,9 +32,17 @@ export class RootStore {
|
||||
workspace: IWorkspaceStore;
|
||||
project: IProjectStore;
|
||||
issue: IIssueStore;
|
||||
|
||||
module: IModuleStore;
|
||||
moduleIssue: IModuleIssueStore;
|
||||
moduleFilter: IModuleFilterStore;
|
||||
moduleIssueKanBanView: IModuleIssueKanBanViewStore;
|
||||
|
||||
cycle: ICycleStore;
|
||||
cycleIssue: ICycleIssueStore;
|
||||
cycleIssueFilter: ICycleIssueFilterStore;
|
||||
cycleIssueKanBanView: ICycleIssueKanBanViewStore;
|
||||
|
||||
view: IViewStore;
|
||||
issueFilter: IIssueFilterStore;
|
||||
issueDetail: IssueViewDetailStore;
|
||||
@ -42,9 +55,17 @@ export class RootStore {
|
||||
this.workspace = new WorkspaceStore(this);
|
||||
this.project = new ProjectStore(this);
|
||||
this.projectPublish = new ProjectPublishStore(this);
|
||||
|
||||
this.module = new ModuleStore(this);
|
||||
this.moduleIssue = new ModuleIssueStore(this);
|
||||
this.moduleFilter = new ModuleFilterStore(this);
|
||||
this.moduleIssueKanBanView = new ModuleIssueKanBanViewStore(this);
|
||||
|
||||
this.cycle = new CycleStore(this);
|
||||
this.cycleIssue = new CycleIssueStore(this);
|
||||
this.cycleIssueFilter = new CycleIssueFilterStore(this);
|
||||
this.cycleIssueKanBanView = new CycleIssueKanBanViewStore(this);
|
||||
|
||||
this.view = new ViewStore(this);
|
||||
this.issue = new IssueStore(this);
|
||||
this.issueFilter = new IssueFilterStore(this);
|
||||
|
Loading…
Reference in New Issue
Block a user