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:
guru_sainath 2023-10-09 14:28:42 +05:30 committed by GitHub
parent 13874f2357
commit e28919a964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2175 additions and 172 deletions

View File

@ -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>

View 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>
);
});

View File

@ -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";

View 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>
);
});

View File

@ -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>

View File

@ -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}
/>
))}
</>

View File

@ -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}
/>
))}
</>

View File

@ -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} />

View File

@ -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}
/>
)}
</>

View File

@ -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}
/>
))}
</>

View File

@ -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}
/>
))}
</>

View File

@ -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}
/>
))}
</>

View File

@ -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}
/>
))}
</>

View File

@ -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>
);
});
)
);

View File

@ -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}
/>
)}
</>

View 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>
);
});

View File

@ -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>

View File

@ -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>

View 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>
);
});

View 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>
);
});

View 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>
);
});

View 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>
);
});

View 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;
};

View File

@ -100,6 +100,7 @@
"prettier": "^2.8.7",
"tailwind-config-custom": "*",
"tsconfig": "*",
"@plane/ui": "*",
"typescript": "4.7.4"
},
"resolutions": {

View File

@ -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
View 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;

View 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;

View 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;

View File

@ -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(() => {

View File

@ -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 } };

View File

@ -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
View 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;

View 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;

View File

@ -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);