@flowselections/layout-maker 1.8.3 → 1.13.2

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 (68) hide show
  1. package/README.md +139 -132
  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/PrinterSetupWizard.js +1 -1
  16. package/dist-lib/components/PrintersPage.d.ts.map +1 -1
  17. package/dist-lib/components/PrintersPage.js +7 -23
  18. package/dist-lib/components/WerkplekConfiguratiePage.d.ts.map +1 -1
  19. package/dist-lib/components/WerkplekConfiguratiePage.js +35 -24
  20. package/dist-lib/components/WerkplekkenTabel.d.ts +2 -2
  21. package/dist-lib/components/WerkplekkenTabel.d.ts.map +1 -1
  22. package/dist-lib/components/WerkplekkenTabel.js +4 -3
  23. package/dist-lib/index.d.ts +8 -3
  24. package/dist-lib/index.d.ts.map +1 -1
  25. package/dist-lib/index.js +9 -5
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts +491 -145
  27. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.server.d.ts +491 -145
  29. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.d.ts +505 -150
  31. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  32. package/dist-lib/lib/aiLayout.functions.d.ts +491 -145
  33. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  34. package/dist-lib/lib/aiLayout.server.js +9 -9
  35. package/dist-lib/lib/browserPrint.js +5 -5
  36. package/dist-lib/lib/documentBron.d.ts +35 -0
  37. package/dist-lib/lib/documentBron.d.ts.map +1 -0
  38. package/dist-lib/lib/documentBron.js +62 -0
  39. package/dist-lib/lib/documentRoutering.d.ts +23 -0
  40. package/dist-lib/lib/documentRoutering.d.ts.map +1 -0
  41. package/dist-lib/lib/documentRoutering.js +58 -0
  42. package/dist-lib/lib/documentTypes.d.ts +61 -0
  43. package/dist-lib/lib/documentTypes.d.ts.map +1 -1
  44. package/dist-lib/lib/documentTypes.js +64 -0
  45. package/dist-lib/lib/formaatPrinters.d.ts +5 -0
  46. package/dist-lib/lib/formaatPrinters.d.ts.map +1 -1
  47. package/dist-lib/lib/formaatPrinters.js +12 -0
  48. package/dist-lib/lib/layoutPreview.js +7 -7
  49. package/dist-lib/lib/pakbonHtml.js +50 -50
  50. package/dist-lib/lib/printApi.d.ts +20 -5
  51. package/dist-lib/lib/printApi.d.ts.map +1 -1
  52. package/dist-lib/lib/printApi.js +93 -32
  53. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  54. package/dist-lib/lib/printQueue.server.js +72 -1
  55. package/dist-lib/lib/printService.functions.d.ts +1964 -580
  56. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/printerTelling.d.ts +24 -0
  58. package/dist-lib/lib/printerTelling.d.ts.map +1 -0
  59. package/dist-lib/lib/printerTelling.js +41 -0
  60. package/dist-lib/lib/productiebonHtml.js +15 -15
  61. package/dist-lib/lib/profielHtml.js +18 -18
  62. package/dist-lib/lib/supabaseAdmin.server.d.ts +5 -5
  63. package/dist-lib/lib/supabaseAdmin.server.js +43 -43
  64. package/dist-lib/lib/testpagina.js +1 -1
  65. package/dist-lib/lib/useDocumentData.d.ts +4 -0
  66. package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
  67. package/dist-lib/lib/useDocumentData.js +130 -1
  68. package/package.json +6 -1
