@flowselections/pick-schermen-kwekers 1.0.6 → 1.0.7

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 (32) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/components/TemplateModulePage.d.ts +2 -0
  3. package/dist-lib/components/TemplateModulePage.d.ts.map +1 -0
  4. package/dist-lib/components/TemplateModulePage.js +44 -0
  5. package/dist-lib/components/pick/PickScherm.d.ts.map +1 -1
  6. package/dist-lib/components/pick/PickScherm.js +70 -29
  7. package/dist-lib/hooks/usePrintJob.d.ts +22 -0
  8. package/dist-lib/hooks/usePrintJob.d.ts.map +1 -0
  9. package/dist-lib/hooks/usePrintJob.js +48 -0
  10. package/dist-lib/integrations/supabase/auth-middleware.d.ts +890 -21
  11. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  12. package/dist-lib/integrations/supabase/client.d.ts +890 -21
  13. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/client.server.d.ts +890 -21
  15. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/types.d.ts +891 -22
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/types.js +12 -1
  19. package/dist-lib/lib/layoutMaker/pakbonHtml.js +49 -49
  20. package/dist-lib/lib/layoutRenderer.js +17 -17
  21. package/dist-lib/lib/pickApi.d.ts +11 -2
  22. package/dist-lib/lib/pickApi.d.ts.map +1 -1
  23. package/dist-lib/lib/pickApi.js +38 -2
  24. package/dist-lib/lib/pickPrint.d.ts +3 -15
  25. package/dist-lib/lib/pickPrint.d.ts.map +1 -1
  26. package/dist-lib/lib/pickPrint.js +9 -161
  27. package/dist-lib/lib/pickPrintJobText.d.ts +17 -0
  28. package/dist-lib/lib/pickPrintJobText.d.ts.map +1 -0
  29. package/dist-lib/lib/pickPrintJobText.js +49 -0
  30. package/dist-lib/lib/pickTypes.d.ts +17 -0
  31. package/dist-lib/lib/pickTypes.d.ts.map +1 -1
  32. package/package.json +2 -2
