Files
CapaKraken/apps/web/src/hooks/useTimelineLayout.tsx
T
Hartmut 82acc56b8d chore: add pre-commit hooks, tighten ESLint, activate Sentry DSN, publish CI coverage (Phase 1)
- Install husky v9 + lint-staged: pre-commit runs eslint --fix and prettier on staged files
- Tighten ESLint base config: no-console→error, ban-ts-comment (ts-ignore banned, ts-expect-error with description allowed), reportUnusedDisableDirectives→error
- Migrate web app from deprecated `next lint` to `eslint src/` with flat config and react-hooks plugin
- Convert all 5 @ts-ignore to @ts-expect-error with descriptions, remove stale disable comments
- Add NEXT_PUBLIC_SENTRY_DSN to docker-compose.prod.yml and .env.example
- Add coverage artifact upload step to CI test job
- Pre-existing violations (102 warnings) downgraded to warn in web config for Phase 2 cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 14:49:29 +02:00

93 lines
2.7 KiB
TypeScript

"use client";
import { clsx } from "clsx";
import { useMemo } from "react";
import { addDays, createDatePositionCache } from "~/components/timeline/utils.js";
import { formatMonthYear } from "~/lib/format.js";
export function useTimelineLayout(
viewStart: Date,
viewDays: number,
zoom: "day" | "week" | "month",
showWeekends: boolean,
today: Date,
) {
const CELL_WIDTH = zoom === "day" ? 40 : zoom === "week" ? 14 : 4;
// Visible dates, filtered by showWeekends
const dates = useMemo(() => {
const result: Date[] = [];
for (let i = 0; i < viewDays; i++) {
const d = addDays(viewStart, i);
if (!showWeekends && (d.getDay() === 0 || d.getDay() === 6)) continue;
result.push(d);
}
return result;
}, [viewStart, viewDays, showWeekends]);
const visibleDays = dates.length;
const totalCanvasWidth = visibleDays * CELL_WIDTH;
// O(1) position helpers via pre-computed cache
const { toLeft, toWidth } = useMemo(
() => createDatePositionCache(viewStart, viewDays, CELL_WIDTH, showWeekends),
[viewStart, viewDays, CELL_WIDTH, showWeekends],
);
// Grid lines — memoized; identical for every row
const gridLines = useMemo(
() =>
dates.map((date, i) => {
const isToday = date.toDateString() === today.toDateString();
const dow = date.getDay();
const isWeekend = dow === 0 || dow === 6;
return (
<div
key={i}
className={clsx(
"absolute top-0 bottom-0 border-r",
isToday
? "border-brand-300 dark:border-brand-700 border-r-2"
: isWeekend
? "border-brand-200 dark:border-brand-800 bg-brand-50/40 dark:bg-brand-950/20"
: "border-gray-100 dark:border-gray-800",
)}
style={{ left: i * CELL_WIDTH, width: CELL_WIDTH }}
/>
);
}),
[dates, CELL_WIDTH, today],
);
// Month groups for the month header
const monthGroups = useMemo(() => {
const groups: { label: string; colCount: number }[] = [];
for (const d of dates) {
const label = formatMonthYear(d);
const last = groups[groups.length - 1];
if (last && last.label === label) last.colCount++;
else groups.push({ label, colCount: 1 });
}
return groups;
}, [dates]);
// Convert clientX to a Date on the visible timeline
function xToDate(clientX: number, rowCanvasRect: DOMRect): Date {
const x = clientX - rowCanvasRect.left;
const colIndex = Math.max(0, Math.min(dates.length - 1, Math.floor(x / CELL_WIDTH)));
return dates[colIndex] ?? today;
}
return {
CELL_WIDTH,
dates,
visibleDays,
totalCanvasWidth,
toLeft,
toWidth,
gridLines,
monthGroups,
xToDate,
};
}