@flowselections/floriday-afhandeling-module 1.0.41 → 1.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist-lib/components/afhandeling/ControleLijstPrint.js +54 -54
  2. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
  3. package/dist-lib/components/afhandeling/ControlePage.js +51 -9
  4. package/dist-lib/components/afhandeling/DagDetailDialog.js +1 -1
  5. package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
  6. package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
  7. package/dist-lib/components/pickstation/PakbonTemplate.js +70 -70
  8. package/dist-lib/hooks/usePickAgenda.d.ts +1 -1
  9. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1256 -824
  10. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  11. package/dist-lib/integrations/supabase/client.server.d.ts +1256 -824
  12. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  13. package/dist-lib/integrations/supabase/client.server.js +1 -1
  14. package/dist-lib/integrations/supabase/moduleClient.d.ts +1256 -824
  15. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/types.d.ts +1269 -853
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/lib/afhandelingSettings.functions.d.ts +2189 -1321
  19. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  20. package/dist-lib/lib/carrierSignupMail.server.js +26 -26
  21. package/dist-lib/lib/carriers.functions.d.ts +5315 -3155
  22. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/cartAssignments.functions.d.ts +5315 -3155
  24. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  25. package/dist-lib/lib/cartControl.functions.d.ts +10545 -6245
  26. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
  27. package/dist-lib/lib/floriday.functions.d.ts +1259 -825
  28. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  29. package/package.json +1 -1
@@ -70,62 +70,62 @@ export function renderControleLijstHtml(carts, lang = "nl") {
70
70
  });
71
71
  const rows = sorted.length
72
72
  ? sorted
