Skip to content

Commit 633b283

Browse files
committed
feat: add jump to now functionality to schedule timeline
- Add jumpToTime URL parameter (format: YYYY-MM-DDTHH:mm) for sharing specific time views - Implement "Jump to Now" button that scrolls timeline to current time - Add smooth scrolling behavior to timeline based on URL parameter - Update quick navigation buttons (Morning, Afternoon, Evening) to use URL-based time jumps - Enable shareable links to specific schedule times
1 parent 1e628da commit 633b283

File tree

5 files changed

+82
-19
lines changed

5 files changed

+82
-19
lines changed

src/hooks/useTimelineUrlState.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ export interface TimelineState {
99
selectedDay: string; // Dynamic based on festival dates
1010
selectedTime: TimeFilter;
1111
selectedStages: string[];
12+
jumpToTime?: string; // ISO format: YYYY-MM-DDTHH:mm
1213
}
1314

1415
const defaultState: TimelineState = {
1516
timelineView: "list",
1617
selectedDay: "all",
1718
selectedTime: "all",
1819
selectedStages: [],
20+
jumpToTime: undefined,
1921
};
2022

2123
export function useTimelineUrlState() {
@@ -31,6 +33,7 @@ export function useTimelineUrlState() {
3133
selectedStages:
3234
searchParams.get("stages")?.split(",").filter(Boolean) ||
3335
defaultState.selectedStages,
36+
jumpToTime: searchParams.get("jumpTo") || undefined,
3437
};
3538
}, [searchParams]);
3639

@@ -54,6 +57,9 @@ export function useTimelineUrlState() {
5457
if (newState.selectedStages.length > 0) {
5558
newParams.set("stages", newState.selectedStages.join(","));
5659
}
60+
if (newState.jumpToTime) {
61+
newParams.set("jumpTo", newState.jumpToTime);
62+
}
5763

5864
setSearchParams(newParams, { replace: true });
5965
},

src/pages/EditionView/tabs/ScheduleTab/TimelineControls.tsx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TimelineNavigation } from "./TimelineNavigation";
33
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
44
import { FilterToggle } from "@/components/filters/FilterToggle";
55
import { FilterContainer } from "@/components/filters/FilterContainer";
6+
import { format } from "date-fns";
67

78
export function TimelineControls() {
89
const [isExpanded, setIsExpanded] = useState(false);
@@ -16,6 +17,34 @@ export function TimelineControls() {
1617
updateState({ selectedStages: newStages });
1718
}
1819

20+
function handleJumpToNow() {
21+
const now = new Date();
22+
const jumpToTime = format(now, "yyyy-MM-dd'T'HH:mm");
23+
updateState({ jumpToTime });
24+
}
25+
26+
function handleJumpToTime(timeOfDay: "morning" | "afternoon" | "evening") {
27+
const now = new Date();
28+
let targetHour = 12;
29+
30+
switch (timeOfDay) {
31+
case "morning":
32+
targetHour = 9;
33+
break;
34+
case "afternoon":
35+
targetHour = 15;
36+
break;
37+
case "evening":
38+
targetHour = 21;
39+
break;
40+
}
41+
42+
const targetTime = new Date(now);
43+
targetTime.setHours(targetHour, 0, 0, 0);
44+
const jumpToTime = format(targetTime, "yyyy-MM-dd'T'HH:mm");
45+
updateState({ jumpToTime });
46+
}
47+
1948
const activeFilterCount = selectedStages.length;
2049
const hasActiveFilters = activeFilterCount > 0;
2150

@@ -39,14 +68,8 @@ export function TimelineControls() {
3968
<TimelineNavigation
4069
selectedStages={selectedStages}
4170
onStageToggle={handleStageToggle}
42-
onJumpToToday={() => {
43-
// TODO: Implement jump to today functionality
44-
console.log("Jump to today");
45-
}}
46-
onJumpToTime={(timeOfDay) => {
47-
// TODO: Implement jump to time functionality
48-
console.log("Jump to", timeOfDay);
49-
}}
71+
onJumpToToday={handleJumpToNow}
72+
onJumpToTime={handleJumpToTime}
5073
/>
5174
</div>
5275
)}

