Compare commits

...

4 commits

8 changed files with 195 additions and 50 deletions

View file

@ -19,6 +19,7 @@
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"cheerio": "^1.1.0", "cheerio": "^1.1.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",

31
pnpm-lock.yaml generated
View file

@ -29,6 +29,9 @@ dependencies:
'@radix-ui/react-slot': '@radix-ui/react-slot':
specifier: ^1.2.3 specifier: ^1.2.3
version: 1.2.3(@types/react@19.1.8)(react@19.1.0) version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-switch':
specifier: ^1.2.5
version: 1.2.5(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.2.7 specifier: ^1.2.7
version: 1.2.7(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0) version: 1.2.7(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0)
@ -1532,6 +1535,32 @@ packages:
react: 19.1.0 react: 19.1.0
dev: false dev: false
/@radix-ui/react-switch@1.2.5(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0):
resolution: {integrity: sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0)
'@types/react': 19.1.8
'@types/react-dom': 19.1.6(@types/react@19.1.8)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
dev: false
/@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0): /@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0):
resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==} resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==}
peerDependencies: peerDependencies:
@ -1635,7 +1664,7 @@ packages:
/@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0): /@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0):
resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
peerDependencies: peerDependencies:
'@types/react': '*' '@types/react': 19.1.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta: peerDependenciesMeta:
'@types/react': '@types/react':

View file

