@flowselections/layout-maker 1.8.2 → 1.13.1

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 (58) hide show
  1. package/README.md +8 -1
  2. package/dist-lib/components/ComputersLijst.d.ts +2 -1
  3. package/dist-lib/components/ComputersLijst.d.ts.map +1 -1
  4. package/dist-lib/components/ComputersLijst.js +2 -2
  5. package/dist-lib/components/DocumentRouteringPage.d.ts +2 -0
  6. package/dist-lib/components/DocumentRouteringPage.d.ts.map +1 -0
  7. package/dist-lib/components/DocumentRouteringPage.js +115 -0
  8. package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
  9. package/dist-lib/components/LayoutsPage.js +13 -8
  10. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  11. package/dist-lib/components/PrintQueuePage.js +16 -1
  12. package/dist-lib/components/PrintStatusBanner.d.ts +8 -0
  13. package/dist-lib/components/PrintStatusBanner.d.ts.map +1 -0
  14. package/dist-lib/components/PrintStatusBanner.js +15 -0
  15. package/dist-lib/components/PrintersPage.d.ts.map +1 -1
  16. package/dist-lib/components/PrintersPage.js +7 -23
  17. package/dist-lib/components/WerkplekConfiguratiePage.d.ts.map +1 -1
  18. package/dist-lib/components/WerkplekConfiguratiePage.js +35 -24
  19. package/dist-lib/components/WerkplekkenTabel.d.ts +2 -2
  20. package/dist-lib/components/WerkplekkenTabel.d.ts.map +1 -1
  21. package/dist-lib/components/WerkplekkenTabel.js +4 -3
  22. package/dist-lib/index.d.ts +8 -3
  23. package/dist-lib/index.d.ts.map +1 -1
  24. package/dist-lib/index.js +9 -5
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts +491 -145
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  27. package/dist-lib/integrations/supabase/client.server.d.ts +491 -145
  28. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  29. package/dist-lib/integrations/supabase/types.d.ts +505 -150
  30. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  31. package/dist-lib/lib/aiLayout.functions.d.ts +491 -145
  32. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/documentBron.d.ts +35 -0
  34. package/dist-lib/lib/documentBron.d.ts.map +1 -0
  35. package/dist-lib/lib/documentBron.js +62 -0
  36. package/dist-lib/lib/documentRoutering.d.ts +23 -0
  37. package/dist-lib/lib/documentRoutering.d.ts.map +1 -0
  38. package/dist-lib/lib/documentRoutering.js +58 -0
  39. package/dist-lib/lib/documentTypes.d.ts +54 -0
  40. package/dist-lib/lib/documentTypes.d.ts.map +1 -1
  41. package/dist-lib/lib/documentTypes.js +56 -0
  42. package/dist-lib/lib/formaatPrinters.d.ts +5 -0
  43. package/dist-lib/lib/formaatPrinters.d.ts.map +1 -1
  44. package/dist-lib/lib/formaatPrinters.js +12 -0
  45. package/dist-lib/lib/printApi.d.ts +20 -5
  46. package/dist-lib/lib/printApi.d.ts.map +1 -1
  47. package/dist-lib/lib/printApi.js +93 -32
  48. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  49. package/dist-lib/lib/printQueue.server.js +72 -1
  50. package/dist-lib/lib/printService.functions.d.ts +1964 -580
  51. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  52. package/dist-lib/lib/printerTelling.d.ts +24 -0
  53. package/dist-lib/lib/printerTelling.d.ts.map +1 -0
  54. package/dist-lib/lib/printerTelling.js +41 -0
  55. package/dist-lib/lib/useDocumentData.d.ts +4 -0
  56. package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
  57. package/dist-lib/lib/useDocumentData.js +106 -1
  58. package/package.json +7 -2
package/README.md CHANGED
@@ -85,7 +85,7 @@ import {
85
85
  const { jobId } = await printDocument({
86
86
  documenttype: "pakbon", // moet een bestaande documentsoort zijn
87
87
  documentnaam: "PB-2601284",
88
- payload: { order }, // gegevens voor de layout
88
+ payload: { order, order_id: order.id }, // order_id: verplicht kenmerk
89
89
  });
90
90
 
91
91
  // 2. Kant-en-klare PDF (Floriday-document, klantsticker, externe factuur)
@@ -95,8 +95,15 @@ await printPdf({
95
95
  documenttype: "factuur",
96
96
  documentnaam: "F-2026-00123",
97
97
  pdfBase64, // met of zonder data:-voorvoegsel
98
+ payload: { order_id: factuur.id },
98
99
  });
99
100
 
101
+ // Geef in payload altijd een order-kenmerk mee: queue_item_id, order_id of
102
+ // ordernummer. De wachtrij gebruikt dat om dubbele aanvragen te herkennen;
103
+ // zonder kenmerk kunnen twee orders met dezelfde documentnaam binnen tien
104
+ // minuten als dubbel worden gezien.
105
+
106
+
100
107
  // Of expliciet per werkplek:
101
108
  const printerId = await printerVoorWerkplek("PICKSTATION-1", "A4");
102
109
  await printPdf({
@@ -2,7 +2,8 @@ export interface ComputerModel {
2
2
  id: string;
3
3
  naam: string;
4
4
  online: boolean;
5
- aantalPrinters: number;
5
+ /** Printers die het printprogramma op deze computer ziet. */
6
+ aantalGevonden: number;
6
7
  }
7
8
  export declare function useComputers(): {
8
9
  computers: ComputerModel[];
@@ -1 +1 @@
1
- {"version":3,"file":"ComputersLijst.d.ts","sourceRoot":"","sources":["../../src/components/ComputersLijst.tsx"],"names":[],"mappings":"AAoBA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,YAAY;;;;;EA6C3B;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,YAAoB,GACrB,EAAE;IACD,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,OAAO,CAAC;IACvB,qBAAqB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,+BAuHA"}
1
+ {"version":3,"file":"ComputersLijst.d.ts","sourceRoot":"","sources":["../../src/components/ComputersLijst.tsx"],"names":[],"mappings":"AAoBA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,YAAY;;;;;EA6C3B;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,YAAoB,GACrB,EAAE;IACD,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,OAAO,CAAC;IACvB,qBAAqB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,+BAuHA"}
@@ -29,7 +29,7 @@ export function useComputers() {
29
29
  id: String(comp.id),
30
30
  naam: comp.naam,
31
31
  online: !!comp.online,
32
- aantalPrinters: printers.filter((pr) => pr.computer === comp.naam).length,
32
+ aantalGevonden: printers.filter((pr) => pr.computer === comp.naam).length,
33
33
  })));
34
34
  }
