@flowselections/pick-schermen-kwekers 1.0.6 → 1.0.8
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.
- package/README.md +24 -24
- package/dist-lib/components/TemplateModulePage.d.ts +2 -0
- package/dist-lib/components/TemplateModulePage.d.ts.map +1 -0
- package/dist-lib/components/TemplateModulePage.js +44 -0
- package/dist-lib/components/pick/PickScherm.d.ts.map +1 -1
- package/dist-lib/components/pick/PickScherm.js +70 -29
- package/dist-lib/hooks/usePrintJob.d.ts +22 -0
- package/dist-lib/hooks/usePrintJob.d.ts.map +1 -0
- package/dist-lib/hooks/usePrintJob.js +48 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +890 -21
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +890 -21
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +890 -21
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +891 -22
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +12 -1
- package/dist-lib/lib/layoutMaker/pakbonHtml.js +49 -49
- package/dist-lib/lib/layoutRenderer.js +17 -17
- package/dist-lib/lib/pickApi.d.ts +11 -2
- package/dist-lib/lib/pickApi.d.ts.map +1 -1
- package/dist-lib/lib/pickApi.js +38 -2
- package/dist-lib/lib/pickPrint.d.ts +3 -15
- package/dist-lib/lib/pickPrint.d.ts.map +1 -1
- package/dist-lib/lib/pickPrint.js +9 -161
- package/dist-lib/lib/pickPrintJobText.d.ts +17 -0
- package/dist-lib/lib/pickPrintJobText.d.ts.map +1 -0
- package/dist-lib/lib/pickPrintJobText.js +49 -0
- package/dist-lib/lib/pickTypes.d.ts +17 -0
- package/dist-lib/lib/pickTypes.d.ts.map +1 -1
- 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 @@
|
|
|
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":"
|
|
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
|
|
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
|
|
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
|
|
51
|
-
|
|
52
|
-
await completeLogisticsTaskForItem(item,
|
|
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
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
/**
|
|
97
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|