@ -1,18 +1,38 @@
import { EventList } from "@/components/EventList"; import { EventList } from "@/components/EventList";
import { ShowElapsedSwitch } from "@/components/ShowElapsedSwitch";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { getEventsByIds } from "@/db"; import { getEventsByIds } from "@/db";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
export default async function FavoritesPage() { interface FavoritesPageProps {
searchParams: Promise<{ showElapsed?: string }>;
}
export default async function FavoritesPage({
searchParams,
}: FavoritesPageProps) {
const showElapsed = (await searchParams).showElapsed === "true";
const cookieStore = await cookies(); const cookieStore = await cookies();
const favoritesCookie = cookieStore.get("favorites")?.value || "[]"; const favoritesCookie = cookieStore.get("favorites")?.value || "[]";
const favoritesArray: number[] = JSON.parse(favoritesCookie); const favoritesArray: number[] = JSON.parse(favoritesCookie);
const favorites = await getEventsByIds(favoritesArray); const favorites = await getEventsByIds(favoritesArray);
const filteredFavorites = favorites.filter((event) => {
if (showElapsed) return true;
const now = new Date();
return event.startTime > now || event.endTime > now;
});
return ( return (
<div className="flex flex-col items-center justify-items-center p-8 pb-20 gap-4 sm:p-20 w-full"> <div className="flex flex-col items-center justify-items-center p-8 pb-20 gap-4 sm:p-20 w-full">
<h1 className="text-3xl font-bold mb-4 w-full">Oblíbené</h1> <div className="flex flex-col sm:flex-row justify-between items-center w-full gap-2 mb-4">
<h1 className="text-3xl font-bold">Oblíbené</h1>
<ShowElapsedSwitch defaultValue={false} />
</div>
<main className="flex flex-col gap-8 w-full"> <main className="flex flex-col gap-8 w-full">
<EventList events={favorites} showDate showLine /> <EventList events={filteredFavorites} showDate showLine />
</main> </main>
</div> </div>
); );

View file

@ -44,72 +44,72 @@
} }
:root { :root {
--radius: 0.625rem; --radius: 0.65rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.145 0 0); --foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0); --card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0); --card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0); --popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.205 0 0); --primary: oklch(0.705 0.213 47.604);
--primary-foreground: oklch(0.985 0 0); --primary-foreground: oklch(0.98 0.016 73.684);
--secondary: oklch(0.97 0 0); --secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.205 0 0); --secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.97 0 0); --muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.556 0 0); --muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.97 0 0); --accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0); --border: oklch(0.92 0.004 286.32);
--input: oklch(0.922 0 0); --input: oklch(0.92 0.004 286.32);
--ring: oklch(0.708 0 0); --ring: oklch(0.705 0.213 47.604);
--chart-1: oklch(0.646 0.222 41.116); --chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704); --chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429); --chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08); --chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0); --sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.205 0 0); --sidebar-primary: oklch(0.705 0.213 47.604);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.98 0.016 73.684);
--sidebar-accent: oklch(0.97 0 0); --sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.922 0 0); --sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.708 0 0); --sidebar-ring: oklch(0.705 0.213 47.604);
} }
.dark { .dark {
--background: oklch(0.145 0 0); --background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0); --card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0); --popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0); --primary: oklch(0.646 0.222 41.116);
--primary-foreground: oklch(0.205 0 0); --primary-foreground: oklch(0.98 0.016 73.684);
--secondary: oklch(0.269 0 0); --secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0); --muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.708 0 0); --muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.269 0 0); --accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216); --destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%); --border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%); --input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0); --ring: oklch(0.646 0.222 41.116);
--chart-1: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0); --sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0); --sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary: oklch(0.646 0.222 41.116);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.98 0.016 73.684);
--sidebar-accent: oklch(0.269 0 0); --sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0); --sidebar-ring: oklch(0.646 0.222 41.116);
} }
@layer base { @layer base {

View file

@ -42,7 +42,7 @@ export default async function RootLayout({
<SidebarTrigger className="-ml-1" /> <SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" /> <Separator orientation="vertical" className="mr-2 h-4" />
<div className="flex-grow"> <div className="flex-grow">
<h1 className="text-lg font-semibold">inFFo2</h1> <h1 className="text-lg font-semibold"><a href="/">inFFo2</a></h1>
</div> </div>
<a <a
href="https://www.festivalfantazie.cz/files/rozmisteni_ff25.png" href="https://www.festivalfantazie.cz/files/rozmisteni_ff25.png"

View file

@ -1,5 +1,5 @@
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { getAllLines } from "@/db"; import { getAllDays, getAllLines } from "@/db";
interface HomePageProps { interface HomePageProps {
searchParams: Promise<{ message?: string }>; searchParams: Promise<{ message?: string }>;
@ -7,6 +7,7 @@ interface HomePageProps {
export default async function Home({ searchParams }: HomePageProps) { export default async function Home({ searchParams }: HomePageProps) {
const lines = await getAllLines(); const lines = await getAllLines();
const days = await getAllDays();
const message = (await searchParams).message; const message = (await searchParams).message;
console.log("Message from searchParams:", message); console.log("Message from searchParams:", message);
@ -17,11 +18,37 @@ export default async function Home({ searchParams }: HomePageProps) {
{message && ( {message && (
<Alert variant="default"> <Alert variant="default">
<AlertTitle>Refetch all data - Response</AlertTitle> <AlertTitle>Refetch all data - Response</AlertTitle>
<AlertDescription> <AlertDescription>{message}</AlertDescription>
{message}
</AlertDescription>
</Alert> </Alert>
)} )}
<section className="w-full">
<ul className="list-disc pl-5">
<li className="mb-2">
<a href="/favorites" className="text-blue-600 hover:underline">
Oblíbené
</a>
</li>
</ul>
</section>
<section className="w-full">
<h1 className="text-3xl font-bold mb-4">Dny</h1>
<ul className="list-disc pl-5">
{days.map((day) => (
<li key={day} className="mb-2">
<a
href={`/day/${day}`}
className="text-blue-600 hover:underline"
>
{new Date(day).toLocaleDateString("cs-CZ", {
weekday: "long",
day: "numeric",
month: "numeric",
})}
</a>
</li>
))}
</ul>
</section>
<section className="w-full"> <section className="w-full">
<h1 className="text-3xl font-bold mb-4">Linie</h1> <h1 className="text-3xl font-bold mb-4">Linie</h1>
<ul className="list-disc pl-5"> <ul className="list-disc pl-5">

View file

@ -0,0 +1,37 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { Label } from "./ui/label";
import { Switch } from "./ui/switch";
import { useMemo } from "react";
export const ShowElapsedSwitch = ({
defaultValue = false,
}: {
defaultValue?: boolean;
}) => {
const searchParams = useSearchParams();
const router = useRouter();
const showElapsed = useMemo(() => {
if (searchParams.get("showElapsed") === "true") {
return true;
}
if (searchParams.get("showElapsed") === "false") {
return false;
}
return defaultValue;
}, [searchParams, defaultValue]);
function setShowElapsed(showElapsed: boolean) {
const params = new URLSearchParams(searchParams.toString());
params.set("showElapsed", showElapsed ? "true" : "false");
router.push(`?${params.toString()}`);
}
return (
<div className='flex items-center gap-2'>
<Switch name="showElapsed" onClick={() => setShowElapsed(!showElapsed)} />
<Label htmlFor="airplane-mode">Zobrazit proběhlé události</Label>
</div>
);
};

View file

@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as SwitchPrimitive from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitive.Root>
)
}
export { Switch }