diff --git a/src/app/heimservice/page.tsx b/src/app/heimservice/page.tsx new file mode 100644 index 0000000..c8a3a91 --- /dev/null +++ b/src/app/heimservice/page.tsx @@ -0,0 +1,21 @@ +import { RestaurantPanel } from "@/components/heimservice/restaurant-panel"; +import { restaurants } from "@/lib/data"; + +export default function HeimservicePage() { + return ( +
+
+

Heimservice

+

+ Zeitfenster, Zugangsdaten und Rufnummern je Gastronom verwalten. +

+
+ +
+ {restaurants.map((restaurant) => ( + + ))} +
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5eece91..909261c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google"; import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; +import { TopNav } from "@/components/top-nav"; import "./globals.css"; @@ -28,7 +29,10 @@ export default function RootLayout({ children }: LayoutProps<"/">) { className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} > - {children} + + + {children} + {/* App läuft bewusst nur im Light Mode – Systemtheme nicht übernehmen. */} diff --git a/src/components/heimservice/restaurant-panel.tsx b/src/components/heimservice/restaurant-panel.tsx new file mode 100644 index 0000000..e2e27c6 --- /dev/null +++ b/src/components/heimservice/restaurant-panel.tsx @@ -0,0 +1,328 @@ +"use client"; + +import * as React from "react"; +import { + Check, + ExternalLink, + KeyRound, + Pencil, + Phone, + PhoneCall, + Power, + Smartphone, + Voicemail, + type LucideIcon, +} from "lucide-react"; +import { toast } from "sonner"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { telHref } from "@/lib/format"; +import { cn } from "@/lib/utils"; +import type { Restaurant } from "@/lib/types"; + +type Props = { restaurant: Restaurant }; + +/** Ein Kontroll-Panel je Gastronom – Nachbau der 10 Buttons der alten Heimservice-Oberfläche. */ +export function RestaurantPanel({ restaurant }: Props) { + const [windowOn, setWindowOn] = React.useState<[boolean, boolean]>([true, true]); + const [resetOpen, setResetOpen] = React.useState(false); + const [editOpen, setEditOpen] = React.useState(false); + const [callOpen, setCallOpen] = React.useState(false); + const [mobile1, setMobile1] = React.useState(restaurant.mobile ?? ""); + const [mobile2, setMobile2] = React.useState(restaurant.mobile2 ?? ""); + + const numbers = [ + { label: "Telefon 1", phone: restaurant.phone1, icon: PhoneCall }, + restaurant.phone2 && { label: "Telefon 2", phone: restaurant.phone2, icon: PhoneCall }, + mobile1 && { label: "Mobil 1", phone: mobile1, icon: Smartphone }, + mobile2 && { label: "Mobil 2", phone: mobile2, icon: Smartphone }, + ].filter((n): n is { label: string; phone: string; icon: LucideIcon } => Boolean(n)); + + const [window1, window2] = restaurant.heimserviceWindows; + + function toggleWindow(index: 0 | 1) { + setWindowOn((prev) => { + const next: [boolean, boolean] = [...prev]; + next[index] = !next[index]; + toast.success( + next[index] ? `Zeitfenster ${index + 1} aktiviert` : `Zeitfenster ${index + 1} deaktiviert`, + { description: restaurant.name }, + ); + return next; + }); + } + + function resetPassword() { + setResetOpen(false); + toast.success("Passwort zurückgesetzt", { + description: `Neue Zugangsdaten für ${restaurant.name} wurden versendet.`, + }); + } + + function saveMobiles() { + setEditOpen(false); + toast.success("Mobilrufnummern aktualisiert", { description: restaurant.name }); + } + + function sendVoiceTest() { + toast.success("Voice-Message an Telefon 1 gesendet", { description: restaurant.name }); + } + + return ( + + + {restaurant.name} +

{restaurant.city}

+
+ + +
+ + Zeitfenster 1: {window1} + + {window2 && ( + <> + · + + Zeitfenster 2: {window2} + + + )} +
+ +
+ toggleWindow(0)} + /> + toggleWindow(1)} + disabled={!window2} + /> + } + /> +
+ +
+ setResetOpen(true)} + /> + setEditOpen(true)} + /> +
+ +
+ setCallOpen(true)} + /> + +
+
+ + + + + Anrufen + {restaurant.name} + + +
+ {numbers.map(({ label, phone, icon: Icon }) => ( + + ))} +
+
+
+ + + + + Passwort zurücksetzen? + + Die ORDER-Zugangsdaten von {restaurant.name} werden neu vergeben und an den + Gastronomen versendet. + + + + Abbrechen + Zurücksetzen + + + + + + + + Mobilrufnummern bearbeiten + {restaurant.name} + + +
+
+ + setMobile1(e.target.value)} + placeholder="+49 …" + /> +
+
+ + setMobile2(e.target.value)} + placeholder="+49 …" + /> +
+
+ + + }>Abbrechen + + +
+
+
+ ); +} + +function Section({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+

{label}

+
{children}
+
+ ); +} + +function WindowToggle({ + index, + time, + on, + onToggle, + disabled, +}: { + index: 0 | 1; + time: string | undefined; + on: boolean; + onToggle: () => void; + disabled?: boolean; +}) { + return ( + + ); +} + +function Tile({ + icon: Icon, + label, + sublabel, + onClick, + nativeButton, + render, +}: { + icon: typeof Phone; + label: string; + sublabel: string; + onClick?: () => void; + nativeButton?: boolean; + render?: React.ReactElement; +}) { + return ( + + ); +} diff --git a/src/components/top-nav.tsx b/src/components/top-nav.tsx new file mode 100644 index 0000000..a8c3661 --- /dev/null +++ b/src/components/top-nav.tsx @@ -0,0 +1,55 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { LogOut } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +const NAV_ITEMS = [ + { href: "/", label: "Bestellübersicht" }, + { href: "/heimservice", label: "Heimservice" }, +]; + +export function TopNav() { + const pathname = usePathname(); + + return ( +
+
+ Speisekarte24 + + + + +
+
+ ); +} diff --git a/src/lib/data.ts b/src/lib/data.ts index 5d7a01b..d85485e 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -7,7 +7,7 @@ import { type Restaurant, } from "./types"; -const restaurants: Restaurant[] = [ +export const restaurants: Restaurant[] = [ { name: "Muster Ristorante", city: "Taben-Rodt", @@ -15,7 +15,9 @@ const restaurants: Restaurant[] = [ phone1: "+49 6582 123456", phone2: "+49 6582 123457", mobile: "+49 171 2223344", + mobile2: "+49 171 2223345", orderUrl: "https://order.speisekarte24.de/muster-ristorante", + heimserviceWindows: ["10:00 bis 14:30", "17:00 bis 23:00"], }, { name: "Pizzeria Da Vinci", @@ -24,6 +26,7 @@ const restaurants: Restaurant[] = [ phone1: "+49 651 998877", mobile: "+49 160 5544332", orderUrl: "https://order.speisekarte24.de/da-vinci", + heimserviceWindows: ["11:00 bis 14:30", "17:30 bis 22:30"], }, { name: "Sushi Yama", @@ -32,7 +35,9 @@ const restaurants: Restaurant[] = [ phone1: "+49 681 445566", phone2: "+49 681 445567", mobile: "+49 175 1112233", + mobile2: "+49 175 1112234", orderUrl: "https://order.speisekarte24.de/sushi-yama", + heimserviceWindows: ["12:00 bis 15:00", "18:00 bis 23:00"], }, { name: "Burger Station", @@ -41,6 +46,7 @@ const restaurants: Restaurant[] = [ phone1: "+49 6501 334455", mobile: "+49 152 9988776", orderUrl: "https://order.speisekarte24.de/burger-station", + heimserviceWindows: ["11:30 bis 15:00", "17:00 bis 21:00"], }, { name: "Athen Grill", @@ -48,6 +54,8 @@ const restaurants: Restaurant[] = [ hours: "17:00 bis 23:30", phone1: "+49 6571 220011", orderUrl: "https://order.speisekarte24.de/athen-grill", + // Nur Abendschicht, kein zweites Zeitfenster. + heimserviceWindows: ["17:00 bis 23:30", undefined], }, ]; diff --git a/src/lib/types.ts b/src/lib/types.ts index e5c6a7f..770ab10 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -36,7 +36,10 @@ export type Restaurant = { phone1: string; phone2?: string; mobile?: string; + mobile2?: string; orderUrl: string; + /** Zeitfenster für den Heimservice, z. B. Mittags- und Abendschicht. Zweites Fenster optional. */ + heimserviceWindows: [string, string | undefined]; }; export type Customer = {