scroll backwards in time

This commit is contained in:
Oliver Bryan
2026-01-21 18:32:51 +00:00
parent 37fe316ecd
commit 653f617d66

View File

@@ -4,7 +4,7 @@ import {
type IssueResponse, type IssueResponse,
type SprintRecord, type SprintRecord,
} from "@sprint/shared"; } from "@sprint/shared";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { IssueModal } from "@/components/issue-modal"; import { IssueModal } from "@/components/issue-modal";
import { useSelection } from "@/components/selection-provider"; import { useSelection } from "@/components/selection-provider";
import { SprintForm } from "@/components/sprint-form"; import { SprintForm } from "@/components/sprint-form";
@@ -130,14 +130,17 @@ export default function Timeline() {
const timelineRange = useMemo<TimelineRange | null>(() => { const timelineRange = useMemo<TimelineRange | null>(() => {
if (sprints.length === 0) return null; if (sprints.length === 0) return null;
const today = toDate(new Date()); const today = toDate(new Date());
let earliest = toDate(sprints[0].startDate);
let latest = toDate(sprints[0].endDate); let latest = toDate(sprints[0].endDate);
for (const sprint of sprints) { for (const sprint of sprints) {
const start = toDate(sprint.startDate);
const end = toDate(sprint.endDate); const end = toDate(sprint.endDate);
if (start < earliest) earliest = start;
if (end > latest) latest = end; if (end > latest) latest = end;
} }
const rangeStart = today; const rangeStart = startOfWeek(earliest < today ? earliest : today);
const rangeEnd = endOfWeek(addDays(latest, 28)); const rangeEnd = endOfWeek(addDays(latest, 28));
const totalDays = Math.round((rangeEnd.getTime() - rangeStart.getTime()) / DAY_MS); const totalDays = Math.round((rangeEnd.getTime() - rangeStart.getTime()) / DAY_MS);
@@ -183,10 +186,21 @@ export default function Timeline() {
const today = toDate(new Date()); const today = toDate(new Date());
if (today < timelineRange.start || today > timelineRange.end) return null; if (today < timelineRange.start || today > timelineRange.end) return null;
const dayOffset = (today.getTime() - timelineRange.start.getTime()) / DAY_MS; const dayOffset = (today.getTime() - timelineRange.start.getTime()) / DAY_MS;
const left = dayOffset * (WEEK_COLUMN_WIDTH / 7); const leftPx = dayOffset * (WEEK_COLUMN_WIDTH / 7);
return { left: `${left}px`, label: formatTodayLabel(today) }; return { leftPx, label: formatTodayLabel(today) };
}, [timelineRange]); }, [timelineRange]);
const scrollRef = useRef<HTMLDivElement | null>(null);
const hasAutoScrolled = useRef(false);
useEffect(() => {
if (!todayMarker || hasAutoScrolled.current) return;
const container = scrollRef.current;
if (!container) return;
container.scrollLeft = Math.max(0, todayMarker.leftPx);
hasAutoScrolled.current = true;
}, [todayMarker]);
const getSprintBarStyle = (sprint: SprintRecord) => { const getSprintBarStyle = (sprint: SprintRecord) => {
if (!timelineRange) return null; if (!timelineRange) return null;
const start = toDate(sprint.startDate); const start = toDate(sprint.startDate);
@@ -229,7 +243,7 @@ export default function Timeline() {
{selectedOrganisationId && selectedProjectId && sprints.length > 0 && ( {selectedOrganisationId && selectedProjectId && sprints.length > 0 && (
<div className="border"> <div className="border">
<div className="overflow-x-auto"> <div className="overflow-x-auto" ref={scrollRef}>
<div <div
style={{ style={{
minWidth: `${TIMELINE_LABEL_WIDTH + weeks.length * WEEK_COLUMN_WIDTH}px`, minWidth: `${TIMELINE_LABEL_WIDTH + weeks.length * WEEK_COLUMN_WIDTH}px`,
@@ -311,7 +325,7 @@ export default function Timeline() {
{todayMarker && ( {todayMarker && (
<div <div
className="absolute inset-y-0 z-10 pointer-events-none" className="absolute inset-y-0 z-10 pointer-events-none"
style={{ left: todayMarker.left }} style={{ left: `${todayMarker.leftPx}px` }}
> >
<div <div
className={cn("absolute inset-y-0 w-px bg-primary", showTodayLabel && "mt-1")} className={cn("absolute inset-y-0 w-px bg-primary", showTodayLabel && "mt-1")}