Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions src/components/Index/filters/FilterSortControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,34 @@ 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();

useEffect(() => {
const checkMobile = () => {
function checkMobile() {
setIsMobile(window.innerWidth < 768);
};
}

checkMobile();
window.addEventListener("resize", checkMobile);

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;
Expand Down Expand Up @@ -140,19 +140,15 @@ export const FilterSortControls = ({
<DropdownMenuContent className="bg-gray-800 border-purple-400/30">
<DropdownMenuItem
onClick={() => 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
</DropdownMenuItem>
{groups.map((group) => (
<DropdownMenuItem
key={group.id}
onClick={() => 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 && (
Expand Down Expand Up @@ -217,4 +213,4 @@ export const FilterSortControls = ({
)}
</div>
);
};
}
34 changes: 27 additions & 7 deletions src/components/schedule/ScheduleHorizontalTimelineView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, number>;
Expand All @@ -13,14 +15,23 @@ 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],
);
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) {
if (loading || setsLoading) {
return (
<div className="text-center text-purple-300 py-12">
<p>Loading horizontal timeline...</p>
Expand All @@ -39,7 +50,16 @@ export function ScheduleHorizontalTimelineView({
if (!timelineData) {
return (
<div className="text-center text-purple-300 py-12">
<p>No performances scheduled.</p>
<p>Festival dates not available yet.</p>
</div>
);
}

// Check if schedule is published
if (!edition?.published) {
return (
<div className="text-center text-purple-300 py-12">
<p>Schedule not yet published.</p>
</div>
);
}
Expand Down
12 changes: 8 additions & 4 deletions src/hooks/useScheduleData.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -146,4 +150,4 @@ export const useScheduleData = (use24Hour: boolean = false) => {
loading,
error,
};
};
}
49 changes: 31 additions & 18 deletions src/lib/timelineCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,24 @@ 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;
}
});
});
});
// 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
const timeSlots = [];
Expand Down Expand Up @@ -102,4 +99,20 @@ export const calculateTimelineData = (
festivalStart: earliestTime,
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;
}
Loading