@flowselections/pick-schermen-kwekers 1.0.24 → 1.0.29

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.
@@ -12,6 +12,7 @@ import { sendLogisticsEvent } from "../../lib/logisticsApi";
12
12
  import { completeLogisticsTaskForItem } from "../../lib/pickPrint";
13
13
  import { isPdfOnlyFailure, pickReadiness, printedCartIdsOf, pdfSaveWarning, unitRefOf, unitTitleOf, } from "../../lib/pickTypes";
14
14
  import { rowFingerprint, rowsToVerify } from "../../lib/pickVerify";
15
+ import { CART_MISSING_MESSAGE, cartResultLine, cartsForRow, isCartMissingError, reprintCartsIndividually, resettableJobs, runExclusive, resultsFromBatch, resultsFromBatchError, summarizeCartResults, trackableJobs, } from "../../lib/pickCartResults";
15
16
  import { getPickTranslator } from "../../lib/pickI18n";
16
17
  import { friendlyPickError, getPickUiTexts } from "../../lib/pickUiText";
17
18
  import { getPrintJobTexts, printJobStatusLabel } from "../../lib/pickPrintJobText";
@@ -19,6 +20,7 @@ import { defaultFilters, usePickDays, usePickHeartbeat, usePickQueue, } from "..
19
20
  import { usePickDocumentTypes, usePickStatuses, useStationPrintPlek, } from "../../hooks/usePickConfig";
20
21
  import { usePrintBatch, usePrintJob } from "../../hooks/usePrintJob";
21
22
  import { useShippingNoteStatus } from "../../hooks/useShippingNoteStatus";
23
+ import { CartItemRows, PlanWarnings } from "./CartItemRows";
22
24
  import { isWaitingShippingNote, shippingNoteMessage, shippingNoteSourceLabel, } from "../../lib/shippingNoteStatus";
23
25
  const LANG_STORAGE_KEY = "pick.language";
24
26
  /** Melding als de planning voor deze order geen karren heeft vastgelegd. */
@@ -481,6 +483,50 @@ export function PickScherm({ station, onBack }) {
481
483
  setBusyId(null);
482
484
  }
483
485
  }, [station.id, station.lock_timeout_seconds, sessionId, loadCartPlan, refresh, showPrintError]);
486
+ /**
487
+ * Eén vergrendeling voor alle printacties. Gaat direct bij de klik in (ref,
488
+ * niet pas bij de volgende weergave), zodat een dubbelklik nooit een tweede
489
+ * aanvraag start. De knoppen blijven uit tot de hele actie klaar is.
490
+ */
491
+ const actionLock = useRef(false);
492
+ const [actionBusy, setActionBusy] = useState(false);
493
+ const withActionLock = useCallback((fn) => async (...args) => {
494
+ if (actionLock.current)
495
+ return;
496
+ setActionBusy(true);
497
+ try {
498
+ await runExclusive(actionLock, () => fn(...args));
499
+ }
500
+ finally {
501
+ setActionBusy(false);
502
+ }
503
+ }, []);
504
+ /** De kar van de rij staat niet (meer) in het karplan: verversen nodig. */
505
+ const [cartMissing, setCartMissing] = useState(false);
506
+ /** Per kar het resultaat tonen; bij één kar één korte melding. */
507
+ const showCartResults = useCallback((results) => {
508
+ if (results.length === 0)
509
+ return;
510
+ const s = summarizeCartResults(results);
511
+ const description = s.lines.length > 0 ? s.lines.join("\n") : undefined;
512
+ const opts = description ? { description, duration: 10000 } : undefined;
513
+ if (s.kind === "success")
514
+ toast.success(s.title, opts);
515
+ else if (s.kind === "error")
516
+ toast.error(s.title, opts);
517
+ else
518
+ toast.warning(s.title, opts);
519
+ setLastCartResults(results);
520
+ }, []);
521
+ const [lastCartResults, setLastCartResults] = useState([]);
522
+ /** Alleen echt aangemaakte opdrachten volgen; herprints worden gemarkeerd. */
523
+ const startBatchTracking = useCallback((item, results) => {
524
+ const jobs = trackableJobs(results);
525
+ if (jobs.length === 0)
526
+ return;
527
+ finalizedBatch.current = null;
528
+ trackBatch({ queueItemId: item.id, documentNumber: item.document_number ?? null, jobs });
529
+ }, [trackBatch]);
484
530
  /**
485
531
  * Zet een nieuwe printopdracht in de wachtrij voor een al geprint document,
486
532
  * zonder de wachtrijstatus te wijzigen.
@@ -508,6 +554,7 @@ export function PickScherm({ station, onBack }) {
508
554
  }
509
555
  }, [t, sessionId, track, showPrintError]);
510
556
  /** Open een geprinte order bovenaan zonder de wachtrijstatus te wijzigen. */
