From 052f9dea07ca3e932d1c813e0a707baaa4743fe2 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 9 Aug 2025 18:58:45 +0100 Subject: [PATCH 1/4] feat: show timeline only for current edition --- .../Index/filters/FilterSortControls.tsx | 17 +++++++++++++++- .../ScheduleHorizontalTimelineView.tsx | 20 ++++++++++++++++--- src/hooks/useScheduleData.ts | 12 +++++++---- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/components/Index/filters/FilterSortControls.tsx b/src/components/Index/filters/FilterSortControls.tsx index c6787f99..f79308a5 100644 --- a/src/components/Index/filters/FilterSortControls.tsx +++ b/src/components/Index/filters/FilterSortControls.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import type { SortOption, FilterSortState } from "@/hooks/useUrlState"; import { useGenres } from "@/hooks/queries/useGenresQuery"; import { useGroups } from "@/hooks/useGroups"; +import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -37,6 +38,14 @@ export const FilterSortControls = ({ const [isMobile, setIsMobile] = useState(false); const { genres } = useGenres(); const { groups } = useGroups(); + const { edition } = useFestivalEdition(); + + // Redirect to list view if timeline is selected but schedule is not published + useEffect(() => { + if (state.mainView === "timeline" && !edition?.published) { + onStateChange({ mainView: "list" }); + } + }, [state.mainView, edition?.published, onStateChange]); useEffect(() => { const checkMobile = () => { @@ -89,10 +98,16 @@ export const FilterSortControls = ({ variant={state.mainView === "timeline" ? "default" : "ghost"} size="sm" onClick={() => onStateChange({ mainView: "timeline" })} + disabled={!edition?.published} className={ state.mainView === "timeline" ? "bg-purple-600 hover:bg-purple-700 text-white" - : "text-purple-200 hover:text-white hover:bg-white/10" + : edition?.published + ? "text-purple-200 hover:text-white hover:bg-white/10" + : "text-purple-400/50 cursor-not-allowed" + } + title={ + !edition?.published ? "Schedule not yet published" : undefined } > diff --git a/src/components/schedule/ScheduleHorizontalTimelineView.tsx b/src/components/schedule/ScheduleHorizontalTimelineView.tsx index 12b473b7..61cab4f8 100644 --- a/src/components/schedule/ScheduleHorizontalTimelineView.tsx +++ b/src/components/schedule/ScheduleHorizontalTimelineView.tsx @@ -3,6 +3,8 @@ import { useScheduleData } from "@/hooks/useScheduleData"; import { calculateTimelineData } from "@/lib/timelineCalculator"; import { StageLabels } from "./StageLabels"; import { TimelineContainer } from "./TimelineContainer"; +import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; +import { useEditionSetsQuery } from "@/hooks/queries/useEditionSetsQuery"; interface ScheduleHorizontalTimelineViewProps { userVotes: Record; @@ -13,14 +15,17 @@ export function ScheduleHorizontalTimelineView({ userVotes, onVote, }: ScheduleHorizontalTimelineViewProps) { - const { scheduleDays, loading, error } = useScheduleData(); + const { edition } = useFestivalEdition(); + const { data: editionSets = [], isLoading: setsLoading } = + useEditionSetsQuery(edition?.id); + const { scheduleDays, loading, error } = useScheduleData(editionSets); const timelineData = useMemo( () => calculateTimelineData(scheduleDays), [scheduleDays], ); - if (loading) { + if (loading || setsLoading) { return (

Loading horizontal timeline...

@@ -39,7 +44,16 @@ export function ScheduleHorizontalTimelineView({ if (!timelineData) { return (
-

No performances scheduled.

+

Schedule is not published yet.

+
+ ); + } + + // Check if schedule is published + if (!edition?.published) { + return ( +
+

Schedule not yet published.

); } diff --git a/src/hooks/useScheduleData.ts b/src/hooks/useScheduleData.ts index bff60c8f..2cac303f 100644 --- a/src/hooks/useScheduleData.ts +++ b/src/hooks/useScheduleData.ts @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { formatDateTime } from "@/lib/timeUtils"; import { format, startOfDay } from "date-fns"; -import { useOfflineSetsData } from "./useOfflineSetsData"; +import type { FestivalSet } from "@/services/queries"; export interface ScheduleDay { date: string; @@ -35,8 +35,12 @@ export interface ScheduleSet extends ScheduleArtist { artists: ScheduleArtist[]; } -export const useScheduleData = (use24Hour: boolean = false) => { - const { sets, loading, error } = useOfflineSetsData(); +export function useScheduleData( + sets?: FestivalSet[], + use24Hour: boolean = false, +) { + const loading = false; + const error = null; const scheduleDays = useMemo(() => { // Enhanced defensive checks @@ -146,4 +150,4 @@ export const useScheduleData = (use24Hour: boolean = false) => { loading, error, }; -}; +} From d4808ad190801c4df38b67e07db5282f9485d0b7 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 9 Aug 2025 18:59:19 +0100 Subject: [PATCH 2/4] fix(timeline): show only if end/start date exist --- .../ScheduleHorizontalTimelineView.tsx | 16 ++++++---- src/lib/timelineCalculator.ts | 29 +++++++------------ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/components/schedule/ScheduleHorizontalTimelineView.tsx b/src/components/schedule/ScheduleHorizontalTimelineView.tsx index 61cab4f8..8e622c53 100644 --- a/src/components/schedule/ScheduleHorizontalTimelineView.tsx +++ b/src/components/schedule/ScheduleHorizontalTimelineView.tsx @@ -20,10 +20,16 @@ export function ScheduleHorizontalTimelineView({ useEditionSetsQuery(edition?.id); const { scheduleDays, loading, error } = useScheduleData(editionSets); - const timelineData = useMemo( - () => calculateTimelineData(scheduleDays), - [scheduleDays], - ); + const timelineData = useMemo(() => { + if (!edition || !edition.start_date || !edition.end_date) { + return null; + } + return calculateTimelineData( + new Date(edition.start_date), + new Date(edition.end_date), + scheduleDays, + ); + }, [edition, scheduleDays]); if (loading || setsLoading) { return ( @@ -44,7 +50,7 @@ export function ScheduleHorizontalTimelineView({ if (!timelineData) { return (
-

Schedule is not published yet.

+

Festival dates not available yet.

); } diff --git a/src/lib/timelineCalculator.ts b/src/lib/timelineCalculator.ts index c6f24e2d..5961b8f8 100644 --- a/src/lib/timelineCalculator.ts +++ b/src/lib/timelineCalculator.ts @@ -19,27 +19,20 @@ export interface TimelineData { festivalEnd: Date; } -export const calculateTimelineData = ( +export function calculateTimelineData( + festivalStartDate: Date, + festivalEndDate: Date, scheduleDays: ScheduleDay[], -): TimelineData | null => { +): TimelineData | null { if (!scheduleDays || scheduleDays.length === 0) return null; - // Find the earliest and latest times across all days - let earliestTime = new Date("2099-01-01"); - let latestTime = new Date("1900-01-01"); + // Require festival dates to be provided + if (!festivalStartDate || !festivalEndDate) { + return null; + } - scheduleDays.forEach((day) => { - day.stages.forEach((stage) => { - stage.artists.forEach((artist) => { - if (artist.startTime && artist.startTime < earliestTime) { - earliestTime = artist.startTime; - } - if (artist.endTime && artist.endTime > latestTime) { - latestTime = artist.endTime; - } - }); - }); - }); + const earliestTime = new Date(festivalStartDate); + const latestTime = new Date(festivalEndDate); // Create unified time grid from festival start to end const timeSlots = []; @@ -102,4 +95,4 @@ export const calculateTimelineData = ( festivalStart: earliestTime, festivalEnd: latestTime, }; -}; +} From c079080c086441e3d736a78c7793d3553836b9a8 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 9 Aug 2025 19:00:39 +0100 Subject: [PATCH 3/4] fix(timeline): calc earliest time --- src/lib/timelineCalculator.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib/timelineCalculator.ts b/src/lib/timelineCalculator.ts index 5961b8f8..7354ea73 100644 --- a/src/lib/timelineCalculator.ts +++ b/src/lib/timelineCalculator.ts @@ -31,7 +31,11 @@ export function calculateTimelineData( return null; } - const earliestTime = new Date(festivalStartDate); + // Find the earliest set time from all scheduled sets + const earliestSetTime = calculateEarliestSetTime(scheduleDays); + + // Use the earliest set time if available, otherwise fall back to festival start date + const earliestTime = earliestSetTime || new Date(festivalStartDate); const latestTime = new Date(festivalEndDate); // Create unified time grid from festival start to end @@ -96,3 +100,19 @@ export function calculateTimelineData( festivalEnd: latestTime, }; } +function calculateEarliestSetTime(scheduleDays: ScheduleDay[]) { + let earliestSetTime: Date | null = null; + + scheduleDays.forEach((day) => { + day.stages.forEach((stage) => { + stage.artists.forEach((artist) => { + if (artist.startTime) { + if (!earliestSetTime || artist.startTime < earliestSetTime) { + earliestSetTime = artist.startTime; + } + } + }); + }); + }); + return earliestSetTime; +} From 855df7805cb175516a4268fc3db4af725b7cdddc Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 9 Aug 2025 19:04:40 +0100 Subject: [PATCH 4/4] fix(main): remove check for published edition --- .../Index/filters/FilterSortControls.tsx | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/components/Index/filters/FilterSortControls.tsx b/src/components/Index/filters/FilterSortControls.tsx index f79308a5..5f64581a 100644 --- a/src/components/Index/filters/FilterSortControls.tsx +++ b/src/components/Index/filters/FilterSortControls.tsx @@ -2,7 +2,6 @@ import { useState, useEffect } from "react"; import type { SortOption, FilterSortState } from "@/hooks/useUrlState"; import { useGenres } from "@/hooks/queries/useGenresQuery"; import { useGroups } from "@/hooks/useGroups"; -import { useFestivalEdition } from "@/contexts/FestivalEditionContext"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -29,28 +28,20 @@ interface FilterSortControlsProps { onClear: () => void; } -export const FilterSortControls = ({ +export function FilterSortControls({ state, onStateChange, onClear, -}: FilterSortControlsProps) => { +}: FilterSortControlsProps) { const [isFiltersExpanded, setIsFiltersExpanded] = useState(false); const [isMobile, setIsMobile] = useState(false); const { genres } = useGenres(); const { groups } = useGroups(); - const { edition } = useFestivalEdition(); - // Redirect to list view if timeline is selected but schedule is not published useEffect(() => { - if (state.mainView === "timeline" && !edition?.published) { - onStateChange({ mainView: "list" }); - } - }, [state.mainView, edition?.published, onStateChange]); - - useEffect(() => { - const checkMobile = () => { + function checkMobile() { setIsMobile(window.innerWidth < 768); - }; + } checkMobile(); window.addEventListener("resize", checkMobile); @@ -58,13 +49,13 @@ export const FilterSortControls = ({ return () => window.removeEventListener("resize", checkMobile); }, []); - const handleSortChange = (sort: SortOption) => { + function handleSortChange(sort: SortOption) { onStateChange({ sort, sortLocked: false }); - }; + } - const handleRefreshRankings = () => { + function handleRefreshRankings() { onStateChange({ sortLocked: false }); - }; + } const hasActiveFilters = state.stages.length > 0 || state.genres.length > 0 || state.minRating > 0; @@ -98,16 +89,10 @@ export const FilterSortControls = ({ variant={state.mainView === "timeline" ? "default" : "ghost"} size="sm" onClick={() => onStateChange({ mainView: "timeline" })} - disabled={!edition?.published} className={ state.mainView === "timeline" ? "bg-purple-600 hover:bg-purple-700 text-white" - : edition?.published - ? "text-purple-200 hover:text-white hover:bg-white/10" - : "text-purple-400/50 cursor-not-allowed" - } - title={ - !edition?.published ? "Schedule not yet published" : undefined + : "text-purple-200 hover:text-white hover:bg-white/10" } > @@ -155,9 +140,7 @@ export const FilterSortControls = ({ onStateChange({ groupId: undefined })} - className={`text-purple-100 hover:bg-purple-600/30 ${ - !state.groupId ? "bg-purple-600/20" : "" - }`} + className={`text-purple-100 hover:bg-purple-600/30 ${!state.groupId ? "bg-purple-600/20" : ""}`} > All Votes @@ -165,9 +148,7 @@ export const FilterSortControls = ({ onStateChange({ groupId: group.id })} - className={`text-purple-100 hover:bg-purple-600/30 ${ - state.groupId === group.id ? "bg-purple-600/20" : "" - }`} + className={`text-purple-100 hover:bg-purple-600/30 ${state.groupId === group.id ? "bg-purple-600/20" : ""}`} > {group.name} {group.member_count && ( @@ -232,4 +213,4 @@ export const FilterSortControls = ({ )}
); -}; +}