package/README.md CHANGED
@@ -1,24 +1,24 @@
1
- # pick schermen kwekers
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/f7462658-1d60-42f1-8e22-dbefe36f44ef).
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
- ```
1
+ # pick schermen kwekers
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/f7462658-1d60-42f1-8e22-dbefe36f44ef).
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
+ ```
@@ -0,0 +1,2 @@
1
+ export declare function TemplateModulePage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=TemplateModulePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateModulePage.d.ts","sourceRoot":"","sources":["../../src/components/TemplateModulePage.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,kBAAkB,gCAmFjC"}
@@ -0,0 +1,44 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/TemplateModulePage.tsx — EXAMPLE PAGE COMPONENT
4
+ // ============================================================================
5
+ // Pagina-componenten horen in src/components/, NIET alleen in src/routes/.
6
+ // src/routes/ wordt NIET naar npm gepubliceerd (zie tsconfig.build.json exclude).
7
+ //
8
+ // GEEN <Header /> hier — die zit in _authenticated.tsx via staticData.title
9
+ // ============================================================================
10
+ import { useState, useEffect } from "react";
11
+ import { useAuth, supabase, Button, Card, CardContent, CardHeader, CardTitle, toast, } from "@flowselections/core";
12
+ import { TemplateModuleLogo } from './TemplateModuleLogo';
13
+ export function TemplateModulePage() {
14
+ // Auth is guaranteed — this user is already logged in
15
+ const { user, isAdmin } = useAuth();
16
+ // Example state — replace with your own
17
+ const [items, setItems] = useState([]);
18
+ const [loading, setLoading] = useState(true);
19
+ useEffect(() => {
20
+ loadData();
21
+ }, []);
22
+ const loadData = async () => {
23
+ setLoading(true);
24
+ try {
25
+ // Example query — replace "products" with your table
26
+ const { data, error } = await supabase
27
+ .from("products")
28
+ .select("*")
29
+ .limit(10);
30
+ if (error)
31
+ throw error;
32
+ // ✅ Altijd Array.isArray bewaken op server-function data
33
+ // const safeItems = Array.isArray(data?.items) ? data.items : [];
34
+ setItems(Array.isArray(data) ? data : []);
35
+ }
36
+ catch {
37
+ toast.error("Fout bij het laden van gegevens");
38
+ }
39
+ finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [isAdmin && (_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Ingelogd als admin: ", user?.email] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Overzicht" }) }), _jsxs(CardContent, { children: [loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen gegevens gevonden." })) : (_jsxs("p", { className: "text-sm", children: [items.length, " items geladen."] })), _jsx(Button, { onClick: loadData, className: "mt-4", disabled: loading, children: "Vernieuwen" })] })] }), _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(TemplateModuleLogo, { className: "w-6 h-6 text-primary" }), _jsx("span", { children: "SVG-component (voorkeur voor vector-logo's)" })] })] }));
44
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"PickScherm.d.ts","sourceRoot":"","sources":["../../../src/components/pick/PickScherm.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAUtE,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,+BAmVpD"}
1
+ {"version":3,"file":"PickScherm.d.ts","sourceRoot":"","sources":["../../../src/components/pick/PickScherm.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAYtE,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,+BAgapD"}
@@ -1,14 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { Badge, Button, Card, CardContent, Input, toast } from "@flowselections/core";
4
- import { ArrowLeft, Globe, Loader2, Printer, RefreshCw, Search, WifiOff } from "lucide-react";
3
+ import { Badge, Button, Card, CardContent, Input, toast, } from "@flowselections/core";
4
+ import { AlertTriangle, ArrowLeft, CheckCircle2, Globe, Loader2, Printer, RefreshCw, Search, WifiOff, } from "lucide-react";
5
5
  import { LanguageTiles } from "./LanguageTiles";
6
6
  import { QueueTable } from "./QueueTable";
7
7
  import { getSessionId, pickApi } from "../../lib/pickApi";
8
- import { completeLogisticsTaskForItem, documentTypeOf, EmptyLayoutError, MissingLayoutError, printQueueItem, } from "../../lib/pickPrint";
8
+ import { completeLogisticsTaskForItem } from "../../lib/pickPrint";
9
9
  import { getPickTranslator } from "../../lib/pickI18n";
10
+ import { getPrintJobTexts, printJobStatusLabel } from "../../lib/pickPrintJobText";
10
11
  import { defaultFilters, usePickHeartbeat, usePickQueue, } from "../../hooks/usePickQueue";
11
12
  import { usePickDocumentTypes, usePickStatuses } from "../../hooks/usePickConfig";
13
+ import { usePrintJob } from "../../hooks/usePrintJob";
12
14
  const LANG_STORAGE_KEY = "pick.language";
13
15
  /**
14
16
  * Pickscherm. De Logistiek Engine bepaalt welke documenten zichtbaar zijn en
@@ -20,14 +22,15 @@ export function PickScherm({ station, onBack }) {
20
22
  const [filters, setFilters] = useState(defaultFilters);
21
23
  const [busyId, setBusyId] = useState(null);
22
24
  const [pending, setPending] = useState(null);
25
+ const [lastPrinted, setLastPrinted] = useState(null);
26
+ const resetDone = useRef(null);
23
27
  const autoFetched = useRef(false);
24
28
  const { data: statuses } = usePickStatuses();
25
29
  const { data: documentTypes } = usePickDocumentTypes();
26
30
  const { online } = usePickHeartbeat(station.id, null, sessionId);
31
+ const { tracked, track, clear: clearJob } = usePrintJob();
27
32
  const t = useMemo(() => getPickTranslator(lang ?? "nl"), [lang]);
28
- const layoutMissingMessage = useCallback((documentType) => t.lang === "nl"
29
- ? `Geen layout ingesteld voor ${documentType}. Stel deze in in de Layout maker (pakbonnen, verzendbrieven, extra).`
30
- : `No layout configured for ${documentType}. Set it up in the Layout maker.`, [t]);
33
+ const jobTexts = useMemo(() => getPrintJobTexts(t.lang), [t]);
31
34
  useEffect(() => {
32
35
  if (typeof window === "undefined")
33
36
  return;
@@ -47,26 +50,48 @@ export function PickScherm({ station, onBack }) {
47
50
  enabled: lang !== null,
48
51
  });
49
52
  const runPrint = useCallback(async (item) => {
50
- const printed = await printQueueItem(item, t.locale);
51
- await pickApi.completePrint(item.id, null, sessionId);
52
- await completeLogisticsTaskForItem(item, printed);
53
+ const updated = await pickApi.completePrint(item.id, sessionId);
54
+ const jobId = updated?.print_job_id ?? null;
55
+ await completeLogisticsTaskForItem(item, jobId);
56
+ setLastPrinted(updated ?? item);
57
+ if (jobId) {
58
+ resetDone.current = null;
59
+ track({
60
+ jobId,
61
+ queueItemId: item.id,
62
+ documentNumber: (updated ?? item).document_number ?? null,
63
+ });
64
+ }
53
65
  toast.success(`${item.document_number ?? t("document")} ${t("sentToPrinter")}`);
54
66
  refresh();
55
- }, [sessionId, refresh, t]);
56
- /** Zet een printfout om in een begrijpelijke melding, zonder ooit zelf op te maken. */
57
- const showPrintError = useCallback((e, item) => {
58
- if (e instanceof EmptyLayoutError) {
59
- toast.error(t.lang === "nl"
60
- ? `Layout heeft nog geen inhoud (${e.layoutName}).`
61
- : `Layout has no content yet (${e.layoutName}).`);
67
+ }, [sessionId, refresh, t, track]);
68
+ /** Mislukte printopdracht: document terugzetten zodat het weer in de wachtrij komt. */
69
+ useEffect(() => {
70
+ const job = tracked?.job;
71
+ if (!tracked || !job)
62
72
  return;
63
- }
64
- if (e instanceof MissingLayoutError) {
65
- toast.error(layoutMissingMessage(e.documentType || documentTypeOf(item)));
73
+ if (job.status !== "mislukt" && job.status !== "geannuleerd")
66
74
  return;
67
- }
75
+ if (resetDone.current === tracked.jobId)
76
+ return;
77
+ resetDone.current = tracked.jobId;
78
+ void (async () => {
79
+ try {
80
+ await pickApi.resetPrint(tracked.queueItemId, sessionId);
81
+ toast.error(`${jobTexts.failed}: ${job.foutmelding ?? jobTexts.unknown}`);
82
+ }
83
+ catch (e) {
84
+ toast.error(e instanceof Error ? e.message : jobTexts.failed);
85
+ }
86
+ finally {
87
+ refresh();
88
+ }
89
+ })();
90
+ }, [tracked, sessionId, refresh, jobTexts]);
91
+ /** Zet een printfout om in een begrijpelijke melding. */
92
+ const showPrintError = useCallback((e) => {
68
93
  toast.error(e instanceof Error ? e.message : t("notAvailable"));
69
- }, [layoutMissingMessage, t]);
94
+ }, [t]);
70
95
  const handlePick = useCallback(async (item) => {
71
96
  setBusyId(item.id);
72
97
  try {
@@ -80,7 +105,7 @@ export function PickScherm({ station, onBack }) {
80
105
  await runPrint(claimed ?? item);
81
106
  }
82
107
  catch (e) {
83
- showPrintError(e, item);
108
+ showPrintError(e);
84
109
  try {
85
110
  await pickApi.releaseLock(item.id);
86
111
  }
@@ -93,21 +118,32 @@ export function PickScherm({ station, onBack }) {
93
118
  setBusyId(null);
94
119
  }
95
120
  }, [station.id, station.lock_timeout_seconds, sessionId, runPrint, refresh, showPrintError]);
96
- /** Vraagt de Logistiek Engine om de volgende opdracht en laat deze ter bevestiging zien. */
97
- /** Print een al geprint document opnieuw, zonder de wachtrijstatus te wijzigen. */
121
+ /**
122
+ * Zet een nieuwe printopdracht in de wachtrij voor een al geprint document,
123
+ * zonder de wachtrijstatus te wijzigen.
124
+ */
98
125
  const handleReprint = useCallback(async (item) => {
99
126
  setBusyId(item.id);
100
127
  try {
101
- await printQueueItem(item, t.locale);
128
+ const jobId = await pickApi.reprint(item.id, sessionId);
129
+ setLastPrinted(item);
130
+ if (jobId) {
131
+ resetDone.current = jobId;
132
+ track({
133
+ jobId,
134
+ queueItemId: item.id,
135
+ documentNumber: item.document_number ?? null,
136
+ });
137
+ }
102
138
  toast.success(`${item.document_number ?? t("document")} ${t("sentToPrinter")}`);
103
139
  }
104
140
  catch (e) {
105
- showPrintError(e, item);
141
+ showPrintError(e);
106
142
  }
107
143
  finally {
108
144
  setBusyId(null);
109
145
  }
110
- }, [t, showPrintError]);
146
+ }, [t, sessionId, track, showPrintError]);
111
147
  const fetchNext = useCallback(async () => {
112
148
  setBusyId("next");
113
149
  try {
@@ -147,7 +183,7 @@ export function PickScherm({ station, onBack }) {
147
183
  await fetchNext();
148
184
  }
149
185
  catch (e) {
150
- showPrintError(e, item);
186
+ showPrintError(e);
151
187
  try {
152
188
  await pickApi.releaseLock(item.id);
153
189
  }
@@ -194,5 +230,10 @@ export function PickScherm({ station, onBack }) {
194
230
  return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold text-foreground", children: station.name }), _jsx("p", { className: "text-sm text-muted-foreground", children: t("chooseLanguageHint") })] }), onBack && (_jsxs(Button, { variant: "outline", onClick: onBack, children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), " ", t("back")] }))] }), _jsx(LanguageTiles, { onSelect: chooseLang })] }));
195
231
  }
196
232
  const totalPages = Math.max(1, Math.ceil(total / filters.pageSize));
197
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Button, { variant: "outline", size: "lg", onClick: () => setLang(null), children: [_jsx(Globe, { className: "mr-2 h-5 w-5" }), " ", t("changeLanguage")] }), _jsxs("div", { children: [_jsx("h2", { className: "text-xl font-bold text-foreground", children: station.name }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [pendingCount, " ", t("statusToPrint"), " \u00B7 ", printedCount, " ", t("statusPrinted")] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [!online && (_jsxs(Badge, { className: "bg-destructive text-destructive-foreground", children: [_jsx(WifiOff, { className: "mr-1 h-3.5 w-3.5" }), " ", t("offline")] })), _jsx(Button, { variant: "outline", size: "icon", onClick: refresh, "aria-label": t("refresh"), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsxs(Button, { size: "lg", className: "h-14 px-8 text-lg", onClick: () => (pending ? confirmPending() : fetchNext()), disabled: busyId !== null, children: [busyId !== null ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), t("printNext"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] })] }), pending && (_jsx(Card, { className: "border-2 border-primary", children: _jsxs(CardContent, { className: "flex flex-wrap items-center justify-between gap-4 p-5", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: t("confirmPrintTitle") }), _jsx("p", { className: "text-2xl font-bold text-foreground", children: pending.document_number ?? t("document") }), _jsx("p", { className: "text-sm text-muted-foreground", children: [pending.order_number, pending.customer_name].filter(Boolean).join(" · ") })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", size: "lg", onClick: cancelPending, disabled: busyId !== null, children: t("cancel") }), _jsxs(Button, { size: "lg", className: "h-14 px-8 text-lg", onClick: confirmPending, disabled: busyId !== null, children: [busyId === "confirm" ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), t("print")] })] })] }) })), _jsx(Card, { children: _jsx(CardContent, { className: "flex flex-wrap items-center gap-3 p-4", children: _jsxs("div", { className: "relative min-w-64 flex-1", 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: t("search"), value: filters.search, onChange: (e) => setFilters((f) => ({ ...f, search: e.target.value, page: 0 })) })] }) }) }), loading ? (_jsxs("div", { className: "flex items-center justify-center py-16 text-muted-foreground", children: [_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" }), " ", t("loadingQueue")] })) : (_jsx(QueueTable, { items: items, lockedIds: lockedIds, statuses: Array.isArray(statuses) ? statuses : [], documentTypes: Array.isArray(documentTypes) ? documentTypes : [], busyId: busyId, onPick: handlePick, onReprint: handleReprint, t: t })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm text-muted-foreground", children: [t("page"), " ", filters.page + 1, " ", t("of"), " ", totalPages] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", disabled: filters.page === 0, onClick: () => setFilters((f) => ({ ...f, page: Math.max(0, f.page - 1) })), children: t("previous") }), _jsx(Button, { variant: "outline", disabled: filters.page + 1 >= totalPages, onClick: () => setFilters((f) => ({ ...f, page: f.page + 1 })), children: t("next") })] })] })] }));
233
+ const jobStatus = tracked?.job?.status ?? null;
234
+ const jobFailed = jobStatus === "mislukt" || jobStatus === "geannuleerd";
235
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Button, { variant: "outline", size: "lg", onClick: () => setLang(null), children: [_jsx(Globe, { className: "mr-2 h-5 w-5" }), " ", t("changeLanguage")] }), _jsxs("div", { children: [_jsx("h2", { className: "text-xl font-bold text-foreground", children: station.name }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [pendingCount, " ", t("statusToPrint"), " \u00B7 ", printedCount, " ", t("statusPrinted")] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [!online && (_jsxs(Badge, { className: "bg-destructive text-destructive-foreground", children: [_jsx(WifiOff, { className: "mr-1 h-3.5 w-3.5" }), " ", t("offline")] })), _jsx(Button, { variant: "outline", size: "icon", onClick: refresh, "aria-label": t("refresh"), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsxs(Button, { size: "lg", className: "h-14 px-8 text-lg", onClick: () => (pending ? confirmPending() : fetchNext()), disabled: busyId !== null, children: [busyId !== null ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), t("printNext"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] })] }), pending && (_jsx(Card, { className: "border-2 border-primary", children: _jsxs(CardContent, { className: "flex flex-wrap items-center justify-between gap-4 p-5", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: t("confirmPrintTitle") }), _jsx("p", { className: "text-2xl font-bold text-foreground", children: pending.document_number ?? t("document") }), _jsx("p", { className: "text-sm text-muted-foreground", children: [pending.order_number, pending.customer_name].filter(Boolean).join(" · ") })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", size: "lg", onClick: cancelPending, disabled: busyId !== null, children: t("cancel") }), _jsxs(Button, { size: "lg", className: "h-14 px-8 text-lg", onClick: confirmPending, disabled: busyId !== null, children: [busyId === "confirm" ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), t("print")] })] })] }) })), tracked && (_jsx(Card, { className: jobFailed ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex flex-wrap items-center justify-between gap-4 p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [jobFailed ? (_jsx(AlertTriangle, { className: "h-6 w-6 text-destructive" })) : jobStatus === "voltooid" ? (_jsx(CheckCircle2, { className: "h-6 w-6 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" })), _jsxs("div", { children: [_jsxs("p", { className: "text-sm font-medium text-muted-foreground", children: [jobTexts.jobTitle, " ", tracked.documentNumber ? `· ${tracked.documentNumber}` : ""] }), _jsx("p", { className: "text-lg font-semibold text-foreground", children: printJobStatusLabel(jobStatus, t.lang) }), tracked.job?.printer_naam && (_jsxs("p", { className: "text-sm text-muted-foreground", children: [jobTexts.printerLabel, ": ", tracked.job.printer_naam] })), jobFailed && (_jsxs("p", { className: "text-sm text-destructive", children: [tracked.job?.foutmelding ?? jobTexts.unknown, " \u00B7 ", jobTexts.requeued] }))] })] }), _jsxs("div", { className: "flex gap-2", children: [jobFailed && lastPrinted && (_jsxs(Button, { size: "lg", onClick: () => {
236
+ clearJob();
237
+ void handlePick(lastPrinted);
238
+ }, disabled: busyId !== null, children: [_jsx(Printer, { className: "mr-2 h-5 w-5" }), " ", jobTexts.retry] })), _jsx(Button, { variant: "outline", size: "lg", onClick: clearJob, children: jobTexts.close })] })] }) })), _jsx(Card, { children: _jsx(CardContent, { className: "flex flex-wrap items-center gap-3 p-4", children: _jsxs("div", { className: "relative min-w-64 flex-1", 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: t("search"), value: filters.search, onChange: (e) => setFilters((f) => ({ ...f, search: e.target.value, page: 0 })) })] }) }) }), loading ? (_jsxs("div", { className: "flex items-center justify-center py-16 text-muted-foreground", children: [_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" }), " ", t("loadingQueue")] })) : (_jsx(QueueTable, { items: items, lockedIds: lockedIds, statuses: Array.isArray(statuses) ? statuses : [], documentTypes: Array.isArray(documentTypes) ? documentTypes : [], busyId: busyId, onPick: handlePick, onReprint: handleReprint, t: t })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm text-muted-foreground", children: [t("page"), " ", filters.page + 1, " ", t("of"), " ", totalPages] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", disabled: filters.page === 0, onClick: () => setFilters((f) => ({ ...f, page: Math.max(0, f.page - 1) })), children: t("previous") }), _jsx(Button, { variant: "outline", disabled: filters.page + 1 >= totalPages, onClick: () => setFilters((f) => ({ ...f, page: f.page + 1 })), children: t("next") })] })] })] }));
198
239
  }
@@ -0,0 +1,22 @@
1
+ import type { PickPrintJob } from "../lib/pickTypes";
2
+ export interface TrackedPrintJob {
3
+ jobId: string;
4
+ queueItemId: string;
5
+ documentNumber: string | null;
6
+ job: PickPrintJob | null;
7
+ }
8
+ /**
9
+ * Volgt de printopdracht die pick_complete_print heeft weggeschreven. De module
10
+ * print zelf niets: de Layout maker werkt print_jobs bij, hier wordt alleen de
11
+ * status gelezen (realtime + korte poll als terugval).
12
+ */
13
+ export declare function usePrintJob(): {
14
+ tracked: TrackedPrintJob | null;
15
+ track: (args: {
16
+ jobId: string;
17
+ queueItemId: string;
18
+ documentNumber: string | null;
19
+ }) => void;
20
+ clear: () => void;
21
+ };
22
+ //# sourceMappingURL=usePrintJob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePrintJob.d.ts","sourceRoot":"","sources":["../../src/hooks/usePrintJob.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,WAAW;;kBAKhB;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;;EAgD/E"}
@@ -0,0 +1,48 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { pickApi, pickDb } from "../lib/pickApi";
3
+ /**
4
+ * Volgt de printopdracht die pick_complete_print heeft weggeschreven. De module
5
+ * print zelf niets: de Layout maker werkt print_jobs bij, hier wordt alleen de
6
+ * status gelezen (realtime + korte poll als terugval).
7
+ */
8
+ export function usePrintJob() {
9
+ const [tracked, setTracked] = useState(null);
10
+ const jobIdRef = useRef(null);
11
+ const track = useCallback((args) => {
12
+ jobIdRef.current = args.jobId;
13
+ setTracked({ ...args, job: null });
14
+ }, []);
15
+ const clear = useCallback(() => {
16
+ jobIdRef.current = null;
17
+ setTracked(null);
18
+ }, []);
19
+ const jobId = tracked?.jobId ?? null;
20
+ useEffect(() => {
21
+ if (!jobId)
22
+ return;
23
+ let stopped = false;
24
+ const read = async () => {
25
+ try {
26
+ const job = await pickApi.printJobStatus(jobId);
27
+ if (stopped || jobIdRef.current !== jobId)
28
+ return;
29
+ setTracked((prev) => (prev && prev.jobId === jobId ? { ...prev, job } : prev));
30
+ }
31
+ catch {
32
+ // status tijdelijk niet leesbaar; volgende poging volgt
33
+ }
34
+ };
35
+ void read();
36
+ const timer = window.setInterval(read, 2000);
37
+ const channel = pickDb
38
+ .channel(`print_job_${jobId}`)
39
+ .on("postgres_changes", { event: "*", schema: "public", table: "print_jobs", filter: `id=eq.${jobId}` }, () => void read())
40
+ .subscribe();
41
+ return () => {
42
+ stopped = true;
43
+ window.clearInterval(timer);
44
+ pickDb.removeChannel(channel);
45
+ };
46
+ }, [jobId]);
47
+ return { tracked, track, clear };
48
+ }