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
2 changes: 1 addition & 1 deletion src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const SheetContent = React.forwardRef<
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm text-purple-300 opacity-70 ring-offset-background transition-opacity hover:text-purple-100 hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
Expand Down
5 changes: 4 additions & 1 deletion src/pages/EditionView/tabs/ScheduleTab/DayFilterSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export function DayFilterSelect({
<label className="text-sm font-medium text-purple-200">Day</label>
</div>
<Select value={selectedDay} onValueChange={onDayChange}>
<SelectTrigger className="bg-white/10 border-purple-400/30 text-purple-100">
<SelectTrigger
data-testid="day-filter-trigger"
className="bg-white/10 border-purple-400/30 text-purple-100"
>
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-800 border-purple-400/30">
Expand Down
116 changes: 116 additions & 0 deletions src/pages/EditionView/tabs/ScheduleTab/ScheduleFilterSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useState } from "react";
import { Filter } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { DayFilterSelect } from "./DayFilterSelect";
import { TimeFilterSelect } from "./TimeFilterSelect";
import { StageFilterButtons } from "./StageFilterButtons";
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";

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

export function ScheduleFilterSheet({ tab }: ScheduleFilterSheetProps) {
const [open, setOpen] = useState(false);
const {
day,
time,
stages,
updateDay,
updateTime,
updateStages,
clearFilters,
} = useTimelineUrlState(tab);

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

return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
data-testid="schedule-filters-trigger"
className={
hasActiveFilters
? "flex items-center gap-2 bg-purple-600/50 text-purple-100 hover:bg-purple-600/60"
: "flex items-center gap-2 text-purple-300 hover:bg-purple-400/10 hover:text-purple-100"
}
>
<Filter className="h-4 w-4" />
<span className="hidden md:inline">Filters</span>
{hasActiveFilters && (
<Badge
variant="secondary"
data-testid="schedule-filters-badge"
className="bg-purple-800/50 text-purple-100"
>
{activeFilterCount}
</Badge>
)}
</Button>
</SheetTrigger>
<SheetContent
side="bottom"
data-testid="schedule-filter-sheet"
className="bg-gray-900 border-purple-400/30 max-h-[85vh] overflow-y-auto"
>
<SheetHeader>
<SheetTitle className="text-purple-100">Filter schedule</SheetTitle>
<SheetDescription className="text-purple-300">
Narrow the schedule by day, time of day, and stage.
</SheetDescription>
</SheetHeader>

<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-3">
<DayFilterSelect selectedDay={day} onDayChange={updateDay} />
<TimeFilterSelect selectedTime={time} onTimeChange={updateTime} />
<StageFilterButtons
selectedStages={stages}
onStageToggle={handleStageToggle}
/>
</div>

<SheetFooter className="mt-6">
{hasActiveFilters && (
<Button
type="button"
variant="ghost"
onClick={clearFilters}
data-testid="schedule-filters-clear"
className="text-red-400 hover:text-red-300 hover:bg-red-400/10"
>
Clear all
</Button>
)}
<SheetClose asChild>
<Button type="button" className="bg-purple-600 hover:bg-purple-700">
Done
</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);

function handleStageToggle(stageId: string) {
const newStages = stages.includes(stageId)
? stages.filter((id) => id !== stageId)
: [...stages, stageId];
updateStages(newStages);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function StageFilterButtons({
className={
selectedStages.includes(stage.id)
? "bg-purple-600 hover:bg-purple-700 text-xs"
: "border-purple-400 text-purple-400 hover:bg-purple-400 hover:text-white text-xs"
: "bg-white/5 border-purple-400/40 text-purple-300 hover:border-purple-300 hover:bg-purple-400/20 hover:text-purple-100 text-xs"
}
>
{stage.name}
Expand Down
5 changes: 4 additions & 1 deletion src/pages/EditionView/tabs/ScheduleTab/TimeFilterSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export function TimeFilterSelect({
<label className="text-sm font-medium text-purple-200">Time</label>
</div>
<Select value={selectedTime} onValueChange={onTimeChange}>
<SelectTrigger className="bg-white/10 border-purple-400/30 text-purple-100">
<SelectTrigger
data-testid="time-filter-trigger"
className="bg-white/10 border-purple-400/30 text-purple-100"
>
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-800 border-purple-400/30">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Map } from "lucide-react";
import { DayJumpButtons } from "./DayJumpButtons";
import { NowButton } from "./NowButton";
import { Button } from "@/components/ui/button";
import { ScheduleFilterSheet } from "../ScheduleFilterSheet";
import type { ScheduleDay } from "@/hooks/useScheduleData";
import { useScrollEdgeFade } from "./useScrollEdgeFade";

Expand All @@ -22,7 +23,9 @@ interface TimelineToolbarProps {
const SCROLL_FADE_PX = 24;

// Sticky nav toolbar above the Timeline strip. Navigation scrolls, it never
// filters; with a day filter active only that day's button shows.
// filters; with a day filter active only that day's button shows. Hosts the
// Filters trigger (bottom sheet) inline in the same row - it never renders
// on its own line.
export function TimelineToolbar({
days,
selectedDay,
Expand Down Expand Up @@ -89,6 +92,7 @@ export function TimelineToolbar({
{isOverviewExpanded ? "Hide overview" : "Show overview"}
</span>
</Button>
<ScheduleFilterSheet tab="timeline" />
</div>
</div>
);
Expand Down
63 changes: 0 additions & 63 deletions src/pages/EditionView/tabs/ScheduleTab/list/ListFilters.tsx

This file was deleted.

7 changes: 2 additions & 5 deletions src/pages/EditionView/tabs/ScheduleTab/list/ListSchedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,13 @@ export function ListSchedule() {
}

return (
<div className="space-y-6">
<div className="space-y-6" data-testid="list-schedule">

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need testid?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — tests/e2e/schedule-filter-sheet.spec.ts:46 uses it to scope assertions to the List view's schedule content.


Generated by Claude Code

{timeSlots.map((slot, index) => {
const prevSlot = index > 0 ? timeSlots[index - 1] : null;
const showDateHeader =
!prevSlot ||
getFestivalDayKey(slot.time.toISOString(), festival.timezone) !==
getFestivalDayKey(
prevSlot.time.toISOString(),
festival.timezone,
);
getFestivalDayKey(prevSlot.time.toISOString(), festival.timezone);

return (
<TimeSlotGroup
Expand Down
11 changes: 9 additions & 2 deletions src/pages/EditionView/tabs/ScheduleTab/list/ListTab.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { FestivalTimeBadge } from "../FestivalTimeBadge";
import { ListSchedule } from "./ListSchedule";
import { ListFilters } from "./ListFilters";
import { ScheduleFilterSheet } from "../ScheduleFilterSheet";
import { FilterContainer } from "@/components/filters/FilterContainer";

export function ScheduleTabList() {
return (
<>
<FestivalTimeBadge />

<ListFilters />
<FilterContainer>
<div className="flex items-center gap-2 flex-wrap">
<h3 className="text-purple-100 font-medium">Filters</h3>
<div className="ml-auto" />
<ScheduleFilterSheet tab="list" />
</div>
</FilterContainer>
<ListSchedule />
</>
);
Expand Down
Loading
Loading