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:
133
src/lib/data.ts
Normal file
133
src/lib/data.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user