package/README.md CHANGED
@@ -1,132 +1,139 @@
1
- # Layout maker (pakbonnen, verzendbrieven, ectra)
2
-
3
- blank project start
4
-
5
- This project was built with [Lovable](https://lovable.dev).
6
-
7
- ## Build with Lovable
8
-
9
- Continue developing this project in the [Lovable editor](https://lovable.dev/projects/4735fccb-b385-4b48-b4f8-ef135f63f4f7).
10
-
11
- - **Ship faster**: describe what you want to build and Lovable handles the code.
12
- - **Stay in sync**: every change made in Lovable is committed straight to this repository.
13
- - **Full ownership**: this code is yours. Push to `main` on GitHub and your changes sync back into Lovable, ready for your next prompt.
14
-
15
- ## Development
16
-
17
- Prefer working locally? You need Node.js and npm — [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating).
18
-
19
- ```sh
20
- git clone <this-repository-url>
21
- cd <repository-name>
22
- npm i
23
- npm run dev
24
- ```
25
-
26
- ## Installeren in een andere shell
27
-
28
- 1. Draai `sql/print-schema.sql` één keer in de Supabase-database van die shell
29
- (SQL editor). Het script is idempotent en zet tabellen, rechten, RLS,
30
- functies, de geplande taken én de opslagmap `layout-logos` voor de Logo
31
- Bibliotheek klaar. Zorg dat de extensies `pg_cron` en `pg_net` aan staan.
32
-
33
- 2. Zet in de omgeving van de shell:
34
- - `SUPABASE_URL` en `SERVICE_ROLE_KEY` — nodig voor achtergrondprinten
35
- - `CENTRAL_AUTH_URL` en `CENTRAL_AUTH_SECRET` — nodig voor de printdienst
36
- 3. Zorg dat de shell het tick-eindpunt host. De setup-CLI doet dit automatisch
37
- op basis van `flowselections.serverRoutes` in `package.json`. Handmatig
38
- opgezette shells maken zelf dit bestand aan:
39
-
40
- ```ts
41
- // src/routes/api/public/print/queue-tick.ts
42
- import { createFileRoute } from "@tanstack/react-router";
43
- import { behandelQueueTick } from "@flowselections/layout-maker/print";
44
-
45
- export const Route = createFileRoute("/api/public/print/queue-tick")({
46
- server: {
47
- handlers: {
48
- POST: async ({ request }) => {
49
- // De shell levert de databaseverbinding; de module maakt er nooit zelf een.
50
- const { supabaseAdmin } = await import(
51
- "../../../../integrations/supabase/client.server"
52
- );
53
- return behandelQueueTick(request, supabaseAdmin);
54
- },
55
- },
56
- },
57
- });
58
- ```
59
-
60
- Het eindpunt accepteert drie toegangsbewijzen: een runner-sleutel uit
61
- `print_queue_runners.tick_sleutel`, `SUPABASE_PUBLISHABLE_KEY` (de cron) of
62
- het sessietoken van een ingelogde gebruiker (de knop in de Printwachtrij).
63
-
64
-
65
- 4. Klaar. Zodra de module in de shell draait meldt hij zichzelf aan als
66
- verwerker van de printwachtrij. Een nieuwe printopdracht wordt meteen
67
- opgepakt; daarnaast verwerkt de geplande taak `print-queue-tick` elke minuut
68
- de wachtende opdrachten. Reageert een shell een uur lang niet, dan wordt hij
69
- automatisch op inactief gezet. De taak `print-queue-cleanup` ruimt dagelijks
70
- oude opdrachten en bestanden op.
71
-
72
- ## Printen vanuit een andere module
73
-
74
- Andere modules in dezelfde shell gebruiken uitsluitend de print-API van deze
75
- module; zij hoeven de printtabellen niet te kennen en mogen er ook niet zelf in
76
- schrijven (dan blijft de opdracht wachten tot de volgende cronronde).
77
-
78
- ```ts
79
- import {
80
- printDocument, printPdf, printHtml,
81
- printerVoorWerkplek, printerVoorDocumenttype, volgJobStatus,
82
- } from "@flowselections/layout-maker";
83
-
84
- // 1. Document dat via een ontwerp uit deze module wordt opgemaakt
85
- const { jobId } = await printDocument({
86
- documenttype: "pakbon", // moet een bestaande documentsoort zijn
87
- documentnaam: "PB-2601284",
88
- payload: { order }, // gegevens voor de layout
89
- });
90
-
91
- // 2. Kant-en-klare PDF (Floriday-document, klantsticker, externe factuur)
92
- // Zonder printerId wordt de printer bepaald via de gekoppelde layout van
93
- // de documentsoort (of de koppeling op papierformaat).
94
- await printPdf({
95
- documenttype: "factuur",
96
- documentnaam: "F-2026-00123",
97
- pdfBase64, // met of zonder data:-voorvoegsel
98
- });
99
-
100
- // Of expliciet per werkplek:
101
- const printerId = await printerVoorWerkplek("PICKSTATION-1", "A4");
102
- await printPdf({
103
- documenttype: "afleverbrief",
104
- documentnaam: "AFL-12345",
105
- pdfBase64,
106
- printerId: printerId!,
107
- });
108
-
109
-
110
- // 3. Eigen opmaak afdrukken (geen layout, anders wint de layout)
111
- await printHtml({
112
- documenttype: "controlelijst",
113
- documentnaam: "Controlelijst kar 12",
114
- html,
115
- printerId: printerId!,
116
- });
117
-
118
- // Status live volgen (realtime staat aan op print_jobs)
119
- const stop = volgJobStatus(jobId, (s) => console.log(s.status, s.foutmelding));
120
- ```
121
-
122
- Belangrijk:
123
-
124
- - **Documentsoorten** liggen vast in de database: pakbon, factuur, offerte,
125
- orderbevestiging, etiket, verzendlabel, productlabel, barcode_label,
126
- magazijnlijst, controlelijst, transportdocument, sticker, productiebon,
127
- veilingbon, afleverbrief, veilingbrief en vrij.
128
- - **Beslisvolgorde bij het afdrukken**: kant-en-klare PDF > gekoppelde layout >
129
- meegestuurde opmaak > actieve layout van de documentsoort.
130
- - **Dubbelbeveiliging**: een identieke aanvraag binnen 10 minuten wordt
131
- genegeerd; de aanroep werpt dan `DubbeleAanvraagFout`. Geef bij bulkprints per
132
- document een eigen `documentnaam` zodat elke opdracht uniek is.
1
+ # Layout maker (pakbonnen, verzendbrieven, ectra)
2
+
3
+ blank project start
4
+
5
+ This project was built with [Lovable](https://lovable.dev).
6
+
7
+ ## Build with Lovable
8
+
9
+ Continue developing this project in the [Lovable editor](https://lovable.dev/projects/4735fccb-b385-4b48-b4f8-ef135f63f4f7).
10
+
11
+ - **Ship faster**: describe what you want to build and Lovable handles the code.
12
+ - **Stay in sync**: every change made in Lovable is committed straight to this repository.
13
+ - **Full ownership**: this code is yours. Push to `main` on GitHub and your changes sync back into Lovable, ready for your next prompt.
14
+
15
+ ## Development
16
+
17
+ Prefer working locally? You need Node.js and npm — [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating).
18
+
19
+ ```sh
20
+ git clone <this-repository-url>
21
+ cd <repository-name>
22
+ npm i
23
+ npm run dev
24
+ ```
25
+
26
+ ## Installeren in een andere shell
27
+
28
+ 1. Draai `sql/print-schema.sql` één keer in de Supabase-database van die shell
29
+ (SQL editor). Het script is idempotent en zet tabellen, rechten, RLS,
30
+ functies, de geplande taken én de opslagmap `layout-logos` voor de Logo
31
+ Bibliotheek klaar. Zorg dat de extensies `pg_cron` en `pg_net` aan staan.
32
+
33
+ 2. Zet in de omgeving van de shell:
34
+ - `SUPABASE_URL` en `SERVICE_ROLE_KEY` — nodig voor achtergrondprinten
35
+ - `CENTRAL_AUTH_URL` en `CENTRAL_AUTH_SECRET` — nodig voor de printdienst
36
+ 3. Zorg dat de shell het tick-eindpunt host. De setup-CLI doet dit automatisch
37
+ op basis van `flowselections.serverRoutes` in `package.json`. Handmatig
38
+ opgezette shells maken zelf dit bestand aan:
39
+
40
+ ```ts
41
+ // src/routes/api/public/print/queue-tick.ts
42
+ import { createFileRoute } from "@tanstack/react-router";
43
+ import { behandelQueueTick } from "@flowselections/layout-maker/print";
44
+
45
+ export const Route = createFileRoute("/api/public/print/queue-tick")({
46
+ server: {
47
+ handlers: {
48
+ POST: async ({ request }) => {
49
+ // De shell levert de databaseverbinding; de module maakt er nooit zelf een.
50
+ const { supabaseAdmin } = await import(
51
+ "../../../../integrations/supabase/client.server"
52
+ );
53
+ return behandelQueueTick(request, supabaseAdmin);
54
+ },
55
+ },
56
+ },
57
+ });
58
+ ```
59
+
60
+ Het eindpunt accepteert drie toegangsbewijzen: een runner-sleutel uit
61
+ `print_queue_runners.tick_sleutel`, `SUPABASE_PUBLISHABLE_KEY` (de cron) of
62
+ het sessietoken van een ingelogde gebruiker (de knop in de Printwachtrij).
63
+
64
+
65
+ 4. Klaar. Zodra de module in de shell draait meldt hij zichzelf aan als
66
+ verwerker van de printwachtrij. Een nieuwe printopdracht wordt meteen
67
+ opgepakt; daarnaast verwerkt de geplande taak `print-queue-tick` elke minuut
68
+ de wachtende opdrachten. Reageert een shell een uur lang niet, dan wordt hij
69
+ automatisch op inactief gezet. De taak `print-queue-cleanup` ruimt dagelijks
70
+ oude opdrachten en bestanden op.
71
+
72
+ ## Printen vanuit een andere module
73
+
74
+ Andere modules in dezelfde shell gebruiken uitsluitend de print-API van deze
75
+ module; zij hoeven de printtabellen niet te kennen en mogen er ook niet zelf in
76
+ schrijven (dan blijft de opdracht wachten tot de volgende cronronde).
77
+
78
+ ```ts
79
+ import {
80
+ printDocument, printPdf, printHtml,
81
+ printerVoorWerkplek, printerVoorDocumenttype, volgJobStatus,
82
+ } from "@flowselections/layout-maker";
83
+
84
+ // 1. Document dat via een ontwerp uit deze module wordt opgemaakt
85
+ const { jobId } = await printDocument({
86
+ documenttype: "pakbon", // moet een bestaande documentsoort zijn
87
+ documentnaam: "PB-2601284",
88
+ payload: { order, order_id: order.id }, // order_id: verplicht kenmerk
89
+ });
90
+
91
+ // 2. Kant-en-klare PDF (Floriday-document, klantsticker, externe factuur)
92
+ // Zonder printerId wordt de printer bepaald via de gekoppelde layout van
93
+ // de documentsoort (of de koppeling op papierformaat).
94
+ await printPdf({
95
+ documenttype: "factuur",
96
+ documentnaam: "F-2026-00123",
97
+ pdfBase64, // met of zonder data:-voorvoegsel
98
+ payload: { order_id: factuur.id },
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
+
107
+ // Of expliciet per werkplek:
108
+ const printerId = await printerVoorWerkplek("PICKSTATION-1", "A4");
109
+ await printPdf({
110
+ documenttype: "afleverbrief",
111
+ documentnaam: "AFL-12345",
112
+ pdfBase64,
113
+ printerId: printerId!,
114
+ });
115
+
116
+
117
+ // 3. Eigen opmaak afdrukken (geen layout, anders wint de layout)
118
+ await printHtml({
119
+ documenttype: "controlelijst",
120
+ documentnaam: "Controlelijst kar 12",
121
+ html,
122
+ printerId: printerId!,
123
+ });
124
+
125
+ // Status live volgen (realtime staat aan op print_jobs)
126
+ const stop = volgJobStatus(jobId, (s) => console.log(s.status, s.foutmelding));
127
+ ```
128
+
129
+ Belangrijk:
130
+
131
+ - **Documentsoorten** liggen vast in de database: pakbon, factuur, offerte,
132
+ orderbevestiging, etiket, verzendlabel, productlabel, barcode_label,
133
+ magazijnlijst, controlelijst, transportdocument, sticker, productiebon,
134
+ veilingbon, afleverbrief, veilingbrief en vrij.
135
+ - **Beslisvolgorde bij het afdrukken**: kant-en-klare PDF > gekoppelde layout >
136
+ meegestuurde opmaak > actieve layout van de documentsoort.
137
+ - **Dubbelbeveiliging**: een identieke aanvraag binnen 10 minuten wordt
138
+ genegeerd; de aanroep werpt dan `DubbeleAanvraagFout`. Geef bij bulkprints per
139
+ document een eigen `documentnaam` zodat elke opdracht uniek is.
@@ -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