73
- .map(({ cart: c, klaarzet }) => `
74
- <tr>
75
- <td class="mono bold">${esc(c.cartNumber ?? "—")}</td>
76
- <td class="right">${esc(c.cratesOnCart ?? "—")}</td>
77
- <td class="bold">${esc(countryCode(c.country) ?? c.country ?? "—")}</td>
78
- <td class="bold">${esc(klaarzet.label)}</td>
79
- <td>${esc(statusLabel(c.status, c.printedCount))}</td>
73
+ .map(({ cart: c, klaarzet }) => `
74
+ <tr>
75
+ <td class="mono bold">${esc(c.cartNumber ?? "—")}</td>
76
+ <td class="right">${esc(c.cratesOnCart ?? "—")}</td>
77
+ <td class="bold">${esc(countryCode(c.country) ?? c.country ?? "—")}</td>
78
+ <td class="bold">${esc(klaarzet.label)}</td>
79
+ <td>${esc(statusLabel(c.status, c.printedCount))}</td>
80
80
  </tr>`)
81
81
  .join("")
82
82
  : `<tr><td colspan="5" class="empty">${esc(t.noCartsToday)}</td></tr>`;
83
- return `<!DOCTYPE html>
84
- <html lang="${esc(lang)}">
85
- <head>
86
- <meta charset="utf-8" />
87
- <title>${esc(t.checklistTitle)}</title>
88
- <style>
89
- @page { size: A4 portrait; margin: 12mm; }
90
- body { font-family: Arial, Helvetica, sans-serif; color: #000; margin: 0; }
91
- h1 { font-size: 20px; margin: 0; }
92
- .head { display: flex; align-items: baseline; justify-content: space-between;
93
- border-bottom: 1px solid #000; padding-bottom: 6px; margin-bottom: 12px; }
94
- .total { font-size: 12px; margin-bottom: 10px; }
95
- table { width: 100%; border-collapse: collapse; font-size: 11px; }
96
- th { text-align: left; border-bottom: 1px solid #000; padding: 3px 6px 3px 0; }
97
- td { border-bottom: 1px solid #ccc; padding: 6px 6px 6px 0; white-space: nowrap; }
98
- td.right, th.right { text-align: right; }
99
- .mono { font-family: "Courier New", monospace; }
100
- .bold { font-weight: 700; }
101
- .empty { text-align: center; color: #666; padding: 24px 0; border: none; }
102
- .sign { margin-top: 24px; font-size: 11px; }
103
- .sign div { margin-top: 8px; }
104
- .line { display: inline-block; border-bottom: 1px solid #000; width: 220px; }
105
- tr { page-break-inside: avoid; }
106
- thead { display: table-header-group; }
107
- </style>
108
- </head>
109
- <body>
110
- <div class="head"><h1>${esc(t.checklistTitle)}</h1><div>${esc(today)}</div></div>
111
- <div class="total">${esc(t.totalCarts)}: <strong>${carts.length}</strong></div>
112
- <table>
113
- <thead>
114
- <tr>
115
- <th>${esc(t.colKarnr)}</th>
116
- <th class="right">${esc(t.colFusten)}</th>
117
- <th>${esc(t.colLand)}</th>
118
- <th>${esc(t.colKlaarzetten)}</th>
119
- <th>${esc(t.colStatus)}</th>
120
- </tr>
121
- </thead>
122
- <tbody>${rows}</tbody>
123
- </table>
124
- <div class="sign">
125
- <div>${esc(t.checkedBy)}: <span class="line"></span></div>
126
- <div>${esc(t.dateTime)}: <span class="line"></span></div>
127
- <div>${esc(t.signature)}: <span class="line"></span></div>
128
- </div>
129
- </body>
83
+ return `<!DOCTYPE html>
84
+ <html lang="${esc(lang)}">
85
+ <head>
86
+ <meta charset="utf-8" />
87
+ <title>${esc(t.checklistTitle)}</title>
88
+ <style>
89
+ @page { size: A4 portrait; margin: 12mm; }
90
+ body { font-family: Arial, Helvetica, sans-serif; color: #000; margin: 0; }
91
+ h1 { font-size: 20px; margin: 0; }
92
+ .head { display: flex; align-items: baseline; justify-content: space-between;
93
+ border-bottom: 1px solid #000; padding-bottom: 6px; margin-bottom: 12px; }
94
+ .total { font-size: 12px; margin-bottom: 10px; }
95
+ table { width: 100%; border-collapse: collapse; font-size: 11px; }
96
+ th { text-align: left; border-bottom: 1px solid #000; padding: 3px 6px 3px 0; }
97
+ td { border-bottom: 1px solid #ccc; padding: 6px 6px 6px 0; white-space: nowrap; }
98
+ td.right, th.right { text-align: right; }
99
+ .mono { font-family: "Courier New", monospace; }
100
+ .bold { font-weight: 700; }
101
+ .empty { text-align: center; color: #666; padding: 24px 0; border: none; }
102
+ .sign { margin-top: 24px; font-size: 11px; }
103
+ .sign div { margin-top: 8px; }
104
+ .line { display: inline-block; border-bottom: 1px solid #000; width: 220px; }
105
+ tr { page-break-inside: avoid; }
106
+ thead { display: table-header-group; }
107
+ </style>
108
+ </head>
109
+ <body>
110
+ <div class="head"><h1>${esc(t.checklistTitle)}</h1><div>${esc(today)}</div></div>
111
+ <div class="total">${esc(t.totalCarts)}: <strong>${carts.length}</strong></div>
112
+ <table>
113
+ <thead>
114
+ <tr>
115
+ <th>${esc(t.colKarnr)}</th>
116
+ <th class="right">${esc(t.colFusten)}</th>
117
+ <th>${esc(t.colLand)}</th>
118
+ <th>${esc(t.colKlaarzetten)}</th>
119
+ <th>${esc(t.colStatus)}</th>
120
+ </tr>
121
+ </thead>
122
+ <tbody>${rows}</tbody>
123
+ </table>
124
+ <div class="sign">
125
+ <div>${esc(t.checkedBy)}: <span class="line"></span></div>
126
+ <div>${esc(t.dateTime)}: <span class="line"></span></div>
127
+ <div>${esc(t.signature)}: <span class="line"></span></div>
128
+ </div>
129
+ </body>
130
130
  </html>`;
131
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AA6HA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAo1BpE"}
1
+ {"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AA+JA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAw1BpE"}
@@ -3,24 +3,64 @@ import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { supabase } from "../../integrations/supabase/moduleClient";
4
4
  import { Button, Card, Checkbox, Dialog, DialogDescription, DialogTitle, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Badge, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
5
5
  import { Printer, CheckCircle2, AlertTriangle, History, ChevronLeft, ChevronRight } from "lucide-react";
6
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
6
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog";
7
7
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
8
  import { useServerFn } from "@tanstack/react-start";
9
9
  import { getTodaysControlCarts, getControlRescheduleProposals, submitControlSession, listControlHistory, getControlSessionDetail, getControlDraft, saveControlDraft, } from "../../lib/cartControl.functions";
10
10
  import { renderControleLijstHtml } from "./ControleLijstPrint";
11
11
  import { printHtmlDocument, printFoutMelding } from "../../lib/printing";
12
+ function dayDiffFromToday(iso) {
13
+ const d = new Date(iso);
14
+ if (Number.isNaN(d.getTime()))
15
+ return null;
16
+ const today = new Date();
17
+ today.setHours(0, 0, 0, 0);
18
+ d.setHours(0, 0, 0, 0);
19
+ return Math.round((d.getTime() - today.getTime()) / 86400000);
20
+ }
21
+ /** "vandaag", "morgen", "over 3 dagen" of een korte datum. */
22
+ function relDayLabel(iso) {
23
+ const diff = dayDiffFromToday(iso);
24
+ const d = new Date(iso);
25
+ if (diff === null)
26
+ return iso;
27
+ if (diff === 0)
28
+ return "vandaag";
29
+ if (diff === 1)
30
+ return "morgen";
31
+ if (diff === -1)
32
+ return "gisteren";
33
+ if (diff > 1 && diff <= 7)
34
+ return `over ${diff} dagen`;
35
+ if (diff < -1)
36
+ return `${Math.abs(diff)} dagen geleden`;
37
+ return d.toLocaleDateString("nl-NL", { weekday: "short", day: "2-digit", month: "2-digit" });
38
+ }
39
+ function fmtTime(time) {
40
+ if (!time)
41
+ return null;
42
+ const m = /^(\d{1,2}):(\d{2})/.exec(time.trim());
43
+ if (!m)
44
+ return null;
45
+ return `${m[1].padStart(2, "0")}:${m[2]}`;
46
+ }
47
+ /** "vandaag - 10:00" */
48
+ function fmtPickup(iso, time) {
49
+ if (!iso)
50
+ return "—";
51
+ const d = new Date(iso);
52
+ if (Number.isNaN(d.getTime()))
53
+ return iso;
54
+ const t = fmtTime(time);
55
+ return t ? `${relDayLabel(iso)} - ${t}` : relDayLabel(iso);
56
+ }
12
57
  function fmtDate(iso) {
13
58
  if (!iso)
14
59
  return "—";
15
60
  const d = new Date(iso);
16
61
  if (Number.isNaN(d.getTime()))
17
62
  return iso;
18
- return d.toLocaleDateString("nl-NL", {
19
- weekday: "short",
20
- day: "2-digit",
21
- month: "2-digit",
22
- year: "numeric",
23
- });
63
+ return relDayLabel(iso);
24
64
  }
25
65
  function isTomorrowIso(iso) {
26
66
  if (!iso)
@@ -96,7 +136,9 @@ export function ControlePage({ onExit } = {}) {
96
136
  refetchOnMount: "always",
97
137
  refetchOnWindowFocus: true,
98
138
  });
99
- const carts = cartsQ.data?.carts ?? [];
139
+ // Karren die al zijn goedgekeurd (klaargezet/verzonden/geleverd) verdwijnen uit de controlelijst.
140
+ const APPROVED_CART_STATUSES = ["ready", "checked", "shipped", "delivered"];
141
+ const carts = (cartsQ.data?.carts ?? []).filter((c) => !APPROVED_CART_STATUSES.includes(String(c.status ?? "pending")));
100
142
  const currentDraftPayload = useMemo(() => {
101
143
  const nextStates = { ...cartStates };
102
144
  for (const cart of carts) {
@@ -471,7 +513,7 @@ export function ControlePage({ onExit } = {}) {
471
513
  ? "indeterminate"
472
514
  : false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Ophaaldatum" }), _jsx(TableHead, { children: "Uitgeprint" })] }) }), _jsxs(TableBody, { children: [cartsQ.isLoading && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Laden\u2026" }) })), !cartsQ.isLoading && carts.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Geen karren om vandaag te controleren." }) })), pagedCarts.map((c) => {
473
515
  const k = controlCartKey(c);
474
- return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsx(TableCell, { className: "font-mono font-semibold", children: (c.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _jsx(TableCell, { children: fmtDate(c.pickupDate) }), _jsx(TableCell, { children: (c.printedToday ?? (c.printedCount ?? 0) > 0) ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
516
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsx(TableCell, { className: "font-mono font-semibold", children: (c.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _jsx(TableCell, { children: fmtPickup(c.pickupDate, c.pickupTime) }), _jsx(TableCell, { children: (c.printedToday ?? (c.printedCount ?? 0) > 0) ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
475
517
  })] })] }), carts.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t bg-muted/20 px-4 py-3 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [carts.length, " resultaten"] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground", children: "Resultaten per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => {
476
518
  setPageSize(Number(v));
477
519
  setPage(1);
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { Badge, Checkbox, Button, Dialog, DialogTitle, DialogDescription, Input, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
6
6
  import { CalendarClock, ChevronDown, ChevronRight, Printer, Users } from "lucide-react";
7
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
7
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog";
8
8
  import { isSameDay } from "../../lib/pickDate";
9
9
  import { getOrderLines, } from "../../lib/cartAssignments.functions";
10
10
  import { printLayoutDocument, printFoutMelding } from "../../lib/printing";
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogDescription, AlertDialogTitle, toast, } from "@flowselections/core";
4
4
  import { Plus, Pencil, Trash2, Truck, ArrowUp, ArrowDown } from "lucide-react";
5
- import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "../../components/ui/responsive-dialog";
5
+ import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "../ui/responsive-dialog";
6
6
  import { Link } from "@tanstack/react-router";
7
7
  import { useServerFn } from "@tanstack/react-start";
8
8
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react";
3
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Dialog, DialogTitle, DialogDescription, Input, Label, Textarea, toast, } from "@flowselections/core";
6
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
6
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog";
7
7
  import { applyReschedule, listRescheduleHistory, previewReschedule, getPickMoments, setPickDay, } from "../../lib/logisticsEngine/reschedule";
8
8
  import { ENGINE_TASKS_KEY } from "../../hooks/useEngineTasks";
9
9
  import { listCarriers } from "../../lib/carriers.functions";
@@ -14,82 +14,82 @@ export function renderPakbonHtml(order, lang = "nl") {
14
14
  };
15
15
  const rows = order.lines.length
16
16
  ? order.lines
17
- .map((l) => `
18
- <tr>
19
- <td>${escapeHtml(l.description)}</td>
20
- <td style="text-align:right">${l.crates ?? ""}</td>
21
- <td>${escapeHtml(l.unit ?? "")}</td>
17
+ .map((l) => `
18
+ <tr>
19
+ <td>${escapeHtml(l.description)}</td>
20
+ <td style="text-align:right">${l.crates ?? ""}</td>
21
+ <td>${escapeHtml(l.unit ?? "")}</td>
22
22
  </tr>`)
23
23
  .join("")
24
24
  : `<tr><td colspan="3" style="text-align:center;color:#666">${escapeHtml(t.noLines)}</td></tr>`;
25
25
  const cartLabel = t.cartOf(order.cartIndex, order.cartTotal);
26
26
  const fustenLabel = order.cratesOnCart != null ? `${order.cratesOnCart}` : "—";
27
27
  const cartNumber = order.cartNumber ?? `${order.orderNumber}-${order.cartIndex}`;
28
- return `<!doctype html>
29
- <html lang="${escapeHtml(htmlLang)}">
30
- <head>
31
- <meta charset="utf-8">
32
- <title>${escapeHtml(t.packingSlip)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartNumber)}</title>
33
- <style>
34
- @page {
35
- size: A4;
36
- margin: 16mm;
37
- @top-center { content: element(cartno); }
38
- }
39
- body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; color:#111; }
40
- h1 { margin: 0 0 4px; font-size: 26px; }
41
- .sub { color:#444; margin-bottom: 8px; }
42
-
43
- .cart-number-hero {
44
- position: running(cartno);
45
- font-size: 88px;
46
- font-weight: 900;
47
- letter-spacing: 4px;
48
- line-height: 1;
49
- color: #000;
50
- background: #fff;
51
- border: 4px solid #000;
52
- padding: 12px 20px;
53
- display: inline-block;
54
- margin: 6px 0 20px;
55
- font-family: "Arial Black", "Helvetica Neue", Helvetica, Arial, sans-serif;
56
- }
57
-
58
- .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; margin: 8px 0 22px; }
59
- .grid div { font-size: 14px; }
60
- .label { color:#666; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
61
- .value { font-size: 16px; font-weight: 600; }
62
- table { width:100%; border-collapse: collapse; font-size: 14px; }
63
- th, td { padding: 8px 6px; border-bottom: 1px solid #ddd; vertical-align: top; }
64
- th { text-align: left; background:#f5f5f5; }
65
- .barcode-fallback { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 18px; letter-spacing: 4px; padding: 8px 12px; border: 2px solid #111; display: inline-block; margin-top: 18px; }
66
- .footer { margin-top: 28px; font-size: 11px; color:#666; }
67
- </style>
68
- </head>
69
- <body>
70
- <h1>${escapeHtml(t.packingSlip)}</h1>
71
- <div class="sub">${escapeHtml(t.order)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartLabel)}</div>
72
-
73
- <div class="cart-number-hero">${escapeHtml(cartNumber)}</div>
74
-
75
- <div class="grid">
76
- <div><div class="label">${escapeHtml(t.customer)}</div><div class="value">${escapeHtml(order.customerName)}</div></div>
77
- <div><div class="label">${escapeHtml(t.country)}</div><div class="value">${escapeHtml(order.country ?? "—")}</div></div>
78
- <div><div class="label">${escapeHtml(t.deliveryDate)}</div><div class="value">${fmt(order.deliveryDate)}</div></div>
79
- <div><div class="label">${escapeHtml(t.pickDate)}</div><div class="value">${fmt(order.pickDate)}</div></div>
80
- <div><div class="label">${escapeHtml(t.cart)}</div><div class="value">${escapeHtml(cartLabel)}</div></div>
81
- <div><div class="label">${escapeHtml(t.cratesOnThisCart)}</div><div class="value">${escapeHtml(fustenLabel)}</div></div>
82
- </div>
83
-
84
- <table>
85
- <thead><tr><th>${escapeHtml(t.description)}</th><th style="text-align:right;width:90px">${escapeHtml(t.crates)}</th><th style="width:90px">${escapeHtml(t.unit)}</th></tr></thead>
86
- <tbody>${rows}</tbody>
87
- </table>
88
-
89
- <div class="barcode-fallback">*${escapeHtml(order.orderNumber)}*</div>
90
-
91
- <div class="footer">${escapeHtml(t.printedVia)} · ${new Date().toLocaleString(locale)}</div>
92
- </body>
28
+ return `<!doctype html>
29
+ <html lang="${escapeHtml(htmlLang)}">
30
+ <head>
31
+ <meta charset="utf-8">
32
+ <title>${escapeHtml(t.packingSlip)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartNumber)}</title>
33
+ <style>
34
+ @page {
35
+ size: A4;
36
+ margin: 16mm;
37
+ @top-center { content: element(cartno); }
38
+ }
39
+ body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; color:#111; }
40
+ h1 { margin: 0 0 4px; font-size: 26px; }
41
+ .sub { color:#444; margin-bottom: 8px; }
42
+
43
+ .cart-number-hero {
44
+ position: running(cartno);
45
+ font-size: 88px;
46
+ font-weight: 900;
47
+ letter-spacing: 4px;
48
+ line-height: 1;
49
+ color: #000;
50
+ background: #fff;
51
+ border: 4px solid #000;
52
+ padding: 12px 20px;
53
+ display: inline-block;
54
+ margin: 6px 0 20px;
55
+ font-family: "Arial Black", "Helvetica Neue", Helvetica, Arial, sans-serif;
56
+ }
57
+
58
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; margin: 8px 0 22px; }
59
+ .grid div { font-size: 14px; }
60
+ .label { color:#666; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
61
+ .value { font-size: 16px; font-weight: 600; }
62
+ table { width:100%; border-collapse: collapse; font-size: 14px; }
63
+ th, td { padding: 8px 6px; border-bottom: 1px solid #ddd; vertical-align: top; }
64
+ th { text-align: left; background:#f5f5f5; }
65
+ .barcode-fallback { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 18px; letter-spacing: 4px; padding: 8px 12px; border: 2px solid #111; display: inline-block; margin-top: 18px; }
66
+ .footer { margin-top: 28px; font-size: 11px; color:#666; }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <h1>${escapeHtml(t.packingSlip)}</h1>
71
+ <div class="sub">${escapeHtml(t.order)} ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartLabel)}</div>
72
+
73
+ <div class="cart-number-hero">${escapeHtml(cartNumber)}</div>
74
+
75
+ <div class="grid">
76
+ <div><div class="label">${escapeHtml(t.customer)}</div><div class="value">${escapeHtml(order.customerName)}</div></div>
77
+ <div><div class="label">${escapeHtml(t.country)}</div><div class="value">${escapeHtml(order.country ?? "—")}</div></div>
78
+ <div><div class="label">${escapeHtml(t.deliveryDate)}</div><div class="value">${fmt(order.deliveryDate)}</div></div>
79
+ <div><div class="label">${escapeHtml(t.pickDate)}</div><div class="value">${fmt(order.pickDate)}</div></div>
80
+ <div><div class="label">${escapeHtml(t.cart)}</div><div class="value">${escapeHtml(cartLabel)}</div></div>
81
+ <div><div class="label">${escapeHtml(t.cratesOnThisCart)}</div><div class="value">${escapeHtml(fustenLabel)}</div></div>
82
+ </div>
83
+
84
+ <table>
85
+ <thead><tr><th>${escapeHtml(t.description)}</th><th style="text-align:right;width:90px">${escapeHtml(t.crates)}</th><th style="width:90px">${escapeHtml(t.unit)}</th></tr></thead>
86
+ <tbody>${rows}</tbody>
87
+ </table>
88
+
89
+ <div class="barcode-fallback">*${escapeHtml(order.orderNumber)}*</div>
90
+
91
+ <div class="footer">${escapeHtml(t.printedVia)} · ${new Date().toLocaleString(locale)}</div>
92
+ </body>
93
93
  </html>`;
94
94
  }
95
95
  function escapeHtml(s) {
@@ -24,7 +24,7 @@ export interface PickAgendaDay {
24
24
  * Dagen zonder rij tonen simpelweg nullen.
25
25
  */
26
26
  export declare function usePickAgenda(from: string | null, to: string | null): {
27
- days: NoInfer<PickAgendaDay[]>;
27
+ days: PickAgendaDay[];
28
28
  byDate: Map<string, PickAgendaDay>;
29
29
  isLoading: boolean;
30
30
  error: string | null;