diff --git a/src/pages/EditionView/tabs/ScheduleTab/lineup/DaysLineupView.tsx b/src/pages/EditionView/tabs/ScheduleTab/lineup/DaysLineupView.tsx
new file mode 100644
index 00000000..06f13293
--- /dev/null
+++ b/src/pages/EditionView/tabs/ScheduleTab/lineup/DaysLineupView.tsx
@@ -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 (
+
+
No scheduled sets found.
+
+ );
+ }
+
+ return (
+
+ {daysWithSets.map((day) => (
+
+
+ {day.displayDate}
+
+
+ {day.sets.map((set) => (
+
+ ))}
+
+
+ ))}
+
+ );
+}
diff --git a/src/pages/EditionView/tabs/ScheduleTab/lineup/LineupFilters.tsx b/src/pages/EditionView/tabs/ScheduleTab/lineup/LineupFilters.tsx
new file mode 100644
index 00000000..a59ff022
--- /dev/null
+++ b/src/pages/EditionView/tabs/ScheduleTab/lineup/LineupFilters.tsx
@@ -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 (
+
+
+
Filters
+
+
+
setIsExpanded(!isExpanded)}
+ hasActiveFilters={hasActiveFilters}
+ activeFilterCount={activeFilterCount}
+ label="Filters"
+ onClearFilters={hasActiveFilters ? clearFilters : undefined}
+ />
+
+
+ {isExpanded && (
+
+
+
+ {canShowStage && (
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/src/pages/EditionView/tabs/ScheduleTab/lineup/LineupSetItem.tsx b/src/pages/EditionView/tabs/ScheduleTab/lineup/LineupSetItem.tsx
new file mode 100644
index 00000000..c7d2501c
--- /dev/null
+++ b/src/pages/EditionView/tabs/ScheduleTab/lineup/LineupSetItem.tsx
@@ -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 (
+
+
+
+ {set.name}
+
+
+
+
+ );
+}
diff --git a/src/pages/EditionView/tabs/ScheduleTab/lineup/ScheduleLineupView.tsx b/src/pages/EditionView/tabs/ScheduleTab/lineup/ScheduleLineupView.tsx
new file mode 100644
index 00000000..b34a3715
--- /dev/null
+++ b/src/pages/EditionView/tabs/ScheduleTab/lineup/ScheduleLineupView.tsx
@@ -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 (
+
+ );
+ }
+
+ if (!canShowDay) {
+ return
;
+ }
+
+ return (
+
+
+ {canShowStage ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/pages/EditionView/tabs/ScheduleTab/lineup/StagesLineupGrid.tsx b/src/pages/EditionView/tabs/ScheduleTab/lineup/StagesLineupGrid.tsx
new file mode 100644
index 00000000..f2898ff7
--- /dev/null
+++ b/src/pages/EditionView/tabs/ScheduleTab/lineup/StagesLineupGrid.tsx
@@ -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 (
+
+
No scheduled sets found.
+
+ );
+ }
+
+ return (
+
+ {daysWithStages.map((day) => (
+
+
+ {day.displayDate}
+
+
+ {day.stages.map((stage) => (
+
+
+ {stage.name}
+
+
+ {stage.sets
+ .slice()
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map((set) => (
+
+ ))}
+
+
+ ))}
+
+
+ ))}
+
+ );
+}
diff --git a/src/pages/EditionView/tabs/ScheduleTab/list/ListSchedule.tsx b/src/pages/EditionView/tabs/ScheduleTab/list/ListSchedule.tsx
index 5667bee9..dc16b729 100644
--- a/src/pages/EditionView/tabs/ScheduleTab/list/ListSchedule.tsx
+++ b/src/pages/EditionView/tabs/ScheduleTab/list/ListSchedule.tsx
@@ -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;
@@ -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));
@@ -158,10 +155,6 @@ export function ListSchedule() {
);
}
- if (!canShowTime) {
- return
;
- }
-
if (!timeSlots.length) {
return (
diff --git a/src/pages/EditionView/tabs/ScheduleTab/list/ListTab.tsx b/src/pages/EditionView/tabs/ScheduleTab/list/ListTab.tsx
index fe3146ca..ecb92c06 100644
--- a/src/pages/EditionView/tabs/ScheduleTab/list/ListTab.tsx
+++ b/src/pages/EditionView/tabs/ScheduleTab/list/ListTab.tsx
@@ -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 (
<>
-
-
-
-
+ {canShowTime && }
+ {canShowTime ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
>
);
}