Skip to content
Open
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
8 changes: 6 additions & 2 deletions src/pages/EditionView/tabs/ScheduleTab/TimelineTab.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { FestivalTimeBadge } from "./FestivalTimeBadge";
import { Timeline } from "./horizontal/Timeline";
import { ScheduleLineupView } from "./lineup/ScheduleLineupView";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";

export function ScheduleTabTimeline() {
const { canShowTime } = useScheduleReveal();

return (
<>
<FestivalTimeBadge />
<Timeline />
{canShowTime && <FestivalTimeBadge />}
{canShowTime ? <Timeline /> : <ScheduleLineupView tab="timeline" />}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ import { useSetsByEditionQuery as useEditionSetsQuery } from "@/api/sets/useSets
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
import { getFestivalHour } from "@/lib/timeUtils";
import { stagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";
import { ScheduleNotRevealedPlaceholder } from "../ScheduleNotRevealedPlaceholder";

export function Timeline() {
const { festival } = useFestivalEdition();
const { edition } = useRouteContext({
from: "/festivals/$festivalSlug/editions/$editionSlug/schedule/timeline",
});
const { canShowTime } = useScheduleReveal();
const { data: editionSets = [], isLoading: setsLoading } =
useEditionSetsQuery(edition.id);
const { data: stages } = useSuspenseQuery(stagesByEditionQuery(edition.id));
Expand Down Expand Up @@ -123,10 +120,6 @@ export function Timeline() {
);
}

if (!canShowTime) {
return <ScheduleNotRevealedPlaceholder />;
}

return (
<div className="space-y-8">
<div className="relative bg-white/5 rounded-lg p-4">
Expand Down
42 changes: 42 additions & 0 deletions src/pages/EditionView/tabs/ScheduleTab/lineup/DaysLineupView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { LineupSetItem } from "./LineupSetItem";
import type { ScheduleDay } from "@/hooks/useScheduleData";

interface DaysLineupViewProps {
scheduleDays: ScheduleDay[];
}

export function DaysLineupView({ scheduleDays }: DaysLineupViewProps) {
const daysWithSets = scheduleDays
.map((day) => ({
...day,
sets: day.stages
.flatMap((stage) => stage.sets)
.sort((a, b) => a.name.localeCompare(b.name)),
}))
.filter((day) => day.sets.length > 0);

if (!daysWithSets.length) {
return (
<div className="text-center text-purple-300 py-12">
<p>No scheduled sets found.</p>
</div>
);
}

return (
<div className="space-y-8">
{daysWithSets.map((day) => (
<div key={day.date}>
<h2 className="text-lg font-semibold text-purple-100 mb-4 px-4 py-2 bg-purple-900/40 rounded-lg backdrop-blur-sm sticky top-0 z-10">
{day.displayDate}
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{day.sets.map((set) => (
<LineupSetItem key={set.id} set={set} />
))}
</div>
</div>
))}
</div>
);
}
67 changes: 67 additions & 0 deletions src/pages/EditionView/tabs/ScheduleTab/lineup/LineupFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from "react";
import { DayFilterSelect } from "../DayFilterSelect";
import { StageFilterButtons } from "../StageFilterButtons";
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";
import { FilterToggle } from "@/components/filters/FilterToggle";
import { FilterContainer } from "@/components/filters/FilterContainer";
import { cn } from "@/lib/utils";

interface LineupFiltersProps {
tab: "timeline" | "list";
}

export function LineupFilters({ tab }: LineupFiltersProps) {
const [isExpanded, setIsExpanded] = useState(false);
const { canShowStage } = useScheduleReveal();
const { day, stages, updateDay, updateStages, clearFilters } =
useTimelineUrlState(tab);

function handleStageToggle(stageId: string) {
const newStages = stages.includes(stageId)
? stages.filter((id) => id !== stageId)
: [...stages, stageId];
updateStages(newStages);
}

const activeFilterCount =
(day !== "all" ? 1 : 0) + (canShowStage ? stages.length : 0);
const hasActiveFilters = activeFilterCount > 0;

return (
<FilterContainer>
<div className="flex items-center gap-2 flex-wrap">
<h3 className="text-purple-100 font-medium">Filters</h3>
<div className="ml-auto" />

<FilterToggle
isExpanded={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
hasActiveFilters={hasActiveFilters}
activeFilterCount={activeFilterCount}
label="Filters"
onClearFilters={hasActiveFilters ? clearFilters : undefined}
/>
</div>

{isExpanded && (
<div className="mt-4">
<div
className={cn(
"grid grid-cols-1 gap-3 md:gap-4",
canShowStage && "md:grid-cols-2",
)}
>
<DayFilterSelect selectedDay={day} onDayChange={updateDay} />
{canShowStage && (
<StageFilterButtons
selectedStages={stages}
onStageToggle={handleStageToggle}
/>
)}
</div>
</div>
)}
</FilterContainer>
);
}
29 changes: 29 additions & 0 deletions src/pages/EditionView/tabs/ScheduleTab/lineup/LineupSetItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Link, useParams } from "@tanstack/react-router";
import { Card, CardContent } from "@/components/ui/card";
import { VoteButtons } from "../VoteButtons";
import type { ScheduleSet } from "@/hooks/useScheduleData";

interface LineupSetItemProps {
set: ScheduleSet;
}

export function LineupSetItem({ set }: LineupSetItemProps) {
const { festivalSlug, editionSlug } = useParams({
from: "/festivals/$festivalSlug/editions/$editionSlug",
});

return (
<Card className="bg-white/10 backdrop-blur-md border-purple-400/30 hover:border-purple-400/50 transition-colors">
<CardContent className="p-4">
<Link
to="/festivals/$festivalSlug/editions/$editionSlug/sets/$setSlug"
params={{ festivalSlug, editionSlug, setSlug: set.slug ?? "" }}
className="text-white font-semibold hover:text-purple-300 transition-colors block text-lg mb-2"
>
{set.name}
</Link>
<VoteButtons set={set} />
</CardContent>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useMemo } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
import { useRouteContext } from "@tanstack/react-router";
import { useFestivalEdition } from "@/contexts/FestivalEditionContext";
import { useSetsByEditionQuery as useEditionSetsQuery } from "@/api/sets/useSetsByEdition";
import { stagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleData } from "@/hooks/useScheduleData";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
import { ScheduleNotRevealedPlaceholder } from "../ScheduleNotRevealedPlaceholder";
import { LineupFilters } from "./LineupFilters";
import { DaysLineupView } from "./DaysLineupView";
import { StagesLineupGrid } from "./StagesLineupGrid";

interface ScheduleLineupViewProps {
tab: "timeline" | "list";
}

export function ScheduleLineupView({ tab }: ScheduleLineupViewProps) {
const { festival } = useFestivalEdition();
const { canShowDay, canShowStage } = useScheduleReveal();
const route =
`/festivals/$festivalSlug/editions/$editionSlug/schedule/${tab}` as const;
const { edition } = useRouteContext({ from: route });
const { data: editionSets = [], isLoading: setsLoading } =
useEditionSetsQuery(edition.id);
const { data: stages } = useSuspenseQuery(stagesByEditionQuery(edition.id));
const { scheduleDays, loading } = useScheduleData({
sets: editionSets,
stages,
timezone: festival.timezone,
});
const { day: selectedDay, stages: selectedStages } =
useTimelineUrlState(tab);

const filteredScheduleDays = useMemo(() => {
return scheduleDays
.filter((day) => selectedDay === "all" || day.date === selectedDay)
.map((day) => ({
...day,
stages: day.stages.filter(
(stage) =>
!canShowStage ||
selectedStages.length === 0 ||
selectedStages.includes(stage.id),
),
}));
}, [scheduleDays, selectedDay, selectedStages, canShowStage]);

if (loading || setsLoading) {
return (
<div className="text-center text-purple-300 py-12">
<p>Loading schedule...</p>
</div>
);
}

if (!canShowDay) {
return <ScheduleNotRevealedPlaceholder />;
}

return (
<div className="space-y-6">
<LineupFilters tab={tab} />
{canShowStage ? (
<StagesLineupGrid scheduleDays={filteredScheduleDays} />
) : (
<DaysLineupView scheduleDays={filteredScheduleDays} />
)}
</div>
);
}
47 changes: 47 additions & 0 deletions src/pages/EditionView/tabs/ScheduleTab/lineup/StagesLineupGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { LineupSetItem } from "./LineupSetItem";
import type { ScheduleDay } from "@/hooks/useScheduleData";

interface StagesLineupGridProps {
scheduleDays: ScheduleDay[];
}

export function StagesLineupGrid({ scheduleDays }: StagesLineupGridProps) {
const daysWithStages = scheduleDays.filter((day) => day.stages.length > 0);

if (!daysWithStages.length) {
return (
<div className="text-center text-purple-300 py-12">
<p>No scheduled sets found.</p>
</div>
);
}

return (
<div className="space-y-8">
{daysWithStages.map((day) => (
<div key={day.date}>
<h2 className="text-lg font-semibold text-purple-100 mb-4 px-4 py-2 bg-purple-900/40 rounded-lg backdrop-blur-sm sticky top-0 z-10">
{day.displayDate}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{day.stages.map((stage) => (
<div key={stage.id} className="bg-white/5 rounded-lg p-3">
<h3 className="text-purple-200 font-medium mb-3">
{stage.name}
</h3>
<div className="space-y-2">
{stage.sets
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map((set) => (
<LineupSetItem key={set.id} set={set} />
))}
</div>
</div>
))}
</div>
</div>
))}
</div>
);
}
7 changes: 0 additions & 7 deletions src/pages/EditionView/tabs/ScheduleTab/list/ListSchedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { TimeSlotGroup } from "./TimeSlotGroup";
import type { ScheduleSet } from "@/hooks/useScheduleData";
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
import { stagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";
import { ScheduleNotRevealedPlaceholder } from "../ScheduleNotRevealedPlaceholder";

interface TimeSlot {
time: Date;
Expand All @@ -22,7 +20,6 @@ export function ListSchedule() {
const { edition } = useRouteContext({
from: "/festivals/$festivalSlug/editions/$editionSlug/schedule/list",
});
const { canShowTime } = useScheduleReveal();
const { data: editionSets = [], isLoading: setsLoading } =
useEditionSetsQuery(edition.id);
const { data: stages } = useSuspenseQuery(stagesByEditionQuery(edition.id));
Expand Down Expand Up @@ -158,10 +155,6 @@ export function ListSchedule() {
);
}

if (!canShowTime) {
return <ScheduleNotRevealedPlaceholder />;
}

if (!timeSlots.length) {
return (
<div className="text-center text-purple-300 py-12">
Expand Down
17 changes: 13 additions & 4 deletions src/pages/EditionView/tabs/ScheduleTab/list/ListTab.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { FestivalTimeBadge } from "../FestivalTimeBadge";
import { ListSchedule } from "./ListSchedule";
import { ListFilters } from "./ListFilters";
import { ScheduleLineupView } from "../lineup/ScheduleLineupView";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";

export function ScheduleTabList() {
const { canShowTime } = useScheduleReveal();

return (
<>
<FestivalTimeBadge />

<ListFilters />
<ListSchedule />
{canShowTime && <FestivalTimeBadge />}
{canShowTime ? (
<>
<ListFilters />
<ListSchedule />
</>
) : (
<ScheduleLineupView tab="list" />
)}
</>
);
}
Loading