@flowselections/layout-maker 1.51.4 → 1.53.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 (54) hide show
  1. package/README.md +0 -1
  2. package/dist-lib/components/AfdrukKnop.js +64 -64
  3. package/dist-lib/components/LivePrintstatusRegel.d.ts +2 -0
  4. package/dist-lib/components/LivePrintstatusRegel.d.ts.map +1 -0
  5. package/dist-lib/components/LivePrintstatusRegel.js +37 -0
  6. package/dist-lib/components/NetwerkPrintersDialog.js +79 -79
  7. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  8. package/dist-lib/components/PrintQueuePage.js +78 -43
  9. package/dist-lib/components/PrintenPage.d.ts.map +1 -1
  10. package/dist-lib/components/PrintenPage.js +3 -2
  11. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  12. package/dist-lib/components/PrintplekDetailPage.js +30 -2
  13. package/dist-lib/index.d.ts +7 -1
  14. package/dist-lib/index.d.ts.map +1 -1
  15. package/dist-lib/index.js +6 -1
  16. package/dist-lib/integrations/supabase/client.server.js +1 -1
  17. package/dist-lib/integrations/supabase/types.d.ts +167 -0
  18. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  19. package/dist-lib/lib/foutTekst.d.ts +10 -0
  20. package/dist-lib/lib/foutTekst.d.ts.map +1 -1
  21. package/dist-lib/lib/foutTekst.js +28 -0
  22. package/dist-lib/lib/printApi.d.ts +33 -0
  23. package/dist-lib/lib/printApi.d.ts.map +1 -1
  24. package/dist-lib/lib/printApi.js +45 -1
  25. package/dist-lib/lib/printQueue.server.d.ts +6 -0
  26. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  27. package/dist-lib/lib/printQueue.server.js +72 -20
  28. package/dist-lib/lib/printService.functions.d.ts +76 -0
  29. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  30. package/dist-lib/lib/printService.functions.js +130 -0
  31. package/dist-lib/lib/printService.server.d.ts +35 -0
  32. package/dist-lib/lib/printService.server.d.ts.map +1 -1
  33. package/dist-lib/lib/printService.server.js +104 -13
  34. package/dist-lib/lib/printnodeStatus.d.ts +14 -0
  35. package/dist-lib/lib/printnodeStatus.d.ts.map +1 -0
  36. package/dist-lib/lib/printnodeStatus.js +43 -0
  37. package/dist-lib/lib/printnodeWebhook.d.ts +23 -0
  38. package/dist-lib/lib/printnodeWebhook.d.ts.map +1 -0
  39. package/dist-lib/lib/printnodeWebhook.js +137 -0
  40. package/dist-lib/lib/printnodeWebhookBeheer.server.d.ts +45 -0
  41. package/dist-lib/lib/printnodeWebhookBeheer.server.d.ts.map +1 -0
  42. package/dist-lib/lib/printnodeWebhookBeheer.server.js +116 -0
  43. package/dist-lib/lib/queueTick.d.ts.map +1 -1
  44. package/dist-lib/lib/queueTick.js +9 -0
  45. package/dist-lib/lib/shellAdres.d.ts +10 -0
  46. package/dist-lib/lib/shellAdres.d.ts.map +1 -0
  47. package/dist-lib/lib/shellAdres.js +36 -0
  48. package/dist-lib/lib/standaardLayouts.server.d.ts +6 -0
  49. package/dist-lib/lib/standaardLayouts.server.d.ts.map +1 -0
  50. package/dist-lib/lib/standaardLayouts.server.js +61 -0
  51. package/dist-lib/lib/useDocumentData.d.ts +10 -1
  52. package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
  53. package/dist-lib/lib/useDocumentData.js +16 -117
  54. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,6 @@ This project was built with [Lovable](https://lovable.dev).
6
6
 
7
7
  ## Build with Lovable
8
8
 
9
- Continue developing this project in the [Lovable editor](https://lovable.dev/projects/4735fccb-b385-4b48-b4f8-ef135f63f4f7).
10
9
 
11
10
  - **Ship faster**: describe what you want to build and Lovable handles the code.
12
11
  - **Stay in sync**: every change made in Lovable is committed straight to this repository.
@@ -1,64 +1,64 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // ============================================================================
3
- // src/components/AfdrukKnop.tsx — afdrukken met automatisch ingesteld
4
- // papierformaat, stand en de bedoelde printer. Waar mogelijk gaat het
5
- // document direct naar de printer; anders opent het printvenster.
6
- // ============================================================================
7
- import { useState } from "react";
8
- import { Button, toast } from "@flowselections/core";
9
- import { Loader2, Printer } from "lucide-react";
10
- import { drukAf } from "../lib/browserPrint.js";
11
- import { printDirect } from "../lib/directPrint.js";
12
- /**
13
- * @deprecated Verouderd — deze knop gebruikt het browser-printvenster als
14
- * terugval. Gebruik `printHtml()` / `printPdf()` uit `printApi` zodat alles
15
- * via PrintNode loopt.
16
- */
17
- export function AfdrukKnop({ documenttype, formaatCode, orientatie, getHtml, css, documentnaam, label = "Afdrukken", layoutId, kopieen, }) {
18
- const [bezig, setBezig] = useState(false);
19
- const afdrukken = async () => {
20
- setBezig(true);
21
- try {
22
- const html = getHtml();
23
- const direct = await printDirect({
24
- documenttype,
25
- formaatCode,
26
- orientatie,
27
- html,
28
- css,
29
- documentnaam,
30
- layoutId: layoutId ?? null,
31
- kopieen,
32
- });
33
- if (direct.gelukt) {
34
- toast.success(direct.printernaam
35
- ? `Verstuurd naar ${direct.printernaam}`
36
- : "Het document is naar de printer verstuurd");
37
- return;
38
- }
39
- if (direct.reden)
40
- toast.error(direct.reden);
41
- const { printer } = await drukAf({
42
- documenttype,
43
- formaatCode,
44
- orientatie,
45
- html,
46
- css,
47
- documentnaam,
48
- });
49
- if (printer) {
50
- toast.info(`Kies in het printvenster: ${printer.systeemnaam ?? printer.naam}`);
51
- }
52
- else {
53
- toast.info("Er is nog geen vaste printer ingesteld voor dit document.");
54
- }
55
- }
56
- catch {
57
- toast.error("Afdrukken mislukt");
58
- }
59
- finally {
60
- setBezig(false);
61
- }
62
- };
63
- return (_jsxs(Button, { variant: "outline", disabled: bezig, onClick: () => void afdrukken(), children: [bezig ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Printer, { className: "mr-2 h-4 w-4" }), label] }));
64
- }
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/AfdrukKnop.tsx — afdrukken met automatisch ingesteld
4
+ // papierformaat, stand en de bedoelde printer. Waar mogelijk gaat het
5
+ // document direct naar de printer; anders opent het printvenster.
6
+ // ============================================================================
7
+ import { useState } from "react";
8
+ import { Button, toast } from "@flowselections/core";
9
+ import { Loader2, Printer } from "lucide-react";
10
+ import { drukAf } from "../lib/browserPrint.js";
11
+ import { printDirect } from "../lib/directPrint.js";
12
+ /**
13
+ * @deprecated Verouderd — deze knop gebruikt het browser-printvenster als
14
+ * terugval. Gebruik `printHtml()` / `printPdf()` uit `printApi` zodat alles
15
+ * via PrintNode loopt.
16
+ */
17
+ export function AfdrukKnop({ documenttype, formaatCode, orientatie, getHtml, css, documentnaam, label = "Afdrukken", layoutId, kopieen, }) {
18
+ const [bezig, setBezig] = useState(false);
19
+ const afdrukken = async () => {
20
+ setBezig(true);
21
+ try {
22
+ const html = getHtml();
23
+ const direct = await printDirect({
24
+ documenttype,
25
+ formaatCode,
26
+ orientatie,
27
+ html,
28
+ css,
29
+ documentnaam,
30
+ layoutId: layoutId ?? null,
31
+ kopieen,
32
+ });
33
+ if (direct.gelukt) {
34
+ toast.success(direct.printernaam
35
+ ? `Verstuurd naar ${direct.printernaam}`
36
+ : "Het document is naar de printer verstuurd");
37
+ return;
38
+ }
39
+ if (direct.reden)
40
+ toast.error(direct.reden);
41
+ const { printer } = await drukAf({
42
+ documenttype,
43
+ formaatCode,
44
+ orientatie,
45
+ html,
46
+ css,
47
+ documentnaam,
48
+ });
49
+ if (printer) {
50
+ toast.info(`Kies in het printvenster: ${printer.systeemnaam ?? printer.naam}`);
51
+ }
52
+ else {
53
+ toast.info("Er is nog geen vaste printer ingesteld voor dit document.");
54
+ }
55
+ }
56
+ catch {
57
+ toast.error("Afdrukken mislukt");
58
+ }
59
+ finally {
60
+ setBezig(false);
61
+ }
62
+ };
63
+ return (_jsxs(Button, { variant: "outline", disabled: bezig, onClick: () => void afdrukken(), children: [bezig ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Printer, { className: "mr-2 h-4 w-4" }), label] }));
64
+ }
@@ -0,0 +1,2 @@
1
+ export declare function LivePrintstatusRegel(): import("react").JSX.Element;
2
+ //# sourceMappingURL=LivePrintstatusRegel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LivePrintstatusRegel.d.ts","sourceRoot":"","sources":["../../src/components/LivePrintstatusRegel.tsx"],"names":[],"mappings":"AAWA,wBAAgB,oBAAoB,gCA0CnC"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/LivePrintstatusRegel.tsx — één regel "Live printstatus: aan /
4
+ // uit" met de knop "Opnieuw koppelen" (alleen voor herstel).
5
+ // ============================================================================
6
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
7
+ import { useServerFn } from "@tanstack/react-start";
8
+ import { useState } from "react";
9
+ import { Badge, Button, toast } from "@flowselections/core";
10
+ import { webhookOpnieuwKoppelen, webhookStatus } from "../lib/printService.functions.js";
11
+ export function LivePrintstatusRegel() {
12
+ const haal = useServerFn(webhookStatus);
13
+ const koppel = useServerFn(webhookOpnieuwKoppelen);
14
+ const qc = useQueryClient();
15
+ const [bezig, setBezig] = useState(false);
16
+ const { data } = useQuery({ queryKey: ["printnode-webhook-status"], queryFn: () => haal(), refetchInterval: 60000 });
17
+ const s = data && data.ok ? data : null;
18
+ const aan = !!s?.aan;
19
+ return (_jsxs("div", { className: "mb-4 flex flex-wrap items-center gap-3 rounded-md border border-border bg-card px-4 py-3 text-sm", children: [_jsx("span", { className: "font-medium text-foreground", children: "Live printstatus:" }), _jsx(Badge, { variant: aan ? "default" : "secondary", children: aan ? "aan" : "uit" }), _jsx("span", { className: "text-muted-foreground", children: aan
20
+ ? s?.laatstOntvangenOp
21
+ ? `laatste melding ${new Date(s.laatstOntvangenOp).toLocaleString("nl-NL")}`
22
+ : "gekoppeld, wacht op de eerste melding van PrintNode"
23
+ : s?.fout ?? "nog niet gekoppeld — de status wordt elke paar minuten opgevraagd" }), s?.melding && _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", s.melding] }), _jsx(Button, { size: "sm", variant: "outline", className: "ml-auto", disabled: bezig, onClick: async () => {
24
+ setBezig(true);
25
+ try {
26
+ const r = await koppel();
27
+ if (r.ok)
28
+ toast.success("Live printstatus opnieuw gekoppeld");
29
+ else
30
+ toast.error(r.reden);
31
+ }
32
+ finally {
33
+ setBezig(false);
34
+ qc.invalidateQueries({ queryKey: ["printnode-webhook-status"] });
35
+ }
36
+ }, children: bezig ? "Bezig…" : "Opnieuw koppelen" })] }));
37
+ }
@@ -1,79 +1,79 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // ============================================================================
3
- // src/components/NetwerkPrintersDialog.tsx — printers uit het netwerk zoeken,
4
- // toevoegen of koppelen aan een bestaande printer.
5
- // ============================================================================
6
- import { useCallback, useEffect, useState } from "react";
7
- import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, toast, } from "@flowselections/core";
8
- import { Loader2, Printer, RefreshCw } from "lucide-react";
9
- import { supabase } from "../lib/supabaseClient.js";
10
- import { netwerkPrinters } from "../lib/printService.functions.js";
11
- export function NetwerkPrintersDialog({ open, onOpenChange, huidigePrinterId, huidigePrinterNaam, onKlaar, }) {
12
- const [bezig, setBezig] = useState(false);
13
- const [fout, setFout] = useState(null);
14
- const [lijst, setLijst] = useState([]);
15
- const zoeken = useCallback(async () => {
16
- setBezig(true);
17
- setFout(null);
18
- try {
19
- const res = await netwerkPrinters({ data: undefined });
20
- if (!res.ok) {
21
- setFout(res.fout);
22
- setLijst([]);
23
- }
24
- else {
25
- setLijst(Array.isArray(res.printers) ? res.printers : []);
26
- }
27
- }
28
- catch {
29
- setFout("Er kon niet in het netwerk worden gezocht. Probeer het zo opnieuw.");
30
- }
31
- finally {
32
- setBezig(false);
33
- }
34
- }, []);
35
- useEffect(() => {
36
- if (open)
37
- void zoeken();
38
- }, [open, zoeken]);
39
- const toevoegen = async (p) => {
40
- try {
41
- const { error } = await supabase.from("printers").insert({
42
- naam: p.naam,
43
- systeemnaam: p.naam,
44
- locatie: p.computer,
45
- omschrijving: p.omschrijving,
46
- online: p.online,
47
- actief: true,
48
- extern_printer_id: p.id,
49
- });
50
- if (error)
51
- throw error;
52
- toast.success(`${p.naam} is toegevoegd`);
53
- onKlaar?.();
54
- onOpenChange(false);
55
- }
56
- catch {
57
- toast.error("Toevoegen mislukt");
58
- }
59
- };
60
- const koppelen = async (p) => {
61
- if (!huidigePrinterId)
62
- return;
63
- try {
64
- const { error } = await supabase
65
- .from("printers")
66
- .update({ extern_printer_id: p.id, systeemnaam: p.naam, locatie: p.computer })
67
- .eq("id", huidigePrinterId);
68
- if (error)
69
- throw error;
70
- toast.success("Printer gekoppeld");
71
- onKlaar?.();
72
- onOpenChange(false);
73
- }
74
- catch {
75
- toast.error("Koppelen mislukt");
76
- }
77
- };
78
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Printers in het netwerk" }), _jsx(DialogDescription, { children: "Kies een printer om toe te voegen. Staat een printer er niet bij? Zet dan de computer aan waar de printer op is aangesloten." })] }), _jsxs("div", { className: "space-y-2", children: [bezig && (_jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Zoeken\u2026"] })), !bezig && fout && _jsx("p", { className: "text-destructive text-sm", children: fout }), !bezig && !fout && lijst.length === 0 && (_jsx("p", { className: "text-muted-foreground text-sm", children: "Er zijn geen printers gevonden." })), lijst.map((p) => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border p-3", children: [_jsx(Printer, { className: "text-muted-foreground h-5 w-5" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: p.naam }), _jsxs("p", { className: "text-muted-foreground truncate text-xs", children: [p.computer || "Onbekende computer", " \u00B7 ", p.online ? "online" : "offline"] })] }), huidigePrinterId && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void koppelen(p), children: ["Koppelen aan ", huidigePrinterNaam ?? "printer"] })), _jsx(Button, { size: "sm", onClick: () => void toevoegen(p), children: "Toevoegen" })] }, p.id)))] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { variant: "ghost", onClick: () => void zoeken(), disabled: bezig, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), " Opnieuw zoeken"] }) })] }) }));
79
- }
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/NetwerkPrintersDialog.tsx — printers uit het netwerk zoeken,
4
+ // toevoegen of koppelen aan een bestaande printer.
5
+ // ============================================================================
6
+ import { useCallback, useEffect, useState } from "react";
7
+ import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, toast, } from "@flowselections/core";
8
+ import { Loader2, Printer, RefreshCw } from "lucide-react";
9
+ import { supabase } from "../lib/supabaseClient.js";
10
+ import { netwerkPrinters } from "../lib/printService.functions.js";
11
+ export function NetwerkPrintersDialog({ open, onOpenChange, huidigePrinterId, huidigePrinterNaam, onKlaar, }) {
12
+ const [bezig, setBezig] = useState(false);
13
+ const [fout, setFout] = useState(null);
14
+ const [lijst, setLijst] = useState([]);
15
+ const zoeken = useCallback(async () => {
16
+ setBezig(true);
17
+ setFout(null);
18
+ try {
19
+ const res = await netwerkPrinters({ data: undefined });
20
+ if (!res.ok) {
21
+ setFout(res.fout);
22
+ setLijst([]);
23
+ }
24
+ else {
25
+ setLijst(Array.isArray(res.printers) ? res.printers : []);
26
+ }
27
+ }
28
+ catch {
29
+ setFout("Er kon niet in het netwerk worden gezocht. Probeer het zo opnieuw.");
30
+ }
31
+ finally {
32
+ setBezig(false);
33
+ }
34
+ }, []);
35
+ useEffect(() => {
36
+ if (open)
37
+ void zoeken();
38
+ }, [open, zoeken]);
39
+ const toevoegen = async (p) => {
40
+ try {
41
+ const { error } = await supabase.from("printers").insert({
42
+ naam: p.naam,
43
+ systeemnaam: p.naam,
44
+ locatie: p.computer,
45
+ omschrijving: p.omschrijving,
46
+ online: p.online,
47
+ actief: true,
48
+ extern_printer_id: p.id,
49
+ });
50
+ if (error)
51
+ throw error;
52
+ toast.success(`${p.naam} is toegevoegd`);
53
+ onKlaar?.();
54
+ onOpenChange(false);
55
+ }
56
+ catch {
57
+ toast.error("Toevoegen mislukt");
58
+ }
59
+ };
60
+ const koppelen = async (p) => {
61
+ if (!huidigePrinterId)
62
+ return;
63
+ try {
64
+ const { error } = await supabase
65
+ .from("printers")
66
+ .update({ extern_printer_id: p.id, systeemnaam: p.naam, locatie: p.computer })
67
+ .eq("id", huidigePrinterId);
68
+ if (error)
69
+ throw error;
70
+ toast.success("Printer gekoppeld");
71
+ onKlaar?.();
72
+ onOpenChange(false);
73
+ }
74
+ catch {
75
+ toast.error("Koppelen mislukt");
76
+ }
77
+ };
78
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Printers in het netwerk" }), _jsx(DialogDescription, { children: "Kies een printer om toe te voegen. Staat een printer er niet bij? Zet dan de computer aan waar de printer op is aangesloten." })] }), _jsxs("div", { className: "space-y-2", children: [bezig && (_jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Zoeken\u2026"] })), !bezig && fout && _jsx("p", { className: "text-destructive text-sm", children: fout }), !bezig && !fout && lijst.length === 0 && (_jsx("p", { className: "text-muted-foreground text-sm", children: "Er zijn geen printers gevonden." })), lijst.map((p) => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border p-3", children: [_jsx(Printer, { className: "text-muted-foreground h-5 w-5" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: p.naam }), _jsxs("p", { className: "text-muted-foreground truncate text-xs", children: [p.computer || "Onbekende computer", " \u00B7 ", p.online ? "online" : "offline"] })] }), huidigePrinterId && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void koppelen(p), children: ["Koppelen aan ", huidigePrinterNaam ?? "printer"] })), _jsx(Button, { size: "sm", onClick: () => void toevoegen(p), children: "Toevoegen" })] }, p.id)))] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { variant: "ghost", onClick: () => void zoeken(), disabled: bezig, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), " Opnieuw zoeken"] }) })] }) }));
79
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"PrintQueuePage.d.ts","sourceRoot":"","sources":["../../src/components/PrintQueuePage.tsx"],"names":[],"mappings":"AAkFA,wBAAgB,cAAc,gCA4T7B"}
1
+ {"version":3,"file":"PrintQueuePage.d.ts","sourceRoot":"","sources":["../../src/components/PrintQueuePage.tsx"],"names":[],"mappings":"AAkFA,wBAAgB,cAAc,gCAiX7B"}
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo, useRef, useState } from "react";
2
+ import { useEffect, 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
6
  import { PrintStatusBanner } from "./PrintStatusBanner.js";
7
- import { maakPrintJob, updateJobStatus, useLayouts, usePrinters, usePrintJobs, usePrintPlekNamen, } from "../lib/useDocumentData.js";
8
- import { annuleerPrintOpdrachten, printStatus } from "../lib/printService.functions.js";
7
+ import { useLayouts, usePrinters, usePrintJobs, usePrintPlekNamen, } from "../lib/useDocumentData.js";
8
+ import { annuleerPrintOpdrachten, opnieuwPrinten, printTellingen } from "../lib/printService.functions.js";
9
+ import { opnieuwActie, printnodeStatusLabel, SCANBARE_SOORTEN } from "../lib/printnodeStatus.js";
10
+ import { supabase } from "../lib/supabaseClient.js";
9
11
  import { vraagWachtrijRonde } from "../lib/queueTickClient.js";
10
12
  import { documentTypeLabel } from "../lib/documentTypes.js";
11
13
  import { jobPlekLabel } from "../lib/printQueue.js";
@@ -73,12 +75,58 @@ export function PrintQueuePage() {
73
75
  return false;
74
76
  return true;
75
77
  });