35
35
  catch (e) {
@@ -68,7 +68,7 @@ export function ComputersLijst({ computers, loading, fout, onVernieuw, onPrinter
68
68
  if (gevonden)
69
69
  setNieuw(gevonden);
70
70
  }, [computers, toevoegenOpen, wachtOp, nieuw]);
71
- return (_jsxs("div", { className: alleenDialog ? "" : "space-y-3", children: [!alleenDialog && loading && _jsx("p", { className: "text-muted-foreground text-sm", children: "Laden\u2026" }), !alleenDialog && !loading && fout && (_jsx(Card, { children: _jsxs(CardContent, { className: "space-y-3 py-6", children: [_jsx("p", { className: "font-medium", children: "Geen verbinding" }), _jsx("p", { className: "text-muted-foreground text-sm", children: fout }), _jsxs(Button, { variant: "outline", onClick: onVernieuw, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), " Opnieuw proberen"] })] }) })), !alleenDialog && !loading && !fout && computers.length === 0 && (_jsx(Card, { children: _jsxs(CardContent, { className: "space-y-4 py-8", children: [_jsx(PrintProgrammaUitleg, {}), _jsxs(Button, { onClick: () => onToevoegenOpenChange(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Computer toevoegen"] })] }) })), !alleenDialog && computers.map((c) => (_jsxs("div", { className: "bg-card flex items-center gap-4 rounded-xl border p-4", children: [_jsx("div", { className: "bg-muted rounded-lg p-2.5", children: _jsx(Monitor, { className: "text-muted-foreground h-5 w-5" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate font-medium", children: c.naam }), _jsxs("p", { className: "text-muted-foreground text-sm", children: [c.aantalPrinters, " ", c.aantalPrinters === 1 ? "printer" : "printers"] })] }), _jsx("span", { className: `rounded-full px-2.5 py-1 text-xs font-medium ${c.online ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"}`, children: c.online ? "Aan" : "Uit" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onPrintersToevoegen(c.naam), children: "Printers toevoegen" })] }, c.id))), !alleenDialog && computers.length > 0 && _jsx(WerktHetNiet, {}), _jsx(Dialog, { open: toevoegenOpen, onOpenChange: onToevoegenOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Computer toevoegen" }), _jsx(DialogDescription, { children: "Zet het printprogramma klaar op de computer waar de printers aan hangen." })] }), _jsx(PrintProgrammaUitleg, { compact: true }), _jsx("div", { className: "rounded-lg border p-4 text-sm", children: nieuw ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("p", { className: "font-medium", children: ["Computer gevonden: ", nieuw.naam] }), _jsx(Button, { onClick: () => {
71
+ return (_jsxs("div", { className: alleenDialog ? "" : "space-y-3", children: [!alleenDialog && loading && _jsx("p", { className: "text-muted-foreground text-sm", children: "Laden\u2026" }), !alleenDialog && !loading && fout && (_jsx(Card, { children: _jsxs(CardContent, { className: "space-y-3 py-6", children: [_jsx("p", { className: "font-medium", children: "Geen verbinding" }), _jsx("p", { className: "text-muted-foreground text-sm", children: fout }), _jsxs(Button, { variant: "outline", onClick: onVernieuw, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), " Opnieuw proberen"] })] }) })), !alleenDialog && !loading && !fout && computers.length === 0 && (_jsx(Card, { children: _jsxs(CardContent, { className: "space-y-4 py-8", children: [_jsx(PrintProgrammaUitleg, {}), _jsxs(Button, { onClick: () => onToevoegenOpenChange(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Computer toevoegen"] })] }) })), !alleenDialog && computers.map((c) => (_jsxs("div", { className: "bg-card flex items-center gap-4 rounded-xl border p-4", children: [_jsx("div", { className: "bg-muted rounded-lg p-2.5", children: _jsx(Monitor, { className: "text-muted-foreground h-5 w-5" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate font-medium", children: c.naam }), _jsxs("p", { className: "text-muted-foreground text-sm", children: [c.aantalGevonden, " ", c.aantalGevonden === 1 ? "printer gevonden" : "printers gevonden"] })] }), _jsx("span", { className: `rounded-full px-2.5 py-1 text-xs font-medium ${c.online ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"}`, children: c.online ? "Aan" : "Uit" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onPrintersToevoegen(c.naam), children: "Printers toevoegen" })] }, c.id))), !alleenDialog && computers.length > 0 && _jsx(WerktHetNiet, {}), _jsx(Dialog, { open: toevoegenOpen, onOpenChange: onToevoegenOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Computer toevoegen" }), _jsx(DialogDescription, { children: "Zet het printprogramma klaar op de computer waar de printers aan hangen." })] }), _jsx(PrintProgrammaUitleg, { compact: true }), _jsx("div", { className: "rounded-lg border p-4 text-sm", children: nieuw ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("p", { className: "font-medium", children: ["Computer gevonden: ", nieuw.naam] }), _jsx(Button, { onClick: () => {
72
72
  onToevoegenOpenChange(false);
73
73
  onPrintersToevoegen(nieuw.naam);
74
74
  }, children: "Printers van deze computer toevoegen" })] })) : (_jsxs("p", { className: "text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " We wachten tot de computer zich meldt\u2026"] })) }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { variant: "outline", onClick: () => onToevoegenOpenChange(false), children: "Sluiten" }) }), _jsx(WerktHetNiet, {})] }) })] }));
@@ -0,0 +1,2 @@
1
+ export declare function DocumentRouteringPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=DocumentRouteringPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentRouteringPage.d.ts","sourceRoot":"","sources":["../../src/components/DocumentRouteringPage.tsx"],"names":[],"mappings":"AA2CA,wBAAgB,qBAAqB,gCAiTpC"}
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/DocumentRouteringPage.tsx — overzicht: welke documentsoort
4
+ // gaat via welke werkplek naar welke printer.
5
+ // ============================================================================
6
+ import { useMemo, useState } from "react";
7
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
8
+ import { Link } from "@tanstack/react-router";
9
+ import { AlertTriangle, Monitor, Plus, Printer, Trash2 } from "lucide-react";
10
+ import { DOCUMENT_TYPES, documentTypeLabel, isFloridayLayout } from "../lib/documentTypes.js";
11
+ import { documentProfiel } from "../lib/documentProfiles.js";
12
+ import { usePrinters } from "../lib/useDocumentData.js";
13
+ import { useAlleFormaatPrinters, zetFormaatPrinter } from "../lib/formaatPrinters.js";
14
+ import { BRONNEN, HERKOMSTEN, useBronregels, zetBronregel, } from "../lib/documentBron.js";
15
+ import { useLayouts } from "../lib/useDocumentData.js";
16
+ import { useWerkplekPrinters, verwijderWerkplekPrinter, zetWerkplekPrinter, } from "../lib/documentRoutering.js";
17
+ import { useComputers } from "./ComputersLijst.js";
18
+ const GEEN = "geen";
19
+ export function DocumentRouteringPage() {
20
+ const { printers } = usePrinters();
21
+ const { computers } = useComputers();
22
+ const { koppelingen: standaard, reload: herlaadStandaard } = useAlleFormaatPrinters();
23
+ const { koppelingen: werkplekken, reload: herlaadWerkplekken } = useWerkplekPrinters();
24
+ const { regels, reload: herlaadRegels } = useBronregels();
25
+ const { layouts } = useLayouts();
26
+ const [zoek, setZoek] = useState("");
27
+ const [nieuw, setNieuw] = useState(null);
28
+ const [nieuweWerkplek, setNieuweWerkplek] = useState("");
29
+ const [nieuwePrinter, setNieuwePrinter] = useState(GEEN);
30
+ const printerNaam = (id) => printers.find((p) => p.id === id)?.naam ?? "Onbekende printer";
31
+ const standaardFormaat = (type) => documentProfiel(type).formaten[0]?.code ?? "A4";
32
+ /** Formaat zoals de gebruiker het ziet: dat van de actieve layouts zelf. */
33
+ const weergaveFormaat = (type) => {
34
+ const eigen = Array.from(new Set(layouts
35
+ .filter((l) => l.documenttype === type && l.actief)
36
+ .map((l) => l.papierformaat)
37
+ .filter((f) => !!f)));
38
+ if (eigen.length === 1)
39
+ return eigen[0];
40
+ if (eigen.length > 1)
41
+ return `${eigen.length} formaten`;
42
+ return standaardFormaat(type);
43
+ };
44
+ const standaardVoor = (type) => standaard.find((k) => k.documenttype === type && k.formaat_code === standaardFormaat(type))
45
+ ?.printer_id ?? null;
46
+ const werkplekkenVoor = (type) => werkplekken.filter((w) => w.documenttype === type);
47
+ const rijen = useMemo(() => {
48
+ const term = zoek.trim().toLowerCase();
49
+ return DOCUMENT_TYPES.filter((d) => (term ? d.label.toLowerCase().includes(term) : true));
50
+ }, [zoek]);
51
+ const kiesStandaard = async (type, printerId) => {
52
+ try {
53
+ await zetFormaatPrinter(type, standaardFormaat(type), printerId === GEEN ? null : printerId);
54
+ await herlaadStandaard();
55
+ toast.success("Standaardprinter bijgewerkt");
56
+ }
57
+ catch {
58
+ toast.error("Opslaan mislukt");
59
+ }
60
+ };
61
+ const voegWerkplekToe = async (type) => {
62
+ if (!nieuweWerkplek.trim()) {
63
+ toast.error("Kies eerst een werkplek");
64
+ return;
65
+ }
66
+ try {
67
+ await zetWerkplekPrinter(type, nieuweWerkplek, nieuwePrinter === GEEN ? null : nieuwePrinter, standaardFormaat(type));
68
+ await herlaadWerkplekken();
69
+ setNieuw(null);
70
+ setNieuweWerkplek("");
71
+ setNieuwePrinter(GEEN);
72
+ toast.success("Werkplek gekoppeld");
73
+ }
74
+ catch {
75
+ toast.error("Opslaan mislukt");
76
+ }
77
+ };
78
+ const bronVan = (type, herkomst) => regels.find((r) => r.documenttype === type && r.herkomst === herkomst);
79
+ const kiesBron = async (type, herkomst, bron, layoutId) => {
80
+ try {
81
+ await zetBronregel(type, herkomst, bron, layoutId ?? null);
82
+ await herlaadRegels();
83
+ toast.success("Documentbron bijgewerkt");
84
+ }
85
+ catch {
86
+ toast.error("Opslaan mislukt");
87
+ }
88
+ };
89
+ const verwijder = async (id) => {
90
+ try {
91
+ await verwijderWerkplekPrinter(id);
92
+ await herlaadWerkplekken();
93
+ toast.success("Uitzondering verwijderd");
94
+ }
95
+ catch {
96
+ toast.error("Verwijderen mislukt");
97
+ }
98
+ };
99
+ return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Documentroutering" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Bepaal per documentsoort welke printer wordt gebruikt, en welke printer geldt op een specifieke werkplek." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { className: "w-56", placeholder: "Zoek documentsoort", value: zoek, onChange: (e) => setZoek(e.target.value) }), _jsx(Button, { variant: "outline", asChild: true, children: _jsxs(Link, { to: "/layouts-printers/printers", children: [_jsx(Printer, { className: "mr-2 h-4 w-4" }), " Printers"] }) })] })] }), _jsx(Card, { children: _jsxs(CardHeader, { className: "pb-3", children: [_jsx(CardTitle, { className: "text-base", children: "Keuzevolgorde" }), _jsx(CardDescription, { children: "1. printer die de module meestuurt \u00B7 2. uitzondering voor de werkplek \u00B7 3. printer gekoppeld aan het ontwerp \u00B7 4. standaardprinter hieronder \u00B7 5. is er maar \u00E9\u00E9n actieve printer, dan die \u00B7 6. anders blijft de opdracht wachten in de printwachtrij." })] }) }), _jsx("div", { className: "space-y-3", children: rijen.map((d) => {
100
+ const uitzonderingen = werkplekkenVoor(d.value);
101
+ const std = standaardVoor(d.value);
102
+ const zonderPrinter = !std && uitzonderingen.length === 0;
103
+ return (_jsxs(Card, { children: [_jsxs(CardContent, { className: "grid gap-4 py-4 lg:grid-cols-[220px_260px_1fr]", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-medium", children: documentTypeLabel(d.value) }), _jsx("p", { className: "text-muted-foreground text-xs", children: weergaveFormaat(d.value) }), zonderPrinter && (_jsxs("p", { className: "text-destructive mt-1 flex items-center gap-1 text-xs", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5" }), " Geen printer ingesteld"] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-xs uppercase", children: "Standaardprinter" }), _jsxs(Select, { value: std ?? GEEN, onValueChange: (v) => void kiesStandaard(d.value, v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Geen printer" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: GEEN, children: "Geen printer" }), printers.map((p) => (_jsx(SelectItem, { value: p.id, children: p.naam }, p.id)))] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-muted-foreground text-xs uppercase", children: "Per werkplek" }), uitzonderingen.map((w) => (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Monitor, { className: "text-muted-foreground h-4 w-4 shrink-0" }), _jsxs("span", { className: "min-w-0 flex-1 truncate", children: [w.werkplek, " \u2192 ", printerNaam(w.printer_id)] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void verwijder(w.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, w.id))), nieuw === d.value ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Select, { value: nieuweWerkplek, onValueChange: setNieuweWerkplek, children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, { placeholder: "Kies werkplek" }) }), _jsx(SelectContent, { children: computers.map((c) => (_jsx(SelectItem, { value: c.naam, children: c.naam }, c.id))) })] }), _jsxs(Select, { value: nieuwePrinter, onValueChange: setNieuwePrinter, children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, { placeholder: "Kies printer" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: GEEN, children: "Geen printer" }), printers.map((p) => (_jsx(SelectItem, { value: p.id, children: p.naam }, p.id)))] })] }), _jsx(Button, { size: "sm", onClick: () => void voegWerkplekToe(d.value), children: "Opslaan" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setNieuw(null), children: "Annuleren" })] })) : (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
104
+ setNieuw(d.value);
105
+ setNieuweWerkplek("");
106
+ setNieuwePrinter(GEEN);
107
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Werkplek toevoegen"] }))] })] }), _jsxs(CardContent, { className: "border-t pt-4", children: [_jsx("p", { className: "text-muted-foreground mb-2 text-xs uppercase", children: "Bron per herkomst" }), _jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: HERKOMSTEN.map((h) => {
108
+ const regel = bronVan(d.value, h.value);
109
+ const huidig = regel?.bron ?? "auto";
110
+ return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "w-32 shrink-0 text-sm", children: h.label }), _jsxs(Select, { value: huidig, onValueChange: (v) => void kiesBron(d.value, h.value, v, regel?.layout_id ?? null), children: [_jsx(SelectTrigger, { className: "w-52", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: BRONNEN.map((b) => (_jsx(SelectItem, { value: b.value, children: b.label }, b.value))) })] }), huidig === "eigen_layout" && (_jsxs(Select, { value: regel?.layout_id ?? GEEN, onValueChange: (v) => void kiesBron(d.value, h.value, "eigen_layout", v === GEEN ? null : v), children: [_jsx(SelectTrigger, { className: "w-52", children: _jsx(SelectValue, { placeholder: "Standaardontwerp" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: GEEN, children: "Standaardontwerp" }), layouts
111
+ .filter((l) => l.documenttype === d.value)
112
+ .map((l) => (_jsxs(SelectItem, { value: l.id, children: [l.naam, isFloridayLayout(l) ? " (uit Floriday)" : ""] }, l.id)))] })] }))] }, h.value));
113
+ }) })] })] }, d.value));
114
+ }) })] }));
115
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"LayoutsPage.d.ts","sourceRoot":"","sources":["../../src/components/LayoutsPage.tsx"],"names":[],"mappings":"AA0DA,wBAAgB,WAAW,gCAkjB1B"}
1
+ {"version":3,"file":"LayoutsPage.d.ts","sourceRoot":"","sources":["../../src/components/LayoutsPage.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,WAAW,gCA4kB1B"}
@@ -3,9 +3,9 @@ import { useMemo, useState } from "react";
3
3
  import { useAutoRefresh } from "../lib/useAutoRefresh.js";
4
4
  import { useNavigate } from "@tanstack/react-router";
5
5
  import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, toast, } from "@flowselections/core";
