diff --git a/src/KonvaTimeline/yearly-scenario.stories.tsx b/src/KonvaTimeline/yearly-scenario.stories.tsx
index 074e055..50daee9 100644
--- a/src/KonvaTimeline/yearly-scenario.stories.tsx
+++ b/src/KonvaTimeline/yearly-scenario.stories.tsx
@@ -27,6 +27,31 @@ const yearlyStoryData = generateStoryData({
export const YearlyReport: Story = {
args: {
...yearlyStoryData,
- resolution: "1day",
+ resolution: "30min",
+ columnWidth: 120,
+ range: {
+ start: 1698357600000,
+ end: 1712095200000,
+ },
+ tasks: [
+ {
+ id: "1",
+ label: "1Novembre",
+ resourceId: "1",
+ time: {
+ start: 1698793200000,
+ end: 1700434800000,
+ },
+ },
+ {
+ id: "2",
+ label: "26Marzo",
+ resourceId: "1",
+ time: {
+ start: 1711839600000,
+ end: 1711922399000,
+ },
+ },
+ ],
},
};
diff --git a/src/grid/Cell/index.tsx b/src/grid/Cell/index.tsx
index edd2053..a22a2a3 100644
--- a/src/grid/Cell/index.tsx
+++ b/src/grid/Cell/index.tsx
@@ -9,9 +9,13 @@ interface GridCellProps {
column: Interval;
height: number;
index: number;
+ hourInfo: {
+ backHour?: boolean;
+ nextHour?: boolean;
+ };
}
-const GridCell = ({ column, height, index }: GridCellProps) => {
+const GridCell = ({ column, height, index, hourInfo: visibleDayInfo }: GridCellProps) => {
const {
blocksOffset,
columnWidth,
@@ -22,7 +26,28 @@ const GridCell = ({ column, height, index }: GridCellProps) => {
const cellLabel = useMemo(() => displayInterval(column, resolutionUnit), [column, resolutionUnit]);
- const xPos = useMemo(() => columnWidth * (index + blocksOffset), [blocksOffset, columnWidth, index]);
+ const xPos = useMemo(() => {
+ if (resolutionUnit === "day") {
+ if (visibleDayInfo.backHour) {
+ return columnWidth * (index + blocksOffset) + columnWidth / 24;
+ }
+
+ if (visibleDayInfo.nextHour) {
+ return columnWidth * (index + blocksOffset) - columnWidth / 24;
+ }
+ }
+ if (resolutionUnit === "week") {
+ if (visibleDayInfo.backHour) {
+ return columnWidth * (index + blocksOffset) + columnWidth / 168;
+ }
+
+ if (visibleDayInfo.nextHour) {
+ return columnWidth * (index + blocksOffset) - columnWidth / 168;
+ }
+ }
+
+ return columnWidth * (index + blocksOffset);
+ }, [blocksOffset, columnWidth, index, visibleDayInfo, resolutionUnit]);
const yPos = useMemo(() => rowHeight * 0.8, [rowHeight]);
diff --git a/src/grid/CellGroup/index.tsx b/src/grid/CellGroup/index.tsx
index 60edca7..936f722 100644
--- a/src/grid/CellGroup/index.tsx
+++ b/src/grid/CellGroup/index.tsx
@@ -7,11 +7,18 @@ import { displayAboveInterval } from "../../utils/time-resolution";
interface GridCellGroupProps {
column: Interval;
- height: number;
index: number;
+ dayInfo?: {
+ thisMonth?: number;
+ untilNow?: number;
+ }[];
+ hourInfo?: {
+ backHour?: boolean;
+ nextHour?: boolean;
+ };
}
-const GridCellGroup = ({ column, height, index }: GridCellGroupProps) => {
+const GridCellGroup = ({ column, index, dayInfo, hourInfo }: GridCellGroupProps) => {
const {
columnWidth,
resolution: { sizeInUnits, unit, unitAbove },
@@ -21,24 +28,82 @@ const GridCellGroup = ({ column, height, index }: GridCellGroupProps) => {
const cellLabel = useMemo(() => displayAboveInterval(column, unitAbove), [column, unitAbove]);
- const points = useMemo(() => [0, 0, 0, height], [height]);
+ const points = useMemo(() => [0, 0, 0, rowHeight], [rowHeight]);
- const unitAboveInUnitBelow = useMemo(
- () => Duration.fromObject({ [unitAbove]: 1 }).as(unit) / sizeInUnits,
- [sizeInUnits, unit, unitAbove]
- );
+ const unitAboveInUnitBelow = useMemo(() => {
+ if (unitAbove === "month") {
+ return Duration.fromObject({ ["day"]: dayInfo![index].thisMonth }).as("week") / sizeInUnits;
+ }
+
+ return Duration.fromObject({ [unitAbove]: 1 }).as(unit) / sizeInUnits;
+ }, [sizeInUnits, dayInfo, index, unitAbove, unit]);
+
+ const unitAboveSpanInPx = useMemo(() => {
+ return unitAboveInUnitBelow * columnWidth;
+ }, [columnWidth, unitAboveInUnitBelow]);
+
+ const xPos = useMemo(() => {
+ if (unitAbove === "month") {
+ const pxUntil =
+ index !== 0 ? Duration.fromObject({ ["day"]: dayInfo![index - 1].untilNow }).as("week") / sizeInUnits : 0;
+
+ if (hourInfo!.backHour) {
+ const hourInMonthPx = columnWidth / 168;
+ return pxUntil * columnWidth + unitAboveSpanInPx + hourInMonthPx;
+ }
+
+ if (hourInfo!.nextHour) {
+ const hourInMonthPx = columnWidth / 168;
+ return pxUntil * columnWidth + unitAboveSpanInPx - hourInMonthPx;
+ }
- const unitAboveSpanInPx = useMemo(() => unitAboveInUnitBelow * columnWidth, [columnWidth, unitAboveInUnitBelow]);
+ return pxUntil * columnWidth + unitAboveSpanInPx;
+ }
- const xPos = useMemo(() => index * unitAboveSpanInPx, [index, unitAboveSpanInPx]);
+ if (unitAbove === "day") {
+ if (hourInfo!.backHour) {
+ return index * unitAboveSpanInPx + columnWidth / sizeInUnits;
+ }
+
+ if (hourInfo!.nextHour) {
+ return index * unitAboveSpanInPx - columnWidth / sizeInUnits;
+ }
+ }
+
+ if (unitAbove === "week") {
+ if (hourInfo!.backHour) {
+ return index * unitAboveSpanInPx + columnWidth / 24;
+ }
+
+ if (hourInfo!.nextHour) {
+ return index * unitAboveSpanInPx - columnWidth / 24;
+ }
+ }
+
+ return index * unitAboveSpanInPx;
+ }, [index, unitAboveSpanInPx, columnWidth, sizeInUnits, dayInfo, unitAbove, hourInfo]);
const yPos = useMemo(() => rowHeight * 0.3, [rowHeight]);
+ const xPosLabel = useMemo(() => {
+ if (unitAbove === "month") {
+ return xPos - unitAboveSpanInPx;
+ }
+ return index * unitAboveSpanInPx;
+ }, [xPos, unitAboveSpanInPx, unitAbove, index]);
+
return (
-
+
);
};
diff --git a/src/grid/Cells/index.tsx b/src/grid/Cells/index.tsx
index 2a80bd4..be4b359 100644
--- a/src/grid/Cells/index.tsx
+++ b/src/grid/Cells/index.tsx
@@ -1,7 +1,8 @@
-import React, { memo } from "react";
+import React, { memo, useMemo } from "react";
import { KonvaGroup } from "../../@konva";
import { useTimelineContext } from "../../timeline/TimelineContext";
+import { dayDetail, timeBlockTz } from "../../utils/timeBlockArray";
import GridCell from "../Cell";
import GridCellGroup from "../CellGroup";
@@ -10,15 +11,42 @@ interface GridCellsProps {
}
const GridCells = ({ height }: GridCellsProps) => {
- const { aboveTimeBlocks, visibleTimeBlocks } = useTimelineContext();
+ const {
+ interval,
+ aboveTimeBlocks,
+ visibleTimeBlocks,
+ resolution: { unitAbove },
+ } = useTimelineContext();
+
+ const tz = useMemo(() => interval.start!.toISO()!.slice(-6), [interval]);
+
+ const dayInfo = useMemo(
+ () => dayDetail(unitAbove, aboveTimeBlocks, interval),
+ [unitAbove, aboveTimeBlocks, interval]
+ );
+
+ const aboveHourInfo = useMemo(() => timeBlockTz(aboveTimeBlocks, tz), [tz, aboveTimeBlocks]);
+ const visibileHourInfo = useMemo(() => timeBlockTz(visibleTimeBlocks, tz), [tz, visibleTimeBlocks]);
return (
{aboveTimeBlocks.map((column, index) => (
-
+
))}
{visibleTimeBlocks.map((column, index) => (
-
+
))}
);
diff --git a/src/timeline/TimelineContext.tsx b/src/timeline/TimelineContext.tsx
index d8eed55..c905b41 100644
--- a/src/timeline/TimelineContext.tsx
+++ b/src/timeline/TimelineContext.tsx
@@ -105,7 +105,7 @@ export const TimelineProvider = ({
columnWidth: externalColumnWidth,
debug = false,
displayTasksLabel = false,
- dragResolution: externalDragResolution,
+ dragResolution: externalDragResolution = "1min",
enableDrag = true,
enableResize = true,
headerLabel,
@@ -116,7 +116,7 @@ export const TimelineProvider = ({
onTaskChange,
tasks: externalTasks = [],
range: externalRange,
- resolution: externalResolution = "1min",
+ resolution: externalResolution = "1hrs",
resources: externalResources,
rowHeight: externalRowHeight,
timezone: externalTimezone,
@@ -206,7 +206,27 @@ export const TimelineProvider = ({
[interval, resolution]
);
- const aboveTimeBlocks = useMemo(() => interval.splitBy({ [resolution.unitAbove]: 1 }), [interval, resolution]);
+ const aboveTimeBlocks = useMemo(() => {
+ const { unitAbove } = resolution;
+ const blocks: Interval[] = [];
+ const intervalStart = interval.start!;
+ const intervalEnd = interval.end!;
+
+ let blockStart = intervalStart;
+
+ while (blockStart < intervalEnd) {
+ let blockEnd = blockStart.endOf(unitAbove);
+
+ if (blockEnd > intervalEnd) {
+ blockEnd = intervalEnd;
+ }
+
+ blocks.push(Interval.fromDateTimes(blockStart, blockEnd));
+ blockStart = blockEnd.startOf(unitAbove).plus({ [unitAbove]: 1 });
+ }
+
+ return blocks;
+ }, [interval, resolution]);
const columnWidth = useMemo(() => {
logDebug("TimelineProvider", "Calculating columnWidth...");
diff --git a/src/utils/time-resolution.ts b/src/utils/time-resolution.ts
index b092433..7331dd2 100644
--- a/src/utils/time-resolution.ts
+++ b/src/utils/time-resolution.ts
@@ -1,4 +1,4 @@
-import { Interval } from "luxon";
+import { DateTime, Interval } from "luxon";
import { DEFAULT_GRID_COLUMN_WIDTH } from "./dimensions";
@@ -148,16 +148,45 @@ export const displayAboveInterval = (interval: Interval, unit: Scale): string =>
case "hour":
return start.toFormat("dd/MM/yy HH:mm");
case "day":
- return start.toFormat("ccc dd MMM yyyy");
+ return start.toFormat("ccc dd yyyy");
case "week":
return `${start.toFormat("MMM yyyy")} CW ${start.toFormat("WW")}`;
case "month":
- return start.toFormat("yyyy");
+ return start.toFormat("MMM yyyy");
default:
return "N/A";
}
};
+export const getMonth = (interval: Interval): string => {
+ const { start } = interval;
+ if (!start) {
+ return "-";
+ }
+
+ return start.toFormat("M");
+};
+export const getYear = (interval: Interval): string => {
+ const { start } = interval;
+ if (!start) {
+ return "-";
+ }
+
+ return start.toFormat("yyyy");
+};
+
+export const getStartMonthsDay = (start: DateTime): string => {
+ if (!start) {
+ return "-";
+ }
+
+ return start.toFormat("d");
+};
+
+export const daysInMonth = (month: number, year: number) => {
+ return new Date(year, month, 0).getDate();
+};
+
/**
* Util to display an interval in a human readable format
* @param interval the interval to display
diff --git a/src/utils/time.ts b/src/utils/time.ts
index 2dc7a5a..ef1b521 100644
--- a/src/utils/time.ts
+++ b/src/utils/time.ts
@@ -71,7 +71,11 @@ export const getIntervalFromInternalTimeRange = (
timezone: string | undefined
): Interval => {
const tz = timezone || "system";
- const startDateTime = DateTime.fromMillis(start, { zone: tz }).startOf(resolution.unitAbove);
- const endDateTime = DateTime.fromMillis(end, { zone: tz }).endOf(resolution.unitAbove);
+ const startDateTime = DateTime.fromMillis(start, { zone: tz }).startOf(
+ resolution.unitAbove !== "month" ? resolution.unitAbove : resolution.unit
+ );
+ const endDateTime = DateTime.fromMillis(end, { zone: tz }).endOf(
+ resolution.unitAbove !== "month" ? resolution.unitAbove : resolution.unit
+ );
return Interval.fromDateTimes(startDateTime, endDateTime);
};
diff --git a/src/utils/timeBlockArray.ts b/src/utils/timeBlockArray.ts
new file mode 100644
index 0000000..7a19c4d
--- /dev/null
+++ b/src/utils/timeBlockArray.ts
@@ -0,0 +1,78 @@
+import { Interval } from "luxon";
+
+import { daysInMonth, getMonth, getStartMonthsDay, getYear, Scale } from "./time-resolution";
+
+interface VisibleHourInfoProps {
+ backHour?: boolean;
+ nextHour?: boolean;
+}
+
+interface DayDetailProps {
+ thisMonth?: number;
+ untilNow?: number;
+}
+
+export const timeBlockTz = (timeBlock: Interval[], initialTz?: string) => {
+ const dayInfoArray: VisibleHourInfoProps[] = [];
+
+ timeBlock.forEach((column) => {
+ const tzStart = column.start!.toISO()?.slice(-6);
+
+ if (initialTz !== tzStart) {
+ if (Number(initialTz?.slice(1, 3)) - Number(tzStart!.slice(1, 3)) > 0) {
+ dayInfoArray.push({
+ backHour: true,
+ nextHour: false,
+ });
+
+ return;
+ }
+
+ dayInfoArray.push({
+ backHour: false,
+ nextHour: true,
+ });
+ }
+
+ dayInfoArray.push({
+ backHour: false,
+ nextHour: false,
+ });
+
+ return;
+ });
+
+ return dayInfoArray;
+};
+
+export const dayDetail = (unitAbove: Scale, aboveTimeBlocks: Interval[], interval: Interval) => {
+ if (unitAbove === "month") {
+ const dayInfo: DayDetailProps[] = [];
+
+ aboveTimeBlocks.forEach((column, index) => {
+ const month = getMonth(column);
+ const year = getYear(column);
+ const currentMonthDays = daysInMonth(Number(month), Number(year));
+
+ if (index === 0) {
+ const startDay = getStartMonthsDay(interval.start!);
+ const daysToMonthEnd = currentMonthDays - Number(startDay) + 1;
+ dayInfo.push({
+ thisMonth: daysToMonthEnd,
+ untilNow: daysToMonthEnd,
+ });
+
+ return;
+ }
+
+ const n = dayInfo[index - 1].untilNow! + currentMonthDays;
+ dayInfo.push({
+ thisMonth: currentMonthDays,
+ untilNow: n,
+ });
+ });
+
+ return dayInfo;
+ }
+ return [];
+};