src/pages/EditionView/tabs/ScheduleTab/TimelineNavigation.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button } from "@/components/ui/button";
2-
import { Clock, Sun, Sunset, Calendar } from "lucide-react";
2+
import { Clock, Sun, Sunset } from "lucide-react";
33
import { StageFilterButtons } from "./StageFilterButtons";
44

55
interface TimelineNavigationProps {
@@ -15,7 +15,7 @@ export function TimelineNavigation({
1515
onJumpToToday,
1616
onJumpToTime,
1717
}: TimelineNavigationProps) {
18-
function handleJumpToToday() {
18+
function handleJumpToNow() {
1919
onJumpToToday?.();
2020
}
2121

@@ -34,11 +34,11 @@ export function TimelineNavigation({
3434
<Button
3535
variant="outline"
3636
size="sm"
37-
onClick={handleJumpToToday}
37+
onClick={handleJumpToNow}
3838
className="border-purple-400 text-purple-400 hover:bg-purple-400 hover:text-white"
3939
>
40-
<Calendar className="h-3 w-3 mr-2" />
41-
Jump to Today
40+
<Clock className="h-3 w-3 mr-2" />
41+
Jump to Now
4242
</Button>
4343
<Button
4444
variant="outline"

src/pages/EditionView/tabs/ScheduleTab/horizontal/Timeline.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo } from "react";
1+
import { useMemo, useCallback } from "react";
22
import { useScheduleData } from "@/hooks/useScheduleData";
33
import { calculateTimelineData } from "@/lib/timelineCalculator";
44
import { StageLabels } from "./StageLabels";
@@ -19,8 +19,12 @@ export function Timeline() {
1919
editionSets,
2020
stagesQuery.data,
2121
);
22-
const { state: filters } = useTimelineUrlState();
23-
const { selectedDay, selectedTime, selectedStages } = filters;
22+
const { state: filters, updateState } = useTimelineUrlState();
23+
const { selectedDay, selectedTime, selectedStages, jumpToTime } = filters;
24+
25+
const handleScrollComplete = useCallback(() => {
26+
updateState({ jumpToTime: undefined });
27+
}, [updateState]);
2428

2529
const timelineData = useMemo(() => {
2630
if (!edition || !edition.start_date || !edition.end_date) {
@@ -122,7 +126,11 @@ export function Timeline() {
122126
<div className="space-y-8">
123127
<div className="relative bg-white/5 rounded-lg p-4">
124128
<StageLabels stages={timelineData.stages} />
125-
<TimelineContainer timelineData={timelineData} />
129+
<TimelineContainer
130+
timelineData={timelineData}
131+
jumpToTime={jumpToTime}
132+
onScrollComplete={handleScrollComplete}
133+
/>
126134
</div>
127135
</div>
128136
);

src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,41 @@
1-
import { useRef } from "react";
1+
import { useRef, useEffect } from "react";
22
import { TimeScale } from "./TimeScale";
33
import { StageRow } from "./StageRow";
44
import type { TimelineData } from "@/lib/timelineCalculator";
5+
import { differenceInMinutes } from "date-fns";
56

67
interface TimelineContainerProps {
78
timelineData: TimelineData;
9+
jumpToTime?: string;
10+
onScrollComplete?: () => void;
811
}
912

10-
export function TimelineContainer({ timelineData }: TimelineContainerProps) {
13+
export function TimelineContainer({
14+
timelineData,
15+
jumpToTime,
16+
onScrollComplete,
17+
}: TimelineContainerProps) {
1118
const scrollContainerRef = useRef<HTMLDivElement>(null);
1219

20+
useEffect(() => {
21+
if (!jumpToTime || !scrollContainerRef.current) return;
22+
23+
const targetTime = new Date(jumpToTime);
24+
const { festivalStart } = timelineData;
25+
26+
const minutesFromStart = differenceInMinutes(targetTime, festivalStart);
27+
const scrollPosition = minutesFromStart * 2;
28+
29+
scrollContainerRef.current.scrollTo({
30+
left: scrollPosition,
31+
behavior: "smooth",
32+
});
33+
34+
if (onScrollComplete) {
35+
setTimeout(onScrollComplete, 500);
36+
}
37+
}, [jumpToTime, timelineData, onScrollComplete]);
38+
1339
return (
1440
<div
1541
ref={scrollContainerRef}

0 commit comments

Comments
 (0)