diff --git a/packages/frontend/src/pages/Timeline.tsx b/packages/frontend/src/pages/Timeline.tsx index 55d540b..9e96c6a 100644 --- a/packages/frontend/src/pages/Timeline.tsx +++ b/packages/frontend/src/pages/Timeline.tsx @@ -21,7 +21,8 @@ import { import { cn } from "@/lib/utils"; const DAY_MS = 24 * 60 * 60 * 1000; -const TIMELINE_LABEL_WIDTH = "240px"; +const TIMELINE_LABEL_WIDTH = 240; +const WEEK_COLUMN_WIDTH = 140; const addDays = (value: Date, days: number) => new Date(value.getFullYear(), value.getMonth(), value.getDate() + days); @@ -56,7 +57,7 @@ type IssueGroup = { type TimelineRange = { start: Date; end: Date; - durationMs: number; + totalDays: number; }; export default function Timeline() { @@ -133,9 +134,9 @@ export default function Timeline() { const rangeStart = today; const rangeEnd = addDays(today, 60); - const durationMs = rangeEnd.getTime() - rangeStart.getTime() + DAY_MS; + const totalDays = Math.round((rangeEnd.getTime() - rangeStart.getTime()) / DAY_MS); - return { start: rangeStart, end: rangeEnd, durationMs }; + return { start: rangeStart, end: rangeEnd, totalDays }; }, [sprints]); const weeks = useMemo(() => { @@ -168,28 +169,32 @@ export default function Timeline() { const statuses = selectedOrganisation?.Organisation.statuses ?? {}; const gridTemplateColumns = useMemo(() => { - if (weeks.length === 0) return `${TIMELINE_LABEL_WIDTH} 1fr`; - return `${TIMELINE_LABEL_WIDTH} repeat(${weeks.length}, minmax(140px, 1fr))`; + if (weeks.length === 0) return `${TIMELINE_LABEL_WIDTH}px 1fr`; + return `${TIMELINE_LABEL_WIDTH}px repeat(${weeks.length}, ${WEEK_COLUMN_WIDTH}px)`; }, [weeks.length]); const todayMarker = useMemo(() => { if (!timelineRange) return null; const today = toDate(new Date()); if (today < timelineRange.start || today > timelineRange.end) return null; - const left = ((today.getTime() - timelineRange.start.getTime()) / timelineRange.durationMs) * 100; - return { left: `${left}%`, label: formatTodayLabel(today) }; + const dayOffset = (today.getTime() - timelineRange.start.getTime()) / DAY_MS; + const left = dayOffset * (WEEK_COLUMN_WIDTH / 7); + return { left: `${left}px`, label: formatTodayLabel(today) }; }, [timelineRange]); const getSprintBarStyle = (sprint: SprintRecord) => { if (!timelineRange) return null; const start = toDate(sprint.startDate); const end = addDays(toDate(sprint.endDate), 1); - const left = ((start.getTime() - timelineRange.start.getTime()) / timelineRange.durationMs) * 100; - const right = ((end.getTime() - timelineRange.start.getTime()) / timelineRange.durationMs) * 100; - const width = Math.max(right - left, 1); + const dayWidth = WEEK_COLUMN_WIDTH / 7; + const startOffset = (start.getTime() - timelineRange.start.getTime()) / DAY_MS; + const endOffset = (end.getTime() - timelineRange.start.getTime()) / DAY_MS; + const visibleStart = Math.max(0, startOffset); + const visibleEnd = Math.min(timelineRange.totalDays, endOffset); + const width = Math.max(visibleEnd - visibleStart, 0.25) * dayWidth; return { - left: `${left}%`, - width: `${width}%`, + left: `${visibleStart * dayWidth}px`, + width: `${width}px`, backgroundColor: sprint.color || DEFAULT_SPRINT_COLOUR, }; }; @@ -220,7 +225,11 @@ export default function Timeline() { {selectedOrganisationId && selectedProjectId && sprints.length > 0 && (
-
+
-
+
{weeks.map((week, index) => (
))}
@@ -313,7 +327,7 @@ export default function Timeline() {