@flowselections/pick-schermen-kwekers 1.0.41 → 1.0.46
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 +194 -37
- package/dist-lib/components/pick/QueueTable.d.ts +10 -1
- package/dist-lib/components/pick/QueueTable.d.ts.map +1 -1
- package/dist-lib/components/pick/QueueTable.js +57 -16
- package/dist-lib/components/pick/ReferentiedataBanner.js +1 -1
- 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/previewAuthStorage.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.js +22 -4
- package/dist-lib/integrations/supabase/types.d.ts +725 -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/__tests__/pick144.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/pick144.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/pick144.test.js +23 -0
- package/dist-lib/lib/__tests__/pick145.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/pick145.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/pick145.test.js +35 -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/pick144.d.ts +17 -0
- package/dist-lib/lib/pick144.d.ts.map +1 -0
- package/dist-lib/lib/pick144.js +53 -0
- package/dist-lib/lib/pick145.d.ts +23 -0
- package/dist-lib/lib/pick145.d.ts.map +1 -0
- package/dist-lib/lib/pick145.js +88 -0
- package/dist-lib/lib/pickApi.d.ts +17 -0
- package/dist-lib/lib/pickApi.d.ts.map +1 -1
- package/dist-lib/lib/pickApi.js +37 -6
- 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 +31 -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":"AA0DA,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,+BAypEpD"}
|
|
@@ -1,21 +1,25 @@
|
|
|
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";
|
|
9
|
+
import { isPickWindowNotStarted } from "../../lib/pick144";
|
|
10
|
+
import { isForcedFor, lateSignupOf, releaseInfoOf, releaseMessage } from "../../lib/pick145";
|
|
8
11
|
import { PreviewDialog } from "./PreviewDialog";
|
|
9
12
|
import { ShippingNoteScan } from "./ShippingNoteScan";
|
|
10
13
|
import { getSessionId, pickApi } from "../../lib/pickApi";
|
|
11
14
|
import { sendLogisticsEvent } from "../../lib/logisticsApi";
|
|
15
|
+
import { CART_PLAN_MISMATCH_MESSAGE, formatSiteTime, WORKDAYS_UNKNOWN, autoSendMessage, fetchAutoSend, isPickWorkday, mismatchReason, nextWorkday, printErrorText, PRINT_FAILED_HINT, printStageLabel, siteTimeZone, siteToday, } from "../../lib/pick142";
|
|
12
16
|
import { completeLogisticsTaskForItem } from "../../lib/pickPrint";
|
|
13
|
-
import { isPdfOnlyFailure, pickReadiness, printedCartIdsOf, pdfSaveWarning, unitRefOf, statusRefOf, unitTitleOf, } from "../../lib/pickTypes";
|
|
17
|
+
import { cartRowOf, isPdfOnlyFailure, pickReadiness, printedCartIdsOf, pdfSaveWarning, unitRefOf, statusRefOf, unitTitleOf, } from "../../lib/pickTypes";
|
|
14
18
|
import { rowFingerprint, rowsToVerify } from "../../lib/pickVerify";
|
|
15
19
|
import { alreadyPrintedResults, CART_MISSING_MESSAGE, cartResultLine, cartsForRow, isCartMissingError, reprintCartsIndividually, resettableJobs, runExclusive, resultsFromBatch, resultsFromBatchError, summarizeCartResults, trackableJobs, } from "../../lib/pickCartResults";
|
|
16
20
|
import { getPickTranslator } from "../../lib/pickI18n";
|
|
17
21
|
import { friendlyPickError, getPickUiTexts } from "../../lib/pickUiText";
|
|
18
|
-
import { getPrintJobTexts
|
|
22
|
+
import { getPrintJobTexts } from "../../lib/pickPrintJobText";
|
|
19
23
|
import { defaultFilters, usePickDays, usePickHeartbeat, usePickQueue, } from "../../hooks/usePickQueue";
|
|
20
24
|
import { usePickDocumentTypes, usePickStatuses, useStationPrintPlek, } from "../../hooks/usePickConfig";
|
|
21
25
|
import { usePrintBatch, usePrintJob } from "../../hooks/usePrintJob";
|
|
@@ -38,7 +42,7 @@ async function releaseQuietly(itemId) {
|
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
/** 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
|
|
45
|
+
const NOT_READY_NOTICE = "Deze pakbon is nog niet printklaar. Klik op \"Pakbon ophalen\" om de ontbrekende gegevens op te vragen.";
|
|
42
46
|
/**
|
|
43
47
|
* Pickscherm. De Logistiek Engine bepaalt welke documenten zichtbaar zijn en
|
|
44
48
|
* in welke volgorde; dit scherm toont uitsluitend wat de wachtrij teruggeeft.
|
|
@@ -50,6 +54,8 @@ export function PickScherm({ station, onBack }) {
|
|
|
50
54
|
const [chosenDay, setChosenDay] = useState(null);
|
|
51
55
|
const [loadingDay, setLoadingDay] = useState(false);
|
|
52
56
|
const [searchInput, setSearchInput] = useState("");
|
|
57
|
+
/** Telefoon: filters zitten achter een knop zodat de lijst bovenaan begint. */
|
|
58
|
+
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
53
59
|
const [busyId, setBusyId] = useState(null);
|
|
54
60
|
const [pending, setPending] = useState(null);
|
|
55
61
|
const [cartPlan, setCartPlan] = useState(null);
|
|
@@ -57,6 +63,42 @@ export function PickScherm({ station, onBack }) {
|
|
|
57
63
|
const [planStatus, setPlanStatus] = useState(null);
|
|
58
64
|
const [fetchingData, setFetchingData] = useState(false);
|
|
59
65
|
const [readinessById, setReadinessById] = useState({});
|
|
66
|
+
/** Tijdzone van de vestiging (Engine-werktijden); null = onbekend. */
|
|
67
|
+
const [siteTz, setSiteTz] = useState(null);
|
|
68
|
+
const siteTzRef = useRef(null);
|
|
69
|
+
siteTzRef.current = siteTz;
|
|
70
|
+
const pickDayRef = useRef(null);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
void siteTimeZone().then((tz) => {
|
|
73
|
+
setKnownSiteZone(tz);
|
|
74
|
+
setSiteTz(tz);
|
|
75
|
+
});
|
|
76
|
+
}, []);
|
|
77
|
+
/** Order waarvan na de scan het automatisch versturen gevolgd wordt. */
|
|
78
|
+
const [autoSendOrderId, setAutoSendOrderId] = useState(null);
|
|
79
|
+
const [autoSend, setAutoSend] = useState(null);
|
|
80
|
+
const cartPlanRef = useRef(null);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (cartPlan)
|
|
83
|
+
cartPlanRef.current = cartPlan;
|
|
84
|
+
}, [cartPlan]);
|
|
85
|
+
/** Na de scan: stand van het automatisch versturen, elke 10 s (alleen lezen). */
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!autoSendOrderId)
|
|
88
|
+
return;
|
|
89
|
+
let stopped = false;
|
|
90
|
+
const read = async () => {
|
|
91
|
+
const state = await fetchAutoSend(autoSendOrderId, station.slug ?? null);
|
|
92
|
+
if (!stopped)
|
|
93
|
+
setAutoSend(state);
|
|
94
|
+
};
|
|
95
|
+
void read();
|
|
96
|
+
const timer = window.setInterval(() => void read(), 10000);
|
|
97
|
+
return () => {
|
|
98
|
+
stopped = true;
|
|
99
|
+
window.clearInterval(timer);
|
|
100
|
+
};
|
|
101
|
+
}, [autoSendOrderId, station.slug]);
|
|
60
102
|
/** De vastgehouden rij is door de Engine vervangen of bestaat niet meer. */
|
|
61
103
|
const [rowGone, setRowGone] = useState(false);
|
|
62
104
|
/** Moment waarop dit scherm een printopdracht verstuurde (voor "wacht al X minuten"). */
|
|
@@ -98,7 +140,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
98
140
|
toast.success(result.message ?? "Verzendbrieven worden opgehaald.");
|
|
99
141
|
}
|
|
100
142
|
else {
|
|
101
|
-
toast.info("
|
|
143
|
+
toast.info("De verzendbrieven worden zo vanzelf opgehaald. Je hoeft niets te doen.");
|
|
102
144
|
}
|
|
103
145
|
}
|
|
104
146
|
finally {
|
|
@@ -112,12 +154,14 @@ export function PickScherm({ station, onBack }) {
|
|
|
112
154
|
*/
|
|
113
155
|
const track = useCallback((args) => {
|
|
114
156
|
trackJobRaw(args);
|
|
157
|
+
setAutoSendOrderId(cartPlanRef.current?.orderId ?? null);
|
|
115
158
|
watchShippingNotes([
|
|
116
159
|
{ jobId: args.jobId, cartId: args.cartId ?? null, cartLabel: args.cartLabel ?? null },
|
|
117
160
|
]);
|
|
118
161
|
}, [trackJobRaw, watchShippingNotes]);
|
|
119
162
|
const trackBatch = useCallback((args) => {
|
|
120
163
|
trackBatchRaw(args);
|
|
164
|
+
setAutoSendOrderId(cartPlanRef.current?.orderId ?? null);
|
|
121
165
|
watchShippingNotes(args.jobs);
|
|
122
166
|
}, [trackBatchRaw, watchShippingNotes]);
|
|
123
167
|
/**
|
|
@@ -164,14 +208,25 @@ export function PickScherm({ station, onBack }) {
|
|
|
164
208
|
enabled: lang !== null,
|
|
165
209
|
pickDayOverride: chosenDay,
|
|
166
210
|
});
|
|
211
|
+
/** Geprint maar nog niet gescand, van een eerdere pickdag (blijft open tot de scan). */
|
|
212
|
+
const viewDay = chosenDay ?? pickDay ?? null;
|
|
213
|
+
const { data: earlierRaw, refetch: refetchEarlier } = useQuery({
|
|
214
|
+
queryKey: ["pick", "unscannedEarlier", station.id, viewDay ?? "-"],
|
|
215
|
+
enabled: lang !== null && !!viewDay,
|
|
216
|
+
refetchInterval: 30000,
|
|
217
|
+
queryFn: () => pickApi.unscannedEarlier({ stationId: station.id, beforeDay: viewDay }),
|
|
218
|
+
});
|
|
219
|
+
const earlierRows = Array.isArray(earlierRaw) ? earlierRaw : [];
|
|
167
220
|
// Eén toast per nieuwe syncfout; de rode balk blijft staan tot hij is opgelost.
|
|
221
|
+
const SYNC_ERROR_MESSAGE = "De lijst kon niet worden ververst. Controleer de wifi en klik op ‘Wachtrij verversen’.";
|
|
168
222
|
const lastSyncError = useRef(null);
|
|
169
223
|
useEffect(() => {
|
|
170
224
|
if (syncError && syncError !== lastSyncError.current) {
|
|
171
|
-
toast.error(
|
|
225
|
+
toast.error(SYNC_ERROR_MESSAGE, { description: `Details voor beheer: ${syncError}` });
|
|
172
226
|
}
|
|
173
227
|
lastSyncError.current = syncError;
|
|
174
228
|
}, [syncError]);
|
|
229
|
+
pickDayRef.current = chosenDay ?? pickDay ?? null;
|
|
175
230
|
const { data: pickDays } = usePickDays(lang !== null);
|
|
176
231
|
const safePickDays = Array.isArray(pickDays) ? pickDays : [];
|
|
177
232
|
const dayLabel = useCallback((day) => new Date(`${day}T00:00:00`).toLocaleDateString(t.lang === "en" ? "en-GB" : "nl-NL", {
|
|
@@ -197,6 +252,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
197
252
|
/** Kiest een andere pickdag en laat de Engine die dag alvast klaarzetten. */
|
|
198
253
|
const chooseDay = useCallback(async (value) => {
|
|
199
254
|
const day = value === "current" ? null : value;
|
|
255
|
+
const previousDay = chosenDay;
|
|
200
256
|
const previousPending = pending;
|
|
201
257
|
daySelectionVersion.current += 1;
|
|
202
258
|
setPending(null);
|
|
@@ -223,24 +279,33 @@ export function PickScherm({ station, onBack }) {
|
|
|
223
279
|
toast.success(ui.pickDayLoaded);
|
|
224
280
|
}
|
|
225
281
|
catch (error) {
|
|
282
|
+
// Bijv. niet-werkdag: terug naar de vorige dag, tekst van de Engine tonen.
|
|
283
|
+
setChosenDay(previousDay);
|
|
226
284
|
toast.error(error instanceof Error ? error.message : ui.genericError);
|
|
227
285
|
}
|
|
228
286
|
finally {
|
|
229
287
|
setLoadingDay(false);
|
|
230
288
|
}
|
|
231
|
-
}, [pending, refresh, ui]);
|
|
289
|
+
}, [pending, refresh, ui, chosenDay]);
|
|
232
290
|
/**
|
|
233
291
|
* Verschuift de pickdag één dag vooruit of achteruit. Zonder handmatige keuze
|
|
234
292
|
* is de kalenderdag van vandaag het startpunt, niet de dag van de Engine.
|
|
235
293
|
*/
|
|
236
|
-
const shiftDay = useCallback((delta) => {
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
294
|
+
const shiftDay = useCallback(async (delta) => {
|
|
295
|
+
// Vandaag in de tijdzone van de vestiging; onbekend = niet verschuiven.
|
|
296
|
+
const todayIso = siteToday(siteTz);
|
|
297
|
+
const base = chosenDay ?? pickDay ?? todayIso;
|
|
298
|
+
if (!base) {
|
|
299
|
+
toast.error(WORKDAYS_UNKNOWN);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const iso = await nextWorkday(base, delta, isPickWorkday);
|
|
303
|
+
if (!iso) {
|
|
304
|
+
toast.error(WORKDAYS_UNKNOWN);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
242
307
|
void chooseDay(iso === todayIso || (pickDay && iso === pickDay) ? "current" : iso);
|
|
243
|
-
}, [chosenDay, pickDay, chooseDay]);
|
|
308
|
+
}, [chosenDay, pickDay, chooseDay, siteTz]);
|
|
244
309
|
/**
|
|
245
310
|
* Haalt het vastgelegde karplan én de toestand van de planning uit de bron.
|
|
246
311
|
* Er wordt niets verzonnen: is het plan er niet, of is het nog niet
|
|
@@ -336,6 +401,8 @@ export function PickScherm({ station, onBack }) {
|
|
|
336
401
|
const fresh = todo.filter((item) => !verifyMemo.current.has(item.id)).length;
|
|
337
402
|
if (fresh > 0)
|
|
338
403
|
setCheckProgress({ done: 0, total: fresh });
|
|
404
|
+
else
|
|
405
|
+
setCheckProgress({ done: 0, total: 0 });
|
|
339
406
|
running = true;
|
|
340
407
|
const next = {};
|
|
341
408
|
try {
|
|
@@ -347,14 +414,24 @@ export function PickScherm({ station, onBack }) {
|
|
|
347
414
|
let entry = {
|
|
348
415
|
checking: false,
|
|
349
416
|
ready: false,
|
|
350
|
-
reason: "De
|
|
417
|
+
reason: "De pakbon is nog niet klaar. Klik over een minuut op ‘Pakbon ophalen’.",
|
|
418
|
+
waitUntil: null,
|
|
351
419
|
};
|
|
352
420
|
if (r && !r.error) {
|
|
353
421
|
const state = pickReadiness(r.plan, r.status, item);
|
|
354
|
-
|
|
422
|
+
const rowWait = rowWaitOf(item.payload);
|
|
423
|
+
const notPrintable = !rowWait.printable || r.plan?.printable === false;
|
|
424
|
+
const waitUntil = notPrintable ? rowWait.until ?? r.plan?.waitUntil ?? null : null;
|
|
425
|
+
const st = r.status;
|
|
426
|
+
const ownCartId = (() => { const p = (item.payload ?? {}); return typeof p["cartId"] === "string" ? p["cartId"] : null; })();
|
|
427
|
+
const notStarted = !state.ready && isPickWindowNotStarted(st) && !isForcedFor(item, pickDayRef.current)
|
|
428
|
+
? releaseMessage(releaseInfoOf(r.plan?.raw, ownCartId, item, st), siteTzRef.current)
|
|
429
|
+
: null;
|
|
430
|
+
const lateSignup = lateSignupOf(item, r.plan?.raw);
|
|
431
|
+
entry = { checking: false, ready: state.ready && !notPrintable && !notStarted, reason: state.reason, waitUntil, notPrintable, notStarted, lateSignup };
|
|
355
432
|
}
|
|
356
433
|
else if (r?.error && isRowGoneError(r.error)) {
|
|
357
|
-
entry = { checking: false, ready: false, reason: String(r.error), replaced: true };
|
|
434
|
+
entry = { checking: false, ready: false, reason: String(r.error), replaced: true, waitUntil: null };
|
|
358
435
|
}
|
|
359
436
|
next[item.id] = entry;
|
|
360
437
|
verifyMemo.current.set(item.id, {
|
|
@@ -415,9 +492,15 @@ export function PickScherm({ station, onBack }) {
|
|
|
415
492
|
/* het seintje is aanvullend op de bewaking */
|
|
416
493
|
}
|
|
417
494
|
}
|
|
495
|
+
let lastMismatches = [];
|
|
418
496
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
419
497
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
420
|
-
const
|
|
498
|
+
const checked = await pickApi.queueCartPlanChecked(item.id);
|
|
499
|
+
const plan = checked.plan;
|
|
500
|
+
const own = cartRowOf(item);
|
|
501
|
+
lastMismatches = own
|
|
502
|
+
? checked.mismatches.filter((m) => m.cartId === own.cartId)
|
|
503
|
+
: checked.mismatches;
|
|
421
504
|
let status = null;
|
|
422
505
|
const statusRef = statusRefOf(item, plan);
|
|
423
506
|
try {
|
|
@@ -444,7 +527,42 @@ export function PickScherm({ station, onBack }) {
|
|
|
444
527
|
return true;
|
|
445
528
|
}
|
|
446
529
|
}
|
|
447
|
-
|
|
530
|
+
if (lastMismatches.length > 0) {
|
|
531
|
+
// Nooit stil blijven hangen: duidelijke melding en de reden naar de bewaking.
|
|
532
|
+
const reason = mismatchReason(lastMismatches);
|
|
533
|
+
setReadinessById((prev) => ({
|
|
534
|
+
...prev,
|
|
535
|
+
[item.id]: { checking: false, ready: false, reason: CART_PLAN_MISMATCH_MESSAGE, mismatch: true },
|
|
536
|
+
}));
|
|
537
|
+
toast.error(CART_PLAN_MISMATCH_MESSAGE, { description: `Details voor beheer: ${reason}` });
|
|
538
|
+
try {
|
|
539
|
+
await pickApi.reportMissingCartPlans();
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
/* bewaking is aanvullend */
|
|
543
|
+
}
|
|
544
|
+
if (ref) {
|
|
545
|
+
try {
|
|
546
|
+
await sendLogisticsEvent({
|
|
547
|
+
event_type: "order.changed",
|
|
548
|
+
order_ref: ref,
|
|
549
|
+
shadow: false,
|
|
550
|
+
payload: {
|
|
551
|
+
reason: "pick.cart_plan_mismatch",
|
|
552
|
+
detail: reason,
|
|
553
|
+
mismatches: lastMismatches,
|
|
554
|
+
queue_item_id: item.id,
|
|
555
|
+
idempotency_key: `pick.cart_plan_mismatch:${item.id}:${reason}`,
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
/* het seintje is aanvullend op de bewaking */
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
toast.warning(planReason ?? "De pakbon is nog niet klaar. Klik over een minuut op ‘Pakbon ophalen’.");
|
|
448
566
|
return false;
|
|
449
567
|
}
|
|
450
568
|
finally {
|
|
@@ -481,7 +599,12 @@ export function PickScherm({ station, onBack }) {
|
|
|
481
599
|
setPending(restored);
|
|
482
600
|
setActiveCartId(tracked.cartId);
|
|
483
601
|
}
|
|
484
|
-
|
|
602
|
+
{
|
|
603
|
+
const friendly = printErrorText(job.foutmelding);
|
|
604
|
+
toast.error(friendly?.message ?? `Niet geprint. ${PRINT_FAILED_HINT}`, {
|
|
605
|
+
description: job.foutmelding ? `Details voor beheer: ${job.foutmelding}` : undefined,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
485
608
|
}
|
|
486
609
|
catch (e) {
|
|
487
610
|
toast.error(e instanceof Error ? e.message : jobTexts.failed);
|
|
@@ -522,6 +645,11 @@ export function PickScherm({ station, onBack }) {
|
|
|
522
645
|
return expiredClaim;
|
|
523
646
|
}, [t, handleRowGone]);
|
|
524
647
|
const handlePick = useCallback(async (item) => {
|
|
648
|
+
const block = printBlockMessage(item, siteTz);
|
|
649
|
+
if (block) {
|
|
650
|
+
toast.warning(block);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
525
653
|
setBusyId(item.id);
|
|
526
654
|
try {
|
|
527
655
|
const claimed = await pickApi.claim({
|
|
@@ -548,7 +676,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
548
676
|
finally {
|
|
549
677
|
setBusyId(null);
|
|
550
678
|
}
|
|
551
|
-
}, [station.id, station.lock_timeout_seconds, sessionId, loadCartPlan, refresh, showPrintError]);
|
|
679
|
+
}, [station.id, station.lock_timeout_seconds, sessionId, loadCartPlan, refresh, showPrintError, siteTz]);
|
|
552
680
|
/**
|
|
553
681
|
* Eén vergrendeling voor alle printacties. Gaat direct bij de klik in (ref,
|
|
554
682
|
* niet pas bij de volgende weergave), zodat een dubbelklik nooit een tweede
|
|
@@ -598,6 +726,11 @@ export function PickScherm({ station, onBack }) {
|
|
|
598
726
|
* zonder de wachtrijstatus te wijzigen.
|
|
599
727
|
*/
|
|
600
728
|
const handleReprint = useCallback(async (item) => {
|
|
729
|
+
const block = printBlockMessage(item, siteTz);
|
|
730
|
+
if (block) {
|
|
731
|
+
toast.warning(block);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
601
734
|
setBusyId(item.id);
|
|
602
735
|
try {
|
|
603
736
|
const jobId = await pickApi.reprint(item.id, sessionId);
|
|
@@ -618,7 +751,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
618
751
|
finally {
|
|
619
752
|
setBusyId(null);
|
|
620
753
|
}
|
|
621
|
-
}, [t, sessionId, track, showPrintError]);
|
|
754
|
+
}, [t, sessionId, track, showPrintError, siteTz]);
|
|
622
755
|
/** Open een geprinte order bovenaan zonder de wachtrijstatus te wijzigen. */
|
|
623
756
|
const handleReprintLocked = useMemo(() => withActionLock(handleReprint), [withActionLock, handleReprint]);
|
|
624
757
|
const selectPrintedForReprint = useCallback(async (item) => {
|
|
@@ -644,6 +777,11 @@ export function PickScherm({ station, onBack }) {
|
|
|
644
777
|
* ontbrekende gegevens bij de bron opgevraagd.
|
|
645
778
|
*/
|
|
646
779
|
const printAllForItemInner = useCallback(async (item) => {
|
|
780
|
+
const block = printBlockMessage(item, siteTz);
|
|
781
|
+
if (block) {
|
|
782
|
+
toast.warning(block);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
647
785
|
setBusyId(item.id);
|
|
648
786
|
setCartMissing(false);
|
|
649
787
|
try {
|
|
@@ -747,6 +885,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
747
885
|
showCartResults,
|
|
748
886
|
refresh,
|
|
749
887
|
showPrintError,
|
|
888
|
+
siteTz,
|
|
750
889
|
]);
|
|
751
890
|
const printAllForItem = useMemo(() => withActionLock(printAllForItemInner), [withActionLock, printAllForItemInner]);
|
|
752
891
|
/**
|
|
@@ -768,6 +907,10 @@ export function PickScherm({ station, onBack }) {
|
|
|
768
907
|
for (const item of visible) {
|
|
769
908
|
if (targets.length >= cartsPerPrint)
|
|
770
909
|
break;
|
|
910
|
+
if (!isRowPrintable(item)) {
|
|
911
|
+
skipped.push({ title: unitTitleOf(item), reason: printBlockMessage(item, siteTz) ?? "nog niet printklaar" });
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
771
914
|
const state = readinessById[item.id];
|
|
772
915
|
if (!state || state.checking) {
|
|
773
916
|
skipped.push({ title: unitTitleOf(item), reason: "de pakbon wordt nog gecontroleerd" });
|
|
@@ -805,7 +948,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
805
948
|
finally {
|
|
806
949
|
setBatchPrinting(false);
|
|
807
950
|
}
|
|
808
|
-
}, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItemInner, showPrintError]);
|
|
951
|
+
}, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItemInner, showPrintError, siteTz]);
|
|
809
952
|
const printNextCarts = useMemo(() => withActionLock(printNextCartsInner), [withActionLock, printNextCartsInner]);
|
|
810
953
|
const fetchNext = useCallback(async (silent = false) => {
|
|
811
954
|
const requestedDayVersion = daySelectionVersion.current;
|
|
@@ -1015,7 +1158,8 @@ export function PickScherm({ station, onBack }) {
|
|
|
1015
1158
|
setPending(restored);
|
|
1016
1159
|
setActiveCartId(failed[0]?.cartId ?? null);
|
|
1017
1160
|
const firstError = failed[0]?.job?.foutmelding ?? jobTexts.unknown;
|
|
1018
|
-
toast.error(
|
|
1161
|
+
toast.error(printErrorText(firstError)?.message ??
|
|
1162
|
+
`${failed.length} ${failed.length === 1 ? "pakbon is" : "pakbonnen zijn"} niet geprint. ${PRINT_FAILED_HINT}`, { description: `Details voor beheer: ${firstError}` });
|
|
1019
1163
|
clearBatch();
|
|
1020
1164
|
setBusyId(null);
|
|
1021
1165
|
refresh();
|
|
@@ -1030,15 +1174,20 @@ export function PickScherm({ station, onBack }) {
|
|
|
1030
1174
|
setActiveCartId(null);
|
|
1031
1175
|
setBusyId(null);
|
|
1032
1176
|
refresh();
|
|
1033
|
-
|
|
1177
|
+
// Geen automatisch "Opdracht bevestigen": alleen na een klik op "Volgende printen".
|
|
1034
1178
|
})().catch((error) => {
|
|
1035
1179
|
finalizedBatch.current = null;
|
|
1036
1180
|
setBusyId(null);
|
|
1037
1181
|
showPrintError(error);
|
|
1038
1182
|
});
|
|
1039
|
-
}, [batch, sessionId, pending, jobTexts, clearBatch, refresh,
|
|
1183
|
+
}, [batch, sessionId, pending, jobTexts, clearBatch, refresh, showPrintError]);
|
|
1040
1184
|
/** Tik op een al geprinte kar: die pakbon nog een keer naar de printer sturen. */
|
|
1041
1185
|
const reprintCart = useCallback(async (item, cart) => {
|
|
1186
|
+
const block = printBlockMessage(item, siteTz);
|
|
1187
|
+
if (block) {
|
|
1188
|
+
toast.warning(block);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1042
1191
|
setBusyId("confirm");
|
|
1043
1192
|
try {
|
|
1044
1193
|
const jobId = await pickApi.reprintCart(item.id, sessionId, cart.cartId);
|
|
@@ -1060,7 +1209,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
1060
1209
|
finally {
|
|
1061
1210
|
setBusyId(null);
|
|
1062
1211
|
}
|
|
1063
|
-
}, [sessionId, t, track, showPrintError]);
|
|
1212
|
+
}, [sessionId, t, track, showPrintError, siteTz]);
|
|
1064
1213
|
/** Eén losse pakbon (kar) printen vanaf de knop op de karregel. */
|
|
1065
1214
|
const printSingleCartInner = useCallback(async (item, cart) => {
|
|
1066
1215
|
// Alleen een kar uit het karplan van deze rij; bij een eigen kar alleen die.
|
|
@@ -1197,8 +1346,10 @@ export function PickScherm({ station, onBack }) {
|
|
|
1197
1346
|
? 100
|
|
1198
1347
|
: Math.round((checkDone / checkTotal) * 100);
|
|
1199
1348
|
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
|
-
:
|
|
1349
|
+
? "Wachtrij laden…"
|
|
1350
|
+
: checkDone > 0
|
|
1351
|
+
? `Pakbonnen controleren (${checkDone} van ${checkTotal})…`
|
|
1352
|
+
: `Pakbonnen controleren (${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
1353
|
setCartMissing(false);
|
|
1203
1354
|
setRowGone(false);
|
|
1204
1355
|
setPending(null);
|
|
@@ -1211,9 +1362,9 @@ export function PickScherm({ station, onBack }) {
|
|
|
1211
1362
|
setLastCartResults((prev) => prev.filter((x) => x.cartId !== r.cartId));
|
|
1212
1363
|
void reprintCart(lastPrinted, cart);
|
|
1213
1364
|
}, 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
|
|
1365
|
+
? "De pakbon wordt opgevraagd. Zodra alles compleet is, verschijnt de printknop weer."
|
|
1215
1366
|
: planReason
|
|
1216
|
-
? `${planReason} Klik op "Pakbon ophalen" om hem
|
|
1367
|
+
? `${planReason} Klik op "Pakbon ophalen" om hem op te vragen.`
|
|
1217
1368
|
: 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
1369
|
const done = printedCartIdsOf(pending).includes(cart.cartId);
|
|
1219
1370
|
const active = cart.cartId === activeCartId;
|
|
@@ -1240,7 +1391,7 @@ export function PickScherm({ station, onBack }) {
|
|
|
1240
1391
|
const cart = cartPlan?.carts.find((c) => c.cartId === entry.cartId) ?? null;
|
|
1241
1392
|
const item = lastPrinted ?? pending;
|
|
1242
1393
|
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:
|
|
1394
|
+
})] })] }) })), 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
1395
|
const minutes = stuckMinutesOf(tracked.job, sentAtRef.current.get(tracked.jobId) ?? null, nowTick);
|
|
1245
1396
|
if (minutes === null)
|
|
1246
1397
|
return null;
|
|
@@ -1253,15 +1404,21 @@ export function PickScherm({ station, onBack }) {
|
|
|
1253
1404
|
}, 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
1405
|
clearJob();
|
|
1255
1406
|
clearShippingNotes();
|
|
1407
|
+
setAutoSendOrderId(null);
|
|
1408
|
+
setAutoSend(null);
|
|
1256
1409
|
}, 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
|
-
:
|
|
1410
|
+
? "Pakbon en verzendbrief zijn naar de printer gestuurd."
|
|
1411
|
+
: autoSendMessage(autoSend, siteTz) ?? "De verzendbrief is nog niet geprint." }), shippingNotes.map((note) => {
|
|
1259
1412
|
const message = shippingNoteMessage(note.state);
|
|
1260
1413
|
const failed = note.state.status === "mislukt" || note.state.status === "geblokkeerd";
|
|
1261
1414
|
const waiting = isWaitingShippingNote(note.state.status);
|
|
1262
1415
|
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
|
-
|
|
1416
|
+
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)) ||
|
|
1417
|
+
(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
|
|
1418
|
+
? `Volgende poging: ${formatSiteTime(note.state.nextAttemptAt, siteTz)}`
|
|
1265
1419
|
: ""] }))] })] }, 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" })), "
|
|
1420
|
+
})] }) })), _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, { className: "z-[100] bg-popover opacity-100", 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 () => {
|
|
1421
|
+
await printAllForItem(row);
|
|
1422
|
+
void refetchEarlier();
|
|
1423
|
+
}, 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, pickDay: chosenDay ?? pickDay ?? null, 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
1424
|
}
|
|
@@ -11,7 +11,16 @@ 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;
|
|
17
|
+
notStarted?: string | null;
|
|
18
|
+
lateSignup?: boolean;
|
|
14
19
|
}>;
|
|
20
|
+
/** Gekozen pickdag (YYYY-MM-DD), voor de vertrekdag. */
|
|
21
|
+
pickDay?: string | null;
|
|
22
|
+
/** Tijdzone van de vestiging; null = tijden onbekend. */
|
|
23
|
+
siteTimeZone?: string | null;
|
|
15
24
|
/** Rijen met een printopdracht die nog niet voltooid is. */
|
|
16
25
|
waitingIds?: Set<string>;
|
|
17
26
|
onPick: (item: PickQueueItem) => void;
|
|
@@ -23,6 +32,6 @@ interface Props {
|
|
|
23
32
|
readOnly?: boolean;
|
|
24
33
|
t?: PickTranslator;
|
|
25
34
|
}
|
|
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;
|
|
35
|
+
export declare function QueueTable({ items, lockedIds, statuses, documentTypes, busyId, readinessById, waitingIds, onPick, onReprint, onPrintAll, onSelectPrinted, onMarkPrinted, onPreview, readOnly, t: translator, siteTimeZone, pickDay, }: Props): import("react").JSX.Element;
|
|
27
36
|
export {};
|
|
28
37
|
//# sourceMappingURL=QueueTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueueTable.d.ts","sourceRoot":"","sources":["../../../src/components/pick/QueueTable.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"QueueTable.d.ts","sourceRoot":"","sources":["../../../src/components/pick/QueueTable.tsx"],"names":[],"mappings":"AAaA,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,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACzO,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,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;AA8DD,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,EACnB,OAAc,GACf,EAAE,KAAK,+BAqfP"}
|