6
- import { Copy, FileText, Images, Pencil, Plus, Search, Settings2, Trash2 } from "lucide-react";
6
+ import { Copy, FileText, Pencil, Plus, Search, Settings2, Trash2 } from "lucide-react";
7
7
  import { dupliceerLayout, maakLayout, updateLayout, useLayouts, verwijderLayout, } from "../lib/useDocumentData.js";
8
- import { DOCUMENT_TYPES, PAPIERFORMATEN, documentTypeLabel } from "../lib/documentTypes.js";
8
+ import { DOCUMENT_TYPES, FLORIDAY_LAYOUT_TOELICHTING, PAPIERFORMATEN, documentTypeLabel, isFloridayLayout, } from "../lib/documentTypes.js";
9
9
  import { DOCUMENT_PROFIELEN, documentProfiel, primaireProfielen, standaardConfig, } from "../lib/documentProfiles.js";
10
10
  function klantOntwerpLayout(l) {
11
11
  if (l.documenttype !== "sticker")
@@ -20,6 +20,7 @@ export function LayoutsPage() {
20
20
  const [zoek, setZoek] = useState("");
21
21
  const [typeFilter, setTypeFilter] = useState("alle");
22
22
  const [statusFilter, setStatusFilter] = useState("alle");
23
+ const [bronFilter, setBronFilter] = useState("alle");
23
24
  const [wizardOpen, setWizardOpen] = useState(false);
24
25
  const [stap, setStap] = useState(1);
25
26
  const [naam, setNaam] = useState("");
@@ -89,8 +90,12 @@ export function LayoutsPage() {
89
90
  return false;
90
91
  if (statusFilter === "inactief" && l.actief)
91
92
  return false;
93
+ if (bronFilter === "floriday" && !isFloridayLayout(l))
94
+ return false;
95
+ if (bronFilter === "eigen" && isFloridayLayout(l))
96
+ return false;
92
97
  return true;
93
- }), [layouts, zoek, typeFilter, statusFilter]);
98
+ }), [layouts, zoek, typeFilter, statusFilter, bronFilter]);
94
99
  const aanmaken = async () => {
95
100
  if (!naam.trim()) {
96
101
  toast.error("Geef de layout een naam");
@@ -139,15 +144,15 @@ export function LayoutsPage() {
139
144
  setBezig(false);
140
145
  }
141
146
  };
142
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Documentlayouts" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Ontwerp pakbonnen, facturen, etiketten en alle andere documenten." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/layouts-printers/logos" }), children: [_jsx(Images, { className: "mr-2 h-4 w-4" }), " Logo's"] }), _jsxs(Button, { onClick: () => setWizardOpen(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuwe layout"] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsx(CardTitle, { className: "text-base", children: "Overzicht" }), _jsx(CardDescription, { children: loading ? "Laden…" : `${gefilterd.length} van ${layouts.length} layouts` })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsxs("div", { className: "relative md:col-span-2", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek op naam of omschrijving", value: zoek, onChange: (e) => setZoek(e.target.value) })] }), _jsxs(Select, { value: typeFilter, onValueChange: setTypeFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Documenttype" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle documenttypes" }), DOCUMENT_TYPES.map((d) => (_jsx(SelectItem, { value: d.value, children: d.label }, d.value)))] })] }), _jsxs(Select, { value: statusFilter, onValueChange: setStatusFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Status" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle statussen" }), _jsx(SelectItem, { value: "actief", children: "Actief" }), _jsx(SelectItem, { value: "inactief", children: "Inactief" })] })] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { children: "Documenttype" }), _jsx(TableHead, { children: "Formaat" }), _jsx(TableHead, { children: "Versie" }), _jsx(TableHead, { children: "Actief" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsxs(TableBody, { children: [gefilterd.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "py-10 text-center text-sm text-muted-foreground", children: "Geen layouts gevonden." }) })), gefilterd.map((l) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2 font-medium", children: [_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" }), l.naam, l.is_standaard && _jsx(Badge, { variant: "secondary", children: "Standaard" })] }), l.omschrijving && (_jsx("span", { className: "text-xs text-muted-foreground", children: l.omschrijving }))] }), _jsx(TableCell, { children: documentTypeLabel(l.documenttype) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [l.papierformaat, " \u00B7 ", Number(l.breedte_mm), " \u00D7 ", Number(l.hoogte_mm), " mm"] }), _jsxs(TableCell, { children: ["v", l.huidige_versie] }), _jsx(TableCell, { children: _jsx(Switch, { checked: l.actief, onCheckedChange: async (v) => {
147
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Documentlayouts" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Ontwerp pakbonnen, facturen, etiketten en alle andere documenten." })] }), _jsx("div", { className: "flex gap-2", children: _jsxs(Button, { onClick: () => setWizardOpen(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuwe layout"] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsx(CardTitle, { className: "text-base", children: "Overzicht" }), _jsx(CardDescription, { children: loading ? "Laden…" : `${gefilterd.length} van ${layouts.length} layouts` })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-5", children: [_jsxs("div", { className: "relative md:col-span-2", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek op naam of omschrijving", value: zoek, onChange: (e) => setZoek(e.target.value) })] }), _jsxs(Select, { value: typeFilter, onValueChange: setTypeFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Documenttype" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle documenttypes" }), DOCUMENT_TYPES.map((d) => (_jsx(SelectItem, { value: d.value, children: d.label }, d.value)))] })] }), _jsxs(Select, { value: statusFilter, onValueChange: setStatusFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Status" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle statussen" }), _jsx(SelectItem, { value: "actief", children: "Actief" }), _jsx(SelectItem, { value: "inactief", children: "Inactief" })] })] }), _jsxs(Select, { value: bronFilter, onValueChange: setBronFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Herkomst" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle herkomsten" }), _jsx(SelectItem, { value: "eigen", children: "Eigen ontwerp" }), _jsx(SelectItem, { value: "floriday", children: "Uit Floriday" })] })] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { children: "Documenttype" }), _jsx(TableHead, { children: "Formaat" }), _jsx(TableHead, { children: "Versie" }), _jsx(TableHead, { children: "Actief" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsxs(TableBody, { children: [gefilterd.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "py-10 text-center text-sm text-muted-foreground", children: "Geen layouts gevonden." }) })), gefilterd.map((l) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2 font-medium", children: [_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" }), l.naam, l.is_standaard && _jsx(Badge, { variant: "secondary", children: "Standaard" }), isFloridayLayout(l) && _jsx(Badge, { variant: "outline", children: "Uit Floriday" })] }), (l.omschrijving || isFloridayLayout(l)) && (_jsx("span", { className: "text-xs text-muted-foreground", children: isFloridayLayout(l) ? FLORIDAY_LAYOUT_TOELICHTING : l.omschrijving }))] }), _jsx(TableCell, { children: documentTypeLabel(l.documenttype) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [l.papierformaat, " \u00B7 ", Number(l.breedte_mm), " \u00D7 ", Number(l.hoogte_mm), " mm"] }), _jsxs(TableCell, { children: ["v", l.huidige_versie] }), _jsx(TableCell, { children: _jsx(Switch, { checked: l.actief, onCheckedChange: async (v) => {
143
148
  await updateLayout(l.id, { actief: v });
144
149
  void reload();
145
- } }) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [klantOntwerpLayout(l) ? (_jsx(Badge, { variant: "outline", children: "Ontwerp van klant" })) : (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(l.documenttype === "productiebon"
150
+ } }) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [isFloridayLayout(l) ? (_jsx("span", { className: "text-xs text-muted-foreground", children: "Niet aanpasbaar \u2014 komt uit Floriday" })) : klantOntwerpLayout(l) ? (_jsx(Badge, { variant: "outline", children: "Ontwerp van klant" })) : (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(l.documenttype === "productiebon"
146
151
  ? {
147
152
  to: "/layouts-printers/productiebon/$layoutId",
148
153
  params: { layoutId: l.id },
149
154
  }
150
- : { to: "/layouts-printers/pakbon/$layoutId", params: { layoutId: l.id } }), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), " Ontwerpen"] })), _jsx(Button, { variant: "ghost", size: "sm", title: "Naam, documenttype en formaat wijzigen", onClick: () => openBewerken(l), children: _jsx(Settings2, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: async () => {
155
+ : { to: "/layouts-printers/pakbon/$layoutId", params: { layoutId: l.id } }), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), " Ontwerpen"] })), !isFloridayLayout(l) && (_jsx(Button, { variant: "ghost", size: "sm", title: "Naam, documenttype en formaat wijzigen", onClick: () => openBewerken(l), children: _jsx(Settings2, { className: "h-4 w-4" }) })), !isFloridayLayout(l) && (_jsx(Button, { variant: "ghost", size: "sm", title: "Kopie maken", onClick: async () => {
151
156
  try {
152
157
  await dupliceerLayout(l.id);
153
158
  toast.success("Layout gedupliceerd");
@@ -156,7 +161,7 @@ export function LayoutsPage() {
156
161
  catch {
157
162
  toast.error("Dupliceren mislukt");
158
163
  }
159
- }, children: _jsx(Copy, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: async () => {
164
+ }, children: _jsx(Copy, { className: "h-4 w-4" }) })), !isFloridayLayout(l) && (_jsx(Button, { variant: "ghost", size: "sm", title: "Layout verwijderen", onClick: async () => {
160
165
  try {
161
166
  await verwijderLayout(l.id);
162
167
  toast.success("Layout verwijderd");
@@ -165,7 +170,7 @@ export function LayoutsPage() {
165
170
  catch {
166
171
  toast.error("Verwijderen mislukt");
167
172
  }
168
- }, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) })] }, l.id)))] })] })] })] }), _jsx(Dialog, { open: wizardOpen, onOpenChange: (v) => {
173
+ }, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }) })] }, l.id)))] })] })] })] }), _jsx(Dialog, { open: wizardOpen, onOpenChange: (v) => {
169
174
  setWizardOpen(v);
170
175
  if (!v)
171
176
  setStap(1);
@@ -1 +1 @@
1
- {"version":3,"file":"PrintQueuePage.d.ts","sourceRoot":"","sources":["../../src/components/PrintQueuePage.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,cAAc,gCAoN7B"}
1
+ {"version":3,"file":"PrintQueuePage.d.ts","sourceRoot":"","sources":["../../src/components/PrintQueuePage.tsx"],"names":[],"mappings":"AAuDA,wBAAgB,cAAc,gCAqN7B"}
@@ -3,6 +3,7 @@ import { useMemo, useRef, useState } from "react";
3
3
  import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
4
4
  import { RotateCcw, Search, XCircle } from "lucide-react";
5
5
  import { bijgewerktTekst, useAutoRefresh } from "../lib/useAutoRefresh.js";
6
+ import { PrintStatusBanner } from "./PrintStatusBanner.js";
6
7
  import { maakPrintJob, updateJobStatus, useLayouts, usePrinters, usePrintJobs } from "../lib/useDocumentData.js";
7
8
  import { printStatus } from "../lib/printService.functions.js";
8
9
  import { vraagWachtrijRonde } from "../lib/queueTickClient.js";
@@ -14,6 +15,20 @@ const STATUS_VARIANT = {
14
15
  mislukt: "destructive",
15
16
  geannuleerd: "outline",
16
17
  };
18
+ /**
19
+ * Heeft deze opdracht een kenmerk waarmee hij van andere orders te
20
+ * onderscheiden is? Zo niet, dan valt de dubbelbescherming terug op het
21
+ * document zelf en kan de aanleverende module dit beter aanvullen.
22
+ */
23
+ function heeftOrderKenmerk(job) {
24
+ const p = (job.payload ?? {});
25
+ if (typeof p !== "object" || p === null)
26
+ return false;
27
+ return ["queue_item_id", "order_id", "ordernummer", "order_number", "orderId"].some((k) => {
28
+ const w = p[k];
29
+ return (typeof w === "string" && w.trim() !== "") || typeof w === "number";
30
+ });
31
+ }
17
32
  export function PrintQueuePage() {
18
33
  const { jobs, loading, reload } = usePrintJobs();
19
34
  const { printers } = usePrinters();
@@ -68,7 +83,7 @@ export function PrintQueuePage() {
68
83
  await reload(true);
69
84
  };
70
85
  const bijgewerkt = useAutoRefresh(ronde, lopende ? 5000 : 20000);
71
- return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Printwachtrij" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Volg printopdrachten, herstart mislukte prints en print documenten opnieuw." })] }), _jsx("span", { className: "text-muted-foreground text-xs", children: bijgewerktTekst(bijgewerkt) })] }), tickFout && (_jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: tickFout })), _jsx("div", { className: "grid gap-4 md:grid-cols-4", children: ["wachtend", "bezig", "voltooid", "mislukt"].map((s) => (_jsx(Card, { children: _jsxs(CardHeader, { className: "pb-2", children: [_jsx(CardDescription, { className: "capitalize", children: s }), _jsx(CardTitle, { className: "text-2xl", children: tellingen[s] ?? 0 })] }) }, s))) }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsx(CardTitle, { className: "text-base", children: "Opdrachten" }), _jsx(CardDescription, { children: loading ? "Laden…" : `${gefilterd.length} opdrachten` })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsxs("div", { className: "relative md:col-span-2", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek op document of gebruiker", value: zoek, onChange: (e) => setZoek(e.target.value) })] }), _jsxs(Select, { value: status, onValueChange: setStatus, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle statussen" }), ["wachtend", "bezig", "voltooid", "mislukt", "geannuleerd"].map((s) => (_jsx(SelectItem, { value: s, children: s }, s)))] })] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Document" }), _jsx(TableHead, { children: "Layout" }), _jsx(TableHead, { children: "Printer" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Tijdstip" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsxs(TableBody, { children: [gefilterd.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "py-10 text-center text-sm text-muted-foreground", children: "Geen printopdrachten." }) })), gefilterd.map((j) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("span", { className: "font-medium", children: j.documentnaam ?? documentTypeLabel(j.documenttype) }), j.is_herprint && (_jsx(Badge, { variant: "outline", className: "ml-2", children: "herprint" })), j.foutmelding && (_jsx("span", { className: "block text-xs text-destructive", children: j.foutmelding }))] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: j.layout_id ? layoutNaam.get(j.layout_id) ?? "—" : "—" }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: j.printer_id ? printerNaam.get(j.printer_id) ?? "—" : "—" }), _jsx(TableCell, { children: _jsx(Badge, { variant: STATUS_VARIANT[j.status] ?? "outline", children: j.status }) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: new Date(j.created_at).toLocaleString("nl-NL") }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: async () => {
86
+ return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Printwachtrij" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Volg printopdrachten, herstart mislukte prints en print documenten opnieuw." })] }), _jsx("span", { className: "text-muted-foreground text-xs", children: bijgewerktTekst(bijgewerkt) })] }), _jsx(PrintStatusBanner, { fout: tickFout }), _jsx("div", { className: "grid gap-4 md:grid-cols-4", children: ["wachtend", "bezig", "voltooid", "mislukt"].map((s) => (_jsx(Card, { children: _jsxs(CardHeader, { className: "pb-2", children: [_jsx(CardDescription, { className: "capitalize", children: s }), _jsx(CardTitle, { className: "text-2xl", children: tellingen[s] ?? 0 })] }) }, s))) }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsx(CardTitle, { className: "text-base", children: "Opdrachten" }), _jsx(CardDescription, { children: loading ? "Laden…" : `${gefilterd.length} opdrachten` })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsxs("div", { className: "relative md:col-span-2", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek op document of gebruiker", value: zoek, onChange: (e) => setZoek(e.target.value) })] }), _jsxs(Select, { value: status, onValueChange: setStatus, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle statussen" }), ["wachtend", "bezig", "voltooid", "mislukt", "geannuleerd"].map((s) => (_jsx(SelectItem, { value: s, children: s }, s)))] })] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Document" }), _jsx(TableHead, { children: "Layout" }), _jsx(TableHead, { children: "Printer" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Tijdstip" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsxs(TableBody, { children: [gefilterd.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "py-10 text-center text-sm text-muted-foreground", children: "Geen printopdrachten." }) })), gefilterd.map((j) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("span", { className: "font-medium", children: j.documentnaam ?? documentTypeLabel(j.documenttype) }), j.is_herprint && (_jsx(Badge, { variant: "outline", className: "ml-2", children: "herprint" })), !heeftOrderKenmerk(j) && (_jsx(Badge, { variant: "outline", className: "ml-2 text-amber-600", children: "geen orderkenmerk" })), j.foutmelding && (_jsx("span", { className: "block text-xs text-destructive", children: j.foutmelding }))] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: j.layout_id ? layoutNaam.get(j.layout_id) ?? "—" : "—" }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: j.printer_id ? printerNaam.get(j.printer_id) ?? "—" : "—" }), _jsx(TableCell, { children: _jsx(Badge, { variant: STATUS_VARIANT[j.status] ?? "outline", children: j.status }) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: new Date(j.created_at).toLocaleString("nl-NL") }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: async () => {
72
87
  if (!j.layout_id)
73
88
  return;
74
89
  try {
@@ -0,0 +1,8 @@
1
+ export declare function PrintStatusBanner({ fout, offlineWerkplekken, toonWerkplekLink, }: {
2
+ /** Foutmelding van de printdienst, als die er is. */
3
+ fout?: string | null;
4
+ /** Namen van werkplekken die nu uit staan. */
5
+ offlineWerkplekken?: string[];
6
+ toonWerkplekLink?: boolean;
7
+ }): import("react").JSX.Element | null;
8
+ //# sourceMappingURL=PrintStatusBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrintStatusBanner.d.ts","sourceRoot":"","sources":["../../src/components/PrintStatusBanner.tsx"],"names":[],"mappings":"AAQA,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,kBAAuB,EACvB,gBAAuB,GACxB,EAAE;IACD,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,sCA2CA"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/PrintStatusBanner.tsx — één melding over de printdienst en de
4
+ // werkplekken, zodat Printers en Printwachtrij hetzelfde verhaal vertellen.
5
+ // ============================================================================
6
+ import { Link } from "@tanstack/react-router";
7
+ import { AlertTriangle } from "lucide-react";
8
+ export function PrintStatusBanner({ fout, offlineWerkplekken = [], toonWerkplekLink = true, }) {
9
+ const sleutelProbleem = !!fout && /toegangsbewijs|401|unauthorized|token|sleutel/i.test(fout);
10
+ if (!fout && offlineWerkplekken.length === 0)
11
+ return null;
12
+ return (_jsxs("div", { className: "border-destructive/30 bg-destructive/5 text-destructive space-y-1 rounded-xl border p-4 text-sm", children: [_jsxs("p", { className: "flex items-center gap-2 font-medium", children: [_jsx(AlertTriangle, { className: "h-4 w-4" }), " Er kan nu niet worden afgedrukt"] }), fout && (_jsxs("p", { children: [fout, " ", sleutelProbleem && (_jsxs(_Fragment, { children: ["Controleer de sleutel van de printdienst bij", " ", _jsx(Link, { to: "/instellingen", className: "underline", children: "Instellingen" }), "."] }))] })), offlineWerkplekken.length > 0 && (_jsxs("p", { children: [offlineWerkplekken.length === 1
13
+ ? `Werkplek ${offlineWerkplekken[0]} staat uit`
14
+ : `${offlineWerkplekken.length} werkplekken staan uit (${offlineWerkplekken.join(", ")})`, ". Opdrachten blijven wachten tot het printprogramma op die computer draait.", toonWerkplekLink && (_jsxs(_Fragment, { children: [" ", _jsx(Link, { to: "/layouts-printers/printers", className: "underline", children: "Naar werkplekken" })] }))] }))] }));
15
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"PrintersPage.d.ts","sourceRoot":"","sources":["../../src/components/PrintersPage.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,YAAY,gCA6O3B"}
1
+ {"version":3,"file":"PrintersPage.d.ts","sourceRoot":"","sources":["../../src/components/PrintersPage.tsx"],"names":[],"mappings":"AA2BA,wBAAgB,YAAY,gCAiP3B"}
@@ -13,6 +13,8 @@ import { useNetwerkPrinters } from "../lib/useNetwerkPrinters.js";
13
13
  import { PrinterSetupWizard } from "./PrinterSetupWizard.js";
14
14
  import { ComputersLijst, useComputers } from "./ComputersLijst.js";
15
15
  import { PrinterStatsKaarten } from "./PrinterStatsKaarten.js";
16
+ import { PrintStatusBanner } from "./PrintStatusBanner.js";
17
+ import { telPrintersPerComputer } from "../lib/printerTelling.js";
16
18
  import { WerkplekkenTabel } from "./WerkplekkenTabel.js";
17
19
  export function PrintersPage() {
18
20
  const { printers, koppelingen, reload } = usePrinters();
@@ -30,24 +32,7 @@ export function PrintersPage() {
30
32
  await Promise.all([reload(true), herlaadComputers(true), zoek()]);
31
33
  }, 10000, wizard || computerToevoegen);
32
34
  const gekoppeldePrinters = useMemo(() => new Set(koppelingen.map((k) => k.printer_id)), [koppelingen]);
33
- const printersPerComputer = useMemo(() => {
34
- const externNaarComputer = new Map(gevonden.map((g) => [g.id, g.computer]));
35
- const map = new Map();
36
- for (const p of printers) {
37
- const extern = p.extern_printer_id ?? null;
38
- const naam = extern ? externNaarComputer.get(extern) : undefined;
39
- if (!naam)
40
- continue;
41
- const rij = map.get(naam) ?? { totaal: 0, gekoppeld: 0, locaties: [] };
42
- rij.totaal += 1;
43
- if (gekoppeldePrinters.has(p.id))
44
- rij.gekoppeld += 1;
45
- if (p.locatie && !rij.locaties.includes(p.locatie))
46
- rij.locaties.push(p.locatie);
47
- map.set(naam, rij);
48
- }
49
- return map;
50
- }, [printers, gevonden, gekoppeldePrinters]);
35
+ const printersPerComputer = useMemo(() => telPrintersPerComputer(printers, gevonden, gekoppeldePrinters), [printers, gevonden, gekoppeldePrinters]);
51
36
  const alleRijen = computers.map((c) => {
52
37
  const info = printersPerComputer.get(c.naam);
53
38
  return {
@@ -55,8 +40,7 @@ export function PrintersPage() {
55
40
  naam: c.naam,
56
41
  online: c.online,
57
42
  locatie: info?.locaties.join(" · ") || "Geen locatie",
58
- aantalPrinters: info?.totaal ?? c.aantalPrinters,
59
- aantalGekoppeld: info?.gekoppeld ?? 0,
43
+ telling: info ?? { gevonden: c.aantalGevonden, ingesteld: 0, gekoppeld: 0, locaties: [] },
60
44
  };
61
45
  });
62
46
  const locaties = Array.from(new Set(alleRijen.map((r) => r.locatie))).filter((l) => l !== "Geen locatie");
@@ -77,7 +61,7 @@ export function PrintersPage() {
77
61
  .map((p) => p.extern_printer_id ?? null)
78
62
  .filter((e) => !!e);
79
63
  const bekendeLocaties = Array.from(new Set(printers.map((p) => p.locatie).filter((l) => !!l)));
80
- return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Printers" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Beheer per werkplek welke computer is aangemeld en welke printers worden gebruikt." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", asChild: true, children: _jsxs(Link, { to: "/layouts-printers/wachtrij", children: [_jsx(ListChecks, { className: "mr-2 h-4 w-4" }), " Printwachtrij"] }) }), _jsxs(Button, { onClick: () => setComputerToevoegen(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Computer toevoegen"] })] })] }), _jsx(PrinterStatsKaarten, { kaarten: [
64
+ return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Printers" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Beheer per werkplek welke computer is aangemeld en welke printers worden gebruikt." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", asChild: true, children: _jsxs(Link, { to: "/layouts-printers/routering", children: [_jsx(FileText, { className: "mr-2 h-4 w-4" }), " Routering"] }) }), _jsx(Button, { variant: "ghost", asChild: true, children: _jsxs(Link, { to: "/layouts-printers/wachtrij", children: [_jsx(ListChecks, { className: "mr-2 h-4 w-4" }), " Printwachtrij"] }) }), _jsxs(Button, { onClick: () => setComputerToevoegen(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Computer toevoegen"] })] })] }), _jsx(PrintStatusBanner, { fout: computersFout, offlineWerkplekken: computers.filter((c) => !c.online).map((c) => c.naam), toonWerkplekLink: false }), _jsx(PrinterStatsKaarten, { kaarten: [
81
65
  {
82
66
  label: "Werkplekken",
83
67
  waarde: String(computers.length),
@@ -85,9 +69,9 @@ export function PrintersPage() {
85
69
  icon: Monitor,
86
70
  },
87
71
  {
88
- label: "Totaal printers",
72
+ label: "Printers",
89
73
  waarde: String(printers.length),
90
- toelichting: "Ingestelde printers",
74
+ toelichting: `Ingesteld · ${gevonden.length} gevonden op de werkplekken`,
91
75
  icon: Printer,
92
76
  },
93
77
  {
@@ -1 +1 @@
1
- {"version":3,"file":"WerkplekConfiguratiePage.d.ts","sourceRoot":"","sources":["../../src/components/WerkplekConfiguratiePage.tsx"],"names":[],"mappings":"AA2BA,wBAAgB,wBAAwB,gCA6QvC"}
1
+ {"version":3,"file":"WerkplekConfiguratiePage.d.ts","sourceRoot":"","sources":["../../src/components/WerkplekConfiguratiePage.tsx"],"names":[],"mappings":"AAwCA,wBAAgB,wBAAwB,gCA2TvC"}