@flowselections/pick-schermen-kwekers 1.0.41 → 1.0.44
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/PickstationsPage.js +1 -1
- package/dist-lib/components/pick/PickScherm.d.ts.map +1 -1
- package/dist-lib/components/pick/PickScherm.js +174 -36
- 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 +44 -11
- package/dist-lib/components/pick/ReferentiedataBanner.js +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.js +22 -4
- package/dist-lib/integrations/supabase/types.d.ts +368 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/__tests__/pick142.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/pick142.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/pick142.test.js +80 -0
- package/dist-lib/lib/__tests__/pick143.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/pick143.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/pick143.test.js +61 -0
- package/dist-lib/lib/pick142.d.ts +58 -0
- package/dist-lib/lib/pick142.d.ts.map +1 -0
- package/dist-lib/lib/pick142.js +235 -0
- package/dist-lib/lib/pick143.d.ts +31 -0
- package/dist-lib/lib/pick143.d.ts.map +1 -0
- package/dist-lib/lib/pick143.js +93 -0
- package/dist-lib/lib/pickApi.d.ts +13 -0
- package/dist-lib/lib/pickApi.d.ts.map +1 -1
- package/dist-lib/lib/pickApi.js +28 -5
- package/dist-lib/lib/pickCartResults.d.ts +1 -1
- package/dist-lib/lib/pickCartResults.d.ts.map +1 -1
- package/dist-lib/lib/pickCartResults.js +2 -2
- package/dist-lib/lib/pickI18n.js +6 -6
- package/dist-lib/lib/pickPrintJobText.js +2 -2
- package/dist-lib/lib/pickRowState.d.ts +1 -1
- package/dist-lib/lib/pickRowState.d.ts.map +1 -1
- package/dist-lib/lib/pickRowState.js +6 -2
- package/dist-lib/lib/pickTypes.d.ts +29 -1
- package/dist-lib/lib/pickTypes.d.ts.map +1 -1
- package/dist-lib/lib/pickTypes.js +42 -8
- package/dist-lib/lib/pickUiText.d.ts.map +1 -1
- package/dist-lib/lib/pickUiText.js +10 -0
- package/dist-lib/lib/shippingNoteStatus.js +2 -2
- package/package.json +1 -1
|
@@ -57,7 +57,7 @@ export function PickstationsPage() {
|
|
|
57
57
|
toast.error(url);
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Pickstations" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer je pickstations en
|
|
60
|
+
return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Pickstations" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer je pickstations en de link die je met de medewerkers deelt." })] }), _jsxs(Button, { onClick: () => setDraft({ ...emptyDraft }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuw pickstation"] })] }), _jsx(ReferentiedataBanner, {}), isLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden..."] })) : safeStations.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen pickstations." })) : (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3", children: safeStations.map((station) => (_jsx(Card, { className: "border-2", children: _jsxs(CardContent, { className: "space-y-4 p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: station.name }), _jsx("p", { className: "text-sm text-muted-foreground", children: station.description })] }), _jsx("span", { className: "rounded-full px-2 py-1 text-xs font-medium " +
|
|
61
61
|
(station.active
|
|
62
62
|
? "bg-primary/10 text-primary"
|
|
63
63
|
: "bg-muted text-muted-foreground"), children: station.active ? "Actief" : "Inactief" })] }), _jsx("code", { className: "block truncate rounded bg-muted px-2 py-1 text-xs text-muted-foreground", children: publicUrl(station.slug) }), _jsx(StationPrintplek, { station: station }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { className: "flex-1", asChild: true, children: _jsxs("a", { href: publicUrl(station.slug), target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Open pickstation"] }) }), _jsxs(Button, { variant: "outline", onClick: () => copyLink(station), children: [_jsx(Copy, { className: "mr-2 h-4 w-4" }), " Link kopi\u00EBren"] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setDraft({
|
|
@@ -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":"AAwDA,OAAO,EAaL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAuD7B,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAwBD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,+BAwoEpD"}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { useQuery } from "@tanstack/react-query";
|
|
4
4
|
import { Badge, Button, Card, CardContent, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
|
|
5
5
|
import { AlertTriangle, ArrowLeft, CheckCircle2, ChevronLeft, ChevronRight, FileDown, Globe, Loader2, Printer, RefreshCw, Search, WifiOff, Zap, } from "lucide-react";
|
|
6
6
|
import { LanguageTiles } from "./LanguageTiles";
|
|
7
7
|
import { QueueTable } from "./QueueTable";
|
|
8
|
+
import { isRowPrintable, pickDayOf, pickDayShortLabel, printBlockMessage, rowWaitOf, setKnownSiteZone } from "../../lib/pick143";
|
|
8
9
|
import { PreviewDialog } from "./PreviewDialog";
|
|
9
10
|
import { ShippingNoteScan } from "./ShippingNoteScan";
|
|
10
11
|
import { getSessionId, pickApi } from "../../lib/pickApi";
|
|
11
12
|
import { sendLogisticsEvent } from "../../lib/logisticsApi";
|
|
13
|
+
import { CART_PLAN_MISMATCH_MESSAGE, formatSiteTime, WORKDAYS_UNKNOWN, autoSendMessage, fetchAutoSend, isPickWorkday, mismatchReason, nextWorkday, printErrorText, PRINT_FAILED_HINT, printStageLabel, siteTimeZone, siteToday, } from "../../lib/pick142";
|
|
12
14
|
import { completeLogisticsTaskForItem } from "../../lib/pickPrint";
|
|
13
|
-
import { isPdfOnlyFailure, pickReadiness, printedCartIdsOf, pdfSaveWarning, unitRefOf, statusRefOf, unitTitleOf, } from "../../lib/pickTypes";
|
|
15
|
+
import { cartRowOf, isPdfOnlyFailure, pickReadiness, printedCartIdsOf, pdfSaveWarning, unitRefOf, statusRefOf, unitTitleOf, } from "../../lib/pickTypes";
|
|
14
16
|
import { rowFingerprint, rowsToVerify } from "../../lib/pickVerify";
|
|
15
17
|
import { alreadyPrintedResults, CART_MISSING_MESSAGE, cartResultLine, cartsForRow, isCartMissingError, reprintCartsIndividually, resettableJobs, runExclusive, resultsFromBatch, resultsFromBatchError, summarizeCartResults, trackableJobs, } from "../../lib/pickCartResults";
|
|
16
18
|
import { getPickTranslator } from "../../lib/pickI18n";
|
|
17
19
|
import { friendlyPickError, getPickUiTexts } from "../../lib/pickUiText";
|
|
18
|
-
import { getPrintJobTexts
|
|
20
|
+
import { getPrintJobTexts } from "../../lib/pickPrintJobText";
|
|
19
21
|
import { defaultFilters, usePickDays, usePickHeartbeat, usePickQueue, } from "../../hooks/usePickQueue";
|
|
20
22
|
import { usePickDocumentTypes, usePickStatuses, useStationPrintPlek, } from "../../hooks/usePickConfig";
|
|
21
23
|
import { usePrintBatch, usePrintJob } from "../../hooks/usePrintJob";
|
|
@@ -38,7 +40,7 @@ async function releaseQuietly(itemId) {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
/** Melding als de regel nog niet compleet is aangeleverd door de bron. */
|
|
41
|
-
const NOT_READY_NOTICE = "Deze pakbon is nog niet printklaar. Klik op \"Pakbon ophalen\" om de ontbrekende gegevens
|
|
43
|
+
const NOT_READY_NOTICE = "Deze pakbon is nog niet printklaar. Klik op \"Pakbon ophalen\" om de ontbrekende gegevens op te vragen.";
|
|
42
44
|
/**
|
|
43
45
|
* Pickscherm. De Logistiek Engine bepaalt welke documenten zichtbaar zijn en
|
|
44
46
|
* in welke volgorde; dit scherm toont uitsluitend wat de wachtrij teruggeeft.
|
|
@@ -50,6 +52,8 @@ export function PickScherm({ station, onBack }) {
|
|
|
50
52
|
const [chosenDay, setChosenDay] = useState(null);
|
|
51
53
|
const [loadingDay, setLoadingDay] = useState(false);
|
|
52
54
|
const [searchInput, setSearchInput] = useState("");
|
|
55
|
+
/** Telefoon: filters zitten achter een knop zodat de lijst bovenaan begint. */
|
|
56
|
+
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
53
57
|
const [busyId, setBusyId] = useState(null);
|
|
54
58
|
const [pending, setPending] = useState(null);
|
|
55
59
|
const [cartPlan, setCartPlan] = useState(null);
|
|
@@ -57,6 +61,39 @@ export function PickScherm({ station, onBack }) {
|
|
|
57
61
|
const [planStatus, setPlanStatus] = useState(null);
|
|
58
62
|
const [fetchingData, setFetchingData] = useState(false);
|
|
59
63
|
const [readinessById, setReadinessById] = useState({});
|
|
64
|
+
/** Tijdzone van de vestiging (Engine-werktijden); null = onbekend. */
|
|
65
|
+
const [siteTz, setSiteTz] = useState(null);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
void siteTimeZone().then((tz) => {
|
|
68
|
+
setKnownSiteZone(tz);
|
|
69
|
+
setSiteTz(tz);
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
72
|
+
/** Order waarvan na de scan het automatisch versturen gevolgd wordt. */
|
|
73
|
+
const [autoSendOrderId, setAutoSendOrderId] = useState(null);
|
|
74
|
+
const [autoSend, setAutoSend] = useState(null);
|
|
75
|
+
const cartPlanRef = useRef(null);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (cartPlan)
|
|
78
|
+
cartPlanRef.current = cartPlan;
|
|
79
|
+
}, [cartPlan]);
|
|
80
|
+
/** Na de scan: stand van het automatisch versturen, elke 10 s (alleen lezen). */
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!autoSendOrderId)
|
|
83
|
+
return;
|
|
84
|
+
let stopped = false;
|
|
85
|
+
const read = async () => {
|
|
86
|
+
const state = await fetchAutoSend(autoSendOrderId, station.slug ?? null);
|
|
87
|
+
if (!stopped)
|
|
88
|
+
setAutoSend(state);
|
|
89
|
+
};
|
|
90
|
+
void read();
|
|
91
|
+
const timer = window.setInterval(() => void read(), 10000);
|
|
92
|
+
return () => {
|
|
93
|
+
stopped = true;
|
|
94
|
+
window.clearInterval(timer);
|
|
95
|
+
};
|
|
96
|
+
}, [autoSendOrderId, station.slug]);
|
|
60
97
|
/** De vastgehouden rij is door de Engine vervangen of bestaat niet meer. */
|
|
61
98
|
const [rowGone, setRowGone] = useState(false);
|
|
62
99
|
/** Moment waarop dit scherm een printopdracht verstuurde (voor "wacht al X minuten"). */
|
|
@@ -98,7 +135,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
98
135
|
toast.success(result.message ?? "Verzendbrieven worden opgehaald.");
|
|
99
136
|
}
|
|
100
137
|
else {
|
|
101
|
-
toast.info("
|
|
138
|
+
toast.info("De verzendbrieven worden zo vanzelf opgehaald. Je hoeft niets te doen.");
|
|
102
139
|
}
|
|
103
140
|
}
|
|
104
141
|
finally {
|
|
@@ -112,12 +149,14 @@ export function PickScherm({ station, onBack }) {
|
|
|
112
149
|
*/
|
|
113
150
|
const track = useCallback((args) => {
|
|
114
151
|
trackJobRaw(args);
|
|
152
|
+
setAutoSendOrderId(cartPlanRef.current?.orderId ?? null);
|
|
115
153
|
watchShippingNotes([
|
|
116
154
|
{ jobId: args.jobId, cartId: args.cartId ?? null, cartLabel: args.cartLabel ?? null },
|
|
117
155
|
]);
|
|
118
156
|
}, [trackJobRaw, watchShippingNotes]);
|
|
119
157
|
const trackBatch = useCallback((args) => {
|
|
120
158
|
trackBatchRaw(args);
|
|
159
|
+
setAutoSendOrderId(cartPlanRef.current?.orderId ?? null);
|
|
121
160
|
watchShippingNotes(args.jobs);
|
|
122
161
|
}, [trackBatchRaw, watchShippingNotes]);
|
|
123
162
|
/**
|
|
@@ -164,11 +203,21 @@ export function PickScherm({ station, onBack }) {
|
|
|
164
203
|
enabled: lang !== null,
|
|
165
204
|
pickDayOverride: chosenDay,
|
|
166
205
|
});
|
|
206
|
+
/** Geprint maar nog niet gescand, van een eerdere pickdag (blijft open tot de scan). */
|
|
207
|
+
const viewDay = chosenDay ?? pickDay ?? null;
|
|
208
|
+
const { data: earlierRaw, refetch: refetchEarlier } = useQuery({
|
|
209
|
+
queryKey: ["pick", "unscannedEarlier", station.id, viewDay ?? "-"],
|
|
210
|
+
enabled: lang !== null && !!viewDay,
|
|
211
|
+
refetchInterval: 30000,
|
|
212
|
+
queryFn: () => pickApi.unscannedEarlier({ stationId: station.id, beforeDay: viewDay }),
|
|
213
|
+
});
|
|
214
|
+
const earlierRows = Array.isArray(earlierRaw) ? earlierRaw : [];
|
|
167
215
|
// Eén toast per nieuwe syncfout; de rode balk blijft staan tot hij is opgelost.
|
|
216
|
+
const SYNC_ERROR_MESSAGE = "De lijst kon niet worden ververst. Controleer de wifi en klik op ‘Wachtrij verversen’.";
|
|
168
217
|
const lastSyncError = useRef(null);
|
|
169
218
|
useEffect(() => {
|
|
170
219
|
if (syncError && syncError !== lastSyncError.current) {
|
|
171
|
-
toast.error(
|
|
220
|
+
toast.error(SYNC_ERROR_MESSAGE, { description: `Details voor beheer: ${syncError}` });
|
|
172
221
|
}
|
|
173
222
|
lastSyncError.current = syncError;
|
|
174
223
|
}, [syncError]);
|
|
@@ -233,14 +282,21 @@ export function PickScherm({ station, onBack }) {
|
|
|
233
282
|
* Verschuift de pickdag één dag vooruit of achteruit. Zonder handmatige keuze
|
|
234
283
|
* is de kalenderdag van vandaag het startpunt, niet de dag van de Engine.
|
|
235
284
|
*/
|
|
236
|
-
const shiftDay = useCallback((delta) => {
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
285
|
+
const shiftDay = useCallback(async (delta) => {
|
|
286
|
+
// Vandaag in de tijdzone van de vestiging; onbekend = niet verschuiven.
|
|
287
|
+
const todayIso = siteToday(siteTz);
|
|
288
|
+
const base = chosenDay ?? pickDay ?? todayIso;
|
|
289
|
+
if (!base) {
|
|
290
|
+
toast.error(WORKDAYS_UNKNOWN);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const iso = await nextWorkday(base, delta, isPickWorkday);
|
|
294
|
+
if (!iso) {
|
|
295
|
+
toast.error(WORKDAYS_UNKNOWN);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
242
298
|
void chooseDay(iso === todayIso || (pickDay && iso === pickDay) ? "current" : iso);
|
|
243
|
-
}, [chosenDay, pickDay, chooseDay]);
|
|
299
|
+
}, [chosenDay, pickDay, chooseDay, siteTz]);
|
|
244
300
|
/**
|
|
245
301
|
* Haalt het vastgelegde karplan én de toestand van de planning uit de bron.
|
|
246
302
|
* Er wordt niets verzonnen: is het plan er niet, of is het nog niet
|
|
@@ -347,14 +403,18 @@ export function PickScherm({ station, onBack }) {
|
|
|
347
403
|
let entry = {
|
|
348
404
|
checking: false,
|
|
349
405
|
ready: false,
|
|
350
|
-
reason: "De
|
|
406
|
+
reason: "De pakbon is nog niet klaar. Klik over een minuut op ‘Pakbon ophalen’.",
|
|
407
|
+
waitUntil: null,
|
|
351
408
|
};
|
|
352
409
|
if (r && !r.error) {
|
|
353
410
|
const state = pickReadiness(r.plan, r.status, item);
|
|
354
|
-
|
|
411
|
+
const rowWait = rowWaitOf(item.payload);
|
|
412
|
+
const notPrintable = !rowWait.printable || r.plan?.printable === false;
|
|
413
|
+
const waitUntil = notPrintable ? rowWait.until ?? r.plan?.waitUntil ?? null : null;
|
|
414
|
+
entry = { checking: false, ready: state.ready && !notPrintable, reason: state.reason, waitUntil, notPrintable };
|
|
355
415
|
}
|
|
356
416
|
else if (r?.error && isRowGoneError(r.error)) {
|
|
357
|
-
entry = { checking: false, ready: false, reason: String(r.error), replaced: true };
|
|
417
|
+
entry = { checking: false, ready: false, reason: String(r.error), replaced: true, waitUntil: null };
|
|
358
418
|
}
|
|
359
419
|
next[item.id] = entry;
|
|
360
420
|
verifyMemo.current.set(item.id, {
|
|
@@ -415,9 +475,15 @@ export function PickScherm({ station, onBack }) {
|
|
|
415
475
|
/* het seintje is aanvullend op de bewaking */
|
|
416
476
|
}
|
|
417
477
|
}
|
|
478
|
+
let lastMismatches = [];
|
|
418
479
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
419
480
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
420
|
-
const
|
|
481
|
+
const checked = await pickApi.queueCartPlanChecked(item.id);
|
|
482
|
+
const plan = checked.plan;
|
|
483
|
+
const own = cartRowOf(item);
|
|
484
|
+
lastMismatches = own
|
|
485
|
+
? checked.mismatches.filter((m) => m.cartId === own.cartId)
|
|
486
|
+
: checked.mismatches;
|
|
421
487
|
let status = null;
|
|
422
488
|
const statusRef = statusRefOf(item, plan);
|
|
423
489
|
try {
|
|
@@ -444,7 +510,42 @@ export function PickScherm({ station, onBack }) {
|
|
|
444
510
|
return true;
|
|
445
511
|
}
|
|
446
512
|
}
|
|
447
|
-
|
|
513
|
+
if (lastMismatches.length > 0) {
|
|
514
|
+
// Nooit stil blijven hangen: duidelijke melding en de reden naar de bewaking.
|
|
515
|
+
const reason = mismatchReason(lastMismatches);
|
|
516
|
+
setReadinessById((prev) => ({
|
|
517
|
+
...prev,
|
|
518
|
+
[item.id]: { checking: false, ready: false, reason: CART_PLAN_MISMATCH_MESSAGE, mismatch: true },
|
|
519
|
+
}));
|
|
520
|
+
toast.error(CART_PLAN_MISMATCH_MESSAGE, { description: `Details voor beheer: ${reason}` });
|
|
521
|
+
try {
|
|
522
|
+
await pickApi.reportMissingCartPlans();
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
/* bewaking is aanvullend */
|
|
526
|
+
}
|
|
527
|
+
if (ref) {
|
|
528
|
+
try {
|
|
529
|
+
await sendLogisticsEvent({
|
|
530
|
+
event_type: "order.changed",
|
|
531
|
+
order_ref: ref,
|
|
532
|
+
shadow: false,
|
|
533
|
+
payload: {
|
|
534
|
+
reason: "pick.cart_plan_mismatch",
|
|
535
|
+
detail: reason,
|
|
536
|
+
mismatches: lastMismatches,
|
|
537
|
+
queue_item_id: item.id,
|
|
538
|
+
idempotency_key: `pick.cart_plan_mismatch:${item.id}:${reason}`,
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
/* het seintje is aanvullend op de bewaking */
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
toast.warning(planReason ?? "De pakbon is nog niet klaar. Klik over een minuut op ‘Pakbon ophalen’.");
|
|
448
549
|
return false;
|
|
449
550
|
}
|
|
450
551
|
finally {
|
|
@@ -481,7 +582,12 @@ export function PickScherm({ station, onBack }) {
|
|
|
481
582
|
setPending(restored);
|
|
482
583
|
setActiveCartId(tracked.cartId);
|
|
483
584
|
}
|
|
484
|
-
|
|
585
|
+
{
|
|
586
|
+
const friendly = printErrorText(job.foutmelding);
|
|
587
|
+
toast.error(friendly?.message ?? `Niet geprint. ${PRINT_FAILED_HINT}`, {
|
|
588
|
+
description: job.foutmelding ? `Details voor beheer: ${job.foutmelding}` : undefined,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
485
591
|
}
|
|
486
592
|
catch (e) {
|
|
487
593
|
toast.error(e instanceof Error ? e.message : jobTexts.failed);
|
|
@@ -522,6 +628,11 @@ export function PickScherm({ station, onBack }) {
|
|
|
522
628
|
return expiredClaim;
|
|
523
629
|
}, [t, handleRowGone]);
|
|
524
630
|
const handlePick = useCallback(async (item) => {
|
|
631
|
+
const block = printBlockMessage(item, siteTz);
|
|
632
|
+
if (block) {
|
|
633
|
+
toast.warning(block);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
525
636
|
setBusyId(item.id);
|
|
526
637
|
try {
|
|
527
638
|
const claimed = await pickApi.claim({
|
|
@@ -548,7 +659,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
548
659
|
finally {
|
|
549
660
|
setBusyId(null);
|
|
550
661
|
}
|
|
551
|
-
}, [station.id, station.lock_timeout_seconds, sessionId, loadCartPlan, refresh, showPrintError]);
|
|
662
|
+
}, [station.id, station.lock_timeout_seconds, sessionId, loadCartPlan, refresh, showPrintError, siteTz]);
|
|
552
663
|
/**
|
|
553
664
|
* Eén vergrendeling voor alle printacties. Gaat direct bij de klik in (ref,
|
|
554
665
|
* niet pas bij de volgende weergave), zodat een dubbelklik nooit een tweede
|
|
@@ -598,6 +709,11 @@ export function PickScherm({ station, onBack }) {
|
|
|
598
709
|
* zonder de wachtrijstatus te wijzigen.
|
|
599
710
|
*/
|
|
600
711
|
const handleReprint = useCallback(async (item) => {
|
|
712
|
+
const block = printBlockMessage(item, siteTz);
|
|
713
|
+
if (block) {
|
|
714
|
+
toast.warning(block);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
601
717
|
setBusyId(item.id);
|
|
602
718
|
try {
|
|
603
719
|
const jobId = await pickApi.reprint(item.id, sessionId);
|
|
@@ -618,7 +734,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
618
734
|
finally {
|
|
619
735
|
setBusyId(null);
|
|
620
736
|
}
|
|
621
|
-
}, [t, sessionId, track, showPrintError]);
|
|
737
|
+
}, [t, sessionId, track, showPrintError, siteTz]);
|
|
622
738
|
/** Open een geprinte order bovenaan zonder de wachtrijstatus te wijzigen. */
|
|
623
739
|
const handleReprintLocked = useMemo(() => withActionLock(handleReprint), [withActionLock, handleReprint]);
|
|
624
740
|
const selectPrintedForReprint = useCallback(async (item) => {
|
|
@@ -644,6 +760,11 @@ export function PickScherm({ station, onBack }) {
|
|
|
644
760
|
* ontbrekende gegevens bij de bron opgevraagd.
|
|
645
761
|
*/
|
|
646
762
|
const printAllForItemInner = useCallback(async (item) => {
|
|
763
|
+
const block = printBlockMessage(item, siteTz);
|
|
764
|
+
if (block) {
|
|
765
|
+
toast.warning(block);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
647
768
|
setBusyId(item.id);
|
|
648
769
|
setCartMissing(false);
|
|
649
770
|
try {
|
|
@@ -747,6 +868,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
747
868
|
showCartResults,
|
|
748
869
|
refresh,
|
|
749
870
|
showPrintError,
|
|
871
|
+
siteTz,
|
|
750
872
|
]);
|
|
751
873
|
const printAllForItem = useMemo(() => withActionLock(printAllForItemInner), [withActionLock, printAllForItemInner]);
|
|
752
874
|
/**
|
|
@@ -768,6 +890,10 @@ export function PickScherm({ station, onBack }) {
|
|
|
768
890
|
for (const item of visible) {
|
|
769
891
|
if (targets.length >= cartsPerPrint)
|
|
770
892
|
break;
|
|
893
|
+
if (!isRowPrintable(item)) {
|
|
894
|
+
skipped.push({ title: unitTitleOf(item), reason: printBlockMessage(item, siteTz) ?? "nog niet printklaar" });
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
771
897
|
const state = readinessById[item.id];
|
|
772
898
|
if (!state || state.checking) {
|
|
773
899
|
skipped.push({ title: unitTitleOf(item), reason: "de pakbon wordt nog gecontroleerd" });
|
|
@@ -805,7 +931,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
805
931
|
finally {
|
|
806
932
|
setBatchPrinting(false);
|
|
807
933
|
}
|
|
808
|
-
}, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItemInner, showPrintError]);
|
|
934
|
+
}, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItemInner, showPrintError, siteTz]);
|
|
809
935
|
const printNextCarts = useMemo(() => withActionLock(printNextCartsInner), [withActionLock, printNextCartsInner]);
|
|
810
936
|
const fetchNext = useCallback(async (silent = false) => {
|
|
811
937
|
const requestedDayVersion = daySelectionVersion.current;
|
|
@@ -1015,7 +1141,8 @@ export function PickScherm({ station, onBack }) {
|
|
|
1015
1141
|
setPending(restored);
|
|
1016
1142
|
setActiveCartId(failed[0]?.cartId ?? null);
|
|
1017
1143
|
const firstError = failed[0]?.job?.foutmelding ?? jobTexts.unknown;
|
|
1018
|
-
toast.error(
|
|
1144
|
+
toast.error(printErrorText(firstError)?.message ??
|
|
1145
|
+
`${failed.length} ${failed.length === 1 ? "pakbon is" : "pakbonnen zijn"} niet geprint. ${PRINT_FAILED_HINT}`, { description: `Details voor beheer: ${firstError}` });
|
|
1019
1146
|
clearBatch();
|
|
1020
1147
|
setBusyId(null);
|
|
1021
1148
|
refresh();
|
|
@@ -1030,15 +1157,20 @@ export function PickScherm({ station, onBack }) {
|
|
|
1030
1157
|
setActiveCartId(null);
|
|
1031
1158
|
setBusyId(null);
|
|
1032
1159
|
refresh();
|
|
1033
|
-
|
|
1160
|
+
// Geen automatisch "Opdracht bevestigen": alleen na een klik op "Volgende printen".
|
|
1034
1161
|
})().catch((error) => {
|
|
1035
1162
|
finalizedBatch.current = null;
|
|
1036
1163
|
setBusyId(null);
|
|
1037
1164
|
showPrintError(error);
|
|
1038
1165
|
});
|
|
1039
|
-
}, [batch, sessionId, pending, jobTexts, clearBatch, refresh,
|
|
1166
|
+
}, [batch, sessionId, pending, jobTexts, clearBatch, refresh, showPrintError]);
|
|
1040
1167
|
/** Tik op een al geprinte kar: die pakbon nog een keer naar de printer sturen. */
|
|
1041
1168
|
const reprintCart = useCallback(async (item, cart) => {
|
|
1169
|
+
const block = printBlockMessage(item, siteTz);
|
|
1170
|
+
if (block) {
|
|
1171
|
+
toast.warning(block);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1042
1174
|
setBusyId("confirm");
|
|
1043
1175
|
try {
|
|
1044
1176
|
const jobId = await pickApi.reprintCart(item.id, sessionId, cart.cartId);
|
|
@@ -1060,7 +1192,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
1060
1192
|
finally {
|
|
1061
1193
|
setBusyId(null);
|
|
1062
1194
|
}
|
|
1063
|
-
}, [sessionId, t, track, showPrintError]);
|
|
1195
|
+
}, [sessionId, t, track, showPrintError, siteTz]);
|
|
1064
1196
|
/** Eén losse pakbon (kar) printen vanaf de knop op de karregel. */
|
|
1065
1197
|
const printSingleCartInner = useCallback(async (item, cart) => {
|
|
1066
1198
|
// Alleen een kar uit het karplan van deze rij; bij een eigen kar alleen die.
|
|
@@ -1197,8 +1329,8 @@ export function PickScherm({ station, onBack }) {
|
|
|
1197
1329
|
? 100
|
|
1198
1330
|
: Math.round((checkDone / checkTotal) * 100);
|
|
1199
1331
|
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
|
|
1200
|
-
? "Wachtrij
|
|
1201
|
-
: `Pakbonnen controleren
|
|
1332
|
+
? "Wachtrij laden…"
|
|
1333
|
+
: `Pakbonnen controleren (${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"), openJobIds.length > 0 ? ` · ${openJobIds.length} wacht op printer` : ""] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => void 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: () => void 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 || actionBusy), 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" })), "Volgende printen (", cartsPerPrint, " karren)"] })), _jsxs(Button, { size: "lg", className: "hidden h-14 px-8 text-lg md:inline-flex", onClick: () => (pending ? confirmPending() : fetchNextLocked()), disabled: (busyId !== null || actionBusy) || fetchingData, children: [(busyId !== null || actionBusy) || 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")] })] })] }), syncError && (_jsxs("div", { role: "alert", 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" }), _jsxs("span", { children: [SYNC_ERROR_MESSAGE, _jsxs("details", { className: "mt-1 text-xs text-muted-foreground", children: [_jsx("summary", { children: "Details voor beheer" }), syncError] })] })] })), !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." })] })), (cartMissing || rowGone) && (_jsxs("div", { role: "alert", className: "flex flex-col gap-2 rounded-md border-2 border-destructive/50 bg-destructive/5 p-3 text-sm text-destructive sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("span", { className: "flex items-start gap-2", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), rowGone ? ROW_GONE_MESSAGE : CART_MISSING_MESSAGE] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
1202
1334
|
setCartMissing(false);
|
|
1203
1335
|
setRowGone(false);
|
|
1204
1336
|
setPending(null);
|
|
@@ -1211,9 +1343,9 @@ export function PickScherm({ station, onBack }) {
|
|
|
1211
1343
|
setLastCartResults((prev) => prev.filter((x) => x.cartId !== r.cartId));
|
|
1212
1344
|
void reprintCart(lastPrinted, cart);
|
|
1213
1345
|
}, children: "Opnieuw printen" })] }, r.cartId))) }) })), lastCartResults.length > 1 && (_jsxs("div", { className: "rounded-md border p-3 text-sm", "aria-label": "Resultaat per kar", children: [_jsx("p", { className: "mb-1 font-semibold text-foreground", children: "Resultaat per kar" }), _jsx("ul", { className: "space-y-0.5 text-muted-foreground", children: lastCartResults.map((r) => (_jsx("li", { className: r.outcome === "created" ? "text-foreground" : r.outcome === "error" ? "text-destructive" : "", children: cartResultLine(r) }, r.cartId))) })] })), _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 || actionBusy), children: t("cancel") }), _jsxs(Button, { size: "lg", className: "h-14 w-full px-8 text-lg sm:w-auto", onClick: confirmPending, disabled: (busyId !== null || actionBusy) || 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
|
|
1214
|
-
? "De pakbon wordt
|
|
1346
|
+
? "De pakbon wordt opgevraagd. Zodra alles compleet is, verschijnt de printknop weer."
|
|
1215
1347
|
: planReason
|
|
1216
|
-
? `${planReason} Klik op "Pakbon ophalen" om hem
|
|
1348
|
+
? `${planReason} Klik op "Pakbon ophalen" om hem op te vragen.`
|
|
1217
1349
|
: NOT_READY_NOTICE })] }) })), cartPlan && (_jsxs(CardContent, { className: "border-t p-4 sm:p-5", children: [_jsx(OrderStickerMeldingen, { orderId: cartPlan.orderId, slug: station.slug ?? null }), _jsx("div", { className: "mb-3 empty:hidden", children: _jsx(PlanWarnings, { warnings: cartPlan.warnings }) }), _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) => {
|
|
1218
1350
|
const done = printedCartIdsOf(pending).includes(cart.cartId);
|
|
1219
1351
|
const active = cart.cartId === activeCartId;
|
|
@@ -1240,7 +1372,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
1240
1372
|
const cart = cartPlan?.carts.find((c) => c.cartId === entry.cartId) ?? null;
|
|
1241
1373
|
const item = lastPrinted ?? pending;
|
|
1242
1374
|
return (_jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2 text-sm font-medium text-destructive", children: [_jsxs("span", { children: [entry.cartLabel ? `${entry.cartLabel}: ` : "", stuckMessage(minutes), entry.job?.printer_naam ? ` (${entry.job.printer_naam})` : ""] }), cart && item && (_jsx(Button, { size: "sm", variant: "outline", disabled: busyId !== null || actionBusy, onClick: () => void reprintCart(item, cart), children: "Opnieuw proberen" }))] }, entry.jobId));
|
|
1243
|
-
})] })] }) })), 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:
|
|
1375
|
+
})] })] }) })), 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: printStageLabel(tracked.job ?? { status: jobStatus }) }), tracked.job?.printer_naam && (_jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [jobTexts.printerLabel, ": ", tracked.job.printer_naam] })), (() => {
|
|
1244
1376
|
const minutes = stuckMinutesOf(tracked.job, sentAtRef.current.get(tracked.jobId) ?? null, nowTick);
|
|
1245
1377
|
if (minutes === null)
|
|
1246
1378
|
return null;
|
|
@@ -1253,15 +1385,21 @@ export function PickScherm({ station, onBack }) {
|
|
|
1253
1385
|
}, disabled: (busyId !== null || actionBusy) || !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: () => {
|
|
1254
1386
|
clearJob();
|
|
1255
1387
|
clearShippingNotes();
|
|
1388
|
+
setAutoSendOrderId(null);
|
|
1389
|
+
setAutoSend(null);
|
|
1256
1390
|
}, 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")
|
|
1257
|
-
? "
|
|
1258
|
-
:
|
|
1391
|
+
? "Pakbon en verzendbrief zijn naar de printer gestuurd."
|
|
1392
|
+
: autoSendMessage(autoSend, siteTz) ?? "De verzendbrief is nog niet geprint." }), shippingNotes.map((note) => {
|
|
1259
1393
|
const message = shippingNoteMessage(note.state);
|
|
1260
1394
|
const failed = note.state.status === "mislukt" || note.state.status === "geblokkeerd";
|
|
1261
1395
|
const waiting = isWaitingShippingNote(note.state.status);
|
|
1262
1396
|
const typeLabel = shippingNoteSourceLabel(note.state);
|
|
1263
|
-
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:
|
|
1264
|
-
|
|
1397
|
+
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: (note.state.status !== "geprint" && autoSendMessage(autoSend, siteTz)) ||
|
|
1398
|
+
(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
|
|
1399
|
+
? `Volgende poging: ${formatSiteTime(note.state.nextAttemptAt, siteTz)}`
|
|
1265
1400
|
: ""] }))] })] }, note.jobId));
|
|
1266
|
-
})] }) })), _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 || actionBusy), 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" })), "
|
|
1401
|
+
})] }) })), _jsxs(Button, { variant: "outline", size: "lg", className: "h-12 w-full sm:hidden", "aria-expanded": filtersOpen, onClick: () => setFiltersOpen((v) => !v), children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), " ", ui.filters] }), _jsx(Card, { className: filtersOpen ? "" : "hidden sm:block", 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 || actionBusy), 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" })), "Volgende printen (", 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")] })) : (_jsxs(_Fragment, { children: [earlierRows.length > 0 && (_jsxs("section", { className: "mb-4 rounded-lg border border-primary/40 bg-muted/40 p-3", "aria-label": "Nog niet gescand van eerdere dagen", children: [_jsxs("h3", { className: "mb-2 text-sm font-semibold text-foreground", children: ["Nog niet gescand van eerdere dagen (", earlierRows.length, ")"] }), _jsx("ul", { className: "divide-y divide-border", children: earlierRows.map((row) => (_jsxs("li", { className: "flex flex-wrap items-center justify-between gap-2 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: row.document_number ?? row.order_number ?? "Pakbon" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [pickDayShortLabel(pickDayOf(row)), row.customer_name ? ` · ${row.customer_name}` : ""] })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: busyId !== null || actionBusy, onClick: async () => {
|
|
1402
|
+
await printAllForItem(row);
|
|
1403
|
+
void refetchEarlier();
|
|
1404
|
+
}, children: "Opnieuw printen" })] }, row.id))) })] })), _jsx(QueueTable, { items: items, lockedIds: lockedIds, statuses: Array.isArray(statuses) ? statuses : [], documentTypes: Array.isArray(documentTypes) ? documentTypes : [], busyId: busyId, readinessById: readinessById, siteTimeZone: siteTz, waitingIds: waitingQueueIds, onPick: handlePick, onReprint: handleReprintLocked, 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() : fetchNextLocked()), disabled: (busyId !== null || actionBusy) || fetchingData, children: [(busyId !== null || actionBusy) || 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) })] }));
|
|
1267
1405
|
}
|
|
@@ -11,7 +11,12 @@ interface Props {
|
|
|
11
11
|
ready: boolean;
|
|
12
12
|
reason: string | null;
|
|
13
13
|
replaced?: boolean;
|
|
14
|
+
mismatch?: boolean;
|
|
15
|
+
waitUntil?: string | null;
|
|
16
|
+
notPrintable?: boolean;
|
|
14
17
|
}>;
|
|
18
|
+
/** Tijdzone van de vestiging; null = tijden onbekend. */
|
|
19
|
+
siteTimeZone?: string | null;
|
|
15
20
|
/** Rijen met een printopdracht die nog niet voltooid is. */
|
|
16
21
|
waitingIds?: Set<string>;
|
|
17
22
|
onPick: (item: PickQueueItem) => void;
|
|
@@ -23,6 +28,6 @@ interface Props {
|
|
|
23
28
|
readOnly?: boolean;
|
|
24
29
|
t?: PickTranslator;
|
|
25
30
|
}
|
|
26
|
-
export declare function QueueTable({ items, lockedIds, statuses, documentTypes, busyId, readinessById, waitingIds, onPick, onReprint, onPrintAll, onSelectPrinted, onMarkPrinted, onPreview, readOnly, t: translator, }: Props): import("react").JSX.Element;
|
|
31
|
+
export declare function QueueTable({ items, lockedIds, statuses, documentTypes, busyId, readinessById, waitingIds, onPick, onReprint, onPrintAll, onSelectPrinted, onMarkPrinted, onPreview, readOnly, t: translator, siteTimeZone, }: Props): import("react").JSX.Element;
|
|
27
32
|
export {};
|
|
28
33
|
//# 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;AAMvF,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,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACvL,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,4DAA4D;IAC5D,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,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;AA6DD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,SAAS,EACT,QAAQ,EACR,aAAa,EACb,MAAM,EACN,aAAa,EACb,UAAU,EACV,MAAM,EACN,SAAS,EACT,UAAU,EACV,eAAe,EACf,aAAa,EACb,SAAS,EAGT,QAAQ,EACR,CAAC,EAAE,UAAU,EACb,YAAmB,GACpB,EAAE,KAAK,+BA2eP"}
|