Files
arbeitarbeit/src/lib/data.ts
Pascal Linxweiler 401b199978 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>
2026-08-10 08:54:23 +02:00

134 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
UNDELIVERED_AFTER_MS,
type Order,
type OrderStatus,
type OrderType,
type PaymentMethod,
type Restaurant,
} from "./types";
const restaurants: Restaurant[] = [
{
name: "Muster Ristorante",
city: "Taben-Rodt",
hours: "10:00 bis 23:00",
phone1: "+49 6582 123456",
phone2: "+49 6582 123457",
mobile: "+49 171 2223344",
orderUrl: "https://order.speisekarte24.de/muster-ristorante",
},
{
name: "Pizzeria Da Vinci",
city: "Trier",
hours: "11:00 bis 22:30",
phone1: "+49 651 998877",
mobile: "+49 160 5544332",
orderUrl: "https://order.speisekarte24.de/da-vinci",
},
{
name: "Sushi Yama",
city: "Saarbrücken",
hours: "12:00 bis 23:00",
phone1: "+49 681 445566",
phone2: "+49 681 445567",
mobile: "+49 175 1112233",
orderUrl: "https://order.speisekarte24.de/sushi-yama",
},
{
name: "Burger Station",
city: "Konz",
hours: "11:30 bis 21:00",
phone1: "+49 6501 334455",
mobile: "+49 152 9988776",
orderUrl: "https://order.speisekarte24.de/burger-station",
},
{
name: "Athen Grill",
city: "Wittlich",
hours: "17:00 bis 23:30",
phone1: "+49 6571 220011",
orderUrl: "https://order.speisekarte24.de/athen-grill",
},
];
const customers = [
{ name: "Test Tester", email: "agentur@speisekarte24.de", phone: "+49 170 1234567" },
{ name: "Anna Berger", email: "anna.berger@example.com", phone: "+49 151 2345678" },
{ name: "Murat Yilmaz", email: "m.yilmaz@example.com", phone: "+49 176 3456789" },
{ name: "Sophie Klein", email: "sophie.klein@example.com", phone: "+49 152 4567890" },
{ name: "Lukas Weber", email: "lukas.weber@example.com", phone: "+49 160 5678901" },
{ name: "Nina Hoffmann", email: "nina.h@example.com", phone: "+49 171 6789012" },
{ name: "Jonas Krämer", email: "j.kraemer@example.com", phone: "+49 175 7890123" },
{ name: "Elif Demir", email: "elif.demir@example.com", phone: "+49 157 8901234" },
];
/** Status für Bestellungen, die bereits bearbeitet wurden. */
const handledStatuses: OrderStatus[] = [
"uebermittelt",
"uebermittelt",
"uebermittelt",
"telefonisch",
"telefonisch",
"rueckruf",
"storniert",
"fake",
];
/**
* Ältere Bestellungen mit diesem Index bleiben `neu` und kippen dadurch über die
* 15-Minuten-Regel auf "Nicht übermittelt" – bewusst nur eine Handvoll.
*/
const undeliveredIndexes = new Set([4, 11, 23]);
const types: OrderType[] = ["vorbestellung", "direktanruf"];
const payments: PaymentMethod[] = ["bar", "ec", "paypal", "kreditkarte"];
/** Deterministischer PRNG, damit Server- und Client-Render identisch sind. */
function seeded(seed: number) {
let s = seed;
return () => {
s = (s * 1664525 + 1013904223) % 4294967296;
return s / 4294967296;
};
}
/**
* Erzeugt Demo-Bestellungen relativ zu `now`, damit die jüngsten Aufträge innerhalb
* des 15-Minuten-Fensters liegen und ältere als nicht übermittelt gelten.
*/
export function createOrders(now: number, count = 42): Order[] {
const rand = seeded(20260804);
return Array.from({ length: count }, (_, i) => {
const ageMinutes = i * 6 + Math.floor(rand() * 4);
const receivedAt = new Date(now - ageMinutes * 60_000);
const dueAt = new Date(receivedAt.getTime() + (20 + Math.floor(rand() * 55)) * 60_000);
const fresh = ageMinutes * 60_000 < UNDELIVERED_AFTER_MS;
const handled = handledStatuses[Math.floor(rand() * handledStatuses.length)];
const open = fresh || undeliveredIndexes.has(i);
// Bearbeitete Bestellungen wurden innerhalb der Eskalationsfrist bestätigt.
const confirmedAt = open
? undefined
: new Date(receivedAt.getTime() + (1 + Math.floor(rand() * 12)) * 60_000).toISOString();
return {
id: `ord_${1000 + i}`,
number: count - i,
// Frische Bestellungen dürfen offen sein, ältere nur die ausgewählten drei.
status: open ? "neu" : handled,
type: types[Math.floor(rand() * types.length)],
restaurant: restaurants[Math.floor(rand() * restaurants.length)],
customer: customers[Math.floor(rand() * customers.length)],
receivedAt: receivedAt.toISOString(),
dueAt: dueAt.toISOString(),
total: Math.round((6 + rand() * 60) * 100) / 100,
itemCount: 1 + Math.floor(rand() * 7),
payment: payments[Math.floor(rand() * payments.length)],
pdfUrl: `/bestellungen/ord_${1000 + i}.pdf`,
externalUrl: `https://backoffice.speisekarte24.de/bestellungen/${1000 + i}`,
confirmedAt,
events: [],
} satisfies Order;
});
}