@flowselections/pick-schermen-kwekers 1.0.18 → 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/dist-lib/components/pick/PickScherm.d.ts.map +1 -1
- package/dist-lib/components/pick/PickScherm.js +340 -59
- 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/usePickQueue.d.ts +2 -0
- package/dist-lib/hooks/usePickQueue.d.ts.map +1 -1
- package/dist-lib/hooks/usePickQueue.js +30 -0
- 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 +952 -19
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +952 -19
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +952 -19
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +952 -17
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/pickApi.d.ts +29 -0
- package/dist-lib/lib/pickApi.d.ts.map +1 -1
- package/dist-lib/lib/pickApi.js +49 -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
|
@@ -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.
|
|
@@ -35,6 +39,15 @@ export function PickScherm({ station, onBack }) {
|
|
|
35
39
|
const [busyId, setBusyId] = useState(null);
|
|
36
40
|
const [pending, setPending] = useState(null);
|
|
37
41
|
const [cartPlan, setCartPlan] = useState(null);
|
|
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
|
+
});
|
|
38
51
|
const [activeCartId, setActiveCartId] = useState(null);
|
|
39
52
|
const [lastPrinted, setLastPrinted] = useState(null);
|
|
40
53
|
const [previewItem, setPreviewItem] = useState(null);
|
|
@@ -47,9 +60,62 @@ export function PickScherm({ station, onBack }) {
|
|
|
47
60
|
const { data: documentTypes } = usePickDocumentTypes();
|
|
48
61
|
const { online } = usePickHeartbeat(station.id, null, sessionId);
|
|
49
62
|
const { data: printPlek, isLoading: plekLoading } = useStationPrintPlek(station.id);
|
|
50
|
-
const { tracked, track, clear: clearJob } = usePrintJob();
|
|
51
|
-
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();
|
|
52
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);
|
|
53
119
|
const t = useMemo(() => getPickTranslator(lang ?? "nl"), [lang]);
|
|
54
120
|
const jobTexts = useMemo(() => getPrintJobTexts(t.lang), [t]);
|
|
55
121
|
const ui = useMemo(() => getPickUiTexts(t.lang), [t]);
|
|
@@ -73,7 +139,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
73
139
|
return () => clearTimeout(timer);
|
|
74
140
|
}, [searchInput]);
|
|
75
141
|
const effectiveFilters = useMemo(() => ({ ...filters, country: null, sortRules: [] }), [filters]);
|
|
76
|
-
const { items, total, loading, lockedIds, refresh, pendingCount, printedCount, urgentCount, realtimeOk, pickDay, } = usePickQueue({
|
|
142
|
+
const { items, total, loading, lockedIds, refresh, pendingCount, printedCount, urgentCount, realtimeOk, pickDay, rebuiltAt, } = usePickQueue({
|
|
77
143
|
stationId: station.id,
|
|
78
144
|
filters: effectiveFilters,
|
|
79
145
|
enabled: lang !== null,
|
|
@@ -86,6 +152,21 @@ export function PickScherm({ station, onBack }) {
|
|
|
86
152
|
day: "numeric",
|
|
87
153
|
month: "short",
|
|
88
154
|
}), [t.lang]);
|
|
155
|
+
/**
|
|
156
|
+
* De Engine heeft gemeld dat een buffer is gesloten of dat een order is
|
|
157
|
+
* losgekoppeld. De regels zijn opnieuw opgebouwd; de openstaande selectie en
|
|
158
|
+
* het oude karplan gaan weg zodat nooit een verouderde groepering blijft
|
|
159
|
+
* staan. Kar-id's, karnummers en barcodes van geprinte karren wijzigen niet.
|
|
160
|
+
*/
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (rebuiltAt === null)
|
|
163
|
+
return;
|
|
164
|
+
daySelectionVersion.current += 1;
|
|
165
|
+
setPending(null);
|
|
166
|
+
setCartPlan(null);
|
|
167
|
+
setActiveCartId(null);
|
|
168
|
+
autoFetched.current = false;
|
|
169
|
+
}, [rebuiltAt]);
|
|
89
170
|
/** Kiest een andere pickdag en laat de Engine die dag alvast klaarzetten. */
|
|
90
171
|
const chooseDay = useCallback(async (value) => {
|
|
91
172
|
const day = value === "current" ? null : value;
|
|
@@ -134,34 +215,149 @@ export function PickScherm({ station, onBack }) {
|
|
|
134
215
|
void chooseDay(iso === todayIso || (pickDay && iso === pickDay) ? "current" : iso);
|
|
135
216
|
}, [chosenDay, pickDay, chooseDay]);
|
|
136
217
|
/**
|
|
137
|
-
* Haalt het vastgelegde karplan
|
|
138
|
-
*
|
|
139
|
-
*
|
|
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".
|
|
140
221
|
*/
|
|
141
222
|
const loadCartPlan = useCallback(async (item) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
/* bewaking is aanvullend, nooit blokkerend */
|
|
153
|
-
}
|
|
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;
|
|
154
231
|
}
|
|
232
|
+
setPlanStatus(status);
|
|
233
|
+
const readiness = pickReadiness(plan, status, item);
|
|
234
|
+
setPlanReason(readiness.ready ? null : readiness.reason);
|
|
155
235
|
if (!plan || plan.carts.length === 0) {
|
|
156
236
|
setCartPlan(null);
|
|
157
237
|
setActiveCartId(null);
|
|
158
|
-
return null;
|
|
238
|
+
return { plan: null, readiness };
|
|
159
239
|
}
|
|
160
240
|
setCartPlan(plan);
|
|
161
241
|
const printed = new Set(printedCartIdsOf(item));
|
|
162
242
|
setActiveCartId(plan.carts.find((cart) => !printed.has(cart.cartId))?.cartId ?? plan.carts[0].cartId);
|
|
163
|
-
return plan;
|
|
243
|
+
return { plan, readiness };
|
|
164
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 {
|
|
303
|
+
try {
|
|
304
|
+
await pickApi.reportMissingCartPlans();
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
/* bewaking is aanvullend, nooit blokkerend */
|
|
308
|
+
}
|
|
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
|
+
}
|
|
325
|
+
}
|
|
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
|
+
}
|
|
353
|
+
}
|
|
354
|
+
toast.warning(planReason ?? "De pakbon is nog niet compleet. De bron is op de hoogte gebracht.");
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
setFetchingData(false);
|
|
359
|
+
}
|
|
360
|
+
}, [cartPlan, planStatus, planReason, refresh]);
|
|
165
361
|
/**
|
|
166
362
|
* Mislukte printopdracht: alleen de betreffende kar terugzetten. Ging alleen
|
|
167
363
|
* het opslaan van de PDF-kopie mis, dan is het papier wél geprint: de kar
|
|
@@ -302,18 +498,19 @@ export function PickScherm({ station, onBack }) {
|
|
|
302
498
|
* Printknop in de wachtrij: print in één keer alle pakbonnen (karren) van
|
|
303
499
|
* deze order. Nog niet geprint: claimen en alle karren in één batch.
|
|
304
500
|
* Al geprint: bewuste herdruk van alle karren.
|
|
305
|
-
*
|
|
501
|
+
* Is de regel nog niet printklaar, dan wordt er niet geprint maar worden de
|
|
502
|
+
* ontbrekende gegevens bij de bron opgevraagd.
|
|
306
503
|
*/
|
|
307
504
|
const printAllForItem = useCallback(async (item) => {
|
|
308
505
|
setBusyId(item.id);
|
|
309
506
|
try {
|
|
310
507
|
if (item.printed_at) {
|
|
311
|
-
const plan = await loadCartPlan(item);
|
|
508
|
+
const { plan, readiness: state } = await loadCartPlan(item);
|
|
312
509
|
setPending(item);
|
|
313
|
-
if (!plan) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
510
|
+
if (!plan || !state.ready) {
|
|
511
|
+
setBusyId(null);
|
|
512
|
+
toast.warning(state.reason ?? NOT_READY_NOTICE);
|
|
513
|
+
await requestData(item);
|
|
317
514
|
return;
|
|
318
515
|
}
|
|
319
516
|
const jobs = [];
|
|
@@ -335,6 +532,17 @@ export function PickScherm({ station, onBack }) {
|
|
|
335
532
|
refresh();
|
|
336
533
|
return;
|
|
337
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
|
+
}
|
|
338
546
|
const claimed = await pickApi.claim({
|
|
339
547
|
itemId: item.id,
|
|
340
548
|
stationId: station.id,
|
|
@@ -343,14 +551,12 @@ export function PickScherm({ station, onBack }) {
|
|
|
343
551
|
timeout: station.lock_timeout_seconds ?? 120,
|
|
344
552
|
});
|
|
345
553
|
const target = claimed ?? item;
|
|
346
|
-
const plan = await loadCartPlan(target);
|
|
554
|
+
const { plan, readiness: state } = await loadCartPlan(target);
|
|
347
555
|
setPending(target);
|
|
348
|
-
if (!plan) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
setPending(printed);
|
|
353
|
-
refresh();
|
|
556
|
+
if (!plan || !state.ready) {
|
|
557
|
+
setBusyId(null);
|
|
558
|
+
toast.warning(state.reason ?? NOT_READY_NOTICE);
|
|
559
|
+
await requestData(target);
|
|
354
560
|
return;
|
|
355
561
|
}
|
|
356
562
|
const already = new Set(printedCartIdsOf(target));
|
|
@@ -390,11 +596,69 @@ export function PickScherm({ station, onBack }) {
|
|
|
390
596
|
station.lock_timeout_seconds,
|
|
391
597
|
sessionId,
|
|
392
598
|
loadCartPlan,
|
|
599
|
+
requestData,
|
|
393
600
|
trackBatch,
|
|
394
601
|
refresh,
|
|
395
602
|
t,
|
|
396
603
|
showPrintError,
|
|
397
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]);
|
|
398
662
|
const fetchNext = useCallback(async (silent = false) => {
|
|
399
663
|
const requestedDayVersion = daySelectionVersion.current;
|
|
400
664
|
setBusyId("next");
|
|
@@ -459,25 +723,10 @@ export function PickScherm({ station, onBack }) {
|
|
|
459
723
|
const plan = cartPlan;
|
|
460
724
|
if (!item)
|
|
461
725
|
return;
|
|
462
|
-
if (!plan) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
setBusyId("confirm");
|
|
469
|
-
try {
|
|
470
|
-
const printed = await pickApi.completePrint(item.id, sessionId);
|
|
471
|
-
setLastPrinted(printed);
|
|
472
|
-
setPending(printed);
|
|
473
|
-
refresh();
|
|
474
|
-
}
|
|
475
|
-
catch (e) {
|
|
476
|
-
showPrintError(e);
|
|
477
|
-
}
|
|
478
|
-
finally {
|
|
479
|
-
setBusyId(null);
|
|
480
|
-
}
|
|
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);
|
|
481
730
|
return;
|
|
482
731
|
}
|
|
483
732
|
const alreadyPrinted = new Set(printedCartIdsOf(item));
|
|
@@ -545,7 +794,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
545
794
|
}
|
|
546
795
|
}
|
|
547
796
|
}
|
|
548
|
-
}, [pending, cartPlan, handleReprint, sessionId, trackBatch, t, fetchNext, refresh, showPrintError]);
|
|
797
|
+
}, [pending, cartPlan, readiness, requestData, handleReprint, sessionId, trackBatch, t, fetchNext, refresh, showPrintError]);
|
|
549
798
|
/** Rond de order pas af wanneer alle batchopdrachten een eindstatus hebben. */
|
|
550
799
|
useEffect(() => {
|
|
551
800
|
if (!batch || batch.jobs.some((entry) => !entry.job || !["voltooid", "mislukt", "geannuleerd"].includes(entry.job.status)))
|
|
@@ -737,7 +986,26 @@ export function PickScherm({ station, onBack }) {
|
|
|
737
986
|
const jobFailed = jobStatus === "mislukt" || jobStatus === "geannuleerd";
|
|
738
987
|
const batchCompleted = batch?.jobs.filter((entry) => entry.job?.status === "voltooid").length ?? 0;
|
|
739
988
|
const batchFailed = batch?.jobs.filter((entry) => entry.job?.status === "mislukt" || entry.job?.status === "geannuleerd").length ?? 0;
|
|
740
|
-
|
|
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) => {
|
|
741
1009
|
const done = printedCartIdsOf(pending).includes(cart.cartId);
|
|
742
1010
|
const active = cart.cartId === activeCartId;
|
|
743
1011
|
return (_jsxs("div", { role: "button", tabIndex: 0, onClick: () => setActiveCartId(cart.cartId), onKeyDown: (event) => {
|
|
@@ -759,5 +1027,18 @@ export function PickScherm({ station, onBack }) {
|
|
|
759
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: () => {
|
|
760
1028
|
clearJob();
|
|
761
1029
|
void confirmPending();
|
|
762
|
-
}, 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) })] }));
|
|
763
1044
|
}
|
|
@@ -6,6 +6,11 @@ interface Props {
|
|
|
6
6
|
statuses: PickStatus[];
|
|
7
7
|
documentTypes: PickDocumentType[];
|
|
8
8
|
busyId: string | null;
|
|
9
|
+
readinessById: Record<string, {
|
|
10
|
+
checking: boolean;
|
|
11
|
+
ready: boolean;
|
|
12
|
+
reason: string | null;
|
|
13
|
+
}>;
|
|
9
14
|
onPick: (item: PickQueueItem) => void;
|
|
10
15
|
onReprint?: (item: PickQueueItem) => void;
|
|
11
16
|
onPrintAll?: (item: PickQueueItem) => void;
|
|
@@ -15,6 +20,6 @@ interface Props {
|
|
|
15
20
|
readOnly?: boolean;
|
|
16
21
|
t?: PickTranslator;
|
|
17
22
|
}
|
|
18
|
-
export declare function QueueTable({ items, lockedIds, statuses, documentTypes, busyId, onPick, onReprint, onPrintAll, onSelectPrinted, onMarkPrinted, onPreview, readOnly, t: translator, }: Props): import("react").JSX.Element;
|
|
23
|
+
export declare function QueueTable({ items, lockedIds, statuses, documentTypes, busyId, readinessById, onPick, onReprint, onPrintAll, onSelectPrinted, onMarkPrinted, onPreview, readOnly, t: translator, }: Props): import("react").JSX.Element;
|
|
19
24
|
export {};
|
|
20
25
|
//# sourceMappingURL=QueueTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueueTable.d.ts","sourceRoot":"","sources":["../../../src/components/pick/QueueTable.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"QueueTable.d.ts","sourceRoot":"","sources":["../../../src/components/pick/QueueTable.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGvF,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE5E,UAAU,KAAK;IACb,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC5F,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,CAAC,EAAE,cAAc,CAAC;CACpB;AAqBD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,SAAS,EACT,QAAQ,EACR,aAAa,EACb,MAAM,EACN,aAAa,EACb,MAAM,EACN,SAAS,EACT,UAAU,EACV,eAAe,EACf,aAAa,EACb,SAAS,EAGT,QAAQ,EACR,CAAC,EAAE,UAAU,GACd,EAAE,KAAK,+BA4aP"}
|