76
- const tellingen = useMemo(() => {
77
- const t = { wachtend: 0, bezig: 0, voltooid: 0, mislukt: 0, geannuleerd: 0 };
78
- for (const j of jobs)
79
- t[j.status] = (t[j.status] ?? 0) + 1;
80
- return t;
81
- }, [jobs]);
78
+ // Echte tellingen over alle opdrachten (de lijst toont alleen de laatste 200).
79
+ const [tellingen, setTellingen] = useState({});
80
+ const herlaadTellingen = async () => {
81
+ try {
82
+ const t = await printTellingen();
83
+ if (t && typeof t === "object")
84
+ setTellingen(t);
85
+ }
86
+ catch {
87
+ /* tellingen blijven staan */
88
+ }
89
+ };
90
+ const printerById = useMemo(() => new Map(printers.map((p) => [p.id, p])), [printers]);
91
+ // "Gescand": goedgekeurde controlescan van de kar (match op cart_id). Alleen lezen.
92
+ const [gescand, setGescand] = useState(new Set());
93
+ const cartIds = useMemo(() => Array.from(new Set(jobs
94
+ .filter((j) => SCANBARE_SOORTEN.has(j.documenttype))
95
+ .map((j) => String(j.payload?.["cart_id"] ?? ""))
96
+ .filter((c) => c.length > 0))).slice(0, 200), [jobs]);
97
+ useEffect(() => {
98
+ if (cartIds.length === 0)
99
+ return;
100
+ let actief = true;
101
+ void supabase
102
+ .from("logistics_control_scan")
103
+ .select("cart_id")
104
+ .in("cart_id", cartIds)
105
+ .not("approved_at", "is", null)
106
+ .then(({ data }) => {
107
+ if (!actief)
108
+ return;
109
+ const rijen = Array.isArray(data) ? data : [];
110
+ setGescand(new Set(rijen.map((r) => String(r.cart_id ?? "")).filter(Boolean)));
111
+ });
112
+ return () => {
113
+ actief = false;
114
+ };
115
+ }, [cartIds]);
116
+ // Live bijwerken (webhook en wachtrij schrijven in print_jobs).
117
+ useEffect(() => {
118
+ const kanaal = supabase
119
+ .channel("printwachtrij-live")
120
+ .on("postgres_changes", { event: "*", schema: "public", table: "print_jobs" }, () => {
121
+ void reload(true);
122
+ void herlaadTellingen();
123
+ })
124
+ .subscribe();
125
+ return () => {
126
+ void supabase.removeChannel(kanaal);
127
+ };
128
+ // eslint-disable-next-line react-hooks/exhaustive-deps
129
+ }, [reload]);
82
130
  const jobsRef = useRef(jobs);
