Add order management UI

Rebuild the landing page as an order overview for the Speisekarte24 tool:

- Two tabs: all orders and "Nicht übermittelt", the latter fed by the
  15-minute rule and highlighted while it holds entries
- Table, list and grid views behind an icon dropdown, plus search,
  filters, sorting and pagination
- Escalation timeline (three transmissions, tag, voice message, direct
  call, SOS) rendered in the detail dialog, inline in the list view and
  condensed to the current step in the table
- Action definitions live in one module and feed the row menu, the
  detail dialog and the card quick-action bar

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-10 08:54:23 +02:00
parent d2799852fd
commit 401b199978
40 changed files with 7238 additions and 179 deletions

View File

@@ -0,0 +1,180 @@
import {
Ban,
ExternalLink,
MessageCircle,
PhoneCall,
PhoneForwarded,
ShieldAlert,
Smartphone,
Voicemail,
XCircle,
type LucideIcon,
} from "lucide-react";
import { telHref, whatsAppHref } from "@/lib/format";
import type { Order, OrderAction, OrderStatus } from "@/lib/types";
export type { OrderAction };
/** Status, den eine Aktion auf der Bestellung setzt. Aktionen ohne Eintrag ändern nichts. */
export const ACTION_STATUS: Partial<Record<OrderAction, OrderStatus>> = {
uebermittelt: "uebermittelt",
nicht_uebermittelt: "nicht_uebermittelt",
rueckruf: "rueckruf",
storno: "storniert",
fake: "fake",
stornieren: "storniert",
sos: "storniert",
telefonisch_uebermittelt: "telefonisch",
};
/** Aktionen, nach denen der Gastronom als bestätigt gilt. */
export const CONFIRMING_ACTIONS: OrderAction[] = ["uebermittelt", "telefonisch_uebermittelt"];
export const ACTION_TOAST: Partial<Record<OrderAction, string>> = {
uebermittelt: "Als übermittelt markiert",
gastronom_voice: "Voice-Message an Gastronom gesendet",
nicht_uebermittelt: "Als nicht übermittelt markiert – Kunde und Gastronom informiert",
rueckruf: "Rückrufhinweis an Kunden gesendet",
storno: "Bestellung storniert – Kunde per E-Mail informiert",
stornieren: "Bestellung storniert",
fake: "Als Fake markiert",
gastronom_anruf: "Direktanruf beim Gastronom vermerkt",
sos: "SOS ausgelöst – Bestellung storniert, Gastronom für heute geschlossen",
telefonisch_uebermittelt: "Als telefonisch übermittelt markiert",
};
export type ActionDef = {
key: string;
label: string;
/** Kurzform für die Schnellleiste auf der Karte. */
short: string;
icon: LucideIcon;
/** Gesetzt bei Links (Telefon, WhatsApp, ORDER WebApp). */
href?: string;
external?: boolean;
/** Reiner Telefonanschluss – steckt bereits im Anruf-Dialog. */
phone?: boolean;
/** Gesetzt bei Aktionen, die den Bestellstatus ändern oder eine Nachricht auslösen. */
action?: OrderAction;
destructive?: boolean;
};
/** Kontaktwege zum Gastronomen – Buttons 1–6 der alten Oberfläche. */
export function gastronomActions({ restaurant }: Order): ActionDef[] {
const defs: ActionDef[] = [
{
key: "phone1",
label: `Telefon 1 · ${restaurant.phone1}`,
short: "Telefon 1",
phone: true,
icon: PhoneCall,
href: telHref(restaurant.phone1),
action: "gastronom_anruf",
},
];
if (restaurant.phone2) {
defs.push({
key: "phone2",
label: `Telefon 2 · ${restaurant.phone2}`,
short: "Telefon 2",
phone: true,
icon: PhoneCall,
href: telHref(restaurant.phone2),
});
}
if (restaurant.mobile) {
defs.push({
key: "mobile",
label: `Mobil · ${restaurant.mobile}`,
short: "Mobil",
phone: true,
icon: Smartphone,
href: telHref(restaurant.mobile),
});
}
defs.push({
key: "voice",
label: "Voice-Message senden",
short: "Voice",
icon: Voicemail,
action: "gastronom_voice",
});
if (restaurant.mobile) {
defs.push({
key: "whatsapp",
label: "WhatsApp-Message senden",
short: "WhatsApp",
icon: MessageCircle,
href: whatsAppHref(restaurant.mobile),
external: true,
});
}
defs.push({
key: "order",
label: "ORDER WebApp öffnen",
short: "ORDER",
icon: ExternalLink,
href: restaurant.orderUrl,
external: true,
});
return defs;
}
/** Bearbeitungsschritte zur Bestellung – Buttons 7–13 der alten Oberfläche. */
export function bearbeitungActions({ customer }: Order): ActionDef[] {
return [
{
key: "call_customer",
label: `Kunde anrufen · ${customer.phone}`,
short: "Kunde anrufen",
phone: true,
icon: PhoneCall,
href: telHref(customer.phone),
action: "kunde_anrufen",
},
{
key: "phone_transfer",
label: "Telefonisch übermittelt",
short: "Telefonisch",
icon: PhoneForwarded,
action: "telefonisch_uebermittelt",
},
{
key: "not_transferred",
label: "Nicht übermittelt",
short: "Nicht übermittelt",
icon: Ban,
action: "nicht_uebermittelt",
},
{
key: "callback",
label: "Rückrufhinweis senden",
short: "Rückruf",
icon: PhoneForwarded,
action: "rueckruf",
},
{ key: "fake", label: "Als Fake markieren", short: "Fake", icon: ShieldAlert, action: "fake" },
{
key: "storno",
label: "Storno (Kunde per E-Mail)",
short: "Storno",
icon: XCircle,
action: "storno",
destructive: true,
},
{
key: "cancel",
label: "Stornieren (ohne E-Mail)",
short: "Stornieren",
icon: Ban,
action: "stornieren",
destructive: true,
},
];
}