mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: gantt chart resizable blocks, y-axis drag and drop (#1810)
* chore: gantt chart resizable blocks * chore: right scroll added * chore: left scroll added * fix: build errors * chore: remove unnecessary console logs * chore: add block type and remove info toggle * feat: gantt chart blocks y-axis drag and drop * chore: disable drag flag * fix: y-axis drag mutation * fix: scroll container undefined error * fix: negative scroll * fix: negative scroll * style: blocks tooltip consistency
This commit is contained in:
parent
762ef422ca
commit
785a6e8871
@ -113,44 +113,46 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<SelectFilters
|
{issueView !== "gantt_chart" && (
|
||||||
filters={filters}
|
<SelectFilters
|
||||||
onSelect={(option) => {
|
filters={filters}
|
||||||
const key = option.key as keyof typeof filters;
|
onSelect={(option) => {
|
||||||
|
const key = option.key as keyof typeof filters;
|
||||||
|
|
||||||
if (key === "target_date") {
|
if (key === "target_date") {
|
||||||
const valueExists = checkIfArraysHaveSameElements(
|
const valueExists = checkIfArraysHaveSameElements(
|
||||||
filters.target_date ?? [],
|
filters.target_date ?? [],
|
||||||
option.value
|
option.value
|
||||||
);
|
|
||||||
|
|
||||||
setFilters({
|
|
||||||
target_date: valueExists ? null : option.value,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const valueExists = filters[key]?.includes(option.value);
|
|
||||||
|
|
||||||
if (valueExists)
|
|
||||||
setFilters(
|
|
||||||
{
|
|
||||||
[option.key]: ((filters[key] ?? []) as any[])?.filter(
|
|
||||||
(val) => val !== option.value
|
|
||||||
),
|
|
||||||
},
|
|
||||||
!Boolean(viewId)
|
|
||||||
);
|
);
|
||||||
else
|
|
||||||
setFilters(
|
setFilters({
|
||||||
{
|
target_date: valueExists ? null : option.value,
|
||||||
[option.key]: [...((filters[key] ?? []) as any[]), option.value],
|
});
|
||||||
},
|
} else {
|
||||||
!Boolean(viewId)
|
const valueExists = filters[key]?.includes(option.value);
|
||||||
);
|
|
||||||
}
|
if (valueExists)
|
||||||
}}
|
setFilters(
|
||||||
direction="left"
|
{
|
||||||
height="rg"
|
[option.key]: ((filters[key] ?? []) as any[])?.filter(
|
||||||
/>
|
(val) => val !== option.value
|
||||||
|
),
|
||||||
|
},
|
||||||
|
!Boolean(viewId)
|
||||||
|
);
|
||||||
|
else
|
||||||
|
setFilters(
|
||||||
|
{
|
||||||
|
[option.key]: [...((filters[key] ?? []) as any[]), option.value],
|
||||||
|
},
|
||||||
|
!Boolean(viewId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
direction="left"
|
||||||
|
height="rg"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
@ -177,8 +179,9 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg">
|
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg">
|
||||||
<div className="relative divide-y-2 divide-custom-border-200">
|
<div className="relative divide-y-2 divide-custom-border-200">
|
||||||
<div className="space-y-4 pb-3 text-xs">
|
<div className="space-y-4 pb-3 text-xs">
|
||||||
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
{issueView !== "calendar" &&
|
||||||
<>
|
issueView !== "spreadsheet" &&
|
||||||
|
issueView !== "gantt_chart" && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Group by</h4>
|
<h4 className="text-custom-text-200">Group by</h4>
|
||||||
<div className="w-28">
|
<div className="w-28">
|
||||||
@ -206,34 +209,34 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
)}
|
||||||
<h4 className="text-custom-text-200">Order by</h4>
|
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
||||||
<div className="w-28">
|
<div className="flex items-center justify-between">
|
||||||
<CustomMenu
|
<h4 className="text-custom-text-200">Order by</h4>
|
||||||
label={
|
<div className="w-28">
|
||||||
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
<CustomMenu
|
||||||
"Select"
|
label={
|
||||||
}
|
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
||||||
className="!w-full"
|
"Select"
|
||||||
buttonClassName="w-full"
|
}
|
||||||
>
|
className="!w-full"
|
||||||
{ORDER_BY_OPTIONS.map((option) =>
|
buttonClassName="w-full"
|
||||||
groupByProperty === "priority" &&
|
>
|
||||||
option.key === "priority" ? null : (
|
{ORDER_BY_OPTIONS.map((option) =>
|
||||||
<CustomMenu.MenuItem
|
groupByProperty === "priority" && option.key === "priority" ? null : (
|
||||||
key={option.key}
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
key={option.key}
|
||||||
setOrderBy(option.key);
|
onClick={() => {
|
||||||
}}
|
setOrderBy(option.key);
|
||||||
>
|
}}
|
||||||
{option.name}
|
>
|
||||||
</CustomMenu.MenuItem>
|
{option.name}
|
||||||
)
|
</CustomMenu.MenuItem>
|
||||||
)}
|
)
|
||||||
</CustomMenu>
|
)}
|
||||||
</div>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Issue type</h4>
|
<h4 className="text-custom-text-200">Issue type</h4>
|
||||||
@ -263,16 +266,19 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
||||||
<>
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<h4 className="text-custom-text-200">Show sub-issues</h4>
|
||||||
<h4 className="text-custom-text-200">Show sub-issues</h4>
|
<div className="w-28">
|
||||||
<div className="w-28">
|
<ToggleSwitch
|
||||||
<ToggleSwitch
|
value={showSubIssues}
|
||||||
value={showSubIssues}
|
onChange={() => setShowSubIssues(!showSubIssues)}
|
||||||
onChange={() => setShowSubIssues(!showSubIssues)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{issueView !== "calendar" &&
|
||||||
|
issueView !== "spreadsheet" &&
|
||||||
|
issueView !== "gantt_chart" && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Show empty states</h4>
|
<h4 className="text-custom-text-200">Show empty states</h4>
|
||||||
<div className="w-28">
|
<div className="w-28">
|
||||||
@ -282,6 +288,10 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{issueView !== "calendar" &&
|
||||||
|
issueView !== "spreadsheet" &&
|
||||||
|
issueView !== "gantt_chart" && (
|
||||||
<div className="relative flex justify-end gap-x-3">
|
<div className="relative flex justify-end gap-x-3">
|
||||||
<button type="button" onClick={() => resetFilterToDefault()}>
|
<button type="button" onClick={() => resetFilterToDefault()}>
|
||||||
Reset to default
|
Reset to default
|
||||||
@ -294,47 +304,48 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
Set as default
|
Set as default
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 py-3">
|
{issueView !== "gantt_chart" && (
|
||||||
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
|
<div className="space-y-2 py-3">
|
||||||
<div className="flex flex-wrap items-center gap-2 text-custom-text-200">
|
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
|
||||||
{Object.keys(properties).map((key) => {
|
<div className="flex flex-wrap items-center gap-2 text-custom-text-200">
|
||||||
if (key === "estimate" && !isEstimateActive) return null;
|
{Object.keys(properties).map((key) => {
|
||||||
|
if (key === "estimate" && !isEstimateActive) return null;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
issueView === "spreadsheet" &&
|
issueView === "spreadsheet" &&
|
||||||
(key === "attachment_count" ||
|
(key === "attachment_count" ||
|
||||||
key === "link" ||
|
key === "link" ||
|
||||||
key === "sub_issue_count")
|
key === "sub_issue_count")
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
issueView !== "spreadsheet" &&
|
issueView !== "spreadsheet" &&
|
||||||
(key === "created_on" || key === "updated_on")
|
(key === "created_on" || key === "updated_on")
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded border px-2 py-1 text-xs capitalize ${
|
className={`rounded border px-2 py-1 text-xs capitalize ${
|
||||||
properties[key as keyof Properties]
|
properties[key as keyof Properties]
|
||||||
? "border-custom-primary bg-custom-primary text-white"
|
? "border-custom-primary bg-custom-primary text-white"
|
||||||
: "border-custom-border-200"
|
: "border-custom-border-200"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setProperties(key as keyof Properties)}
|
onClick={() => setProperties(key as keyof Properties)}
|
||||||
>
|
>
|
||||||
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// next imports
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { KeyedMutator } from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import cyclesService from "services/cycles.service";
|
||||||
|
// hooks
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { CycleGanttBlock, GanttChartRoot, IBlockUpdateData } from "components/gantt-chart";
|
||||||
// ui
|
|
||||||
import { Tooltip } from "components/ui";
|
|
||||||
// types
|
// types
|
||||||
import { ICycle } from "types";
|
import { ICycle } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cycles: ICycle[];
|
cycles: ICycle[];
|
||||||
|
mutateCycles: KeyedMutator<ICycle[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CyclesListGanttChartView: FC<Props> = ({ cycles }) => {
|
export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
// rendering issues on gantt sidebar
|
// rendering issues on gantt sidebar
|
||||||
const GanttSidebarBlockView = ({ data }: any) => (
|
const GanttSidebarBlockView = ({ data }: any) => (
|
||||||
@ -28,53 +35,65 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// rendering issues on gantt card
|
const handleCycleUpdate = (cycle: ICycle, payload: IBlockUpdateData) => {
|
||||||
const GanttBlockView = ({ data }: { data: ICycle }) => (
|
if (!workspaceSlug || !user) return;
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${data?.id}`}>
|
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 w-[4px] h-full"
|
|
||||||
style={{ backgroundColor: "rgb(var(--color-primary-100))" }}
|
|
||||||
/>
|
|
||||||
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
|
||||||
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
|
||||||
{data?.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle gantt issue start date and target date
|
mutateCycles((prevData) => {
|
||||||
const handleUpdateDates = async (data: any) => {
|
if (!prevData) return prevData;
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
const newList = prevData.map((p) => ({
|
||||||
start_date: data?.start_date,
|
...p,
|
||||||
target_date: data?.target_date,
|
...(p.id === cycle.id
|
||||||
};
|
? {
|
||||||
|
start_date: payload.start_date ? payload.start_date : p.start_date,
|
||||||
|
target_date: payload.target_date ? payload.target_date : p.end_date,
|
||||||
|
sort_order: payload.sort_order ? payload.sort_order.newSortOrder : p.sort_order,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (payload.sort_order) {
|
||||||
|
const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0];
|
||||||
|
newList.splice(payload.sort_order.destinationIndex, 0, removedElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newList;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
const newPayload: any = { ...payload };
|
||||||
|
|
||||||
|
if (newPayload.sort_order && payload.sort_order)
|
||||||
|
newPayload.sort_order = payload.sort_order.newSortOrder;
|
||||||
|
|
||||||
|
cyclesService
|
||||||
|
.patchCycle(workspaceSlug.toString(), cycle.project, cycle.id, newPayload, user)
|
||||||
|
.finally(() => mutateCycles());
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
const blockFormat = (blocks: ICycle[]) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => {
|
? blocks
|
||||||
if (_block?.start_date && _block.target_date) console.log("_block", _block);
|
.filter((b) => b.start_date && b.end_date)
|
||||||
return {
|
.map((block) => ({
|
||||||
start_date: new Date(_block.created_at),
|
data: block,
|
||||||
target_date: new Date(_block.updated_at),
|
id: block.id,
|
||||||
data: _block,
|
sort_order: block.sort_order,
|
||||||
};
|
start_date: new Date(block.start_date ?? ""),
|
||||||
})
|
target_date: new Date(block.end_date ?? ""),
|
||||||
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full overflow-y-auto">
|
<div className="w-full h-full overflow-y-auto">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
title={"Cycles"}
|
title="Cycles"
|
||||||
loaderTitle="Cycles"
|
loaderTitle="Cycles"
|
||||||
blocks={cycles ? blockFormat(cycles) : null}
|
blocks={cycles ? blockFormat(cycles) : null}
|
||||||
blockUpdateHandler={handleUpdateDates}
|
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
|
||||||
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
||||||
blockRender={(data: any) => <GanttBlockView data={data} />}
|
blockRender={(data: any) => <CycleGanttBlock cycle={data as ICycle} />}
|
||||||
|
enableLeftDrag={false}
|
||||||
|
enableRightDrag={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -17,7 +17,7 @@ export const AllCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { data: allCyclesList } = useSWR(
|
const { data: allCyclesList, mutate } = useSWR(
|
||||||
workspaceSlug && projectId ? CYCLES_LIST(projectId.toString()) : null,
|
workspaceSlug && projectId ? CYCLES_LIST(projectId.toString()) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () =>
|
||||||
@ -25,5 +25,5 @@ export const AllCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
return <CyclesView cycles={allCyclesList} viewType={viewType} />;
|
return <CyclesView cycles={allCyclesList} mutateCycles={mutate} viewType={viewType} />;
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ export const CompletedCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { data: completedCyclesList } = useSWR(
|
const { data: completedCyclesList, mutate } = useSWR(
|
||||||
workspaceSlug && projectId ? COMPLETED_CYCLES_LIST(projectId.toString()) : null,
|
workspaceSlug && projectId ? COMPLETED_CYCLES_LIST(projectId.toString()) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () =>
|
||||||
@ -29,5 +29,5 @@ export const CompletedCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
return <CyclesView cycles={completedCyclesList} viewType={viewType} />;
|
return <CyclesView cycles={completedCyclesList} mutateCycles={mutate} viewType={viewType} />;
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ export const DraftCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { data: draftCyclesList } = useSWR(
|
const { data: draftCyclesList, mutate } = useSWR(
|
||||||
workspaceSlug && projectId ? DRAFT_CYCLES_LIST(projectId.toString()) : null,
|
workspaceSlug && projectId ? DRAFT_CYCLES_LIST(projectId.toString()) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () =>
|
||||||
@ -25,5 +25,5 @@ export const DraftCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
return <CyclesView cycles={draftCyclesList} viewType={viewType} />;
|
return <CyclesView cycles={draftCyclesList} mutateCycles={mutate} viewType={viewType} />;
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ export const UpcomingCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { data: upcomingCyclesList } = useSWR(
|
const { data: upcomingCyclesList, mutate } = useSWR(
|
||||||
workspaceSlug && projectId ? UPCOMING_CYCLES_LIST(projectId.toString()) : null,
|
workspaceSlug && projectId ? UPCOMING_CYCLES_LIST(projectId.toString()) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () =>
|
||||||
@ -29,5 +29,5 @@ export const UpcomingCyclesList: React.FC<Props> = ({ viewType }) => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
return <CyclesView cycles={upcomingCyclesList} viewType={viewType} />;
|
return <CyclesView cycles={upcomingCyclesList} mutateCycles={mutate} viewType={viewType} />;
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { mutate } from "swr";
|
import { KeyedMutator, mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
@ -35,10 +35,11 @@ import {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cycles: ICycle[] | undefined;
|
cycles: ICycle[] | undefined;
|
||||||
|
mutateCycles: KeyedMutator<ICycle[]>;
|
||||||
viewType: string | null;
|
viewType: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CyclesView: React.FC<Props> = ({ cycles, viewType }) => {
|
export const CyclesView: React.FC<Props> = ({ cycles, mutateCycles, viewType }) => {
|
||||||
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
|
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
|
||||||
const [selectedCycleToUpdate, setSelectedCycleToUpdate] = useState<ICycle | null>(null);
|
const [selectedCycleToUpdate, setSelectedCycleToUpdate] = useState<ICycle | null>(null);
|
||||||
|
|
||||||
@ -202,7 +203,7 @@ export const CyclesView: React.FC<Props> = ({ cycles, viewType }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<CyclesListGanttChartView cycles={cycles ?? []} />
|
<CyclesListGanttChartView cycles={cycles ?? []} mutateCycles={mutateCycles} />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full grid place-items-center text-center">
|
<div className="h-full grid place-items-center text-center">
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
import { FC } from "react";
|
|
||||||
// next imports
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
|
||||||
// ui
|
|
||||||
import { Tooltip } from "components/ui";
|
|
||||||
// hooks
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
import useGanttChartCycleIssues from "hooks/gantt-chart/cycle-issues-view";
|
import useGanttChartCycleIssues from "hooks/gantt-chart/cycle-issues-view";
|
||||||
|
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
GanttChartRoot,
|
||||||
|
IssueGanttBlock,
|
||||||
|
renderIssueBlocksStructure,
|
||||||
|
} from "components/gantt-chart";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
type Props = {};
|
export const CycleIssuesGanttChartView = () => {
|
||||||
|
|
||||||
export const CycleIssuesGanttChartView: FC<Props> = ({}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||||
|
|
||||||
|
const { orderBy } = useIssuesView();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const { ganttIssues, mutateGanttIssues } = useGanttChartCycleIssues(
|
const { ganttIssues, mutateGanttIssues } = useGanttChartCycleIssues(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
@ -32,77 +39,18 @@ export const CycleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// rendering issues on gantt card
|
|
||||||
const GanttBlockView = ({ data }: any) => (
|
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${data?.id}`}>
|
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 w-[4px] h-full"
|
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "rgb(var(--color-primary-100))" }}
|
|
||||||
/>
|
|
||||||
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
|
||||||
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
|
||||||
{data?.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
{data.infoToggle && (
|
|
||||||
<Tooltip
|
|
||||||
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
|
||||||
className={`z-[999999]`}
|
|
||||||
>
|
|
||||||
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
|
||||||
<span className="material-symbols-rounded text-custom-text-200 text-[18px]">
|
|
||||||
info
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle gantt issue start date and target date
|
|
||||||
const handleUpdateDates = async (data: any) => {
|
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
|
||||||
start_date: data?.start_date,
|
|
||||||
target_date: data?.target_date,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("payload", payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
|
||||||
blocks && blocks.length > 0
|
|
||||||
? blocks.map((_block: any) => {
|
|
||||||
let startDate = new Date(_block.created_at);
|
|
||||||
let targetDate = new Date(_block.updated_at);
|
|
||||||
let infoToggle = true;
|
|
||||||
|
|
||||||
if (_block?.start_date && _block.target_date) {
|
|
||||||
startDate = _block?.start_date;
|
|
||||||
targetDate = _block.target_date;
|
|
||||||
infoToggle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
start_date: new Date(startDate),
|
|
||||||
target_date: new Date(targetDate),
|
|
||||||
infoToggle: infoToggle,
|
|
||||||
data: _block,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full p-3">
|
<div className="w-full h-full p-3">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
title="Cycles"
|
title="Cycles"
|
||||||
loaderTitle="Cycles"
|
loaderTitle="Cycles"
|
||||||
blocks={ganttIssues ? blockFormat(ganttIssues) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={handleUpdateDates}
|
blockUpdateHandler={(block, payload) =>
|
||||||
|
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
||||||
|
}
|
||||||
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
||||||
blockRender={(data: any) => <GanttBlockView data={data} />}
|
blockRender={(data: any) => <IssueGanttBlock issue={data as IIssue} />}
|
||||||
|
enableReorder={orderBy === "sort_order"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
103
apps/app/components/gantt-chart/blocks/block.tsx
Normal file
103
apps/app/components/gantt-chart/blocks/block.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "components/ui";
|
||||||
|
// helpers
|
||||||
|
import { renderShortDate } from "helpers/date-time.helper";
|
||||||
|
// types
|
||||||
|
import { ICycle, IIssue, IModule } from "types";
|
||||||
|
// constants
|
||||||
|
import { MODULE_STATUS } from "constants/module";
|
||||||
|
|
||||||
|
export const IssueGanttBlock = ({ issue }: { issue: IIssue }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}>
|
||||||
|
<a className="relative flex items-center w-full h-full shadow-sm transition-all duration-300">
|
||||||
|
<div
|
||||||
|
className="flex-shrink-0 w-0.5 h-full"
|
||||||
|
style={{ backgroundColor: issue.state_detail?.color }}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h5>{issue.name}</h5>
|
||||||
|
<div>
|
||||||
|
{renderShortDate(issue.start_date ?? "")} to{" "}
|
||||||
|
{renderShortDate(issue.target_date ?? "")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="top-left"
|
||||||
|
>
|
||||||
|
<div className="text-custom-text-100 text-sm truncate py-1 px-2.5 w-full">
|
||||||
|
{issue.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CycleGanttBlock = ({ cycle }: { cycle: ICycle }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href={`/${workspaceSlug}/projects/${cycle.project}/cycles/${cycle.id}`}>
|
||||||
|
<a className="relative flex items-center w-full h-full shadow-sm transition-all duration-300">
|
||||||
|
<div className="flex-shrink-0 w-0.5 h-full bg-custom-primary-100" />
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h5>{cycle.name}</h5>
|
||||||
|
<div>
|
||||||
|
{renderShortDate(cycle.start_date ?? "")} to {renderShortDate(cycle.end_date ?? "")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="top-left"
|
||||||
|
>
|
||||||
|
<div className="text-custom-text-100 text-sm truncate py-1 px-2.5 w-full">
|
||||||
|
{cycle.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModuleGanttBlock = ({ module }: { module: IModule }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href={`/${workspaceSlug}/projects/${module.project}/modules/${module.id}`}>
|
||||||
|
<a className="relative flex items-center w-full h-full shadow-sm transition-all duration-300">
|
||||||
|
<div
|
||||||
|
className="flex-shrink-0 w-0.5 h-full"
|
||||||
|
style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === module.status)?.color }}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h5>{module.name}</h5>
|
||||||
|
<div>
|
||||||
|
{renderShortDate(module.start_date ?? "")} to{" "}
|
||||||
|
{renderShortDate(module.target_date ?? "")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="top-left"
|
||||||
|
>
|
||||||
|
<div className="text-custom-text-100 text-sm truncate py-1 px-2.5 w-full">
|
||||||
|
{module.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
178
apps/app/components/gantt-chart/blocks/blocks-display.tsx
Normal file
178
apps/app/components/gantt-chart/blocks/blocks-display.tsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
// react-beautiful-dnd
|
||||||
|
import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd";
|
||||||
|
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||||
|
// helpers
|
||||||
|
import { ChartDraggable } from "../helpers/draggable";
|
||||||
|
import { renderDateFormat } from "helpers/date-time.helper";
|
||||||
|
// types
|
||||||
|
import { IBlockUpdateData, IGanttBlock } from "../types";
|
||||||
|
|
||||||
|
export const GanttChartBlocks: FC<{
|
||||||
|
itemsContainerWidth: number;
|
||||||
|
blocks: IGanttBlock[] | null;
|
||||||
|
sidebarBlockRender: FC;
|
||||||
|
blockRender: FC;
|
||||||
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
|
enableLeftDrag: boolean;
|
||||||
|
enableRightDrag: boolean;
|
||||||
|
enableReorder: boolean;
|
||||||
|
}> = ({
|
||||||
|
itemsContainerWidth,
|
||||||
|
blocks,
|
||||||
|
sidebarBlockRender,
|
||||||
|
blockRender,
|
||||||
|
blockUpdateHandler,
|
||||||
|
enableLeftDrag,
|
||||||
|
enableRightDrag,
|
||||||
|
enableReorder,
|
||||||
|
}) => {
|
||||||
|
const handleChartBlockPosition = (
|
||||||
|
block: IGanttBlock,
|
||||||
|
totalBlockShifts: number,
|
||||||
|
dragDirection: "left" | "right"
|
||||||
|
) => {
|
||||||
|
let updatedDate = new Date();
|
||||||
|
|
||||||
|
if (dragDirection === "left") {
|
||||||
|
const originalDate = new Date(block.start_date);
|
||||||
|
|
||||||
|
const currentDay = originalDate.getDate();
|
||||||
|
updatedDate = new Date(originalDate);
|
||||||
|
|
||||||
|
updatedDate.setDate(currentDay - totalBlockShifts);
|
||||||
|
} else {
|
||||||
|
const originalDate = new Date(block.target_date);
|
||||||
|
|
||||||
|
const currentDay = originalDate.getDate();
|
||||||
|
updatedDate = new Date(originalDate);
|
||||||
|
|
||||||
|
updatedDate.setDate(currentDay + totalBlockShifts);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockUpdateHandler(block.data, {
|
||||||
|
[dragDirection === "left" ? "start_date" : "target_date"]: renderDateFormat(updatedDate),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOrderChange = (result: DropResult) => {
|
||||||
|
if (!blocks) return;
|
||||||
|
|
||||||
|
const { source, destination, draggableId } = result;
|
||||||
|
|
||||||
|
if (!destination) return;
|
||||||
|
|
||||||
|
if (source.index === destination.index && document) {
|
||||||
|
// const draggedBlock = document.querySelector(`#${draggableId}`) as HTMLElement;
|
||||||
|
// const blockStyles = window.getComputedStyle(draggedBlock);
|
||||||
|
|
||||||
|
// console.log(blockStyles.marginLeft);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedSortOrder = blocks[source.index].sort_order;
|
||||||
|
|
||||||
|
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000;
|
||||||
|
else if (destination.index === blocks.length - 1)
|
||||||
|
updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
|
||||||
|
else {
|
||||||
|
const destinationSortingOrder = blocks[destination.index].sort_order;
|
||||||
|
const relativeDestinationSortingOrder =
|
||||||
|
source.index < destination.index
|
||||||
|
? blocks[destination.index + 1].sort_order
|
||||||
|
: blocks[destination.index - 1].sort_order;
|
||||||
|
|
||||||
|
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removedElement = blocks.splice(source.index, 1)[0];
|
||||||
|
blocks.splice(destination.index, 0, removedElement);
|
||||||
|
|
||||||
|
blockUpdateHandler(removedElement.data, {
|
||||||
|
sort_order: {
|
||||||
|
destinationIndex: destination.index,
|
||||||
|
newSortOrder: updatedSortOrder,
|
||||||
|
sourceIndex: source.index,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative z-[5] mt-[72px] h-full overflow-hidden overflow-y-auto"
|
||||||
|
style={{ width: `${itemsContainerWidth}px` }}
|
||||||
|
>
|
||||||
|
<DragDropContext onDragEnd={handleOrderChange}>
|
||||||
|
<StrictModeDroppable droppableId="gantt">
|
||||||
|
{(droppableProvided, droppableSnapshot) => (
|
||||||
|
<div
|
||||||
|
className="w-full space-y-2"
|
||||||
|
ref={droppableProvided.innerRef}
|
||||||
|
{...droppableProvided.droppableProps}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{blocks &&
|
||||||
|
blocks.length > 0 &&
|
||||||
|
blocks.map(
|
||||||
|
(block, index: number) =>
|
||||||
|
block.start_date &&
|
||||||
|
block.target_date && (
|
||||||
|
<Draggable
|
||||||
|
key={`block-${block.id}`}
|
||||||
|
draggableId={`block-${block.id}`}
|
||||||
|
index={index}
|
||||||
|
isDragDisabled={!enableReorder}
|
||||||
|
>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
droppableSnapshot.isDraggingOver ? "bg-custom-border-100/10" : ""
|
||||||
|
}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
>
|
||||||
|
<ChartDraggable
|
||||||
|
block={block}
|
||||||
|
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
|
||||||
|
enableLeftDrag={enableLeftDrag}
|
||||||
|
enableRightDrag={enableRightDrag}
|
||||||
|
provided={provided}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="rounded shadow-sm bg-custom-background-80 overflow-hidden h-9 flex items-center transition-all"
|
||||||
|
style={{
|
||||||
|
width: `${block.position?.width}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{blockRender({
|
||||||
|
...block.data,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ChartDraggable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{droppableProvided.placeholder}
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</StrictModeDroppable>
|
||||||
|
</DragDropContext>
|
||||||
|
|
||||||
|
{/* sidebar */}
|
||||||
|
{/* <div className="fixed top-0 bottom-0 w-[300px] flex-shrink-0 divide-y divide-custom-border-200 border-r border-custom-border-200 overflow-y-auto">
|
||||||
|
{blocks &&
|
||||||
|
blocks.length > 0 &&
|
||||||
|
blocks.map((block: any, _idx: number) => (
|
||||||
|
<div className="relative h-[40px] bg-custom-background-100" key={`sidebar-blocks-${_idx}`}>
|
||||||
|
{sidebarBlockRender(block?.data)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
2
apps/app/components/gantt-chart/blocks/index.ts
Normal file
2
apps/app/components/gantt-chart/blocks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./block";
|
||||||
|
export * from "./blocks-display";
|
@ -1,82 +0,0 @@
|
|||||||
import { FC, useEffect, useState } from "react";
|
|
||||||
// helpers
|
|
||||||
import { ChartDraggable } from "../helpers/draggable";
|
|
||||||
// data
|
|
||||||
import { datePreview } from "../data";
|
|
||||||
|
|
||||||
export const GanttChartBlocks: FC<{
|
|
||||||
itemsContainerWidth: number;
|
|
||||||
blocks: null | any[];
|
|
||||||
sidebarBlockRender: FC;
|
|
||||||
blockRender: FC;
|
|
||||||
}> = ({ itemsContainerWidth, blocks, sidebarBlockRender, blockRender }) => {
|
|
||||||
const handleChartBlockPosition = (block: any) => {
|
|
||||||
// setChartBlocks((prevData: any) =>
|
|
||||||
// prevData.map((_block: any) => (_block?.data?.id == block?.data?.id ? block : _block))
|
|
||||||
// );
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="relative z-[5] mt-[58px] h-full w-[4000px] divide-x divide-gray-300 overflow-hidden overflow-y-auto"
|
|
||||||
style={{ width: `${itemsContainerWidth}px` }}
|
|
||||||
>
|
|
||||||
<div className="w-full">
|
|
||||||
{blocks &&
|
|
||||||
blocks.length > 0 &&
|
|
||||||
blocks.map((block: any, _idx: number) => (
|
|
||||||
<>
|
|
||||||
{block.start_date && block.target_date && (
|
|
||||||
<ChartDraggable
|
|
||||||
className="relative flex h-[40px] items-center"
|
|
||||||
key={`blocks-${_idx}`}
|
|
||||||
block={block}
|
|
||||||
handleBlock={handleChartBlockPosition}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="relative group inline-flex cursor-pointer items-center font-medium transition-all"
|
|
||||||
style={{ marginLeft: `${block?.position?.marginLeft}px` }}
|
|
||||||
>
|
|
||||||
<div className="flex-shrink-0 relative w-0 h-0 flex items-center invisible group-hover:visible whitespace-nowrap">
|
|
||||||
<div className="absolute right-0 mr-[5px] rounded-sm bg-custom-background-90 px-2 py-0.5 text-xs font-medium">
|
|
||||||
{block?.start_date ? datePreview(block?.start_date) : "-"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="rounded shadow-sm bg-custom-background-100 overflow-hidden relative flex items-center h-[34px] border border-custom-border-200"
|
|
||||||
style={{
|
|
||||||
width: `${block?.position?.width}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{blockRender({
|
|
||||||
...block?.data,
|
|
||||||
infoToggle: block?.infoToggle ? true : false,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-shrink-0 relative w-0 h-0 flex items-center invisible group-hover:visible whitespace-nowrap">
|
|
||||||
<div className="absolute left-0 ml-[5px] mr-[5px] rounded-sm bg-custom-background-90 px-2 py-0.5 text-xs font-medium">
|
|
||||||
{block?.target_date ? datePreview(block?.target_date) : "-"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ChartDraggable>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* sidebar */}
|
|
||||||
{/* <div className="fixed top-0 bottom-0 w-[300px] flex-shrink-0 divide-y divide-custom-border-200 border-r border-custom-border-200 overflow-y-auto">
|
|
||||||
{blocks &&
|
|
||||||
blocks.length > 0 &&
|
|
||||||
blocks.map((block: any, _idx: number) => (
|
|
||||||
<div className="relative h-[40px] bg-custom-background-100" key={`sidebar-blocks-${_idx}`}>
|
|
||||||
{sidebarBlockRender(block?.data)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -25,7 +25,7 @@ export const BiWeekChartView: FC<any> = () => {
|
|||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
|
@ -25,7 +25,7 @@ export const DayChartView: FC<any> = () => {
|
|||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
|
@ -25,7 +25,7 @@ export const HourChartView: FC<any> = () => {
|
|||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { ArrowsPointingInIcon, ArrowsPointingOutIcon } from "@heroicons/react/20/solid";
|
||||||
Bars4Icon,
|
|
||||||
XMarkIcon,
|
|
||||||
ArrowsPointingInIcon,
|
|
||||||
ArrowsPointingOutIcon,
|
|
||||||
} from "@heroicons/react/20/solid";
|
|
||||||
// components
|
// components
|
||||||
import { GanttChartBlocks } from "../blocks";
|
import { GanttChartBlocks } from "components/gantt-chart";
|
||||||
// import { HourChartView } from "./hours";
|
// import { HourChartView } from "./hours";
|
||||||
// import { DayChartView } from "./day";
|
// import { DayChartView } from "./day";
|
||||||
// import { WeekChartView } from "./week";
|
// import { WeekChartView } from "./week";
|
||||||
@ -30,9 +25,9 @@ import {
|
|||||||
getMonthChartItemPositionWidthInMonth,
|
getMonthChartItemPositionWidthInMonth,
|
||||||
} from "../views";
|
} from "../views";
|
||||||
// types
|
// types
|
||||||
import { ChartDataType } from "../types";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
|
||||||
// data
|
// data
|
||||||
import { datePreview, currentViewDataWithView } from "../data";
|
import { currentViewDataWithView } from "../data";
|
||||||
// context
|
// context
|
||||||
import { useChart } from "../hooks";
|
import { useChart } from "../hooks";
|
||||||
|
|
||||||
@ -40,10 +35,13 @@ type ChartViewRootProps = {
|
|||||||
border: boolean;
|
border: boolean;
|
||||||
title: null | string;
|
title: null | string;
|
||||||
loaderTitle: string;
|
loaderTitle: string;
|
||||||
blocks: any;
|
blocks: IGanttBlock[] | null;
|
||||||
blockUpdateHandler: (data: any) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
sidebarBlockRender: FC<any>;
|
sidebarBlockRender: FC<any>;
|
||||||
blockRender: FC<any>;
|
blockRender: FC<any>;
|
||||||
|
enableLeftDrag: boolean;
|
||||||
|
enableRightDrag: boolean;
|
||||||
|
enableReorder: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
||||||
@ -54,6 +52,9 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
sidebarBlockRender,
|
sidebarBlockRender,
|
||||||
blockRender,
|
blockRender,
|
||||||
|
enableLeftDrag,
|
||||||
|
enableRightDrag,
|
||||||
|
enableReorder,
|
||||||
}) => {
|
}) => {
|
||||||
const { currentView, currentViewData, renderView, dispatch, allViews } = useChart();
|
const { currentView, currentViewData, renderView, dispatch, allViews } = useChart();
|
||||||
|
|
||||||
@ -62,13 +63,13 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
const [blocksSidebarView, setBlocksSidebarView] = useState<boolean>(false);
|
const [blocksSidebarView, setBlocksSidebarView] = useState<boolean>(false);
|
||||||
|
|
||||||
// blocks state management starts
|
// blocks state management starts
|
||||||
const [chartBlocks, setChartBlocks] = useState<any[] | null>(null);
|
const [chartBlocks, setChartBlocks] = useState<IGanttBlock[] | null>(null);
|
||||||
|
|
||||||
const renderBlockStructure = (view: any, blocks: any) =>
|
const renderBlockStructure = (view: any, blocks: IGanttBlock[]) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => ({
|
? blocks.map((block: any) => ({
|
||||||
..._block,
|
...block,
|
||||||
position: getMonthChartItemPositionWidthInMonth(view, _block),
|
position: getMonthChartItemPositionWidthInMonth(view, block),
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
@ -154,13 +155,14 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
|
|
||||||
const updatingCurrentLeftScrollPosition = (width: number) => {
|
const updatingCurrentLeftScrollPosition = (width: number) => {
|
||||||
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
||||||
scrollContainer.scrollLeft = width + scrollContainer.scrollLeft;
|
scrollContainer.scrollLeft = width + scrollContainer?.scrollLeft;
|
||||||
setItemsContainerWidth(width + scrollContainer.scrollLeft);
|
setItemsContainerWidth(width + scrollContainer?.scrollLeft);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScrollToCurrentSelectedDate = (currentState: ChartDataType, date: Date) => {
|
const handleScrollToCurrentSelectedDate = (currentState: ChartDataType, date: Date) => {
|
||||||
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
||||||
const clientVisibleWidth: number = scrollContainer.clientWidth;
|
|
||||||
|
const clientVisibleWidth: number = scrollContainer?.clientWidth;
|
||||||
let scrollWidth: number = 0;
|
let scrollWidth: number = 0;
|
||||||
let daysDifference: number = 0;
|
let daysDifference: number = 0;
|
||||||
|
|
||||||
@ -189,9 +191,9 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
||||||
|
|
||||||
const scrollWidth: number = scrollContainer.scrollWidth;
|
const scrollWidth: number = scrollContainer?.scrollWidth;
|
||||||
const clientVisibleWidth: number = scrollContainer.clientWidth;
|
const clientVisibleWidth: number = scrollContainer?.clientWidth;
|
||||||
const currentScrollPosition: number = scrollContainer.scrollLeft;
|
const currentScrollPosition: number = scrollContainer?.scrollLeft;
|
||||||
|
|
||||||
const approxRangeLeft: number =
|
const approxRangeLeft: number =
|
||||||
scrollWidth >= clientVisibleWidth + 1000 ? 1000 : scrollWidth - clientVisibleWidth;
|
scrollWidth >= clientVisibleWidth + 1000 ? 1000 : scrollWidth - clientVisibleWidth;
|
||||||
@ -207,6 +209,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
const scrollContainer = document.getElementById("scroll-container") as HTMLElement;
|
||||||
|
|
||||||
scrollContainer.addEventListener("scroll", onScroll);
|
scrollContainer.addEventListener("scroll", onScroll);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
scrollContainer.removeEventListener("scroll", onScroll);
|
scrollContainer.removeEventListener("scroll", onScroll);
|
||||||
};
|
};
|
||||||
@ -242,7 +245,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
{/* chart header */}
|
{/* chart header */}
|
||||||
<div className="flex w-full flex-shrink-0 flex-wrap items-center gap-5 gap-y-3 whitespace-nowrap p-2">
|
<div className="flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap p-2">
|
||||||
{/* <div
|
{/* <div
|
||||||
className="transition-all border border-custom-border-200 w-[30px] h-[30px] flex justify-center items-center cursor-pointer rounded-sm hover:bg-custom-background-80"
|
className="transition-all border border-custom-border-200 w-[30px] h-[30px] flex justify-center items-center cursor-pointer rounded-sm hover:bg-custom-background-80"
|
||||||
onClick={() => setBlocksSidebarView(() => !blocksSidebarView)}
|
onClick={() => setBlocksSidebarView(() => !blocksSidebarView)}
|
||||||
@ -301,8 +304,8 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="transition-all border border-custom-border-200 w-[30px] h-[30px] flex justify-center items-center cursor-pointer rounded-sm hover:bg-custom-background-80"
|
className="transition-all border border-custom-border-200 p-1 flex justify-center items-center cursor-pointer rounded-sm hover:bg-custom-background-80"
|
||||||
onClick={() => setFullScreenMode(() => !fullScreenMode)}
|
onClick={() => setFullScreenMode((prevData) => !prevData)}
|
||||||
>
|
>
|
||||||
{fullScreenMode ? (
|
{fullScreenMode ? (
|
||||||
<ArrowsPointingInIcon className="h-4 w-4" />
|
<ArrowsPointingInIcon className="h-4 w-4" />
|
||||||
@ -325,6 +328,10 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
|||||||
blocks={chartBlocks}
|
blocks={chartBlocks}
|
||||||
sidebarBlockRender={sidebarBlockRender}
|
sidebarBlockRender={sidebarBlockRender}
|
||||||
blockRender={blockRender}
|
blockRender={blockRender}
|
||||||
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
|
enableLeftDrag={enableLeftDrag}
|
||||||
|
enableRightDrag={enableRightDrag}
|
||||||
|
enableReorder={enableReorder}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,48 +1,55 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// context
|
|
||||||
|
// hooks
|
||||||
import { useChart } from "../hooks";
|
import { useChart } from "../hooks";
|
||||||
|
// types
|
||||||
|
import { IMonthBlock } from "../views";
|
||||||
|
|
||||||
export const MonthChartView: FC<any> = () => {
|
export const MonthChartView: FC<any> = () => {
|
||||||
const { currentView, currentViewData, renderView, dispatch, allViews } = useChart();
|
const { currentViewData, renderView } = useChart();
|
||||||
|
|
||||||
|
const monthBlocks: IMonthBlock[] = renderView;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute flex h-full flex-grow divide-x divide-custom-border-200">
|
<div className="absolute flex h-full flex-grow divide-x divide-custom-border-100/50">
|
||||||
{renderView &&
|
{monthBlocks &&
|
||||||
renderView.length > 0 &&
|
monthBlocks.length > 0 &&
|
||||||
renderView.map((_itemRoot: any, _idxRoot: any) => (
|
monthBlocks.map((block, _idxRoot) => (
|
||||||
<div key={`title-${_idxRoot}`} className="relative flex flex-col">
|
<div key={`month-${block?.month}-${block?.year}`} className="relative flex flex-col">
|
||||||
<div className="relative border-b border-custom-border-200">
|
<div className="relative border-b border-custom-border-200">
|
||||||
<div className="sticky left-0 inline-flex whitespace-nowrap px-2 py-1 text-sm font-medium capitalize">
|
<div className="sticky left-0 inline-flex whitespace-nowrap px-2 py-1 text-sm font-medium capitalize">
|
||||||
{_itemRoot?.title}
|
{block?.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex h-full w-full divide-x divide-custom-border-200">
|
<div className="flex h-full w-full divide-x divide-custom-border-100/50">
|
||||||
{_itemRoot.children &&
|
{block?.children &&
|
||||||
_itemRoot.children.length > 0 &&
|
block?.children.length > 0 &&
|
||||||
_itemRoot.children.map((_item: any, _idx: any) => (
|
block?.children.map((monthDay, _idx) => (
|
||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
_item?.today ? `text-red-500 border-red-500` : `border-custom-border-200`
|
monthDay?.today
|
||||||
|
? `text-red-500 border-red-500`
|
||||||
|
: `border-custom-border-200`
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div>{_item.title}</div>
|
<div>{monthDay?.title}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`relative h-full w-full flex-1 flex justify-center ${
|
className={`relative h-full w-full flex-1 flex justify-center ${
|
||||||
["sat", "sun"].includes(_item?.dayData?.shortTitle || "")
|
["sat", "sun"].includes(monthDay?.dayData?.shortTitle || "")
|
||||||
? `bg-custom-background-90`
|
? `bg-custom-background-90`
|
||||||
: ``
|
: ``
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{_item?.today && (
|
{monthDay?.today && (
|
||||||
<div className="absolute top-0 bottom-0 border border-red-500"> </div>
|
<div className="absolute top-0 bottom-0 w-[1px] bg-red-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@ export const QuarterChartView: FC<any> = () => {
|
|||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
|
@ -25,7 +25,7 @@ export const WeekChartView: FC<any> = () => {
|
|||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
|
@ -25,7 +25,7 @@ export const YearChartView: FC<any> = () => {
|
|||||||
<div
|
<div
|
||||||
key={`sub-title-${_idxRoot}-${_idx}`}
|
key={`sub-title-${_idxRoot}-${_idx}`}
|
||||||
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
|
||||||
style={{ width: `${currentViewData.data.width}px` }}
|
style={{ width: `${currentViewData?.data.width}px` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
|
||||||
|
14
apps/app/components/gantt-chart/helpers/block-structure.tsx
Normal file
14
apps/app/components/gantt-chart/helpers/block-structure.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
import { IGanttBlock } from "components/gantt-chart";
|
||||||
|
|
||||||
|
export const renderIssueBlocksStructure = (blocks: IIssue[]): IGanttBlock[] =>
|
||||||
|
blocks && blocks.length > 0
|
||||||
|
? blocks.map((block) => ({
|
||||||
|
data: block,
|
||||||
|
id: block.id,
|
||||||
|
sort_order: block.sort_order,
|
||||||
|
start_date: new Date(block.start_date ?? ""),
|
||||||
|
target_date: new Date(block.target_date ?? ""),
|
||||||
|
}))
|
||||||
|
: [];
|
@ -1,138 +1,155 @@
|
|||||||
import { useState, useRef } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
|
|
||||||
export const ChartDraggable = ({ children, block, handleBlock, className }: any) => {
|
// react-beautiful-dnd
|
||||||
const [dragging, setDragging] = useState(false);
|
import { DraggableProvided } from "react-beautiful-dnd";
|
||||||
|
import { useChart } from "../hooks";
|
||||||
|
// types
|
||||||
|
import { IGanttBlock } from "../types";
|
||||||
|
|
||||||
const [chartBlockPositionLeft, setChartBlockPositionLeft] = useState(0);
|
type Props = {
|
||||||
const [blockPositionLeft, setBlockPositionLeft] = useState(0);
|
children: any;
|
||||||
const [dragBlockOffsetX, setDragBlockOffsetX] = useState(0);
|
block: IGanttBlock;
|
||||||
|
handleBlock: (totalBlockShifts: number, dragDirection: "left" | "right") => void;
|
||||||
|
enableLeftDrag: boolean;
|
||||||
|
enableRightDrag: boolean;
|
||||||
|
provided: DraggableProvided;
|
||||||
|
};
|
||||||
|
|
||||||
const handleMouseDown = (event: any) => {
|
export const ChartDraggable: React.FC<Props> = ({
|
||||||
const chartBlockPositionLeft: number = block.position.marginLeft;
|
children,
|
||||||
const blockPositionLeft: number = event.target.getBoundingClientRect().left;
|
block,
|
||||||
const dragBlockOffsetX: number = event.clientX - event.target.getBoundingClientRect().left;
|
handleBlock,
|
||||||
|
enableLeftDrag = true,
|
||||||
|
enableRightDrag = true,
|
||||||
|
provided,
|
||||||
|
}) => {
|
||||||
|
const [isLeftResizing, setIsLeftResizing] = useState(false);
|
||||||
|
const [isRightResizing, setIsRightResizing] = useState(false);
|
||||||
|
|
||||||
console.log("--------------------");
|
const parentDivRef = useRef<HTMLDivElement>(null);
|
||||||
console.log("chartBlockPositionLeft", chartBlockPositionLeft);
|
const resizableRef = useRef<HTMLDivElement>(null);
|
||||||
console.log("blockPositionLeft", blockPositionLeft);
|
|
||||||
console.log("dragBlockOffsetX", dragBlockOffsetX);
|
|
||||||
console.log("-->");
|
|
||||||
|
|
||||||
setDragging(true);
|
const { currentViewData } = useChart();
|
||||||
setChartBlockPositionLeft(chartBlockPositionLeft);
|
|
||||||
setBlockPositionLeft(blockPositionLeft);
|
|
||||||
setDragBlockOffsetX(dragBlockOffsetX);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseMove = (event: any) => {
|
const handleDrag = (dragDirection: "left" | "right") => {
|
||||||
if (!dragging) return;
|
if (!currentViewData || !resizableRef.current || !parentDivRef.current || !block.position)
|
||||||
|
return;
|
||||||
|
|
||||||
const currentBlockPosition = event.clientX - dragBlockOffsetX;
|
const resizableDiv = resizableRef.current;
|
||||||
console.log("currentBlockPosition", currentBlockPosition);
|
const parentDiv = parentDivRef.current;
|
||||||
if (currentBlockPosition <= blockPositionLeft) {
|
|
||||||
const updatedPosition = chartBlockPositionLeft - (blockPositionLeft - currentBlockPosition);
|
|
||||||
console.log("updatedPosition", updatedPosition);
|
|
||||||
handleBlock({ ...block, position: { ...block.position, marginLeft: updatedPosition } });
|
|
||||||
} else {
|
|
||||||
const updatedPosition = chartBlockPositionLeft + (blockPositionLeft - currentBlockPosition);
|
|
||||||
console.log("updatedPosition", updatedPosition);
|
|
||||||
handleBlock({ ...block, position: { ...block.position, marginLeft: updatedPosition } });
|
|
||||||
}
|
|
||||||
console.log("--------------------");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const columnWidth = currentViewData.data.width;
|
||||||
setDragging(false);
|
|
||||||
setChartBlockPositionLeft(0);
|
const blockInitialWidth =
|
||||||
setBlockPositionLeft(0);
|
resizableDiv.clientWidth ?? parseInt(block.position.width.toString(), 10);
|
||||||
setDragBlockOffsetX(0);
|
|
||||||
|
let initialWidth = resizableDiv.clientWidth ?? parseInt(block.position.width.toString(), 10);
|
||||||
|
let initialMarginLeft = block?.position?.marginLeft;
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!window) return;
|
||||||
|
|
||||||
|
let delWidth = 0;
|
||||||
|
|
||||||
|
const posFromLeft = e.clientX;
|
||||||
|
const posFromRight = window.innerWidth - e.clientX;
|
||||||
|
|
||||||
|
const scrollContainer = document.querySelector("#scroll-container") as HTMLElement;
|
||||||
|
const appSidebar = document.querySelector("#app-sidebar") as HTMLElement;
|
||||||
|
|
||||||
|
// manually scroll to left if reached the left end while dragging
|
||||||
|
if (posFromLeft - appSidebar.clientWidth <= 70) {
|
||||||
|
if (e.movementX > 0) return;
|
||||||
|
|
||||||
|
delWidth = dragDirection === "left" ? -5 : 5;
|
||||||
|
|
||||||
|
scrollContainer.scrollBy(-1 * Math.abs(delWidth), 0);
|
||||||
|
} else delWidth = dragDirection === "left" ? -1 * e.movementX : e.movementX;
|
||||||
|
|
||||||
|
// manually scroll to right if reached the right end while dragging
|
||||||
|
if (posFromRight <= 70) {
|
||||||
|
if (e.movementX < 0) return;
|
||||||
|
|
||||||
|
delWidth = dragDirection === "left" ? -5 : 5;
|
||||||
|
|
||||||
|
scrollContainer.scrollBy(Math.abs(delWidth), 0);
|
||||||
|
} else delWidth = dragDirection === "left" ? -1 * e.movementX : e.movementX;
|
||||||
|
|
||||||
|
// calculate new width and update the initialMarginLeft using +=
|
||||||
|
const newWidth = Math.round((initialWidth += delWidth) / columnWidth) * columnWidth;
|
||||||
|
|
||||||
|
// block needs to be at least 1 column wide
|
||||||
|
if (newWidth < columnWidth) return;
|
||||||
|
|
||||||
|
resizableDiv.style.width = `${newWidth}px`;
|
||||||
|
if (block.position) block.position.width = newWidth;
|
||||||
|
|
||||||
|
// update the margin left of the block if dragging from the left end
|
||||||
|
if (dragDirection === "left") {
|
||||||
|
// calculate new marginLeft and update the initial marginLeft using -=
|
||||||
|
const newMarginLeft =
|
||||||
|
Math.round((initialMarginLeft -= delWidth) / columnWidth) * columnWidth;
|
||||||
|
|
||||||
|
parentDiv.style.marginLeft = `${newMarginLeft}px`;
|
||||||
|
if (block.position) block.position.marginLeft = newMarginLeft;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
document.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
document.removeEventListener("mouseup", handleMouseUp);
|
||||||
|
|
||||||
|
const totalBlockShifts = Math.ceil(
|
||||||
|
(resizableDiv.clientWidth - blockInitialWidth) / columnWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
handleBlock(totalBlockShifts, dragDirection);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
|
document.addEventListener("mouseup", handleMouseUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onMouseDown={handleMouseDown}
|
id={`block-${block.id}`}
|
||||||
onMouseMove={handleMouseMove}
|
ref={parentDivRef}
|
||||||
onMouseUp={handleMouseUp}
|
className="relative group inline-flex cursor-pointer items-center font-medium transition-all"
|
||||||
className={`${className ? className : ``}`}
|
style={{
|
||||||
|
marginLeft: `${block.position?.marginLeft}px`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{enableLeftDrag && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onMouseDown={() => handleDrag("left")}
|
||||||
|
onMouseEnter={() => setIsLeftResizing(true)}
|
||||||
|
onMouseLeave={() => setIsLeftResizing(false)}
|
||||||
|
className="absolute top-1/2 -left-2.5 -translate-y-1/2 z-[1] w-6 h-10 bg-brand-backdrop rounded-md cursor-col-resize"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`absolute top-1/2 -translate-y-1/2 w-1 h-4/5 rounded-sm bg-custom-background-80 transition-all duration-300 ${
|
||||||
|
isLeftResizing ? "-left-2.5" : "left-1"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{React.cloneElement(children, { ref: resizableRef, ...provided.dragHandleProps })}
|
||||||
|
{enableRightDrag && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onMouseDown={() => handleDrag("right")}
|
||||||
|
onMouseEnter={() => setIsRightResizing(true)}
|
||||||
|
onMouseLeave={() => setIsRightResizing(false)}
|
||||||
|
className="absolute top-1/2 -right-2.5 -translate-y-1/2 z-[1] w-6 h-6 bg-brand-backdrop rounded-md cursor-col-resize"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`absolute top-1/2 -translate-y-1/2 w-1 h-4/5 rounded-sm bg-custom-background-80 transition-all duration-300 ${
|
||||||
|
isRightResizing ? "-right-2.5" : "right-1"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// import { useState } from "react";
|
|
||||||
|
|
||||||
// export const ChartDraggable = ({ children, id, className = "", style }: any) => {
|
|
||||||
// const [dragging, setDragging] = useState(false);
|
|
||||||
|
|
||||||
// const [chartBlockPositionLeft, setChartBlockPositionLeft] = useState(0);
|
|
||||||
// const [blockPositionLeft, setBlockPositionLeft] = useState(0);
|
|
||||||
// const [dragBlockOffsetX, setDragBlockOffsetX] = useState(0);
|
|
||||||
|
|
||||||
// const handleDragStart = (event: any) => {
|
|
||||||
// // event.dataTransfer.setData("text/plain", event.target.id);
|
|
||||||
|
|
||||||
// const chartBlockPositionLeft: number = parseInt(event.target.style.left.slice(0, -2));
|
|
||||||
// const blockPositionLeft: number = event.target.getBoundingClientRect().left;
|
|
||||||
// const dragBlockOffsetX: number = event.clientX - event.target.getBoundingClientRect().left;
|
|
||||||
|
|
||||||
// console.log("chartBlockPositionLeft", chartBlockPositionLeft);
|
|
||||||
// console.log("blockPositionLeft", blockPositionLeft);
|
|
||||||
// console.log("dragBlockOffsetX", dragBlockOffsetX);
|
|
||||||
// console.log("--------------------");
|
|
||||||
|
|
||||||
// setDragging(true);
|
|
||||||
// setChartBlockPositionLeft(chartBlockPositionLeft);
|
|
||||||
// setBlockPositionLeft(blockPositionLeft);
|
|
||||||
// setDragBlockOffsetX(dragBlockOffsetX);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleDragEnd = () => {
|
|
||||||
// setDragging(false);
|
|
||||||
// setChartBlockPositionLeft(0);
|
|
||||||
// setBlockPositionLeft(0);
|
|
||||||
// setDragBlockOffsetX(0);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleDragOver = (event: any) => {
|
|
||||||
// event.preventDefault();
|
|
||||||
// if (dragging) {
|
|
||||||
// const scrollContainer = document.getElementById(`block-parent-${id}`) as HTMLElement;
|
|
||||||
// const currentBlockPosition = event.clientX - dragBlockOffsetX;
|
|
||||||
// console.log('currentBlockPosition')
|
|
||||||
// if (currentBlockPosition <= blockPositionLeft) {
|
|
||||||
// const updatedPosition = chartBlockPositionLeft - (blockPositionLeft - currentBlockPosition);
|
|
||||||
// console.log("updatedPosition", updatedPosition);
|
|
||||||
// if (scrollContainer) scrollContainer.style.left = `${updatedPosition}px`;
|
|
||||||
// } else {
|
|
||||||
// const updatedPosition = chartBlockPositionLeft + (blockPositionLeft - currentBlockPosition);
|
|
||||||
// console.log("updatedPosition", updatedPosition);
|
|
||||||
// if (scrollContainer) scrollContainer.style.left = `${updatedPosition}px`;
|
|
||||||
// }
|
|
||||||
// console.log("--------------------");
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleDrop = (event: any) => {
|
|
||||||
// event.preventDefault();
|
|
||||||
// setDragging(false);
|
|
||||||
// setChartBlockPositionLeft(0);
|
|
||||||
// setBlockPositionLeft(0);
|
|
||||||
// setDragBlockOffsetX(0);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div
|
|
||||||
// id={id}
|
|
||||||
// draggable
|
|
||||||
// onDragStart={handleDragStart}
|
|
||||||
// onDragEnd={handleDragEnd}
|
|
||||||
// onDragOver={handleDragOver}
|
|
||||||
// onDrop={handleDrop}
|
|
||||||
// className={`${className} ${dragging ? "dragging" : ""}`}
|
|
||||||
// style={style}
|
|
||||||
// >
|
|
||||||
// {children}
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
1
apps/app/components/gantt-chart/helpers/index.ts
Normal file
1
apps/app/components/gantt-chart/helpers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./block-structure";
|
43
apps/app/components/gantt-chart/hooks/block-update.tsx
Normal file
43
apps/app/components/gantt-chart/hooks/block-update.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { KeyedMutator } from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
|
// types
|
||||||
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
|
import { IBlockUpdateData } from "../types";
|
||||||
|
|
||||||
|
export const updateGanttIssue = (
|
||||||
|
issue: IIssue,
|
||||||
|
payload: IBlockUpdateData,
|
||||||
|
mutate: KeyedMutator<any>,
|
||||||
|
user: ICurrentUserResponse | undefined,
|
||||||
|
workspaceSlug: string | undefined
|
||||||
|
) => {
|
||||||
|
if (!issue || !workspaceSlug || !user) return;
|
||||||
|
|
||||||
|
mutate((prevData: IIssue[]) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
|
const newList = prevData.map((p) => ({
|
||||||
|
...p,
|
||||||
|
...(p.id === issue.id ? payload : {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (payload.sort_order) {
|
||||||
|
const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0];
|
||||||
|
removedElement.sort_order = payload.sort_order.newSortOrder;
|
||||||
|
newList.splice(payload.sort_order.destinationIndex, 0, removedElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newList;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
const newPayload: any = { ...payload };
|
||||||
|
|
||||||
|
if (newPayload.sort_order && payload.sort_order)
|
||||||
|
newPayload.sort_order = payload.sort_order.newSortOrder;
|
||||||
|
|
||||||
|
issuesService
|
||||||
|
.patchIssue(workspaceSlug, issue.project, issue.id, newPayload, user)
|
||||||
|
.finally(() => mutate());
|
||||||
|
};
|
@ -1 +1,5 @@
|
|||||||
|
export * from "./blocks";
|
||||||
|
export * from "./helpers";
|
||||||
|
export * from "./hooks";
|
||||||
export * from "./root";
|
export * from "./root";
|
||||||
|
export * from "./types";
|
||||||
|
@ -3,15 +3,20 @@ import { FC } from "react";
|
|||||||
import { ChartViewRoot } from "./chart";
|
import { ChartViewRoot } from "./chart";
|
||||||
// context
|
// context
|
||||||
import { ChartContextProvider } from "./contexts";
|
import { ChartContextProvider } from "./contexts";
|
||||||
|
// types
|
||||||
|
import { IBlockUpdateData, IGanttBlock } from "./types";
|
||||||
|
|
||||||
type GanttChartRootProps = {
|
type GanttChartRootProps = {
|
||||||
border?: boolean;
|
border?: boolean;
|
||||||
title: null | string;
|
title: null | string;
|
||||||
loaderTitle: string;
|
loaderTitle: string;
|
||||||
blocks: any;
|
blocks: IGanttBlock[] | null;
|
||||||
blockUpdateHandler: (data: any) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
sidebarBlockRender: FC<any>;
|
sidebarBlockRender: FC<any>;
|
||||||
blockRender: FC<any>;
|
blockRender: FC<any>;
|
||||||
|
enableLeftDrag?: boolean;
|
||||||
|
enableRightDrag?: boolean;
|
||||||
|
enableReorder?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartRoot: FC<GanttChartRootProps> = ({
|
export const GanttChartRoot: FC<GanttChartRootProps> = ({
|
||||||
@ -22,6 +27,9 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
|
|||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
sidebarBlockRender,
|
sidebarBlockRender,
|
||||||
blockRender,
|
blockRender,
|
||||||
|
enableLeftDrag = true,
|
||||||
|
enableRightDrag = true,
|
||||||
|
enableReorder = true,
|
||||||
}) => (
|
}) => (
|
||||||
<ChartContextProvider>
|
<ChartContextProvider>
|
||||||
<ChartViewRoot
|
<ChartViewRoot
|
||||||
@ -32,6 +40,9 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
|
|||||||
blockUpdateHandler={blockUpdateHandler}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
sidebarBlockRender={sidebarBlockRender}
|
sidebarBlockRender={sidebarBlockRender}
|
||||||
blockRender={blockRender}
|
blockRender={blockRender}
|
||||||
|
enableLeftDrag={enableLeftDrag}
|
||||||
|
enableRightDrag={enableRightDrag}
|
||||||
|
enableReorder={enableReorder}
|
||||||
/>
|
/>
|
||||||
</ChartContextProvider>
|
</ChartContextProvider>
|
||||||
);
|
);
|
||||||
|
@ -5,10 +5,32 @@ export type allViewsType = {
|
|||||||
data: Object | null;
|
data: Object | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IGanttBlock {
|
||||||
|
data: any;
|
||||||
|
id: string;
|
||||||
|
position?: {
|
||||||
|
marginLeft: number;
|
||||||
|
width: number;
|
||||||
|
};
|
||||||
|
sort_order: number;
|
||||||
|
start_date: Date;
|
||||||
|
target_date: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBlockUpdateData {
|
||||||
|
sort_order?: {
|
||||||
|
destinationIndex: number;
|
||||||
|
newSortOrder: number;
|
||||||
|
sourceIndex: number;
|
||||||
|
};
|
||||||
|
start_date?: string;
|
||||||
|
target_date?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChartContextData {
|
export interface ChartContextData {
|
||||||
allViews: allViewsType[];
|
allViews: allViewsType[];
|
||||||
currentView: "hours" | "day" | "week" | "bi_week" | "month" | "quarter" | "year";
|
currentView: "hours" | "day" | "week" | "bi_week" | "month" | "quarter" | "year";
|
||||||
currentViewData: any;
|
currentViewData: ChartDataType | undefined;
|
||||||
renderView: any;
|
renderView: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// types
|
// types
|
||||||
import { ChartDataType } from "../types";
|
import { ChartDataType, IGanttBlock } from "../types";
|
||||||
// data
|
// data
|
||||||
import { weeks, months } from "../data";
|
import { weeks, months } from "../data";
|
||||||
// helpers
|
// helpers
|
||||||
@ -19,7 +19,35 @@ type GetAllDaysInMonthInMonthViewType = {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
today: boolean;
|
today: boolean;
|
||||||
};
|
};
|
||||||
const getAllDaysInMonthInMonthView = (month: number, year: number) => {
|
|
||||||
|
interface IMonthChild {
|
||||||
|
active: boolean;
|
||||||
|
date: Date;
|
||||||
|
day: number;
|
||||||
|
dayData: {
|
||||||
|
key: number;
|
||||||
|
shortTitle: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
title: string;
|
||||||
|
today: boolean;
|
||||||
|
weekNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMonthBlock {
|
||||||
|
children: IMonthChild[];
|
||||||
|
month: number;
|
||||||
|
monthData: {
|
||||||
|
key: number;
|
||||||
|
shortTitle: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
}
|
||||||
|
[];
|
||||||
|
|
||||||
|
const getAllDaysInMonthInMonthView = (month: number, year: number): IMonthChild[] => {
|
||||||
const day: GetAllDaysInMonthInMonthViewType[] = [];
|
const day: GetAllDaysInMonthInMonthViewType[] = [];
|
||||||
const numberOfDaysInMonth = getNumberOfDaysInMonth(month, year);
|
const numberOfDaysInMonth = getNumberOfDaysInMonth(month, year);
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
@ -45,7 +73,7 @@ const getAllDaysInMonthInMonthView = (month: number, year: number) => {
|
|||||||
return day;
|
return day;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateMonthDataByMonthAndYearInMonthView = (month: number, year: number) => {
|
const generateMonthDataByMonthAndYearInMonthView = (month: number, year: number): IMonthBlock => {
|
||||||
const currentMonth: number = month;
|
const currentMonth: number = month;
|
||||||
const currentYear: number = year;
|
const currentYear: number = year;
|
||||||
|
|
||||||
@ -162,7 +190,11 @@ export const getNumberOfDaysBetweenTwoDatesInMonth = (startDate: Date, endDate:
|
|||||||
return daysDifference;
|
return daysDifference;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType, itemData: any) => {
|
// calc item scroll position and width
|
||||||
|
export const getMonthChartItemPositionWidthInMonth = (
|
||||||
|
chartData: ChartDataType,
|
||||||
|
itemData: IGanttBlock
|
||||||
|
) => {
|
||||||
let scrollPosition: number = 0;
|
let scrollPosition: number = 0;
|
||||||
let scrollWidth: number = 0;
|
let scrollWidth: number = 0;
|
||||||
|
|
||||||
|
@ -100,8 +100,6 @@ export const generateYearChart = (yearPayload: ChartDataType, side: null | "left
|
|||||||
.map((monthData: any) => monthData.children.length)
|
.map((monthData: any) => monthData.children.length)
|
||||||
.reduce((partialSum: number, a: number) => partialSum + a, 0) * yearPayload.data.width;
|
.reduce((partialSum: number, a: number) => partialSum + a, 0) * yearPayload.data.width;
|
||||||
|
|
||||||
console.log("scrollWidth", scrollWidth);
|
|
||||||
|
|
||||||
return { state: renderState, payload: renderPayload, scrollWidth: scrollWidth };
|
return { state: renderState, payload: renderPayload, scrollWidth: scrollWidth };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
import { FC } from "react";
|
|
||||||
// next imports
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
|
||||||
// ui
|
|
||||||
import { Tooltip } from "components/ui";
|
|
||||||
// hooks
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
|
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
|
||||||
|
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
GanttChartRoot,
|
||||||
|
IssueGanttBlock,
|
||||||
|
renderIssueBlocksStructure,
|
||||||
|
} from "components/gantt-chart";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
type Props = {};
|
export const IssueGanttChartView = () => {
|
||||||
|
|
||||||
export const IssueGanttChartView: FC<Props> = ({}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { orderBy } = useIssuesView();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const { ganttIssues, mutateGanttIssues } = useGanttChartIssues(
|
const { ganttIssues, mutateGanttIssues } = useGanttChartIssues(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string
|
projectId as string
|
||||||
@ -31,76 +38,19 @@ export const IssueGanttChartView: FC<Props> = ({}) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// rendering issues on gantt card
|
|
||||||
const GanttBlockView = ({ data }: any) => (
|
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${data?.id}`}>
|
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm font-normal">
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 w-[4px] h-full"
|
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "rgb(var(--color-primary-100))" }}
|
|
||||||
/>
|
|
||||||
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
|
||||||
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
|
||||||
{data?.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
{data.infoToggle && (
|
|
||||||
<Tooltip
|
|
||||||
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
|
||||||
className={`z-[999999]`}
|
|
||||||
>
|
|
||||||
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
|
||||||
<span className="material-symbols-rounded text-custom-text-200 text-[18px]">
|
|
||||||
info
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle gantt issue start date and target date
|
|
||||||
const handleUpdateDates = async (data: any) => {
|
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
|
||||||
start_date: data?.start_date,
|
|
||||||
target_date: data?.target_date,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
|
||||||
blocks && blocks.length > 0
|
|
||||||
? blocks.map((_block: any) => {
|
|
||||||
let startDate = new Date(_block.created_at);
|
|
||||||
let targetDate = new Date(_block.updated_at);
|
|
||||||
let infoToggle = true;
|
|
||||||
|
|
||||||
if (_block?.start_date && _block.target_date) {
|
|
||||||
startDate = _block?.start_date;
|
|
||||||
targetDate = _block.target_date;
|
|
||||||
infoToggle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
start_date: new Date(startDate),
|
|
||||||
target_date: new Date(targetDate),
|
|
||||||
infoToggle: infoToggle,
|
|
||||||
data: _block,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
border={false}
|
border={false}
|
||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={ganttIssues ? blockFormat(ganttIssues) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={handleUpdateDates}
|
blockUpdateHandler={(block, payload) =>
|
||||||
|
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
||||||
|
}
|
||||||
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
||||||
blockRender={(data: any) => <GanttBlockView data={data} />}
|
blockRender={(data: any) => <IssueGanttBlock issue={data as IIssue} />}
|
||||||
|
enableReorder={orderBy === "sort_order"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -248,7 +248,11 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
await addIssueToModule(res.id, payload.module);
|
await addIssueToModule(res.id, payload.module);
|
||||||
|
|
||||||
if (issueView === "calendar") mutate(calendarFetchKey);
|
if (issueView === "calendar") mutate(calendarFetchKey);
|
||||||
if (issueView === "gantt_chart") mutate(ganttFetchKey);
|
if (issueView === "gantt_chart")
|
||||||
|
mutate(ganttFetchKey, {
|
||||||
|
start_target_date: true,
|
||||||
|
order_by: "sort_order",
|
||||||
|
});
|
||||||
if (issueView === "spreadsheet") mutate(spreadsheetFetchKey);
|
if (issueView === "spreadsheet") mutate(spreadsheetFetchKey);
|
||||||
if (groupedIssues) mutateMyIssues();
|
if (groupedIssues) mutateMyIssues();
|
||||||
|
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// next imports
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
|
||||||
// ui
|
|
||||||
import { Tooltip } from "components/ui";
|
|
||||||
// hooks
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
import useGanttChartModuleIssues from "hooks/gantt-chart/module-issues-view";
|
import useGanttChartModuleIssues from "hooks/gantt-chart/module-issues-view";
|
||||||
|
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
GanttChartRoot,
|
||||||
|
IssueGanttBlock,
|
||||||
|
renderIssueBlocksStructure,
|
||||||
|
} from "components/gantt-chart";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
|
|
||||||
@ -15,6 +22,10 @@ export const ModuleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { orderBy } = useIssuesView();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const { ganttIssues, mutateGanttIssues } = useGanttChartModuleIssues(
|
const { ganttIssues, mutateGanttIssues } = useGanttChartModuleIssues(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
@ -32,77 +43,18 @@ export const ModuleIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// rendering issues on gantt card
|
|
||||||
const GanttBlockView = ({ data }: any) => (
|
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${data?.id}`}>
|
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 w-[4px] h-full"
|
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "rgb(var(--color-primary-100))" }}
|
|
||||||
/>
|
|
||||||
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
|
||||||
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
|
||||||
{data?.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
{data.infoToggle && (
|
|
||||||
<Tooltip
|
|
||||||
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
|
||||||
className={`z-[999999]`}
|
|
||||||
>
|
|
||||||
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
|
||||||
<span className="material-symbols-rounded text-custom-text-200 text-[18px]">
|
|
||||||
info
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle gantt issue start date and target date
|
|
||||||
const handleUpdateDates = async (data: any) => {
|
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
|
||||||
start_date: data?.start_date,
|
|
||||||
target_date: data?.target_date,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("payload", payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
|
||||||
blocks && blocks.length > 0
|
|
||||||
? blocks.map((_block: any) => {
|
|
||||||
let startDate = new Date(_block.created_at);
|
|
||||||
let targetDate = new Date(_block.updated_at);
|
|
||||||
let infoToggle = true;
|
|
||||||
|
|
||||||
if (_block?.start_date && _block.target_date) {
|
|
||||||
startDate = _block?.start_date;
|
|
||||||
targetDate = _block.target_date;
|
|
||||||
infoToggle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
start_date: new Date(startDate),
|
|
||||||
target_date: new Date(targetDate),
|
|
||||||
infoToggle: infoToggle,
|
|
||||||
data: _block,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full p-3">
|
<div className="w-full h-full p-3">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
title="Modules"
|
title="Modules"
|
||||||
loaderTitle="Modules"
|
loaderTitle="Modules"
|
||||||
blocks={ganttIssues ? blockFormat(ganttIssues) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={handleUpdateDates}
|
blockUpdateHandler={(block, payload) =>
|
||||||
|
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
||||||
|
}
|
||||||
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
||||||
blockRender={(data: any) => <GanttBlockView data={data} />}
|
blockRender={(data: any) => <IssueGanttBlock issue={data as IIssue} />}
|
||||||
|
enableReorder={orderBy === "sort_order"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// next imports
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { KeyedMutator } from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import modulesService from "services/modules.service";
|
||||||
|
// hooks
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
import { GanttChartRoot, IBlockUpdateData, ModuleGanttBlock } from "components/gantt-chart";
|
||||||
// ui
|
|
||||||
import { Tooltip } from "components/ui";
|
|
||||||
// types
|
// types
|
||||||
import { IModule } from "types";
|
import { IModule } from "types";
|
||||||
// constants
|
// constants
|
||||||
@ -13,11 +17,14 @@ import { MODULE_STATUS } from "constants/module";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modules: IModule[];
|
modules: IModule[];
|
||||||
|
mutateModules: KeyedMutator<IModule[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModulesListGanttChartView: FC<Props> = ({ modules }) => {
|
export const ModulesListGanttChartView: FC<Props> = ({ modules, mutateModules }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
// rendering issues on gantt sidebar
|
// rendering issues on gantt sidebar
|
||||||
const GanttSidebarBlockView = ({ data }: any) => (
|
const GanttSidebarBlockView = ({ data }: any) => (
|
||||||
@ -32,42 +39,52 @@ export const ModulesListGanttChartView: FC<Props> = ({ modules }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// rendering issues on gantt card
|
const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => {
|
||||||
const GanttBlockView = ({ data }: { data: IModule }) => (
|
if (!workspaceSlug || !user) return;
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/modules/${data?.id}`}>
|
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 w-[4px] h-full"
|
|
||||||
style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === data.status)?.color }}
|
|
||||||
/>
|
|
||||||
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
|
||||||
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
|
||||||
{data?.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle gantt issue start date and target date
|
mutateModules((prevData) => {
|
||||||
const handleUpdateDates = async (data: any) => {
|
if (!prevData) return prevData;
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
const newList = prevData.map((p) => ({
|
||||||
start_date: data?.start_date,
|
...p,
|
||||||
target_date: data?.target_date,
|
...(p.id === module.id
|
||||||
};
|
? {
|
||||||
|
start_date: payload.start_date ? payload.start_date : p.start_date,
|
||||||
|
target_date: payload.target_date ? payload.target_date : p.target_date,
|
||||||
|
sort_order: payload.sort_order ? payload.sort_order.newSortOrder : p.sort_order,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (payload.sort_order) {
|
||||||
|
const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0];
|
||||||
|
newList.splice(payload.sort_order.destinationIndex, 0, removedElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newList;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
const newPayload: any = { ...payload };
|
||||||
|
|
||||||
|
if (newPayload.sort_order && payload.sort_order)
|
||||||
|
newPayload.sort_order = payload.sort_order.newSortOrder;
|
||||||
|
|
||||||
|
modulesService
|
||||||
|
.patchModule(workspaceSlug.toString(), module.project, module.id, newPayload, user)
|
||||||
|
.finally(() => mutateModules());
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
const blockFormat = (blocks: IModule[]) =>
|
||||||
blocks && blocks.length > 0
|
blocks && blocks.length > 0
|
||||||
? blocks.map((_block: any) => {
|
? blocks
|
||||||
if (_block?.start_date && _block.target_date) console.log("_block", _block);
|
.filter((b) => b.start_date && b.target_date)
|
||||||
return {
|
.map((block) => ({
|
||||||
start_date: new Date(_block.created_at),
|
data: block,
|
||||||
target_date: new Date(_block.updated_at),
|
id: block.id,
|
||||||
data: _block,
|
sort_order: block.sort_order,
|
||||||
};
|
start_date: new Date(block.start_date ?? ""),
|
||||||
})
|
target_date: new Date(block.target_date ?? ""),
|
||||||
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -76,9 +93,9 @@ export const ModulesListGanttChartView: FC<Props> = ({ modules }) => {
|
|||||||
title="Modules"
|
title="Modules"
|
||||||
loaderTitle="Modules"
|
loaderTitle="Modules"
|
||||||
blocks={modules ? blockFormat(modules) : null}
|
blocks={modules ? blockFormat(modules) : null}
|
||||||
blockUpdateHandler={handleUpdateDates}
|
blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)}
|
||||||
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
||||||
blockRender={(data: any) => <GanttBlockView data={data} />}
|
blockRender={(data: any) => <ModuleGanttBlock module={data as IModule} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -103,9 +103,7 @@ export const ProjectSidebarList: FC = () => {
|
|||||||
? (projectsList[destination.index + 1].sort_order as number)
|
? (projectsList[destination.index + 1].sort_order as number)
|
||||||
: (projectsList[destination.index - 1].sort_order as number);
|
: (projectsList[destination.index - 1].sort_order as number);
|
||||||
|
|
||||||
updatedSortOrder = Math.round(
|
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||||
(destinationSortingOrder + relativeDestinationSortingOrder) / 2
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate<IProject[]>(
|
mutate<IProject[]>(
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// next imports
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
|
||||||
import { GanttChartRoot } from "components/gantt-chart";
|
|
||||||
// ui
|
|
||||||
import { Tooltip } from "components/ui";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
|
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
|
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
GanttChartRoot,
|
||||||
|
IssueGanttBlock,
|
||||||
|
renderIssueBlocksStructure,
|
||||||
|
} from "components/gantt-chart";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
|
|
||||||
@ -15,6 +21,8 @@ export const ViewIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, viewId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query;
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const { ganttIssues, mutateGanttIssues } = useGanttChartViewIssues(
|
const { ganttIssues, mutateGanttIssues } = useGanttChartViewIssues(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
@ -32,77 +40,17 @@ export const ViewIssuesGanttChartView: FC<Props> = ({}) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// rendering issues on gantt card
|
|
||||||
const GanttBlockView = ({ data }: any) => (
|
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${data?.id}`}>
|
|
||||||
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 w-[4px] h-full"
|
|
||||||
style={{ backgroundColor: data?.state_detail?.color || "rgb(var(--color-primary-100))" }}
|
|
||||||
/>
|
|
||||||
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
|
|
||||||
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
|
|
||||||
{data?.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
{data.infoToggle && (
|
|
||||||
<Tooltip
|
|
||||||
tooltipContent={`No due-date set, rendered according to last updated date.`}
|
|
||||||
className={`z-[999999]`}
|
|
||||||
>
|
|
||||||
<div className="flex-shrink-0 mx-2 w-[18px] h-[18px] overflow-hidden flex justify-center items-center">
|
|
||||||
<span className="material-symbols-rounded text-custom-text-200 text-[18px]">
|
|
||||||
info
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle gantt issue start date and target date
|
|
||||||
const handleUpdateDates = async (data: any) => {
|
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
|
||||||
start_date: data?.start_date,
|
|
||||||
target_date: data?.target_date,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("payload", payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const blockFormat = (blocks: any) =>
|
|
||||||
blocks && blocks.length > 0
|
|
||||||
? blocks.map((_block: any) => {
|
|
||||||
let startDate = new Date(_block.created_at);
|
|
||||||
let targetDate = new Date(_block.updated_at);
|
|
||||||
let infoToggle = true;
|
|
||||||
|
|
||||||
if (_block?.start_date && _block.target_date) {
|
|
||||||
startDate = _block?.start_date;
|
|
||||||
targetDate = _block.target_date;
|
|
||||||
infoToggle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
start_date: new Date(startDate),
|
|
||||||
target_date: new Date(targetDate),
|
|
||||||
infoToggle: infoToggle,
|
|
||||||
data: _block,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full p-3">
|
<div className="w-full h-full p-3">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
title="Issue Views"
|
title="Issue Views"
|
||||||
loaderTitle="Issue Views"
|
loaderTitle="Issue Views"
|
||||||
blocks={ganttIssues ? blockFormat(ganttIssues) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={handleUpdateDates}
|
blockUpdateHandler={(block, payload) =>
|
||||||
|
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
||||||
|
}
|
||||||
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
sidebarBlockRender={(data: any) => <GanttSidebarBlockView data={data} />}
|
||||||
blockRender={(data: any) => <GanttBlockView data={data} />}
|
blockRender={(data: any) => <IssueGanttBlock issue={data as IIssue} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,13 +2,23 @@ import { objToQueryParams } from "helpers/string.helper";
|
|||||||
import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "types";
|
import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "types";
|
||||||
|
|
||||||
const paramsToKey = (params: any) => {
|
const paramsToKey = (params: any) => {
|
||||||
const { state, priority, assignees, created_by, labels, target_date, sub_issue } = params;
|
const {
|
||||||
|
state,
|
||||||
|
priority,
|
||||||
|
assignees,
|
||||||
|
created_by,
|
||||||
|
labels,
|
||||||
|
target_date,
|
||||||
|
sub_issue,
|
||||||
|
start_target_date,
|
||||||
|
} = params;
|
||||||
|
|
||||||
let stateKey = state ? state.split(",") : [];
|
let stateKey = state ? state.split(",") : [];
|
||||||
let priorityKey = priority ? priority.split(",") : [];
|
let priorityKey = priority ? priority.split(",") : [];
|
||||||
let assigneesKey = assignees ? assignees.split(",") : [];
|
let assigneesKey = assignees ? assignees.split(",") : [];
|
||||||
let createdByKey = created_by ? created_by.split(",") : [];
|
let createdByKey = created_by ? created_by.split(",") : [];
|
||||||
let labelsKey = labels ? labels.split(",") : [];
|
let labelsKey = labels ? labels.split(",") : [];
|
||||||
|
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
|
||||||
const targetDateKey = target_date ?? "";
|
const targetDateKey = target_date ?? "";
|
||||||
const type = params.type ? params.type.toUpperCase() : "NULL";
|
const type = params.type ? params.type.toUpperCase() : "NULL";
|
||||||
const groupBy = params.group_by ? params.group_by.toUpperCase() : "NULL";
|
const groupBy = params.group_by ? params.group_by.toUpperCase() : "NULL";
|
||||||
@ -21,7 +31,7 @@ const paramsToKey = (params: any) => {
|
|||||||
createdByKey = createdByKey.sort().join("_");
|
createdByKey = createdByKey.sort().join("_");
|
||||||
labelsKey = labelsKey.sort().join("_");
|
labelsKey = labelsKey.sort().join("_");
|
||||||
|
|
||||||
return `${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${targetDateKey}_${sub_issue}`;
|
return `${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${targetDateKey}_${sub_issue}_${startTargetDate}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const inboxParamsToKey = (params: any) => {
|
const inboxParamsToKey = (params: any) => {
|
||||||
|
@ -2,6 +2,8 @@ import useSWR from "swr";
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -10,15 +12,27 @@ const useGanttChartCycleIssues = (
|
|||||||
projectId: string | undefined,
|
projectId: string | undefined,
|
||||||
cycleId: string | undefined
|
cycleId: string | undefined
|
||||||
) => {
|
) => {
|
||||||
|
const { orderBy, filters, showSubIssues } = useIssuesView();
|
||||||
|
|
||||||
|
const params: any = {
|
||||||
|
order_by: orderBy,
|
||||||
|
type: filters?.type ? filters?.type : undefined,
|
||||||
|
sub_issue: showSubIssues,
|
||||||
|
start_target_date: true,
|
||||||
|
};
|
||||||
|
|
||||||
// all issues under the workspace and project
|
// all issues under the workspace and project
|
||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
||||||
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString()) : null,
|
workspaceSlug && projectId && cycleId
|
||||||
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)
|
||||||
|
: null,
|
||||||
workspaceSlug && projectId && cycleId
|
workspaceSlug && projectId && cycleId
|
||||||
? () =>
|
? () =>
|
||||||
cyclesService.getCycleIssuesWithParams(
|
cyclesService.getCycleIssuesWithParams(
|
||||||
workspaceSlug.toString(),
|
workspaceSlug.toString(),
|
||||||
projectId.toString(),
|
projectId.toString(),
|
||||||
cycleId.toString()
|
cycleId.toString(),
|
||||||
|
params
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
@ -2,15 +2,27 @@ import useSWR from "swr";
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => {
|
const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => {
|
||||||
|
const { orderBy, filters, showSubIssues } = useIssuesView();
|
||||||
|
|
||||||
|
const params: any = {
|
||||||
|
order_by: orderBy,
|
||||||
|
type: filters?.type ? filters?.type : undefined,
|
||||||
|
sub_issue: showSubIssues,
|
||||||
|
start_target_date: true,
|
||||||
|
};
|
||||||
|
|
||||||
// all issues under the workspace and project
|
// all issues under the workspace and project
|
||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId) : null,
|
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString())
|
? () =>
|
||||||
|
issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import useSWR from "swr";
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { MODULE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
import { MODULE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -10,15 +12,27 @@ const useGanttChartModuleIssues = (
|
|||||||
projectId: string | undefined,
|
projectId: string | undefined,
|
||||||
moduleId: string | undefined
|
moduleId: string | undefined
|
||||||
) => {
|
) => {
|
||||||
|
const { orderBy, filters, showSubIssues } = useIssuesView();
|
||||||
|
|
||||||
|
const params: any = {
|
||||||
|
order_by: orderBy,
|
||||||
|
type: filters?.type ? filters?.type : undefined,
|
||||||
|
sub_issue: showSubIssues,
|
||||||
|
start_target_date: true,
|
||||||
|
};
|
||||||
|
|
||||||
// all issues under the workspace and project
|
// all issues under the workspace and project
|
||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
||||||
workspaceSlug && projectId && moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString()) : null,
|
workspaceSlug && projectId && moduleId
|
||||||
|
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
|
||||||
|
: null,
|
||||||
workspaceSlug && projectId && moduleId
|
workspaceSlug && projectId && moduleId
|
||||||
? () =>
|
? () =>
|
||||||
modulesService.getModuleIssuesWithParams(
|
modulesService.getModuleIssuesWithParams(
|
||||||
workspaceSlug.toString(),
|
workspaceSlug.toString(),
|
||||||
projectId.toString(),
|
projectId.toString(),
|
||||||
moduleId.toString()
|
moduleId.toString(),
|
||||||
|
params
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
@ -17,14 +17,15 @@ const useGanttChartViewIssues = (
|
|||||||
|
|
||||||
// all issues under the view
|
// all issues under the view
|
||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
||||||
workspaceSlug && projectId && viewId ? VIEW_ISSUES(viewId.toString(), viewGanttParams) : null,
|
workspaceSlug && projectId && viewId
|
||||||
|
? VIEW_ISSUES(viewId.toString(), { ...viewGanttParams, start_target_date: true })
|
||||||
|
: null,
|
||||||
workspaceSlug && projectId && viewId
|
workspaceSlug && projectId && viewId
|
||||||
? () =>
|
? () =>
|
||||||
issuesService.getIssuesWithParams(
|
issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), {
|
||||||
workspaceSlug.toString(),
|
...viewGanttParams,
|
||||||
projectId.toString(),
|
start_target_date: true,
|
||||||
viewGanttParams
|
})
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ const Sidebar: React.FC<SidebarProps> = observer(({ toggleSidebar, setToggleSide
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id="app-sidebar"
|
||||||
className={`fixed md:relative inset-y-0 flex flex-col bg-custom-sidebar-background-100 h-full flex-shrink-0 flex-grow-0 border-r border-custom-sidebar-border-200 z-20 duration-300 ${
|
className={`fixed md:relative inset-y-0 flex flex-col bg-custom-sidebar-background-100 h-full flex-shrink-0 flex-grow-0 border-r border-custom-sidebar-border-200 z-20 duration-300 ${
|
||||||
store?.theme?.sidebarCollapsed ? "" : "md:w-[280px]"
|
store?.theme?.sidebarCollapsed ? "" : "md:w-[280px]"
|
||||||
} ${toggleSidebar ? "left-0" : "-left-full md:left-0"}`}
|
} ${toggleSidebar ? "left-0" : "-left-full md:left-0"}`}
|
||||||
|
@ -50,7 +50,7 @@ const ProjectModules: NextPage = () => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: modules } = useSWR<IModule[]>(
|
const { data: modules, mutate: mutateModules } = useSWR(
|
||||||
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () => modulesService.getModules(workspaceSlug as string, projectId as string)
|
? () => modulesService.getModules(workspaceSlug as string, projectId as string)
|
||||||
@ -139,7 +139,9 @@ const ProjectModules: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{modulesView === "gantt_chart" && <ModulesListGanttChartView modules={modules} />}
|
{modulesView === "gantt_chart" && (
|
||||||
|
<ModulesListGanttChartView modules={modules} mutateModules={mutateModules} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
@ -75,7 +75,7 @@ class ProjectIssuesServices extends APIService {
|
|||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
moduleId: string,
|
moduleId: string,
|
||||||
data: any,
|
data: Partial<IModule>,
|
||||||
user: ICurrentUserResponse | undefined
|
user: ICurrentUserResponse | undefined
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.patch(
|
return this.patch(
|
||||||
@ -127,7 +127,7 @@ class ProjectIssuesServices extends APIService {
|
|||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
moduleId: string,
|
moduleId: string,
|
||||||
queries?: Partial<IIssueViewOptions>
|
queries?: any
|
||||||
): Promise<
|
): Promise<
|
||||||
| IIssue[]
|
| IIssue[]
|
||||||
| {
|
| {
|
||||||
|
1
apps/app/types/cycles.d.ts
vendored
1
apps/app/types/cycles.d.ts
vendored
@ -29,6 +29,7 @@ export interface ICycle {
|
|||||||
owned_by: IUser;
|
owned_by: IUser;
|
||||||
project: string;
|
project: string;
|
||||||
project_detail: IProjectLite;
|
project_detail: IProjectLite;
|
||||||
|
sort_order: number;
|
||||||
start_date: string | null;
|
start_date: string | null;
|
||||||
started_issues: number;
|
started_issues: number;
|
||||||
total_issues: number;
|
total_issues: number;
|
||||||
|
1
apps/app/types/modules.d.ts
vendored
1
apps/app/types/modules.d.ts
vendored
@ -43,6 +43,7 @@ export interface IModule {
|
|||||||
name: string;
|
name: string;
|
||||||
project: string;
|
project: string;
|
||||||
project_detail: IProjectLite;
|
project_detail: IProjectLite;
|
||||||
|
sort_order: number;
|
||||||
start_date: string | null;
|
start_date: string | null;
|
||||||
started_issues: number;
|
started_issues: number;
|
||||||
status: "backlog" | "planned" | "in-progress" | "paused" | "completed" | "cancelled" | null;
|
status: "backlog" | "planned" | "in-progress" | "paused" | "completed" | "cancelled" | null;
|
||||||
|
142
yarn.lock
142
yarn.lock
@ -2993,26 +2993,26 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69"
|
||||||
integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==
|
integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==
|
||||||
|
|
||||||
"@sentry-internal/tracing@7.61.1":
|
"@sentry-internal/tracing@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.61.1.tgz#8055b7dfbf89b7089a591b27e05484d5f6773948"
|
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.62.0.tgz#f14400f20a32844f2895a8a333080d52fa32cd1d"
|
||||||
integrity sha512-E8J6ZMXHGdWdmgKBK/ounuUppDK65c4Hphin6iVckDGMEATn0auYAKngeyRUMLof1167DssD8wxcIA4aBvmScA==
|
integrity sha512-LHT8i2c93JhQ1uBU1cqb5AIhmHPWlyovE4ZQjqEizk6Fk7jXc9L8kKhaIWELVPn8Xg6YtfGWhRBZk3ssj4JpfQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/core" "7.61.1"
|
"@sentry/core" "7.62.0"
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/browser@7.61.1":
|
"@sentry/browser@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.61.1.tgz#ce5005ea76d4c2e91c09a43b218c25cc5e9c1340"
|
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.62.0.tgz#0b00a0ed8e4cd4873f7ec413b094ec6b170bb085"
|
||||||
integrity sha512-v6Wv0O/PF+sqji+WWpJmxAlQafsiKmsXQLzKAIntVjl3HbYO5oVS3ubCyqfxSlLxIhM5JuHcEOLn6Zi3DPtpcw==
|
integrity sha512-e52EPiRtPTZv+9iFIZT3n8qNozc8ymqT0ra7QwkwbVuF9fWSCOc1gzkTa9VKd/xwcGzOfglozl2O+Zz4GtoGUg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry-internal/tracing" "7.61.1"
|
"@sentry-internal/tracing" "7.62.0"
|
||||||
"@sentry/core" "7.61.1"
|
"@sentry/core" "7.62.0"
|
||||||
"@sentry/replay" "7.61.1"
|
"@sentry/replay" "7.62.0"
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/cli@^1.74.6":
|
"@sentry/cli@^1.74.6":
|
||||||
@ -3027,88 +3027,88 @@
|
|||||||
proxy-from-env "^1.1.0"
|
proxy-from-env "^1.1.0"
|
||||||
which "^2.0.2"
|
which "^2.0.2"
|
||||||
|
|
||||||
"@sentry/core@7.61.1":
|
"@sentry/core@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.61.1.tgz#8043c7cecf5ca0601f6c61979fb2880ceac37287"
|
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.62.0.tgz#3d9571741b052b1f2fa8fb8ae0088de8e79b4f4e"
|
||||||
integrity sha512-WTRt0J33KhUbYuDQZ5G58kdsNeQ5JYrpi6o+Qz+1xTv60DQq/tBGRJ7d86SkmdnGIiTs6W1hsxAtyiLS0y9d2A==
|
integrity sha512-l6n+c3mSlWa+FhT/KBrAU1BtbaLYCljf5MuGlH6NKRpnBcrZCbzk8ZuFcSND+gr2SqxycQkhEWX1zxVHPDdZxw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/integrations@7.61.1":
|
"@sentry/integrations@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.61.1.tgz#ca9bf2fc59c852f5e73543bb7e69b181a4ef2d45"
|
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.62.0.tgz#fad35d8de97890b35269d132636218ae157dab22"
|
||||||
integrity sha512-mdmWzUQmW1viOiW0/Gi6AQ5LXukqhuefjzLdn5o6HMxiAgskIpNX+0+BOQ/6162/o7mHWSTNEHqEzMNTK2ppLw==
|
integrity sha512-BNlW4xczhbL+zmmc8kFZunjKBrVYZsAltQ/gMuaHw5iiEr+chVMgQDQ2A9EVB7WEtuTJQ0XmeqofH2nAk2qYHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
localforage "^1.8.1"
|
localforage "^1.8.1"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/nextjs@^7.36.0":
|
"@sentry/nextjs@^7.36.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.61.1.tgz#556bd48740dd67694ee54aaed042a22c255290ed"
|
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.62.0.tgz#6a5362dc03c768e8ef855ea7c26f94dddc40d7eb"
|
||||||
integrity sha512-ssq0AX+QaDzLSeA45lQLt3OVkzUNiNsI5loMU9gq+Bsts3KOHnykturFvdrb5T3WuIucE6PsswNjZIWqP+lrMg==
|
integrity sha512-Hg5D8dAgGkn+ZoTh2SSOx35hcVJUf9QO4D2FKFmPwFpnrpP/thcusE7m2k6jsUlK6jBvZhtC0rcZk26K3WsioA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@rollup/plugin-commonjs" "24.0.0"
|
"@rollup/plugin-commonjs" "24.0.0"
|
||||||
"@sentry/core" "7.61.1"
|
"@sentry/core" "7.62.0"
|
||||||
"@sentry/integrations" "7.61.1"
|
"@sentry/integrations" "7.62.0"
|
||||||
"@sentry/node" "7.61.1"
|
"@sentry/node" "7.62.0"
|
||||||
"@sentry/react" "7.61.1"
|
"@sentry/react" "7.62.0"
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
"@sentry/webpack-plugin" "1.20.0"
|
"@sentry/webpack-plugin" "1.20.0"
|
||||||
chalk "3.0.0"
|
chalk "3.0.0"
|
||||||
rollup "2.78.0"
|
rollup "2.78.0"
|
||||||
stacktrace-parser "^0.1.10"
|
stacktrace-parser "^0.1.10"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/node@7.61.1":
|
"@sentry/node@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.61.1.tgz#bc49d321d0a511936f8bdd0bbd3ddc5e01b8d98c"
|
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.62.0.tgz#8ccac64974748705103fccd3cf40f76003bad94a"
|
||||||
integrity sha512-+crVAeymXdWZcDuwU9xySf4sVv2fHOFlr13XqeXl73q4zqKJM1IX4VUO9On3+jTyGfB5SCAuBBYpzA3ehBfeYw==
|
integrity sha512-2z1JmYV97eJ8zwshJA15hppjRdUeMhbaL8LSsbdtx7vTMmjuaIGfPR4EnI4Fhuw+J1Nnf5sE/CRKpZCCa74vXw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry-internal/tracing" "7.61.1"
|
"@sentry-internal/tracing" "7.62.0"
|
||||||
"@sentry/core" "7.61.1"
|
"@sentry/core" "7.62.0"
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
cookie "^0.4.1"
|
cookie "^0.4.1"
|
||||||
https-proxy-agent "^5.0.0"
|
https-proxy-agent "^5.0.0"
|
||||||
lru_map "^0.3.3"
|
lru_map "^0.3.3"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/react@7.61.1":
|
"@sentry/react@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.61.1.tgz#88a62fe9a847ffb0feeff935c49737abd7904007"
|
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.62.0.tgz#8fa7246ba61f57c007893d76dcd5784b4e12d34e"
|
||||||
integrity sha512-n8xNT05gdERpETvq3GJZ2lP6HZYLRQQoUDc13egDzKf840MzCjle0LiLmsVhRv8AL1GnWaIPwnvTGvS4BuNlvw==
|
integrity sha512-jCQEs6lYGQdqj6XXWdR+i5IzJMgrSzTFI/TSMSeTdAeldmppg7uuRuJlBJGaWsxoiwed539Vn3kitRswn1ugeA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/browser" "7.61.1"
|
"@sentry/browser" "7.62.0"
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
hoist-non-react-statics "^3.3.2"
|
hoist-non-react-statics "^3.3.2"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/replay@7.61.1":
|
"@sentry/replay@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.61.1.tgz#20cdb5f31b5ce25a7afe11bcaaf67b1f875d2833"
|
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.62.0.tgz#9131c24ae2e797ae47983834ba88b3b5c7f6e566"
|
||||||
integrity sha512-Nsnnzx8c+DRjnfQ0Md11KGdY21XOPa50T2B3eBEyFAhibvYEc/68PuyVWkMBQ7w9zo/JV+q6HpIXKD0THUtqZA==
|
integrity sha512-mSbqtV6waQAvWTG07uR211jft63HduRXdHq+1xuaKulDcZ9chOkYqOCMpL0HjRIANEiZRTDDKlIo4s+3jkY5Ug==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/core" "7.61.1"
|
"@sentry/core" "7.62.0"
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
"@sentry/utils" "7.61.1"
|
"@sentry/utils" "7.62.0"
|
||||||
|
|
||||||
"@sentry/types@7.61.1":
|
"@sentry/types@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.61.1.tgz#225912689459c92e62f0b6e3ff145f6dbf72ff0e"
|
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.62.0.tgz#f15729f656459ffa3a5998fafe9d17ee7fb1c9ff"
|
||||||
integrity sha512-CpPKL+OfwYOduRX9AT3p+Ie1fftgcCPd5WofTVVq7xeWRuerOOf2iJd0v+8yHQ25omgres1YOttDkCcvQRn4Jw==
|
integrity sha512-oPy/fIT3o2VQWLTq01R2W/jt13APYMqZCVa0IT3lF9lgxzgfTbeZl3nX2FgCcc8ntDZC0dVw03dL+wLvjPqQpQ==
|
||||||
|
|
||||||
"@sentry/utils@7.61.1":
|
"@sentry/utils@7.62.0":
|
||||||
version "7.61.1"
|
version "7.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.61.1.tgz#1545db778b7309d122a7f04eb0e803173c80c581"
|
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.62.0.tgz#915501c6056d704a9625239a1f584a7b2e4492ea"
|
||||||
integrity sha512-pUPXoiuYrTEPcBHjRizFB6eZEGm/6cTBwdWSHUjkGKvt19zuZ1ixFJQV6LrIL/AMeiQbmfQ+kTd/8SR7E9rcTQ==
|
integrity sha512-12w+Lpvn2iaocgjf6AxhtBz7XG8iFE5aMyt9BTuQp1/7sOjtEVNHlDlGrHbtPqxNCmL2SEcmNHka1panLqWHDw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/types" "7.61.1"
|
"@sentry/types" "7.62.0"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
tslib "^2.4.1 || ^1.9.3"
|
||||||
|
|
||||||
"@sentry/webpack-plugin@1.20.0":
|
"@sentry/webpack-plugin@1.20.0":
|
||||||
@ -6439,9 +6439,9 @@ levn@^0.4.1:
|
|||||||
type-check "~0.4.0"
|
type-check "~0.4.0"
|
||||||
|
|
||||||
lib0@^0.2.42, lib0@^0.2.74:
|
lib0@^0.2.42, lib0@^0.2.74:
|
||||||
version "0.2.79"
|
version "0.2.80"
|
||||||
resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.79.tgz#b82ee41bfab31a4358bbc0c8ad0645394149a4a9"
|
resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.80.tgz#97f560c1240b947b825f9923fdfa45c1b4bd7cb8"
|
||||||
integrity sha512-fIdPbxzMVq10wt3ou1lp3/f9n5ciHZ6t+P1vyGy3XXr018AntTYM4eg24sNFcNq8SYDQwmhhoGdS58IlYBzfBw==
|
integrity sha512-1yVb13p19DrgbL7M/zQmRe/5tQrm37QlCHOssk+G8Q9qnZBh6Azfk876zhaxmKqyMnFGbQqBjH+CV0zkpr+TTw==
|
||||||
dependencies:
|
dependencies:
|
||||||
isomorphic.js "^0.2.4"
|
isomorphic.js "^0.2.4"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user