557
+ const handleReprintLocked = useMemo(() => withActionLock(handleReprint), [withActionLock, handleReprint]);
511
558
  const selectPrintedForReprint = useCallback(async (item) => {
512
559
  setBusyId(item.id);
513
560
  try {
@@ -530,34 +577,23 @@ export function PickScherm({ station, onBack }) {
530
577
  * Is de regel nog niet printklaar, dan wordt er niet geprint maar worden de
531
578
  * ontbrekende gegevens bij de bron opgevraagd.
532
579
  */
533
- const printAllForItem = useCallback(async (item) => {
580
+ const printAllForItemInner = useCallback(async (item) => {
534
581
  setBusyId(item.id);
582
+ setCartMissing(false);
535
583
  try {
536
584
  if (item.printed_at) {
537
585
  const { plan, readiness: state } = await loadCartPlan(item);
538
586
  setPending(item);
539
587
  if (!plan || !state.ready) {
540
- setBusyId(null);
541
588
  toast.warning(state.reason ?? NOT_READY_NOTICE);
542
589
  await requestData(item);
543
590
  return;
544
591
  }
545
- const jobs = [];
546
- for (const cart of plan.carts) {
547
- const jobId = await pickApi.reprintCart(item.id, sessionId, cart.cartId);
548
- if (jobId)
549
- jobs.push({ cartId: cart.cartId, cartLabel: cart.cartLabel, jobId });
550
- }
592
+ const carts = cartsForRow(item, plan);
593
+ const results = await reprintCartsIndividually(carts, (cartId) => pickApi.reprintCart(item.id, sessionId, cartId));
551
594
  setLastPrinted(item);
552
- if (jobs.length > 0) {
553
- finalizedBatch.current = null;
554
- trackBatch({
555
- queueItemId: item.id,
556
- documentNumber: item.document_number ?? null,
557
- jobs,
558
- });
559
- }
560
- toast.success(`${jobs.length} ${jobs.length === 1 ? "pakbon" : "pakbonnen"} ${t("sentToPrinter")}`);
595
+ startBatchTracking(item, results);
596
+ showCartResults(results);
561
597
  refresh();
562
598
  return;
563
599
  }
@@ -567,11 +603,12 @@ export function PickScherm({ station, onBack }) {
567
603
  const preflight = await loadCartPlan(item);
568
604
  setPending(item);
569
605
  if (!preflight.plan || !preflight.readiness.ready) {
570
- setBusyId(null);
571
606
  toast.warning(preflight.readiness.reason ?? NOT_READY_NOTICE);
572
607
  await requestData(item);
573
608
  return;
574
609
  }
610
+ // Rij met eigen kar: alleen die kar, en die moet in het karplan staan.
611
+ cartsForRow(item, preflight.plan);
575
612
  const claimed = await pickApi.claim({
576
613
  itemId: item.id,
577
614
  stationId: station.id,
@@ -583,27 +620,40 @@ export function PickScherm({ station, onBack }) {
583
620
  const { plan, readiness: state } = await loadCartPlan(target);
584
621
  setPending(target);
585
622
  if (!plan || !state.ready) {
586
- setBusyId(null);
587
623
  toast.warning(state.reason ?? NOT_READY_NOTICE);
588
624
  await requestData(target);
589
625
  return;
590
626
  }
591
- const already = new Set(printedCartIdsOf(target));
592
- const todo = plan.carts.filter((cart) => !already.has(cart.cartId));
593
- const result = await pickApi.completePrintCarts(target.id, sessionId, todo.map((cart) => cart.cartId));
594
- setLastPrinted(result.item);
595
- setPending(result.item);
596
- finalizedBatch.current = null;
597
- trackBatch({
598
- queueItemId: target.id,
599
- documentNumber: result.item.document_number ?? null,
600
- jobs: result.jobs,
601
- });
602
- toast.success(`${result.jobs.length} ${result.jobs.length === 1 ? "pakbon" : "pakbonnen"} ${t("sentToPrinter")}`);
627
+ const already = printedCartIdsOf(target);
628
+ const todo = cartsForRow(target, plan).filter((cart) => !already.includes(cart.cartId));
629
+ if (todo.length === 0) {
630
+ toast.info("Alle karren van deze rij zijn al geprint.");
631
+ return;
632
+ }
633
+ let results;
634
+ try {
635
+ const result = await pickApi.completePrintCarts(target.id, sessionId, todo.map((cart) => cart.cartId));
636
+ results = resultsFromBatch(todo, result.carts, already);
637
+ setLastPrinted(result.item);
638
+ setPending(result.item);
639
+ }
640
+ catch (e) {
641
+ results = resultsFromBatchError(todo, e, already);
642
+ showCartResults(results);
643
+ throw e;
644
+ }
645
+ startBatchTracking(target, results);
646
+ showCartResults(results);
603
647
  refresh();
604
648
  }
605
649
  catch (e) {
606
- showPrintError(e);
650
+ if (isCartMissingError(e)) {
651
+ setCartMissing(true);
652
+ toast.error(CART_MISSING_MESSAGE);
653
+ }
654
+ else {
655
+ showPrintError(e);
656
+ }
607
657
  if (!item.printed_at) {
608
658
  try {
609
659
  await pickApi.releaseLock(item.id);
@@ -626,11 +676,12 @@ export function PickScherm({ station, onBack }) {
626
676
  sessionId,
627
677
  loadCartPlan,
628
678
  requestData,
629
- trackBatch,
679
+ startBatchTracking,
680
+ showCartResults,
630
681
  refresh,
631
- t,
632
682
  showPrintError,
633
683
  ]);
684
+ const printAllForItem = useMemo(() => withActionLock(printAllForItemInner), [withActionLock, printAllForItemInner]);
634
685
  /**
635
686
  * Batchknop: print de bovenste n printklare regels in de bestaande volgorde
636
687
  * (volle karren voor deelkarren, zoals de lijst sorteert). Per regel draait
@@ -639,7 +690,7 @@ export function PickScherm({ station, onBack }) {
639
690
  * precies één printactie krijgt. Niet-printklare karren worden overgeslagen
640
691
  * met een leesbare melding per kar.
641
692
  */
642
- const printNextCarts = useCallback(async () => {
693
+ const printNextCartsInner = useCallback(async () => {
643
694
  if (!cartsPerPrint || batchPrinting)
644
695
  return;
645
696
  setBatchPrinting(true);
@@ -674,10 +725,10 @@ export function PickScherm({ station, onBack }) {
674
725
  toast.warning(`${s.title}: ${s.reason}`);
675
726
  const doneUnits = new Set();
676
727
  for (const item of targets) {
677
- const unitKey = `${item.document_type_id ?? "-"}|${item.order_number ?? unitRefOf(item)}`;
728
+ const unitKey = `${item.document_type_id ?? "-"}|${unitRefOf(item)}`;
678
729
  if (doneUnits.has(unitKey))
679
730
  continue;
680
- await printAllForItem(item);
731
+ await printAllForItemInner(item);
681
732
  doneUnits.add(unitKey);
682
733
  }
683
734
  }
@@ -687,7 +738,8 @@ export function PickScherm({ station, onBack }) {
687
738
  finally {
688
739
  setBatchPrinting(false);
689
740
  }
690
- }, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItem, showPrintError]);
741
+ }, [cartsPerPrint, batchPrinting, items, readinessById, printAllForItemInner, showPrintError]);
742
+ const printNextCarts = useMemo(() => withActionLock(printNextCartsInner), [withActionLock, printNextCartsInner]);
691
743
  const fetchNext = useCallback(async (silent = false) => {
692
744
  const requestedDayVersion = daySelectionVersion.current;
693
745
  setBusyId("next");
@@ -743,65 +795,72 @@ export function PickScherm({ station, onBack }) {
743
795
  setBusyId(null);
744
796
  }
745
797
  }, [station.id, station.lock_timeout_seconds, sessionId, refresh, t, loadCartPlan, chosenDay, pickDay]);
798
+ const fetchNextLocked = useMemo(() => withActionLock(() => fetchNext()), [withActionLock, fetchNext]);
746
799
  /**
747
800
  * Print alle nog niet geprinte karren (pakbonnen) van deze order in één keer
748
801
  * en haalt daarna een nieuwe order op.
749
802
  */
750
- const confirmPending = useCallback(async () => {
803
+ const confirmPendingInner = useCallback(async () => {
751
804
  const item = pending;
752
805
  const plan = cartPlan;
753
806
  if (!item)
754
807
  return;
808
+ setCartMissing(false);
755
809
  if (!plan || !readiness.ready) {
756
810
  // Niet printklaar: nooit printen, altijd eerst de gegevens opvragen.
757
811
  toast.warning(readiness.reason ?? NOT_READY_NOTICE);
758
812
  await requestData(item);
759
813
  return;
760
814
  }
761
- const alreadyPrinted = new Set(printedCartIdsOf(item));
762
- const todo = plan.carts.filter((cart) => !alreadyPrinted.has(cart.cartId));
815
+ let rowCarts;
816
+ try {
817
+ rowCarts = cartsForRow(item, plan);
818
+ }
819
+ catch {
820
+ setCartMissing(true);
821
+ toast.error(CART_MISSING_MESSAGE);
822
+ return;
823
+ }
824
+ const already = printedCartIdsOf(item);
825
+ const todo = rowCarts.filter((cart) => !already.includes(cart.cartId));
763
826
  if (todo.length === 0) {
764
- // Alles al geprint: bewuste herdruk van ALLE pakbonnen van deze order.
827
+ // Alles al geprint: bewuste herdruk, per kar apart.
765
828
  setBusyId("confirm");
766
829
  try {
767
- const jobs = [];
768
- for (const cart of plan.carts) {
769
- const jobId = await pickApi.reprintCart(item.id, sessionId, cart.cartId);
770
- if (jobId)
771
- jobs.push({ cartId: cart.cartId, cartLabel: cart.cartLabel, jobId });
772
- }
830
+ const results = await reprintCartsIndividually(rowCarts, (cartId) => pickApi.reprintCart(item.id, sessionId, cartId));
773
831
  setLastPrinted(item);
774
- if (jobs.length > 0) {
775
- finalizedBatch.current = null;
776
- trackBatch({ queueItemId: item.id, documentNumber: item.document_number ?? null, jobs });
777
- }
778
- toast.success(`${jobs.length} ${jobs.length === 1 ? "pakbon" : "pakbonnen"} ${t("sentToPrinter")}`);
832
+ startBatchTracking(item, results);
833
+ showCartResults(results);
779
834
  refresh();
780
835
  }
781
- catch (e) {
782
- showPrintError(e);
783
- }
784
836
  finally {
785
837
  setBusyId(null);
786
838
  }
787
839
  return;
788
840
  }
789
841
  setBusyId("confirm");
842
+ let keepBusy = false;
790
843
  try {
791
844
  const result = await pickApi.completePrintCarts(item.id, sessionId, todo.map((cart) => cart.cartId));
845
+ const results = resultsFromBatch(todo, result.carts, already);
792
846
  setLastPrinted(result.item);
793
847
  setPending(result.item);
794
- finalizedBatch.current = null;
795
- trackBatch({
796
- queueItemId: item.id,
797
- documentNumber: result.item.document_number ?? null,
798
- jobs: result.jobs,
799
- });
800
- toast.success(`${result.jobs.length} ${result.jobs.length === 1 ? "pakbon" : "pakbonnen"} ${t("sentToPrinter")}`);
848
+ const jobs = trackableJobs(results);
849
+ startBatchTracking(item, results);
850
+ showCartResults(results);
851
+ // Met opdrachten blijft de knop uit tot de batch klaar is (zie afronding).
852
+ keepBusy = jobs.length > 0;
801
853
  refresh();
802
854
  }
803
855
  catch (e) {
804
- const expired = showPrintError(e);
856
+ if (isCartMissingError(e)) {
857
+ setCartMissing(true);
858
+ toast.error(CART_MISSING_MESSAGE);
859
+ refresh();
860
+ return;
861
+ }
862
+ showCartResults(resultsFromBatchError(todo, e, already));
863
+ const { expiredClaim: expired } = friendlyPickError(e, t.lang);
805
864
  try {
806
865
  await pickApi.releaseLock(item.id);
807
866
  }
@@ -823,7 +882,12 @@ export function PickScherm({ station, onBack }) {
823
882
  }
824
883
  }
825
884
  }
826
- }, [pending, cartPlan, readiness, requestData, handleReprint, sessionId, trackBatch, t, fetchNext, refresh, showPrintError]);
885
+ finally {
886
+ if (!keepBusy)
887
+ setBusyId(null);
888
+ }
889
+ }, [pending, cartPlan, readiness, requestData, sessionId, startBatchTracking, showCartResults, t, fetchNext, refresh]);
890
+ const confirmPending = useMemo(() => withActionLock(confirmPendingInner), [withActionLock, confirmPendingInner]);
827
891
  /** Rond de order pas af wanneer alle batchopdrachten een eindstatus hebben. */
828
892
  useEffect(() => {
829
893
  if (!batch || batch.jobs.some((entry) => !entry.job || !["voltooid", "mislukt", "geannuleerd"].includes(entry.job.status)))
@@ -842,7 +906,19 @@ export function PickScherm({ station, onBack }) {
842
906
  description: pdfSaveWarning(pdfOnly[0]?.job) ?? undefined,
843
907
  });
844
908
  }
845
- const failed = ended.filter((entry) => !isPdfOnlyFailure(entry.job));
909
+ const failedAll = ended.filter((entry) => !isPdfOnlyFailure(entry.job));
910
+ // Herprint van een al geprinte kar: nooit terugzetten, alleen melden.
911
+ const failedReprints = failedAll.filter((entry) => entry.reprint === true);
912
+ if (failedReprints.length > 0) {
913
+ toast.error(failedReprints.length === 1
914
+ ? `${failedReprints[0].cartLabel ?? failedReprints[0].cartId} — herprint mislukt; de kar blijft geprint staan.`
915
+ : `${failedReprints.length} herprints mislukt; de karren blijven geprint staan.`, {
916
+ description: failedReprints
917
+ .map((e) => `${e.cartLabel ?? e.cartId} — fout: ${e.job?.foutmelding ?? jobTexts.unknown}`)
918
+ .join("\n"),
919
+ });
920
+ }
921
+ const failed = resettableJobs(failedAll);
846
922
  if (failed.length > 0) {
847
923
  const restored = await pickApi.resetPrintJobs(batch.queueItemId, sessionId, failed.map((entry) => ({ jobId: entry.jobId, cartId: entry.cartId })));
848
924
  setPending(restored);
@@ -895,33 +971,48 @@ export function PickScherm({ station, onBack }) {
895
971
  }
896
972
  }, [sessionId, t, track, showPrintError]);
897
973
  /** Eén losse pakbon (kar) printen vanaf de knop op de karregel. */
898
- const printSingleCart = useCallback(async (item, cart) => {
974
+ const printSingleCartInner = useCallback(async (item, cart) => {
975
+ // Alleen een kar uit het karplan van deze rij; bij een eigen kar alleen die.
976
+ try {
977
+ const allowed = cartsForRow(item, cartPlan);
978
+ if (!allowed.some((c) => c.cartId === cart.cartId))
979
+ throw new Error(CART_MISSING_MESSAGE);
980
+ }
981
+ catch {
982
+ setCartMissing(true);
983
+ toast.error(CART_MISSING_MESSAGE);
984
+ return;
985
+ }
899
986
  const done = printedCartIdsOf(item).includes(cart.cartId);
900
987
  if (done) {
901
988
  await reprintCart(item, cart);
902
989
  return;
903
990
  }
904
991
  setBusyId("confirm");
992
+ const already = printedCartIdsOf(item);
905
993
  try {
906
994
  const result = await pickApi.completePrintCarts(item.id, sessionId, [cart.cartId]);
995
+ const results = resultsFromBatch([cart], result.carts, already);
907
996
  setLastPrinted(result.item);
908
997
  setPending(result.item);
909
- finalizedBatch.current = null;
910
- trackBatch({
911
- queueItemId: item.id,
912
- documentNumber: result.item.document_number ?? null,
913
- jobs: result.jobs,
914
- });
915
- toast.success(`${cart.cartLabel} · ${t("sentToPrinter")}`);
998
+ startBatchTracking(item, results);
999
+ showCartResults(results);
916
1000
  refresh();
917
1001
  }
918
1002
  catch (e) {
919
- showPrintError(e);
1003
+ if (isCartMissingError(e)) {
1004
+ setCartMissing(true);
1005
+ toast.error(CART_MISSING_MESSAGE);
1006
+ }
1007
+ else {
1008
+ showCartResults(resultsFromBatchError([cart], e, already));
1009
+ }
920
1010
  }
921
1011
  finally {
922
1012
  setBusyId(null);
923
1013
  }
924
- }, [reprintCart, sessionId, trackBatch, t, refresh, showPrintError]);
1014
+ }, [cartPlan, reprintCart, sessionId, startBatchTracking, showCartResults, refresh]);
1015
+ const printSingleCart = useMemo(() => withActionLock(printSingleCartInner), [withActionLock, printSingleCartInner]);
925
1016
  /**
926
1017
  * Handmatig afmelden: het papier is geprint maar de regel bleef openstaan.
927
1018
  * Zet de order op "Geprint" zonder nieuwe printopdracht en logt de reden.
@@ -990,7 +1081,7 @@ export function PickScherm({ station, onBack }) {
990
1081
  useEffect(() => {
991
1082
  if (lang === null || loading)
992
1083
  return;
993
- if (pending !== null || busyId !== null)
1084
+ if (pending !== null || (busyId !== null || actionBusy))
994
1085
  return;
995
1086
  if (autoFetched.current)
996
1087
  return;
@@ -1030,33 +1121,39 @@ export function PickScherm({ station, onBack }) {
1030
1121
  : Math.round((checkDone / checkTotal) * 100);
1031
1122
  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
1032
1123
  ? "Wachtrij wordt geladen…"
1033
- : `Pakbonnen controleren bij de bron (${checkDone} van ${checkTotal})` }), _jsxs("span", { className: "tabular-nums", children: [loadPercent, "%"] })] }), _jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-muted", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all duration-300", style: { width: `${Math.max(5, loadPercent)}%` } }) })] })), _jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:flex-wrap md:items-center md:justify-between", children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)] items-center gap-3", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 shrink-0 px-3 sm:px-4", onClick: () => setLang(null), "aria-label": t("changeLanguage"), children: [_jsx(Globe, { className: "h-5 w-5 sm:mr-2" }), _jsx("span", { className: "hidden sm:inline", children: t("changeLanguage") })] }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "truncate text-lg font-bold text-foreground sm:text-xl", children: station.name }), _jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [pendingCount, " ", t("statusToPrint"), " \u00B7 ", printedCount, " ", t("statusPrinted")] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => shiftDay(-1), disabled: loadingDay || !(chosenDay ?? pickDay), "aria-label": ui.pickDayPrev, children: [_jsx(ChevronLeft, { className: "h-5 w-5 sm:mr-1" }), _jsx("span", { className: "hidden sm:inline", children: ui.pickDayPrev })] }), _jsx("div", { className: "flex h-12 items-center rounded-md border bg-muted px-4 text-sm font-semibold text-foreground", children: chosenDay ? dayLabel(chosenDay) : ui.pickDayToday }), _jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => shiftDay(1), disabled: loadingDay || !(chosenDay ?? pickDay), "aria-label": ui.pickDayNext, children: [_jsx("span", { className: "hidden sm:inline", children: ui.pickDayNext }), _jsx(ChevronRight, { className: "h-5 w-5 sm:ml-1" })] }), chosenDay && (_jsx(Button, { variant: "secondary", size: "lg", className: "h-12 px-4", onClick: () => void chooseDay("current"), disabled: loadingDay, children: ui.pickDayBackToToday }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [!online && (_jsxs(Badge, { className: "bg-destructive text-destructive-foreground", children: [_jsx(WifiOff, { className: "mr-1 h-3.5 w-3.5" }), " ", t("offline")] })), _jsx(Button, { variant: "outline", size: "icon", className: "h-12 w-12 shrink-0", onClick: refresh, "aria-label": t("refresh"), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsxs(Button, { variant: "outline", size: "lg", className: "hidden h-14 px-5 md:inline-flex", disabled: fetchingNotes, onClick: () => void fetchShippingNotes(), children: [fetchingNotes ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(FileDown, { className: "mr-2 h-5 w-5" })), fetchingNotes ? "Ophalen…" : "Verzendbrieven ophalen"] }), cartsPerPrint != null && (_jsxs(Button, { size: "lg", className: "hidden h-14 px-6 text-lg md:inline-flex", disabled: loading || batchPrinting || busyId != null, onClick: () => void printNextCarts(), children: [batchPrinting ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), "Print eerstvolgende ", cartsPerPrint, " karren"] })), _jsxs(Button, { size: "lg", className: "hidden h-14 px-8 text-lg md:inline-flex", onClick: () => (pending ? confirmPending() : fetchNext()), disabled: busyId !== null || fetchingData, children: [busyId !== null || fetchingData ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : pending && !readiness.ready ? (_jsx(RefreshCw, { className: "mr-2 h-5 w-5" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), pending && !readiness.ready ? "Pakbon ophalen" : t("printNext"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] })] }), 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: ["Pickwachtrij kan niet worden bijgewerkt: ", 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." })] })), _jsx(ShippingNoteScan, { stationId: station.id }), !pending && emptyNotice && (_jsx("p", { className: "text-sm text-muted-foreground", children: emptyNotice })), pending && (_jsxs(Card, { className: "border-2 border-primary", children: [_jsxs(CardContent, { className: "flex flex-col gap-4 p-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between sm:p-5", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: t("confirmPrintTitle") }), _jsx("p", { className: "truncate text-xl font-bold text-foreground sm:text-2xl", children: pending.document_number ?? t("document") }), _jsx("p", { className: "truncate text-sm text-muted-foreground", children: [pending.order_number, pending.customer_name].filter(Boolean).join(" · ") })] }), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [_jsx(Button, { variant: "outline", size: "lg", className: "h-12 w-full sm:w-auto", onClick: cancelPending, disabled: busyId !== null, children: t("cancel") }), _jsxs(Button, { size: "lg", className: "h-14 w-full px-8 text-lg sm:w-auto", onClick: confirmPending, disabled: busyId !== null || fetchingData, variant: readiness.ready ? "default" : "secondary", children: [busyId === "confirm" || fetchingData ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : readiness.ready ? (_jsx(Printer, { className: "mr-2 h-5 w-5" })) : (_jsx(RefreshCw, { className: "mr-2 h-5 w-5" })), readiness.ready ? t("print") : "Pakbon ophalen"] })] })] }), !readiness.ready && (_jsx(CardContent, { className: "border-t p-4 sm:p-5", children: _jsxs("div", { className: "flex items-start gap-2 rounded-md border bg-muted/30 p-3 text-sm text-muted-foreground", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("span", { children: fetchingData
1124
+ : `Pakbonnen controleren bij de bron (${checkDone} van ${checkTotal})` }), _jsxs("span", { className: "tabular-nums", children: [loadPercent, "%"] })] }), _jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-muted", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all duration-300", style: { width: `${Math.max(5, loadPercent)}%` } }) })] })), _jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:flex-wrap md:items-center md:justify-between", children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)] items-center gap-3", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 shrink-0 px-3 sm:px-4", onClick: () => setLang(null), "aria-label": t("changeLanguage"), children: [_jsx(Globe, { className: "h-5 w-5 sm:mr-2" }), _jsx("span", { className: "hidden sm:inline", children: t("changeLanguage") })] }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "truncate text-lg font-bold text-foreground sm:text-xl", children: station.name }), _jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [pendingCount, " ", t("statusToPrint"), " \u00B7 ", printedCount, " ", t("statusPrinted")] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => shiftDay(-1), disabled: loadingDay || !(chosenDay ?? pickDay), "aria-label": ui.pickDayPrev, children: [_jsx(ChevronLeft, { className: "h-5 w-5 sm:mr-1" }), _jsx("span", { className: "hidden sm:inline", children: ui.pickDayPrev })] }), _jsx("div", { className: "flex h-12 items-center rounded-md border bg-muted px-4 text-sm font-semibold text-foreground", children: chosenDay ? dayLabel(chosenDay) : ui.pickDayToday }), _jsxs(Button, { variant: "outline", size: "lg", className: "h-12 px-3", onClick: () => shiftDay(1), disabled: loadingDay || !(chosenDay ?? pickDay), "aria-label": ui.pickDayNext, children: [_jsx("span", { className: "hidden sm:inline", children: ui.pickDayNext }), _jsx(ChevronRight, { className: "h-5 w-5 sm:ml-1" })] }), chosenDay && (_jsx(Button, { variant: "secondary", size: "lg", className: "h-12 px-4", onClick: () => void chooseDay("current"), disabled: loadingDay, children: ui.pickDayBackToToday }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [!online && (_jsxs(Badge, { className: "bg-destructive text-destructive-foreground", children: [_jsx(WifiOff, { className: "mr-1 h-3.5 w-3.5" }), " ", t("offline")] })), _jsx(Button, { variant: "outline", size: "icon", className: "h-12 w-12 shrink-0", onClick: refresh, "aria-label": t("refresh"), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsxs(Button, { variant: "outline", size: "lg", className: "hidden h-14 px-5 md:inline-flex", disabled: fetchingNotes, onClick: () => void fetchShippingNotes(), children: [fetchingNotes ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : (_jsx(FileDown, { className: "mr-2 h-5 w-5" })), fetchingNotes ? "Ophalen…" : "Verzendbrieven ophalen"] }), cartsPerPrint != null && (_jsxs(Button, { size: "lg", className: "hidden h-14 px-6 text-lg md:inline-flex", disabled: loading || batchPrinting || (busyId != null || 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" })), "Print eerstvolgende ", 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"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] })] }), 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: ["Pickwachtrij kan niet worden bijgewerkt: ", 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 && (_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" }), CART_MISSING_MESSAGE] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => {
1125
+ setCartMissing(false);
1126
+ setPending(null);
1127
+ setCartPlan(null);
1128
+ setActiveCartId(null);
1129
+ refresh();
1130
+ }, children: "Wachtrij verversen" })] })), 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
1034
1131
  ? "De pakbon wordt bij de bron opgevraagd. Zodra alles compleet is, verschijnt de printknop weer."
1035
1132
  : planReason
1036
1133
  ? `${planReason} Klik op "Pakbon ophalen" om hem bij de bron op te vragen.`
1037
- : NOT_READY_NOTICE })] }) })), cartPlan && (_jsx(CardContent, { className: "border-t p-4 sm:p-5", children: _jsxs("div", { className: "grid gap-3 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-semibold text-foreground", children: "Karren van deze order" }), _jsx("div", { className: "max-h-72 space-y-2 overflow-y-auto rounded-md border p-2", children: cartPlan.carts.map((cart) => {
1038
- const done = printedCartIdsOf(pending).includes(cart.cartId);
1039
- const active = cart.cartId === activeCartId;
1040
- return (_jsxs("div", { role: "button", tabIndex: 0, onClick: () => setActiveCartId(cart.cartId), onKeyDown: (event) => {
1041
- if (event.key === "Enter" || event.key === " ") {
1042
- event.preventDefault();
1043
- setActiveCartId(cart.cartId);
1044
- }
1045
- }, className: "flex w-full cursor-pointer items-center justify-between gap-2 rounded-md border p-3 text-left transition hover:bg-muted/50 " +
1046
- (active ? "border-primary bg-primary/5" : ""), children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: cart.cartLabel }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [cart.isFullCart ? "Volle kar" : "Halve kar", " · ", cart.items.length, " ", cart.items.length === 1 ? "product" : "producten", cart.isShared ? " · gedeelde kar" : "", done ? " · geprint" : ""] })] }), _jsxs(Button, { type: "button", variant: done ? "outline" : "secondary", size: "sm", disabled: busyId !== null, onClick: (event) => {
1047
- event.stopPropagation();
1134
+ : NOT_READY_NOTICE })] }) })), cartPlan && (_jsxs(CardContent, { className: "border-t p-4 sm:p-5", children: [_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) => {
1135
+ const done = printedCartIdsOf(pending).includes(cart.cartId);
1136
+ const active = cart.cartId === activeCartId;
1137
+ return (_jsxs("div", { role: "button", tabIndex: 0, onClick: () => setActiveCartId(cart.cartId), onKeyDown: (event) => {
1138
+ if (event.key === "Enter" || event.key === " ") {
1139
+ event.preventDefault();
1048
1140
  setActiveCartId(cart.cartId);
1049
- void printSingleCart(pending, cart);
1050
- }, children: [_jsx(Printer, { className: "mr-2 h-4 w-4" }), done ? "Opnieuw" : "Printen"] })] }, cart.cartId));
1051
- }) })] }), activeCartId && (() => {
1052
- const activeCart = cartPlan.carts.find((cart) => cart.cartId === activeCartId);
1053
- if (!activeCart)
1054
- return null;
1055
- return (_jsxs("div", { className: "rounded-md border bg-muted/20 p-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("p", { className: "text-lg font-bold text-foreground", children: activeCart.cartLabel }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [activeCart.isFullCart ? "Volle kar" : "Halve kar (deel van de order)", activeCart.isShared ? " · gedeeld" : ""] })] }), activeCart.barcode && _jsx(Badge, { variant: "outline", children: activeCart.barcode })] }), _jsx("div", { className: "mt-4 divide-y rounded-md border bg-background", children: activeCart.items.length > 0 ? activeCart.items.map((cartItem, index) => (_jsxs("div", { className: "flex items-center justify-between gap-3 p-3", children: [_jsx("span", { className: "min-w-0 truncate text-sm text-foreground", children: cartItem.productName }), _jsx("span", { className: "shrink-0 text-sm font-semibold text-foreground", children: cartItem.quantity ?? cartItem.crates ?? 0 })] }, cartItem.itemId ?? cartItem.orderItemId ?? `${activeCart.cartId}-${index}`))) : (_jsx("p", { className: "p-3 text-sm text-muted-foreground", children: "Geen regels voor deze kar." })) })] }));
1056
- })()] }) }))] })), batch && (_jsx(Card, { className: batchFailed > 0 ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex items-center gap-3 p-4", children: [batchCompleted + batchFailed === batch.jobs.length ? (batchFailed > 0 ? _jsx(AlertTriangle, { className: "h-6 w-6 shrink-0 text-destructive" }) : _jsx(CheckCircle2, { className: "h-6 w-6 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [jobTexts.jobTitle, batch.documentNumber ? ` · ${batch.documentNumber}` : ""] }), _jsxs("p", { className: "font-semibold text-foreground", children: [batchCompleted, " van ", batch.jobs.length, " pakbonnen voltooid", batchFailed > 0 ? ` · ${batchFailed} mislukt` : ""] })] })] }) })), tracked && (_jsx(Card, { className: jobFailed ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex flex-col gap-4 p-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [jobFailed ? (_jsx(AlertTriangle, { className: "h-6 w-6 shrink-0 text-destructive" })) : jobStatus === "voltooid" ? (_jsx(CheckCircle2, { className: "h-6 w-6 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm font-medium text-muted-foreground", children: [jobTexts.jobTitle, " ", tracked.documentNumber ? `· ${tracked.documentNumber}` : "", tracked.cartLabel ? ` · ${tracked.cartLabel}` : ""] }), _jsx("p", { className: "text-lg font-semibold text-foreground", children: printJobStatusLabel(jobStatus, t.lang) }), tracked.job?.printer_naam && (_jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [jobTexts.printerLabel, ": ", tracked.job.printer_naam] })), jobFailed && (_jsxs("p", { className: "text-sm text-destructive", children: [tracked.job?.foutmelding ?? jobTexts.unknown, " \u00B7 ", jobTexts.requeued] })), !jobFailed && pdfSaveWarning(tracked.job) && (_jsx("p", { className: "text-sm font-medium text-destructive", children: pdfSaveWarning(tracked.job) }))] })] }), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [jobFailed && lastPrinted && (_jsxs(Button, { size: "lg", className: "h-12 w-full sm:w-auto", onClick: () => {
1141
+ }
1142
+ }, className: "flex w-full cursor-pointer items-center justify-between gap-2 rounded-md border p-3 text-left transition hover:bg-muted/50 " +
1143
+ (active ? "border-primary bg-primary/5" : ""), children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: cart.cartLabel }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [cart.isFullCart ? "Volle kar" : "Halve kar", " · ", cart.items.length, " ", cart.items.length === 1 ? "product" : "producten", cart.isShared ? " · gedeelde kar" : "", done ? " · geprint" : ""] })] }), _jsxs(Button, { type: "button", variant: done ? "outline" : "secondary", size: "sm", disabled: (busyId !== null || actionBusy), onClick: (event) => {
1144
+ event.stopPropagation();
1145
+ setActiveCartId(cart.cartId);
1146
+ void printSingleCart(pending, cart);
1147
+ }, children: [_jsx(Printer, { className: "mr-2 h-4 w-4" }), done ? "Opnieuw" : "Printen"] })] }, cart.cartId));
1148
+ }) })] }), activeCartId && (() => {
1149
+ const activeCart = cartPlan.carts.find((cart) => cart.cartId === activeCartId);
1150
+ if (!activeCart)
1151
+ return null;
1152
+ return (_jsxs("div", { className: "rounded-md border bg-muted/20 p-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("p", { className: "text-lg font-bold text-foreground", children: activeCart.cartLabel }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [activeCart.isFullCart ? "Volle kar" : "Halve kar (deel van de order)", activeCart.isShared ? " · gedeeld" : ""] })] }), activeCart.barcode && _jsx(Badge, { variant: "outline", children: activeCart.barcode })] }), _jsx("div", { className: "mt-4 divide-y rounded-md border bg-background", children: _jsx(CartItemRows, { items: activeCart.items, cartId: activeCart.cartId }) })] }));
1153
+ })()] })] }))] })), batch && (_jsx(Card, { className: batchFailed > 0 ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex items-center gap-3 p-4", children: [batchCompleted + batchFailed === batch.jobs.length ? (batchFailed > 0 ? _jsx(AlertTriangle, { className: "h-6 w-6 shrink-0 text-destructive" }) : _jsx(CheckCircle2, { className: "h-6 w-6 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [jobTexts.jobTitle, batch.documentNumber ? ` · ${batch.documentNumber}` : ""] }), _jsxs("p", { className: "font-semibold text-foreground", children: [batchCompleted, " van ", batch.jobs.length, " pakbonnen voltooid", batchFailed > 0 ? ` · ${batchFailed} mislukt` : ""] })] })] }) })), tracked && (_jsx(Card, { className: jobFailed ? "border-2 border-destructive" : "border-2 border-primary/40", children: _jsxs(CardContent, { className: "flex flex-col gap-4 p-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [jobFailed ? (_jsx(AlertTriangle, { className: "h-6 w-6 shrink-0 text-destructive" })) : jobStatus === "voltooid" ? (_jsx(CheckCircle2, { className: "h-6 w-6 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "h-6 w-6 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm font-medium text-muted-foreground", children: [jobTexts.jobTitle, " ", tracked.documentNumber ? `· ${tracked.documentNumber}` : "", tracked.cartLabel ? ` · ${tracked.cartLabel}` : ""] }), _jsx("p", { className: "text-lg font-semibold text-foreground", children: printJobStatusLabel(jobStatus, t.lang) }), tracked.job?.printer_naam && (_jsxs("p", { className: "truncate text-sm text-muted-foreground", children: [jobTexts.printerLabel, ": ", tracked.job.printer_naam] })), jobFailed && (_jsxs("p", { className: "text-sm text-destructive", children: [tracked.job?.foutmelding ?? jobTexts.unknown, " \u00B7 ", jobTexts.requeued] })), !jobFailed && pdfSaveWarning(tracked.job) && (_jsx("p", { className: "text-sm font-medium text-destructive", children: pdfSaveWarning(tracked.job) }))] })] }), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [jobFailed && lastPrinted && (_jsxs(Button, { size: "lg", className: "h-12 w-full sm:w-auto", onClick: () => {
1057
1154
  clearJob();
1058
1155
  void confirmPending();
1059
- }, disabled: busyId !== null || !pending, children: [_jsx(Printer, { className: "mr-2 h-5 w-5" }), " ", jobTexts.retry] })), _jsx(Button, { variant: "outline", size: "lg", className: "h-12 w-full sm:w-auto", onClick: () => {
1156
+ }, 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: () => {
1060
1157
  clearJob();
1061
1158
  clearShippingNotes();
1062
1159
  }, 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")
@@ -1069,5 +1166,5 @@ export function PickScherm({ station, onBack }) {
1069
1166
  return (_jsxs("div", { className: "flex items-start gap-3", children: [failed ? (_jsx(AlertTriangle, { className: "mt-0.5 h-5 w-5 shrink-0 text-destructive" })) : note.state.status === "geprint" ? (_jsx(CheckCircle2, { className: "mt-0.5 h-5 w-5 shrink-0 text-primary" })) : (_jsx(Loader2, { className: "mt-0.5 h-5 w-5 shrink-0 animate-spin text-muted-foreground" })), _jsxs("div", { className: "min-w-0", children: [(note.cartLabel || typeLabel) && (_jsx("p", { className: "truncate text-sm text-muted-foreground", children: [note.cartLabel, typeLabel].filter(Boolean).join(" · ") })), _jsx("p", { className: failed ? "text-sm font-medium text-destructive" : "text-sm text-foreground", children: message ?? "Verzendbriefstatus wordt opgehaald." }), waiting && (note.state.attempts !== null || note.state.nextAttemptAt) && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [note.state.attempts !== null ? `Pogingen: ${note.state.attempts}` : "", note.state.attempts !== null && note.state.nextAttemptAt ? " · " : "", note.state.nextAttemptAt
1070
1167
  ? `Volgende poging: ${new Date(note.state.nextAttemptAt).toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" })}`
1071
1168
  : ""] }))] })] }, note.jobId));
1072
- })] }) })), _jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-wrap items-center gap-3 p-3 sm:p-4", children: [_jsxs("div", { className: "relative w-full min-w-0 flex-1 sm:min-w-64", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "h-12 pl-9 text-base", placeholder: t("search"), value: searchInput, onChange: (e) => setSearchInput(e.target.value) })] }), _jsxs(Select, { value: chosenDay ?? "current", onValueChange: (v) => void chooseDay(v), children: [_jsx(SelectTrigger, { className: "h-12 w-full sm:w-56", disabled: loadingDay, children: _jsx(SelectValue, { placeholder: ui.pickDay }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "current", children: ui.pickDayToday }), safePickDays.map((d) => (_jsxs(SelectItem, { value: d.day, children: [dayLabel(d.day), d.orders > 0 ? ` (${d.orders})` : ""] }, d.day)))] })] }), _jsxs(Select, { value: filters.documentTypeId ?? "all", onValueChange: (v) => setFilters((f) => ({ ...f, documentTypeId: v === "all" ? null : v, page: 0 })), children: [_jsx(SelectTrigger, { className: "h-12 w-full sm:w-52", children: _jsx(SelectValue, { placeholder: ui.allDocumentTypes }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: ui.allDocumentTypes }), (Array.isArray(documentTypes) ? documentTypes : []).map((dt) => (_jsx(SelectItem, { value: dt.id, children: dt.name }, dt.id)))] })] }), _jsx(Button, { variant: filters.onlyPending ? "default" : "outline", className: "h-12", onClick: () => setFilters((f) => ({ ...f, onlyPending: !f.onlyPending, page: 0 })), children: ui.onlyPending }), _jsxs(Button, { variant: filters.urgentOnly ? "default" : "outline", className: "h-12", onClick: () => setFilters((f) => ({ ...f, urgentOnly: !f.urgentOnly, page: 0 })), children: [_jsx(Zap, { className: "mr-2 h-4 w-4" }), ui.onlyUrgent, urgentCount > 0 ? ` (${urgentCount})` : ""] })] }) }), !realtimeOk && (_jsx("p", { className: "text-sm text-muted-foreground", children: ui.connectionLost })), cartsPerPrint != null && (_jsx("div", { className: "flex justify-end md:hidden", children: _jsxs(Button, { className: "h-12", disabled: loading || batchPrinting || busyId != null, onClick: () => void printNextCarts(), children: [batchPrinting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Printer, { className: "mr-2 h-4 w-4" })), "Print eerstvolgende ", cartsPerPrint, " karren"] }) })), _jsx("div", { className: "md:hidden", children: _jsxs(Button, { variant: "outline", className: "h-12 w-full", disabled: fetchingNotes, onClick: () => void fetchShippingNotes(), children: [fetchingNotes ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(FileDown, { className: "mr-2 h-4 w-4" })), fetchingNotes ? "Ophalen…" : "Verzendbrieven ophalen"] }) }), loading ? (_jsxs("div", { className: "flex items-center justify-center py-16 text-muted-foreground", children: [_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" }), " ", t("loadingQueue")] })) : (_jsx(QueueTable, { items: items, lockedIds: lockedIds, statuses: Array.isArray(statuses) ? statuses : [], documentTypes: Array.isArray(documentTypes) ? documentTypes : [], busyId: busyId, readinessById: readinessById, onPick: handlePick, onReprint: handleReprint, onPrintAll: printAllForItem, onSelectPrinted: selectPrintedForReprint, onMarkPrinted: markPrinted, onPreview: setPreviewItem, t: t })), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("span", { className: "text-center text-sm text-muted-foreground sm:text-left", children: [t("page"), " ", filters.page + 1, " ", t("of"), " ", totalPages] }), _jsxs("div", { className: "grid grid-cols-2 gap-2 sm:flex", children: [_jsx(Button, { variant: "outline", className: "h-12 w-full sm:w-auto", disabled: filters.page === 0, onClick: () => setFilters((f) => ({ ...f, page: Math.max(0, f.page - 1) })), children: t("previous") }), _jsx(Button, { variant: "outline", className: "h-12 w-full sm:w-auto", disabled: filters.page + 1 >= totalPages, onClick: () => setFilters((f) => ({ ...f, page: f.page + 1 })), children: t("next") })] })] }), _jsx("div", { className: "fixed inset-x-0 bottom-0 z-40 border-t bg-background/95 p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] backdrop-blur md:hidden", children: _jsxs(Button, { size: "lg", className: "h-14 w-full text-lg", onClick: () => (pending ? confirmPending() : fetchNext()), disabled: busyId !== null || fetchingData, children: [busyId !== null || fetchingData ? (_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" })) : pending && !readiness.ready ? (_jsx(RefreshCw, { className: "mr-2 h-5 w-5" })) : (_jsx(Printer, { className: "mr-2 h-5 w-5" })), _jsxs("span", { className: "truncate", children: [pending && !readiness.ready ? "Pakbon ophalen" : t("printNext"), pending?.document_number ? ` · ${pending.document_number}` : ""] })] }) }), _jsx(PreviewDialog, { item: previewItem, stationSlug: station.slug, onClose: () => setPreviewItem(null) })] }));
1169
+ })] }) })), _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" })), "Print eerstvolgende ", cartsPerPrint, " karren"] }) })), _jsx("div", { className: "md:hidden", children: _jsxs(Button, { variant: "outline", className: "h-12 w-full", disabled: fetchingNotes, onClick: () => void fetchShippingNotes(), children: [fetchingNotes ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(FileDown, { className: "mr-2 h-4 w-4" })), fetchingNotes ? "Ophalen…" : "Verzendbrieven ophalen"] }) }), loading ? (_jsxs("div", { className: "flex items-center justify-center py-16 text-muted-foreground", children: [_jsx(Loader2, { className: "mr-2 h-5 w-5 animate-spin" }), " ", t("loadingQueue")] })) : (_jsx(QueueTable, { items: items, lockedIds: lockedIds, statuses: Array.isArray(statuses) ? statuses : [], documentTypes: Array.isArray(documentTypes) ? documentTypes : [], busyId: busyId, readinessById: readinessById, onPick: handlePick, onReprint: 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) })] }));
1073
1170
  }