@flowselections/pick-schermen-kwekers 1.0.19 → 1.0.20
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/pick/PickScherm.d.ts.map +1 -1
- package/dist-lib/components/pick/PickScherm.js +322 -69
- package/dist-lib/components/pick/QueueTable.d.ts +6 -1
- package/dist-lib/components/pick/QueueTable.d.ts.map +1 -1
- package/dist-lib/components/pick/QueueTable.js +30 -22
- package/dist-lib/hooks/useShippingNoteStatus.d.ts +27 -0
- package/dist-lib/hooks/useShippingNoteStatus.d.ts.map +1 -0
- package/dist-lib/hooks/useShippingNoteStatus.js +132 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +740 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +740 -1
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +740 -1
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +743 -2
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -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 +10 -0
- package/dist-lib/lib/pickApi.d.ts.map +1 -1
- package/dist-lib/lib/pickApi.js +21 -0
- package/dist-lib/lib/pickTypes.d.ts +59 -2
- package/dist-lib/lib/pickTypes.d.ts.map +1 -1
- package/dist-lib/lib/pickTypes.js +149 -24
- package/dist-lib/lib/shippingNoteStatus.d.ts +35 -0
- package/dist-lib/lib/shippingNoteStatus.d.ts.map +1 -0
- package/dist-lib/lib/shippingNoteStatus.js +118 -0
- package/package.json +1 -1
- package/dist-lib/components/TemplateModulePage.d.ts +0 -2
- package/dist-lib/components/TemplateModulePage.d.ts.map +0 -1
- package/dist-lib/components/TemplateModulePage.js +0 -44
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
|
+
```
|
|
@@ -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":"AAqCA,OAAO,EAUL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AA0B7B,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAeD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,+BAgrDpD"}
|
|
@@ -1,26 +1,30 @@
|
|
|
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 { useQuery } from "@tanstack/react-query";
|
|
3
4
|
import { Badge, Button, Card, CardContent, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
|
|
4
|
-
import { AlertTriangle, ArrowLeft, CheckCircle2, ChevronLeft, ChevronRight, Globe, Loader2, Printer, RefreshCw, Search, WifiOff, Zap, } from "lucide-react";
|
|
5
|
+
import { AlertTriangle, ArrowLeft, CheckCircle2, ChevronLeft, ChevronRight, FileDown, Globe, Loader2, Printer, RefreshCw, Search, WifiOff, Zap, } from "lucide-react";
|
|
5
6
|
import { LanguageTiles } from "./LanguageTiles";
|
|
6
7
|
import { QueueTable } from "./QueueTable";
|
|
7
8
|
import { PreviewDialog } from "./PreviewDialog";
|
|
8
9
|
import { ShippingNoteScan } from "./ShippingNoteScan";
|
|
9
10
|
import { getSessionId, pickApi } from "../../lib/pickApi";
|
|
11
|
+
import { sendLogisticsEvent } from "../../lib/logisticsApi";
|
|
10
12
|
import { completeLogisticsTaskForItem } from "../../lib/pickPrint";
|
|
11
|
-
import { isPdfOnlyFailure, printedCartIdsOf, pdfSaveWarning, } from "../../lib/pickTypes";
|
|
13
|
+
import { isPdfOnlyFailure, pickReadiness, printedCartIdsOf, pdfSaveWarning, unitRefOf, unitTitleOf, } from "../../lib/pickTypes";
|
|
12
14
|
import { getPickTranslator } from "../../lib/pickI18n";
|
|
13
15
|
import { friendlyPickError, getPickUiTexts } from "../../lib/pickUiText";
|
|
14
16
|
import { getPrintJobTexts, printJobStatusLabel } from "../../lib/pickPrintJobText";
|
|
15
17
|
import { defaultFilters, usePickDays, usePickHeartbeat, usePickQueue, } from "../../hooks/usePickQueue";
|
|
16
18
|
import { usePickDocumentTypes, usePickStatuses, useStationPrintPlek, } from "../../hooks/usePickConfig";
|
|
17
19
|
import { usePrintBatch, usePrintJob } from "../../hooks/usePrintJob";
|
|
20
|
+
import { useShippingNoteStatus } from "../../hooks/useShippingNoteStatus";
|
|
21
|
+
import { isWaitingShippingNote, shippingNoteMessage, shippingNoteSourceLabel, } from "../../lib/shippingNoteStatus";
|
|
18
22
|
const LANG_STORAGE_KEY = "pick.language";
|
|
19
23
|
/** Melding als de planning voor deze order geen karren heeft vastgelegd. */
|
|
20
24
|
/** Papier is geprint, maar de PDF-kopie op de werkplek is niet opgeslagen. */
|
|
21
25
|
const PDF_SAVE_NOTICE = "De pakbon is geprint, maar de PDF is niet op de werkplek opgeslagen. Geef de PDF-map in Layout maker opnieuw vrij.";
|
|
22
|
-
/** Melding als de
|
|
23
|
-
const
|
|
26
|
+
/** Melding als de regel nog niet compleet is aangeleverd door de bron. */
|
|
27
|
+
const NOT_READY_NOTICE = "Deze pakbon is nog niet printklaar. Klik op \"Pakbon ophalen\" om de ontbrekende gegevens bij de bron op te vragen.";
|
|
24
28
|
/**
|
|
25
29
|
* Pickscherm. De Logistiek Engine bepaalt welke documenten zichtbaar zijn en
|
|
26
30
|
* in welke volgorde; dit scherm toont uitsluitend wat de wachtrij teruggeeft.
|
|
@@ -36,6 +40,14 @@ export function PickScherm({ station, onBack }) {
|
|
|
36
40
|
const [pending, setPending] = useState(null);
|
|
37
41
|
const [cartPlan, setCartPlan] = useState(null);
|
|
38
42
|
const [planReason, setPlanReason] = useState(null);
|
|
43
|
+
const [planStatus, setPlanStatus] = useState(null);
|
|
44
|
+
const [fetchingData, setFetchingData] = useState(false);
|
|
45
|
+
const [readinessById, setReadinessById] = useState({});
|
|
46
|
+
/** Voortgang van de pakboncontrole: hoeveel van de zichtbare regels zijn gecontroleerd. */
|
|
47
|
+
const [checkProgress, setCheckProgress] = useState({
|
|
48
|
+
done: 0,
|
|
49
|
+
total: 0,
|
|
50
|
+
});
|
|
39
51
|
const [activeCartId, setActiveCartId] = useState(null);
|
|
40
52
|
const [lastPrinted, setLastPrinted] = useState(null);
|
|
41
53
|
const [previewItem, setPreviewItem] = useState(null);
|
|
@@ -48,9 +60,62 @@ export function PickScherm({ station, onBack }) {
|
|
|
48
60
|
const { data: documentTypes } = usePickDocumentTypes();
|
|
49
61
|
const { online } = usePickHeartbeat(station.id, null, sessionId);
|
|
50
62
|
const { data: printPlek, isLoading: plekLoading } = useStationPrintPlek(station.id);
|
|
51
|
-
const { tracked, track, clear: clearJob } = usePrintJob();
|
|
52
|
-
const { batch, trackBatch, clearBatch } = usePrintBatch();
|
|
63
|
+
const { tracked, track: trackJobRaw, clear: clearJob } = usePrintJob();
|
|
64
|
+
const { batch, trackBatch: trackBatchRaw, clearBatch } = usePrintBatch();
|
|
65
|
+
const { shippingNotes, watchShippingNotes, clearShippingNotes, refreshShippingNotesNow, } = useShippingNoteStatus();
|
|
53
66
|
const finalizedBatch = useRef(null);
|
|
67
|
+
const [fetchingNotes, setFetchingNotes] = useState(false);
|
|
68
|
+
/**
|
|
69
|
+
* Handmatig alle openstaande verzendbrieven laten ophalen. Eén aanroep per
|
|
70
|
+
* klik; idempotent en alleen-lezen richting Floriday.
|
|
71
|
+
*/
|
|
72
|
+
const fetchShippingNotes = useCallback(async () => {
|
|
73
|
+
if (fetchingNotes)
|
|
74
|
+
return;
|
|
75
|
+
setFetchingNotes(true);
|
|
76
|
+
try {
|
|
77
|
+
const result = await refreshShippingNotesNow();
|
|
78
|
+
if (result.ok) {
|
|
79
|
+
toast.success(result.message ?? "Verzendbrieven worden opgehaald.");
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
toast.info("Ophalen kon nu niet gestart worden — de verwerking gaat zelf verder.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
setFetchingNotes(false);
|
|
87
|
+
}
|
|
88
|
+
}, [fetchingNotes, refreshShippingNotesNow]);
|
|
89
|
+
/**
|
|
90
|
+
* Elke geslaagde pakbonprint start ook de statuscontrole van de verzendbrief
|
|
91
|
+
* bij dezelfde kar. Het teruggegeven print_job-id is de sleutel; opvragen is
|
|
92
|
+
* alleen-lezen en maakt nooit een nieuwe aanvraag of printopdracht.
|
|
93
|
+
*/
|
|
94
|
+
const track = useCallback((args) => {
|
|
95
|
+
trackJobRaw(args);
|
|
96
|
+
watchShippingNotes([
|
|
97
|
+
{ jobId: args.jobId, cartId: args.cartId ?? null, cartLabel: args.cartLabel ?? null },
|
|
98
|
+
]);
|
|
99
|
+
}, [trackJobRaw, watchShippingNotes]);
|
|
100
|
+
const trackBatch = useCallback((args) => {
|
|
101
|
+
trackBatchRaw(args);
|
|
102
|
+
watchShippingNotes(args.jobs);
|
|
103
|
+
}, [trackBatchRaw, watchShippingNotes]);
|
|
104
|
+
/**
|
|
105
|
+
* Ingestelde werkwijze van de Logistiek Engine. cartsPerPrint bepaalt hoeveel
|
|
106
|
+
* karren de batchknop in één klik print. Is er geen actieve werkwijze (null),
|
|
107
|
+
* dan komt er geen knop; dit scherm valt nooit terug op een eigen aantal.
|
|
108
|
+
*/
|
|
109
|
+
const { data: pickModeInfo } = useQuery({
|
|
110
|
+
queryKey: ["logistics-pick-mode"],
|
|
111
|
+
queryFn: () => pickApi.pickMode(),
|
|
112
|
+
refetchInterval: 30000,
|
|
113
|
+
staleTime: 15000,
|
|
114
|
+
});
|
|
115
|
+
const cartsPerPrint = pickModeInfo?.cartsPerPrint && pickModeInfo.cartsPerPrint > 1
|
|
116
|
+
? pickModeInfo.cartsPerPrint
|
|
117
|
+
: null;
|
|
118
|
+
const [batchPrinting, setBatchPrinting] = useState(false);
|
|
54
119
|
const t = useMemo(() => getPickTranslator(lang ?? "nl"), [lang]);
|
|
55
120
|
const jobTexts = useMemo(() => getPrintJobTexts(t.lang), [t]);
|
|
56
121
|
const ui = useMemo(() => getPickUiTexts(t.lang), [t]);
|
|
@@ -150,44 +215,149 @@ export function PickScherm({ station, onBack }) {
|
|
|
150
215
|
void chooseDay(iso === todayIso || (pickDay && iso === pickDay) ? "current" : iso);
|
|
151
216
|
}, [chosenDay, pickDay, chooseDay]);
|
|
152
217
|
/**
|
|
153
|
-
* Haalt het vastgelegde karplan
|
|
154
|
-
*
|
|
155
|
-
*
|
|
218
|
+
* Haalt het vastgelegde karplan én de toestand van de planning uit de bron.
|
|
219
|
+
* Er wordt niets verzonnen: is het plan er niet, of is het nog niet
|
|
220
|
+
* vrijgegeven, dan blijft de regel "wacht op gegevens".
|
|
156
221
|
*/
|
|
157
222
|
const loadCartPlan = useCallback(async (item) => {
|
|
158
|
-
|
|
223
|
+
const plan = await pickApi.queueCartPlan(item.id);
|
|
224
|
+
const ref = unitRefOf(item);
|
|
225
|
+
let status = null;
|
|
226
|
+
try {
|
|
227
|
+
status = ref ? await pickApi.cartPlanStatus(ref) : null;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
status = null;
|
|
231
|
+
}
|
|
232
|
+
setPlanStatus(status);
|
|
233
|
+
const readiness = pickReadiness(plan, status, item);
|
|
234
|
+
setPlanReason(readiness.ready ? null : readiness.reason);
|
|
159
235
|
if (!plan || plan.carts.length === 0) {
|
|
160
|
-
|
|
161
|
-
|
|
236
|
+
setCartPlan(null);
|
|
237
|
+
setActiveCartId(null);
|
|
238
|
+
return { plan: null, readiness };
|
|
239
|
+
}
|
|
240
|
+
setCartPlan(plan);
|
|
241
|
+
const printed = new Set(printedCartIdsOf(item));
|
|
242
|
+
setActiveCartId(plan.carts.find((cart) => !printed.has(cart.cartId))?.cartId ?? plan.carts[0].cartId);
|
|
243
|
+
return { plan, readiness };
|
|
244
|
+
}, []);
|
|
245
|
+
/** Printklaar volgens de bron: plan aanwezig, vrijgegeven en met barcodes. */
|
|
246
|
+
const readiness = useMemo(() => pickReadiness(cartPlan, planStatus, pending), [cartPlan, planStatus, pending]);
|
|
247
|
+
/**
|
|
248
|
+
* Controleert iedere zichtbare regel rechtstreeks bij de Engine. Tijdens de
|
|
249
|
+
* controle blijft printen dicht; alleen een actuele ready+released-status en
|
|
250
|
+
* een volledig karplan met barcodes geven de printknop vrij.
|
|
251
|
+
*/
|
|
252
|
+
const visibleIdsKey = useMemo(() => (Array.isArray(items) ? items.map((item) => item.id).join(",") : ""), [items]);
|
|
253
|
+
const itemsRef = useRef([]);
|
|
254
|
+
itemsRef.current = Array.isArray(items) ? items : [];
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
let cancelled = false;
|
|
257
|
+
const verifyVisibleRows = async () => {
|
|
258
|
+
const visibleItems = itemsRef.current;
|
|
259
|
+
setCheckProgress({ done: 0, total: visibleItems.length });
|
|
260
|
+
if (visibleItems.length === 0)
|
|
261
|
+
return;
|
|
262
|
+
let done = 0;
|
|
263
|
+
await Promise.all(visibleItems.map(async (item) => {
|
|
264
|
+
let entry = {
|
|
265
|
+
checking: false,
|
|
266
|
+
ready: false,
|
|
267
|
+
reason: "De bron kon de pakbon nog niet bevestigen.",
|
|
268
|
+
};
|
|
269
|
+
try {
|
|
270
|
+
const [plan, status] = await Promise.all([
|
|
271
|
+
pickApi.queueCartPlan(item.id),
|
|
272
|
+
pickApi.cartPlanStatus(unitRefOf(item)),
|
|
273
|
+
]);
|
|
274
|
+
const state = pickReadiness(plan, status, item);
|
|
275
|
+
entry = { checking: false, ready: state.ready, reason: state.reason };
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// fail closed: de regel blijft "Pakbon ophalen"
|
|
279
|
+
}
|
|
280
|
+
if (cancelled)
|
|
281
|
+
return;
|
|
282
|
+
done += 1;
|
|
283
|
+
setReadinessById((prev) => ({ ...prev, [item.id]: entry }));
|
|
284
|
+
setCheckProgress({ done, total: visibleItems.length });
|
|
285
|
+
}));
|
|
286
|
+
};
|
|
287
|
+
void verifyVisibleRows();
|
|
288
|
+
const timer = window.setInterval(() => void verifyVisibleRows(), 15000);
|
|
289
|
+
return () => {
|
|
290
|
+
cancelled = true;
|
|
291
|
+
window.clearInterval(timer);
|
|
292
|
+
};
|
|
293
|
+
}, [visibleIdsKey]);
|
|
294
|
+
/**
|
|
295
|
+
* Vraagt de ontbrekende gegevens op bij de module die ze moet leveren en
|
|
296
|
+
* blijft daarna kijken tot de regel printklaar is. Deze module maakt of
|
|
297
|
+
* wijzigt zelf nooit karren, nummers of barcodes.
|
|
298
|
+
*/
|
|
299
|
+
const requestData = useCallback(async (item) => {
|
|
300
|
+
setFetchingData(true);
|
|
301
|
+
const ref = unitRefOf(item);
|
|
302
|
+
try {
|
|
162
303
|
try {
|
|
163
304
|
await pickApi.reportMissingCartPlans();
|
|
164
|
-
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
165
|
-
plan = await pickApi.queueCartPlan(item.id);
|
|
166
305
|
}
|
|
167
306
|
catch {
|
|
168
307
|
/* bewaking is aanvullend, nooit blokkerend */
|
|
169
308
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
309
|
+
if (ref) {
|
|
310
|
+
try {
|
|
311
|
+
await sendLogisticsEvent({
|
|
312
|
+
event_type: "order.changed",
|
|
313
|
+
order_ref: ref,
|
|
314
|
+
shadow: false,
|
|
315
|
+
payload: {
|
|
316
|
+
reason: "pick.data_incomplete",
|
|
317
|
+
missing: pickReadiness(cartPlan, planStatus, item).missing,
|
|
318
|
+
queue_item_id: item.id,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
/* het seintje is aanvullend op de bewaking */
|
|
324
|
+
}
|
|
179
325
|
}
|
|
180
|
-
|
|
181
|
-
|
|
326
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
328
|
+
const plan = await pickApi.queueCartPlan(item.id);
|
|
329
|
+
let status = null;
|
|
330
|
+
try {
|
|
331
|
+
status = ref ? await pickApi.cartPlanStatus(ref) : null;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
status = null;
|
|
335
|
+
}
|
|
336
|
+
const next = pickReadiness(plan, status, item);
|
|
337
|
+
setPlanStatus(status);
|
|
338
|
+
setPlanReason(next.ready ? null : next.reason);
|
|
339
|
+
if (plan && plan.carts.length > 0) {
|
|
340
|
+
setCartPlan(plan);
|
|
341
|
+
const printed = new Set(printedCartIdsOf(item));
|
|
342
|
+
setActiveCartId(plan.carts.find((cart) => !printed.has(cart.cartId))?.cartId ?? plan.carts[0].cartId);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
setCartPlan(null);
|
|
346
|
+
setActiveCartId(null);
|
|
347
|
+
}
|
|
348
|
+
if (next.ready) {
|
|
349
|
+
toast.success("De pakbon is opgehaald — je kunt nu printen.");
|
|
350
|
+
refresh();
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
182
353
|
}
|
|
183
|
-
|
|
354
|
+
toast.warning(planReason ?? "De pakbon is nog niet compleet. De bron is op de hoogte gebracht.");
|
|
355
|
+
return false;
|
|
184
356
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return plan;
|
|
190
|
-
}, []);
|
|
357
|
+
finally {
|
|
358
|
+
setFetchingData(false);
|
|
359
|
+
}
|
|
360
|
+
}, [cartPlan, planStatus, planReason, refresh]);
|
|
191
361
|
/**
|
|
192
362
|
* Mislukte printopdracht: alleen de betreffende kar terugzetten. Ging alleen
|
|
193
363
|
* het opslaan van de PDF-kopie mis, dan is het papier wél geprint: de kar
|
|
@@ -328,18 +498,19 @@ export function PickScherm({ station, onBack }) {
|
|
|
328
498
|
* Printknop in de wachtrij: print in één keer alle pakbonnen (karren) van
|
|
329
499
|
* deze order. Nog niet geprint: claimen en alle karren in één batch.
|
|
330
500
|
* Al geprint: bewuste herdruk van alle karren.
|
|
331
|
-
*
|
|
501
|
+
* Is de regel nog niet printklaar, dan wordt er niet geprint maar worden de
|
|
502
|
+
* ontbrekende gegevens bij de bron opgevraagd.
|
|
332
503
|
*/
|
|
333
504
|
const printAllForItem = useCallback(async (item) => {
|
|
334
505
|
setBusyId(item.id);
|
|
335
506
|
try {
|
|
336
507
|
if (item.printed_at) {
|
|
337
|
-
const plan = await loadCartPlan(item);
|
|
508
|
+
const { plan, readiness: state } = await loadCartPlan(item);
|
|
338
509
|
setPending(item);
|
|
339
|
-
if (!plan) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
510
|
+
if (!plan || !state.ready) {
|
|
511
|
+
setBusyId(null);
|
|
512
|
+
toast.warning(state.reason ?? NOT_READY_NOTICE);
|
|
513
|
+
await requestData(item);
|
|
343
514
|
return;
|
|
344
515
|
}
|
|
345
516
|
const jobs = [];
|
|
@@ -361,6 +532,17 @@ export function PickScherm({ station, onBack }) {
|
|
|
361
532
|
refresh();
|
|
362
533
|
return;
|
|
363
534
|
}
|
|
535
|
+
// Ook bij een rij die zojuist als klaar is getoond, direct vóór het
|
|
536
|
+
// claimen opnieuw bij de Engine controleren. Een verouderde rij kan zo
|
|
537
|
+
// nooit alsnog een printopdracht starten.
|
|
538
|
+
const preflight = await loadCartPlan(item);
|
|
539
|
+
setPending(item);
|
|
540
|
+
if (!preflight.plan || !preflight.readiness.ready) {
|
|
541
|
+
setBusyId(null);
|
|
542
|
+
toast.warning(preflight.readiness.reason ?? NOT_READY_NOTICE);
|
|
543
|
+
await requestData(item);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
364
546
|
const claimed = await pickApi.claim({
|
|
365
547
|
itemId: item.id,
|
|
366
548
|
stationId: station.id,
|
|
@@ -369,14 +551,12 @@ export function PickScherm({ station, onBack }) {
|
|
|
369
551
|
timeout: station.lock_timeout_seconds ?? 120,
|
|
370
552
|
});
|
|
371
553
|
const target = claimed ?? item;
|
|
372
|
-
const plan = await loadCartPlan(target);
|
|
554
|
+
const { plan, readiness: state } = await loadCartPlan(target);
|
|
373
555
|
setPending(target);
|
|
374
|
-
if (!plan) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
setPending(printed);
|
|
379
|
-
refresh();
|
|
556
|
+
if (!plan || !state.ready) {
|
|
557
|
+
setBusyId(null);
|
|
558
|
+
toast.warning(state.reason ?? NOT_READY_NOTICE);
|
|
559
|
+
await requestData(target);
|
|
380
560
|
return;
|
|
381
561
|
}
|
|
382
562
|
const already = new Set(printedCartIdsOf(target));
|
|
@@ -416,11 +596,69 @@ export function PickScherm({ station, onBack }) {
|
|
|
416
596
|
station.lock_timeout_seconds,
|
|
417
597
|
sessionId,
|
|
418
598
|
loadCartPlan,
|
|
599
|
+
requestData,
|
|
419
600
|
trackBatch,
|
|
420
601
|
refresh,
|
|
421
602
|
t,
|
|
422
603
|
showPrintError,
|
|
423
604
|
]);
|
|
605
|
+
/**
|
|
606
|
+
* Batchknop: print de bovenste n printklare regels in de bestaande volgorde
|
|
607
|
+
* (volle karren voor deelkarren, zoals de lijst sorteert). Per regel draait
|
|
608
|
+
* exact dezelfde printactie als de knop op die regel; karren van een eenheid
|
|
609
|
+
* die in deze klik al geprint zijn, worden overgeslagen zodat elke kar
|
|
610
|
+
* precies één printactie krijgt. Niet-printklare karren worden overgeslagen
|
|
611
|
+
* met een leesbare melding per kar.
|
|
612
|
+
*/
|
|
613
|
+
const printNextCarts = useCallback(async () => {
|
|
614
|
+
if (!cartsPerPrint || batchPrinting)
|
|
615
|
+
return;
|
|
616
|
+
setBatchPrinting(true);
|
|
617
|
+
try {
|
|
618
|
+
const visible = (Array.isArray(items) ? items : []).filter((item) => !item.printed_at);
|
|
619
|
+
const targets = [];
|
|
620
|
+
const skipped = [];
|
|
621
|
+
for (const item of visible) {
|
|
622
|
+
if (targets.length >= cartsPerPrint)
|
|
623
|
+
break;
|
|
624
|
+
const state = readinessById[item.id];
|
|
625
|
+
if (!state || state.checking) {
|
|
626
|
+
skipped.push({ title: unitTitleOf(item), reason: "de pakbon wordt nog gecontroleerd" });
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (!state.ready) {
|
|
630
|
+
skipped.push({ title: unitTitleOf(item), reason: state.reason ?? "nog niet printklaar" });
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
targets.push(item);
|
|
634
|
+
}
|
|
635
|
+
if (targets.length === 0) {
|
|
636
|
+
toast.warning("Er is geen enkele kar printklaar.");
|
|
637
|
+
for (const s of skipped.slice(0, 5))
|
|
638
|
+
toast.warning(`${s.title}: ${s.reason}`);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (targets.length < cartsPerPrint) {
|
|
642
|
+
toast.info(`Er zijn maar ${targets.length} van de ${cartsPerPrint} karren printklaar; alleen die worden geprint.`);
|
|
643
|
+
}
|
|
644
|
+
for (const s of skipped.slice(0, 5))
|
|
645
|
+
toast.warning(`${s.title}: ${s.reason}`);
|
|
646
|
+
const doneUnits = new Set();
|
|
647
|
+
for (const item of targets) {
|
|
648
|
+
const unitKey = `${item.document_type_id ?? "-"}|${item.order_number ?? unitRefOf(item)}`;
|
|
649
|
+
if (doneUnits.has(unitKey))
|
|
650
|
+
continue;
|
|
651
|
+
await printAllForItem(item);
|
|
652
|
+
doneUnits.add(unitKey);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch (e) {
|
|
656
|
+
showPrintError(e);
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
setBatchPrinting(false);
|
|
660
|
+
}
|
|
661
|
+
}, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItem, showPrintError]);
|
|
424
662
|
const fetchNext = useCallback(async (silent = false) => {
|
|
425
663
|
const requestedDayVersion = daySelectionVersion.current;
|
|
426
664
|
setBusyId("next");
|
|
@@ -485,25 +723,10 @@ export function PickScherm({ station, onBack }) {
|
|
|
485
723
|
const plan = cartPlan;
|
|
486
724
|
if (!item)
|
|
487
725
|
return;
|
|
488
|
-
if (!plan) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
setBusyId("confirm");
|
|
495
|
-
try {
|
|
496
|
-
const printed = await pickApi.completePrint(item.id, sessionId);
|
|
497
|
-
setLastPrinted(printed);
|
|
498
|
-
setPending(printed);
|
|
499
|
-
refresh();
|
|
500
|
-
}
|
|
501
|
-
catch (e) {
|
|
502
|
-
showPrintError(e);
|
|
503
|
-
}
|
|
504
|
-
finally {
|
|
505
|
-
setBusyId(null);
|
|
506
|
-
}
|
|
726
|
+
if (!plan || !readiness.ready) {
|
|
727
|
+
// Niet printklaar: nooit printen, altijd eerst de gegevens opvragen.
|
|
728
|
+
toast.warning(readiness.reason ?? NOT_READY_NOTICE);
|
|
729
|
+
await requestData(item);
|
|
507
730
|
return;
|
|
508
731
|
}
|
|
509
732
|
const alreadyPrinted = new Set(printedCartIdsOf(item));
|
|
@@ -571,7 +794,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
571
794
|
}
|
|
572
795
|
}
|
|
573
796
|
}
|
|
574
|
-
}, [pending, cartPlan, handleReprint, sessionId, trackBatch, t, fetchNext, refresh, showPrintError]);
|
|
797
|
+
}, [pending, cartPlan, readiness, requestData, handleReprint, sessionId, trackBatch, t, fetchNext, refresh, showPrintError]);
|
|
575
798
|
/** Rond de order pas af wanneer alle batchopdrachten een eindstatus hebben. */
|
|
576
799
|
useEffect(() => {
|
|
577
800
|
if (!batch || batch.jobs.some((entry) => !entry.job || !["voltooid", "mislukt", "geannuleerd"].includes(entry.job.status)))
|
|
@@ -763,9 +986,26 @@ export function PickScherm({ station, onBack }) {
|
|
|
763
986
|
const jobFailed = jobStatus === "mislukt" || jobStatus === "geannuleerd";
|
|
764
987
|
const batchCompleted = batch?.jobs.filter((entry) => entry.job?.status === "voltooid").length ?? 0;
|
|
765
988
|
const batchFailed = batch?.jobs.filter((entry) => entry.job?.status === "mislukt" || entry.job?.status === "geannuleerd").length ?? 0;
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
989
|
+
/**
|
|
990
|
+
* Laadbalk: zolang de wachtrij laadt of de bron de pakbonnen nog bevestigt,
|
|
991
|
+
* ziet de picker hoe ver het klaarzetten is. De teksten in de regels blijven
|
|
992
|
+
* daardoor niet onverwacht "springen" zonder uitleg.
|
|
993
|
+
*/
|
|
994
|
+
const checkTotal = checkProgress.total;
|
|
995
|
+
const checkDone = Math.min(checkProgress.done, checkTotal);
|
|
996
|
+
const busyLoading = loading || loadingDay || (checkTotal > 0 && checkDone < checkTotal);
|
|
997
|
+
const loadPercent = loading || loadingDay
|
|
998
|
+
? 15
|
|
999
|
+
: checkTotal === 0
|
|
1000
|
+
? 100
|
|
1001
|
+
: Math.round((checkDone / checkTotal) * 100);
|
|
1002
|
+
return (_jsxs("div", { className: "space-y-4 pb-24 md:pb-0", children: [busyLoading && (_jsxs("div", { className: "space-y-1", role: "status", "aria-live": "polite", "aria-label": "Gegevens worden geladen", children: [_jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsx("span", { children: loading || loadingDay
|
|
1003
|
+
? "Wachtrij wordt geladen…"
|
|
1004
|
+
: `Pakbonnen controleren bij de bron (${checkDone} van ${checkTotal})` }), _jsxs("span", { className: "tabular-nums", children: [loadPercent, "%"] })] }), _jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-muted", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all duration-300", style: { width: `${Math.max(5, loadPercent)}%` } }) })] })), _jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:flex-wrap md:items-center md:justify-between", children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)] items-center gap-3", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 shrink-0 px-3 sm:px-4", onClick: () => setLang(null), "aria-label": t("changeLanguage"), children: [_jsx(Globe, { className: "h-5 w-5 sm:mr-2" }), _jsx("span", { className: "hidden sm:inline", children: t("changeLanguage") })] }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "truncate text-lg font-bold text-foreground sm:text-xl", children: station.name }), _jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [pendingCount, " ", t("statusToPrint"), " \u00B7 ", printedCount, " ", t("statusPrinted")] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => shiftDay(-1), disabled: loadingDay || !(chosenDay ?? pickDay), "aria-label": ui.pickDayPrev, children: [_jsx(ChevronLeft, { className: "h-5 w-5 sm:mr-1" }), _jsx("span", { className: "hidden sm:inline", children: ui.pickDayPrev })] }), _jsx("div", { className: "flex h-12 items-center rounded-md border bg-muted px-4 text-sm font-semibold text-foreground", children: chosenDay ? dayLabel(chosenDay) : ui.pickDayToday }), _jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => shiftDay(1), disabled: loadingDay || !(chosenDay ?? pickDay), "aria-label": ui.pickDayNext, children: [_jsx("span", { className: "hidden sm:inline", children: ui.pickDayNext }), _jsx(ChevronRight, { className: "h-5 w-5 sm:ml-1" })] }), chosenDay && (_jsx(Button, { variant: "secondary", size: "lg", className: "h-12 px-4", onClick: () => void chooseDay("current"), disabled: loadingDay, children: ui.pickDayBackToToday }))] }), _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", className: "h-12 w-12 shrink-0", onClick: refresh, "aria-label": t("refresh"), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsxs(Button, { variant: "outline", size: "lg", className: "hidden h-14 px-5 md:inline-flex", disabled: fetchingNotes, onClick: () => void fetchShippingNotes(), children: [fetchingNotes ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(FileDown, { className: "mr-2 h-5 w-5" })), fetchingNotes ? "Ophalen…" : "Verzendbrieven ophalen"] }), cartsPerPrint != null && (_jsxs(Button, { size: "lg", className: "hidden h-14 px-6 text-lg md:inline-flex", disabled: loading || batchPrinting || busyId != null, onClick: () => void printNextCarts(), children: [batchPrinting ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), "Print eerstvolgende ", cartsPerPrint, " karren"] })), _jsxs(Button, { size: "lg", className: "hidden h-14 px-8 text-lg md:inline-flex", onClick: () => (pending ? confirmPending() : fetchNext()), disabled: busyId !== null || fetchingData, children: [busyId !== null || fetchingData ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : pending && !readiness.ready ? (_jsx(RefreshCw, { className: "mr-2 h-5 w-5" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), pending && !readiness.ready ? "Pakbon ophalen" : t("printNext"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] })] }), !plekLoading && !printPlek && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border-2 border-destructive/50 bg-destructive/5 p-3 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("span", { children: "Nog geen printplek gekoppeld \u2014 de opdracht blijft in de wachtrij staan." })] })), _jsx(ShippingNoteScan, { stationId: station.id }), !pending && emptyNotice && (_jsx("p", { className: "text-sm text-muted-foreground", children: emptyNotice })), pending && (_jsxs(Card, { className: "border-2 border-primary", children: [_jsxs(CardContent, { className: "flex flex-col gap-4 p-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between sm:p-5", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: t("confirmPrintTitle") }), _jsx("p", { className: "truncate text-xl font-bold text-foreground sm:text-2xl", children: pending.document_number ?? t("document") }), _jsx("p", { className: "truncate text-sm text-muted-foreground", children: [pending.order_number, pending.customer_name].filter(Boolean).join(" · ") })] }), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [_jsx(Button, { variant: "outline", size: "lg", className: "h-12 w-full sm:w-auto", onClick: cancelPending, disabled: busyId !== null, children: t("cancel") }), _jsxs(Button, { size: "lg", className: "h-14 w-full px-8 text-lg sm:w-auto", onClick: confirmPending, disabled: busyId !== null || fetchingData, variant: readiness.ready ? "default" : "secondary", children: [busyId === "confirm" || fetchingData ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : readiness.ready ? (_jsx(Printer, { className: "mr-2 h-5 w-5" })) : (_jsx(RefreshCw, { className: "mr-2 h-5 w-5" })), readiness.ready ? t("print") : "Pakbon ophalen"] })] })] }), !readiness.ready && (_jsx(CardContent, { className: "border-t p-4 sm:p-5", children: _jsxs("div", { className: "flex items-start gap-2 rounded-md border bg-muted/30 p-3 text-sm text-muted-foreground", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("span", { children: fetchingData
|
|
1005
|
+
? "De pakbon wordt bij de bron opgevraagd. Zodra alles compleet is, verschijnt de printknop weer."
|
|
1006
|
+
: planReason
|
|
1007
|
+
? `${planReason} Klik op "Pakbon ophalen" om hem bij de bron op te vragen.`
|
|
1008
|
+
: NOT_READY_NOTICE })] }) })), cartPlan && (_jsx(CardContent, { className: "border-t p-4 sm:p-5", children: _jsxs("div", { className: "grid gap-3 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-semibold text-foreground", children: "Karren van deze order" }), _jsx("div", { className: "max-h-72 space-y-2 overflow-y-auto rounded-md border p-2", children: cartPlan.carts.map((cart) => {
|
|
769
1009
|
const done = printedCartIdsOf(pending).includes(cart.cartId);
|
|
770
1010
|
const active = cart.cartId === activeCartId;
|
|
771
1011
|
return (_jsxs("div", { role: "button", tabIndex: 0, onClick: () => setActiveCartId(cart.cartId), onKeyDown: (event) => {
|
|
@@ -787,5 +1027,18 @@ export function PickScherm({ station, onBack }) {
|
|
|
787
1027
|
})()] }) }))] })), batch && (_jsx(Card, { className: batchFailed > 0 ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex items-center gap-3 p-4", children: [batchCompleted + batchFailed === batch.jobs.length ? (batchFailed > 0 ? _jsx(AlertTriangle, { className: "h-6 w-6 shrink-0 text-destructive" }) : _jsx(CheckCircle2, { className: "h-6 w-6 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [jobTexts.jobTitle, batch.documentNumber ? ` · ${batch.documentNumber}` : ""] }), _jsxs("p", { className: "font-semibold text-foreground", children: [batchCompleted, " van ", batch.jobs.length, " pakbonnen voltooid", batchFailed > 0 ? ` · ${batchFailed} mislukt` : ""] })] })] }) })), tracked && (_jsx(Card, { className: jobFailed ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex flex-col gap-4 p-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [jobFailed ? (_jsx(AlertTriangle, { className: "h-6 w-6 shrink-0 text-destructive" })) : jobStatus === "voltooid" ? (_jsx(CheckCircle2, { className: "h-6 w-6 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm font-medium text-muted-foreground", children: [jobTexts.jobTitle, " ", tracked.documentNumber ? `· ${tracked.documentNumber}` : "", tracked.cartLabel ? ` · ${tracked.cartLabel}` : ""] }), _jsx("p", { className: "text-lg font-semibold text-foreground", children: printJobStatusLabel(jobStatus, t.lang) }), tracked.job?.printer_naam && (_jsxs("p", { className: "truncate 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] })), !jobFailed && pdfSaveWarning(tracked.job) && (_jsx("p", { className: "text-sm font-medium text-destructive", children: pdfSaveWarning(tracked.job) }))] })] }), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [jobFailed && lastPrinted && (_jsxs(Button, { size: "lg", className: "h-12 w-full sm:w-auto", onClick: () => {
|
|
788
1028
|
clearJob();
|
|
789
1029
|
void confirmPending();
|
|
790
|
-
}, disabled: busyId !== null || !pending, children: [_jsx(Printer, { className: "mr-2 h-5 w-5" }), " ", jobTexts.retry] })), _jsx(Button, { variant: "outline", size: "lg", className: "h-12 w-full sm:w-auto", onClick:
|
|
1030
|
+
}, disabled: busyId !== null || !pending, children: [_jsx(Printer, { className: "mr-2 h-5 w-5" }), " ", jobTexts.retry] })), _jsx(Button, { variant: "outline", size: "lg", className: "h-12 w-full sm:w-auto", onClick: () => {
|
|
1031
|
+
clearJob();
|
|
1032
|
+
clearShippingNotes();
|
|
1033
|
+
}, children: jobTexts.close })] })] }) })), shippingNotes.length > 0 && (_jsx(Card, { className: "border-2 border-primary/20", children: _jsxs(CardContent, { className: "flex flex-col gap-2 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-foreground", children: shippingNotes.every((note) => note.state.status === "geprint")
|
|
1034
|
+
? "Printactie volledig geslaagd — pakbon en verzendbrief zijn geprint."
|
|
1035
|
+
: "Printactie nog niet volledig: de verzendbrief is nog niet geprint." }), shippingNotes.map((note) => {
|
|
1036
|
+
const message = shippingNoteMessage(note.state);
|
|
1037
|
+
const failed = note.state.status === "mislukt" || note.state.status === "geblokkeerd";
|
|
1038
|
+
const waiting = isWaitingShippingNote(note.state.status);
|
|
1039
|
+
const typeLabel = shippingNoteSourceLabel(note.state);
|
|
1040
|
+
return (_jsxs("div", { className: "flex items-start gap-3", children: [failed ? (_jsx(AlertTriangle, { className: "mt-0.5 h-5 w-5 shrink-0 text-destructive" })) : note.state.status === "geprint" ? (_jsx(CheckCircle2, { className: "mt-0.5 h-5 w-5 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "mt-0.5 h-5 w-5 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [(note.cartLabel || typeLabel) && (_jsx("p", { className: "truncate text-sm text-muted-foreground", children: [note.cartLabel, typeLabel].filter(Boolean).join(" · ") })), _jsx("p", { className: failed ? "text-sm font-medium text-destructive" : "text-sm text-foreground", children: message ?? "Verzendbriefstatus wordt opgehaald." }), waiting && (note.state.attempts !== null || note.state.nextAttemptAt) && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [note.state.attempts !== null ? `Pogingen: ${note.state.attempts}` : "", note.state.attempts !== null && note.state.nextAttemptAt ? " · " : "", note.state.nextAttemptAt
|
|
1041
|
+
? `Volgende poging: ${new Date(note.state.nextAttemptAt).toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" })}`
|
|
1042
|
+
: ""] }))] })] }, note.jobId));
|
|
1043
|
+
})] }) })), _jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-wrap items-center gap-3 p-3 sm:p-4", children: [_jsxs("div", { className: "relative w-full min-w-0 flex-1 sm:min-w-64", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "h-12 pl-9 text-base", placeholder: t("search"), value: searchInput, onChange: (e) => setSearchInput(e.target.value) })] }), _jsxs(Select, { value: chosenDay ?? "current", onValueChange: (v) => void chooseDay(v), children: [_jsx(SelectTrigger, { className: "h-12 w-full sm:w-56", disabled: loadingDay, children: _jsx(SelectValue, { placeholder: ui.pickDay }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "current", children: ui.pickDayToday }), safePickDays.map((d) => (_jsxs(SelectItem, { value: d.day, children: [dayLabel(d.day), d.orders > 0 ? ` (${d.orders})` : ""] }, d.day)))] })] }), _jsxs(Select, { value: filters.documentTypeId ?? "all", onValueChange: (v) => setFilters((f) => ({ ...f, documentTypeId: v === "all" ? null : v, page: 0 })), children: [_jsx(SelectTrigger, { className: "h-12 w-full sm:w-52", children: _jsx(SelectValue, { placeholder: ui.allDocumentTypes }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: ui.allDocumentTypes }), (Array.isArray(documentTypes) ? documentTypes : []).map((dt) => (_jsx(SelectItem, { value: dt.id, children: dt.name }, dt.id)))] })] }), _jsx(Button, { variant: filters.onlyPending ? "default" : "outline", className: "h-12", onClick: () => setFilters((f) => ({ ...f, onlyPending: !f.onlyPending, page: 0 })), children: ui.onlyPending }), _jsxs(Button, { variant: filters.urgentOnly ? "default" : "outline", className: "h-12", onClick: () => setFilters((f) => ({ ...f, urgentOnly: !f.urgentOnly, page: 0 })), children: [_jsx(Zap, { className: "mr-2 h-4 w-4" }), ui.onlyUrgent, urgentCount > 0 ? ` (${urgentCount})` : ""] })] }) }), !realtimeOk && (_jsx("p", { className: "text-sm text-muted-foreground", children: ui.connectionLost })), cartsPerPrint != null && (_jsx("div", { className: "flex justify-end md:hidden", children: _jsxs(Button, { className: "h-12", disabled: loading || batchPrinting || busyId != null, onClick: () => void printNextCarts(), children: [batchPrinting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-4 w-4" })), "Print eerstvolgende ", cartsPerPrint, " karren"] }) })), _jsx("div", { className: "md:hidden", children: _jsxs(Button, { variant: "outline", className: "h-12 w-full", disabled: fetchingNotes, onClick: () => void fetchShippingNotes(), children: [fetchingNotes ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(FileDown, { className: "mr-2 h-4 w-4" })), fetchingNotes ? "Ophalen…" : "Verzendbrieven ophalen"] }) }), 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, readinessById: readinessById, onPick: handlePick, onReprint: handleReprint, onPrintAll: printAllForItem, onSelectPrinted: selectPrintedForReprint, onMarkPrinted: markPrinted, onPreview: setPreviewItem, t: t })), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("span", { className: "text-center text-sm text-muted-foreground sm:text-left", children: [t("page"), " ", filters.page + 1, " ", t("of"), " ", totalPages] }), _jsxs("div", { className: "grid grid-cols-2 gap-2 sm:flex", children: [_jsx(Button, { variant: "outline", className: "h-12 w-full sm:w-auto", disabled: filters.page === 0, onClick: () => setFilters((f) => ({ ...f, page: Math.max(0, f.page - 1) })), children: t("previous") }), _jsx(Button, { variant: "outline", className: "h-12 w-full sm:w-auto", disabled: filters.page + 1 >= totalPages, onClick: () => setFilters((f) => ({ ...f, page: f.page + 1 })), children: t("next") })] })] }), _jsx("div", { className: "fixed inset-x-0 bottom-0 z-40 border-t bg-background/95 p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] backdrop-blur md:hidden", children: _jsxs(Button, { size: "lg", className: "h-14 w-full text-lg", onClick: () => (pending ? confirmPending() : fetchNext()), disabled: busyId !== null || fetchingData, children: [busyId !== null || fetchingData ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : pending && !readiness.ready ? (_jsx(RefreshCw, { className: "mr-2 h-5 w-5" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), _jsxs("span", { className: "truncate", children: [pending && !readiness.ready ? "Pakbon ophalen" : t("printNext"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] }) }), _jsx(PreviewDialog, { item: previewItem, stationSlug: station.slug, onClose: () => setPreviewItem(null) })] }));
|
|
791
1044
|
}
|