83
131
  jobsRef.current = jobs;
84
132
  const lopende = jobs.some((j) => j.status === "wachtend" || j.status === "bezig");
@@ -94,19 +142,9 @@ export function PrintQueuePage() {
94
142
  setTickFout(e instanceof Error ? e.message : "Achtergrondprinten werkt nu niet.");
95
143
  }
96
144
  }
97
- const open = jobsRef.current.filter((j) => j.extern_job_id && (j.status === "bezig" || j.status === "wachtend"));
98
- for (const j of open) {
99
- try {
100
- const res = await printStatus({ data: { externJobId: String(j.extern_job_id) } });
101
- if (!res.ok)
102
- break;
103
- if (res.status !== j.status)
104
- await updateJobStatus(j.id, res.status);
105
- }
106
- catch {
107
- break;
108
- }
109
- }
145
+ // Statussen worden op de server bijgewerkt (webhook + terugval-polling);
146
+ // deze pagina schrijft zelf niets.
147
+ await herlaadTellingen();
110
148
  await reload(true);
111
149
  };
112
150
  const bijgewerkt = useAutoRefresh(ronde, lopende ? 5000 : 20000);
@@ -137,31 +175,28 @@ export function PrintQueuePage() {
137
175
  ? layoutNaam.get(j.gebruikte_layout_id) ?? "Onbekende layout"
138
176
  : j.gebruikte_bron
139
177
  ? "geen layout"
140
- : "nog niet verwerkt" }), j.gebruikte_bron && (_jsx(Badge, { variant: "outline", className: "ml-2", children: BRON_LABEL[j.gebruikte_bron] ?? j.gebruikte_bron }))] })] }), _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 () => {
141
- if (!j.layout_id)
142
- return;
178
+ : "nog niet verwerkt" }), j.gebruikte_bron && (_jsx(Badge, { variant: "outline", className: "ml-2", children: BRON_LABEL[j.gebruikte_bron] ?? j.gebruikte_bron }))] })] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: j.printer_id ? printerNaam.get(j.printer_id) ?? "—" : "—" }), _jsxs(TableCell, { children: [_jsx(Badge, { variant: STATUS_VARIANT[j.status] ?? "outline", children: printnodeStatusLabel(j) }), gescand.has(String(j.payload?.["cart_id"] ?? "")) &&
179
+ SCANBARE_SOORTEN.has(j.documenttype) &&
180
+ !j.is_herprint && j.status === "voltooid" && (_jsx(Badge, { variant: "default", className: "ml-2", children: "Gescand" })), j.printnode_bericht && j.status === "mislukt" && (_jsx("span", { className: "block text-xs text-destructive", children: j.printnode_bericht }))] }), _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: [opnieuwActie(j) && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: async () => {
181
+ const pr = j.printer_id ? printerById.get(j.printer_id) : null;
182
+ if (pr && !pr.online) {
183
+ const door = window.confirm(`Printer "${pr.naam}" (of de computer) is nu offline. De opdracht blijft wachten tot hij weer aan staat, en verloopt anders. Toch versturen?`);
184
+ if (!door)
185
+ return;
186
+ }
143
187
  try {
144
- await maakPrintJob({
145
- layout_id: j.layout_id,
146
- printer_id: j.printer_id,
147
- documenttype: j.documenttype,
148
- documentnaam: j.documentnaam ?? "Herprint",
149
- kopieen: j.kopieen ?? 1,
150
- is_herprint: true,
151
- bron_job_id: j.id,
152
- payload: {
153
- ...(j.payload && typeof j.payload === "object" ? j.payload : {}),
154
- is_herprint: true,
155
- bron_job_id: j.id,
156
- },
157
- });
188
+ const r = await opnieuwPrinten({ data: { jobId: j.id } });
189
+ if (!r.ok) {
190
+ toast.error(r.fout);
191
+ return;
192
+ }
158
193
  toast.success("Opnieuw in de wachtrij gezet");
159
- void reload();
194
+ void reload(true);
160
195
  }
161
- catch {
162
- toast.error("Herprint mislukt");
196
+ catch (fout) {
197
+ toast.error(fout instanceof Error ? fout.message : "Opnieuw printen mislukt");
163
198
  }
164
- }, children: [_jsx(RotateCcw, { className: "mr-2 h-4 w-4" }), " Opnieuw"] }), (j.status === "wachtend" || j.status === "bezig") && (_jsx(Button, { variant: "ghost", size: "sm", onClick: async () => {
199
+ }, children: [_jsx(RotateCcw, { className: "mr-2 h-4 w-4" }), opnieuwActie(j) === "opnieuw_proberen" ? "Opnieuw proberen" : "Opnieuw printen"] })), (j.status === "wachtend" || j.status === "bezig") && (_jsx(Button, { variant: "ghost", size: "sm", onClick: async () => {
165
200
  try {
166
201
  const resultaat = await annuleerPrintOpdrachten({ data: { jobIds: [j.id] } });
167
202
  if (resultaat.externMislukt > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"PrintenPage.d.ts","sourceRoot":"","sources":["../../src/components/PrintenPage.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,WAAW,CAAC,EAC1B,GAAoB,GACrB,EAAE;IACD,GAAG,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC;CACpC,+BAsBA"}
1
+ {"version":3,"file":"PrintenPage.d.ts","sourceRoot":"","sources":["../../src/components/PrintenPage.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,WAAW,CAAC,EAC1B,GAAoB,GACrB,EAAE;IACD,GAAG,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC;CACpC,+BAuBA"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // ============================================================================
3
3
  // src/components/PrintenPage.tsx — één menu-item "Printen" met tabbladen voor
4
4
  // printplekken (plekken, computers en printers samen) en standaardregels.
@@ -8,6 +8,7 @@ import { MapPin, Route as RouteIcon } from "lucide-react";
8
8
  import { ModuleTabs } from "./ModuleTabs.js";
9
9
  import { PrintplekkenPage } from "./PrintplekkenPage.js";
10
10
  import { DocumentRouteringPage } from "./DocumentRouteringPage.js";
11
+ import { LivePrintstatusRegel } from "./LivePrintstatusRegel.js";
11
12
  const TABS = [
12
13
  {
13
14
  id: "printplekken",
@@ -18,5 +19,5 @@ const TABS = [
18
19
  ];
19
20
  export function PrintenPage({ tab = "printplekken", }) {
20
21
  const [actiefTab, setActiefTab] = useState(tab);
21
- return (_jsx(_Fragment, { children: _jsx(ModuleTabs, { titel: "Printen", omschrijving: "Stel per plek in het bedrijf in welke computers, printers en documenten daar horen.", tabs: TABS, actiefTab: actiefTab, onTabWijzig: (nieuwTab) => setActiefTab(nieuwTab === "routering" ? "routering" : "printplekken"), children: actiefTab === "routering" ? (_jsx(DocumentRouteringPage, { ingebed: true })) : (_jsx(PrintplekkenPage, { ingebed: true })) }) }));
22
+ return (_jsx(_Fragment, { children: _jsxs(ModuleTabs, { titel: "Printen", omschrijving: "Stel per plek in het bedrijf in welke computers, printers en documenten daar horen.", tabs: TABS, actiefTab: actiefTab, onTabWijzig: (nieuwTab) => setActiefTab(nieuwTab === "routering" ? "routering" : "printplekken"), children: [_jsx(LivePrintstatusRegel, {}), actiefTab === "routering" ? (_jsx(DocumentRouteringPage, { ingebed: true })) : (_jsx(PrintplekkenPage, { ingebed: true }))] }) }));
22
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PrintplekDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/PrintplekDetailPage.tsx"],"names":[],"mappings":"AA8DA,wBAAgB,mBAAmB,gCAmmBlC"}
1
+ {"version":3,"file":"PrintplekDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/PrintplekDetailPage.tsx"],"names":[],"mappings":"AA+DA,wBAAgB,mBAAmB,gCAooBlC"}
@@ -19,6 +19,7 @@ import { DOCUMENT_TYPES, documentTypeLabel } from "../lib/documentTypes.js";
19
19
  import { drukTestpaginaAf } from "../lib/testpagina.js";
20
20
  import { TestprintDialog } from "./TestprintDialog.js";
21
21
  import { leesbareFout } from "../lib/foutTekst.js";
22
+ import { supabase } from "../lib/supabaseClient.js";
22
23
  import { useRouteringOverzicht } from "../lib/printRoutering.js";
23
24
  const BRON_LABEL = {
24
25
  printplek: "Printplek-eigen",
@@ -35,6 +36,30 @@ export function PrintplekDetailPage() {
35
36
  const [verwijderOpen, setVerwijderOpen] = useState(false);
36
37
  const { plekken, computers: plekComputers, regels, koppelingen, toegewezen, reload } = usePrintplekken();
37
38
  const { computers, reload: herlaadComputers } = useComputers();
39
+ // Live computerstatus uit de PrintNode-webhook, op PrintNode-computer-id.
40
+ const [statusPerId, setStatusPerId] = useState(new Map());
41
+ useEffect(() => {
42
+ const laad = async () => {
43
+ const { data } = await supabase.from("print_computer_status").select("extern_computer_id, verbonden");
44
+ const rijen = Array.isArray(data) ? data : [];
45
+ setStatusPerId(new Map(rijen.map((r) => [String(r.extern_computer_id), !!r.verbonden])));
46
+ };
47
+ void laad();
48
+ const kanaal = supabase
49
+ .channel("computerstatus-live")
50
+ .on("postgres_changes", { event: "*", schema: "public", table: "print_computer_status" }, () => void laad())
51
+ .subscribe();
52
+ return () => {
53
+ void supabase.removeChannel(kanaal);
54
+ };
55
+ }, []);
56
+ const computerAan = (naam) => {
57
+ const k = computers.find((x) => x.naam === naam);
58
+ if (!k)
59
+ return false;
60
+ const live = statusPerId.get(String(k.id));
61
+ return live ?? k.online;
62
+ };
38
63
  const { printers } = usePrinters();
39
64
  const { gevonden } = useNetwerkPrinters();
40
65
  const [naamBewerken, setNaamBewerken] = useState(false);
@@ -47,6 +72,9 @@ export function PrintplekDetailPage() {
47
72
  const [teVerwijderen, setTeVerwijderen] = useState(null);
48
73
  const plek = plekken.find((p) => p.id === plekId) ?? null;
49
74
  const eigenComputers = plekComputers.filter((c) => c.print_plek_id === plekId);
75
+ const uitgevallen = eigenComputers
76
+ .filter((c) => computers.some((k) => k.naam === c.computer_naam) && !computerAan(c.computer_naam))
77
+ .map((c) => c.computer_naam);
50
78
  const vrijeComputers = computers.filter((c) => !plekComputers.some((pc) => pc.computer_naam === c.naam));
51
79
  const andereplekken = plekken.filter((p) => p.id !== plekId);
52
80
  // De live scan bepaalt alleen online-status en op welke computer een printer hangt.
@@ -186,7 +214,7 @@ export function PrintplekDetailPage() {
186
214
  }, children: _jsx(Check, { className: "h-4 w-4" }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => setNaamBewerken(false), children: _jsx(X, { className: "h-4 w-4" }) })] })) : (_jsx("h1", { className: "cursor-pointer text-xl font-semibold", onClick: () => {
187
215
  setNaam(plek.naam);
188
216
  setNaamBewerken(true);
189
- }, children: plek.naam })), _jsx("p", { className: "text-muted-foreground text-sm", children: plek.omschrijving ?? "Hier stel je alles in wat er op deze plek wordt afgedrukt." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => setTestprintOpen(true), children: [_jsx(Printer, { className: "mr-2 h-4 w-4" }), " Testprint controleren"] }), _jsxs(Button, { variant: "ghost", className: "text-destructive", onClick: () => setVerwijderOpen(true), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), " Verwijderen"] })] })] }), _jsx(PrintplekVerwijderenDialog, { plek: verwijderOpen ? { id: plek.id, naam: plek.naam } : null, onOpenChange: setVerwijderOpen, onKlaar: () => void navigate({ to: "/layouts-printers/printplekken" }) }), _jsx(TestprintDialog, { open: testprintOpen, onOpenChange: setTestprintOpen, printplekId: plekId, printplekNaam: plek.naam }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Monitor, { className: "h-4 w-4" }), " Computers op deze plek"] }), _jsx(CardDescription, { children: "Het bolletje laat zien of de computer nu aan staat." })] }), vrijeComputers.length > 0 ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("select", { className: selectKlasse, value: computerToevoegen, onChange: (e) => setComputerToevoegen(e.target.value), "aria-label": "Computer kiezen", children: [_jsx("option", { value: "", children: "Computer kiezen\u2026" }), vrijeComputers.map((c) => (_jsx("option", { value: c.naam, children: c.naam }, c.id)))] }), _jsxs(Button, { size: "sm", disabled: !computerToevoegen, onClick: async () => {
217
+ }, children: plek.naam })), _jsx("p", { className: "text-muted-foreground text-sm", children: plek.omschrijving ?? "Hier stel je alles in wat er op deze plek wordt afgedrukt." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => setTestprintOpen(true), children: [_jsx(Printer, { className: "mr-2 h-4 w-4" }), " Testprint controleren"] }), _jsxs(Button, { variant: "ghost", className: "text-destructive", onClick: () => setVerwijderOpen(true), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), " Verwijderen"] })] })] }), _jsx(PrintplekVerwijderenDialog, { plek: verwijderOpen ? { id: plek.id, naam: plek.naam } : null, onOpenChange: setVerwijderOpen, onKlaar: () => void navigate({ to: "/layouts-printers/printplekken" }) }), _jsx(TestprintDialog, { open: testprintOpen, onOpenChange: setTestprintOpen, printplekId: plekId, printplekNaam: plek.naam }), uitgevallen.length > 0 && (_jsxs("div", { role: "alert", className: "rounded-lg border border-destructive bg-destructive/10 px-4 py-3 text-sm text-destructive", children: [_jsx("strong", { children: "Computer niet verbonden:" }), " ", uitgevallen.join(", "), ". Opdrachten voor deze plek worden pas geprint als de computer weer verbonden is, en verlopen anders."] })), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Monitor, { className: "h-4 w-4" }), " Computers op deze plek"] }), _jsx(CardDescription, { children: "Het bolletje laat zien of de computer nu aan staat." })] }), vrijeComputers.length > 0 ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("select", { className: selectKlasse, value: computerToevoegen, onChange: (e) => setComputerToevoegen(e.target.value), "aria-label": "Computer kiezen", children: [_jsx("option", { value: "", children: "Computer kiezen\u2026" }), vrijeComputers.map((c) => (_jsx("option", { value: c.naam, children: c.naam }, c.id)))] }), _jsxs(Button, { size: "sm", disabled: !computerToevoegen, onClick: async () => {
190
218
  try {
191
219
  await koppelComputerAanPrintplek(plekId, computerToevoegen);
192
220
  setComputerToevoegen("");
@@ -196,7 +224,7 @@ export function PrintplekDetailPage() {
196
224
  toast.error("Toevoegen mislukt");
197
225
  }
198
226
  }, children: [_jsx(Plus, { className: "mr-1 h-4 w-4" }), " Computer toevoegen"] })] })) : null] }), _jsx(CardContent, { className: "space-y-2", children: eigenComputers.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nog geen computers gekoppeld." })) : (eigenComputers.map((c) => {
199
- const aan = computers.find((k) => k.naam === c.computer_naam)?.online ?? false;
227
+ const aan = computerAan(c.computer_naam);
200
228
  return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border px-3 py-2 text-sm", children: [_jsx("span", { className: `h-2 w-2 rounded-full ${aan ? "bg-emerald-500" : "bg-muted-foreground"}` }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: c.computer_naam }), _jsx("span", { className: "text-muted-foreground text-xs", children: aan ? "Aan" : "Uit" }), _jsxs(Button, { size: "sm", variant: "ghost", className: "text-destructive", onClick: () => setTeVerwijderen(c.computer_naam), children: [_jsx(Trash2, { className: "mr-1 h-3.5 w-3.5" }), " Verwijderen"] })] }, c.id));
201
229
  })) })] }), _jsx(ComputerVerwijderenDialog, { open: !!teVerwijderen, onOpenChange: (v) => !v && setTeVerwijderen(null), computerNaam: teVerwijderen, printplekId: plekId, printers: teVerwijderenPrinters, andereplekken: andereplekken, onKlaar: () => {
202
230
  void herlaadComputers(true);