@flowselections/floriday-afhandeling-module 1.0.8 → 1.0.10

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.
Files changed (94) hide show
  1. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +9 -0
  2. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -0
  3. package/dist-lib/components/afhandeling/DagDetailDialog.js +145 -0
  4. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  5. package/dist-lib/components/afhandeling/LeveringenPage.js +17 -69
  6. package/dist-lib/components/afhandeling/PickAgendaView.d.ts +6 -0
  7. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -0
  8. package/dist-lib/components/afhandeling/PickAgendaView.js +40 -0
  9. package/dist-lib/components/afhandeling/PickingPage.d.ts +2 -0
  10. package/dist-lib/components/afhandeling/PickingPage.d.ts.map +1 -0
  11. package/dist-lib/components/afhandeling/PickingPage.js +5 -0
  12. package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts +9 -0
  13. package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts.map +1 -0
  14. package/dist-lib/components/afhandeling/PlanningAgendaPage.js +7 -0
  15. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts +12 -0
  16. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts.map +1 -0
  17. package/dist-lib/components/afhandeling/ServiceAreaSelect.js +79 -0
  18. package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
  19. package/dist-lib/components/afhandeling/VervoerderForm.js +73 -12
  20. package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
  21. package/dist-lib/components/afhandeling/VervoerdersPage.js +23 -5
  22. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +13 -0
  23. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -0
  24. package/dist-lib/components/afhandeling/WeekAgendaView.js +142 -0
  25. package/dist-lib/components/pickstation/LanguagePicker.d.ts +5 -0
  26. package/dist-lib/components/pickstation/LanguagePicker.d.ts.map +1 -0
  27. package/dist-lib/components/pickstation/LanguagePicker.js +5 -0
  28. package/dist-lib/components/pickstation/PakbonTemplate.d.ts +20 -0
  29. package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -0
  30. package/dist-lib/components/pickstation/PakbonTemplate.js +76 -0
  31. package/dist-lib/components/pickstation/printDocuments.d.ts +3 -0
  32. package/dist-lib/components/pickstation/printDocuments.d.ts.map +1 -0
  33. package/dist-lib/components/pickstation/printDocuments.js +53 -0
  34. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts +2 -0
  35. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -0
  36. package/dist-lib/components/settings/AfhandelingSettingsCard.js +72 -0
  37. package/dist-lib/components/settings/VervoerdersSettingsCard.d.ts +2 -0
  38. package/dist-lib/components/settings/VervoerdersSettingsCard.d.ts.map +1 -0
  39. package/dist-lib/components/settings/VervoerdersSettingsCard.js +30 -0
  40. package/dist-lib/hooks/useOrderPlanning.d.ts +17 -0
  41. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -0
  42. package/dist-lib/hooks/useOrderPlanning.js +131 -0
  43. package/dist-lib/index.d.ts +5 -0
  44. package/dist-lib/index.d.ts.map +1 -1
  45. package/dist-lib/index.js +16 -6
  46. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2793 -137
  47. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  48. package/dist-lib/integrations/supabase/client.d.ts +2793 -137
  49. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  50. package/dist-lib/integrations/supabase/client.server.d.ts +2793 -137
  51. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  52. package/dist-lib/integrations/supabase/types.d.ts +2870 -131
  53. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  54. package/dist-lib/integrations/supabase/types.js +2 -0
  55. package/dist-lib/lib/afhandelingSettings.functions.d.ts +18597 -0
  56. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -0
  57. package/dist-lib/lib/afhandelingSettings.functions.js +98 -0
  58. package/dist-lib/lib/carrierSignupMail.server.d.ts +56 -0
  59. package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -0
  60. package/dist-lib/lib/carrierSignupMail.server.js +229 -0
  61. package/dist-lib/lib/carriers.functions.d.ts +34091 -14161
  62. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/carriers.functions.js +69 -6
  64. package/dist-lib/lib/cartAssignments.functions.d.ts +46479 -0
  65. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -0
  66. package/dist-lib/lib/cartAssignments.functions.js +130 -0
  67. package/dist-lib/lib/cartMoveImpact.d.ts +8 -0
  68. package/dist-lib/lib/cartMoveImpact.d.ts.map +1 -0
  69. package/dist-lib/lib/cartMoveImpact.js +26 -0
  70. package/dist-lib/lib/floriday.functions.d.ts +2798 -137
  71. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  72. package/dist-lib/lib/floriday.functions.js +178 -93
  73. package/dist-lib/lib/geo/euServiceAreas.d.ts +30 -0
  74. package/dist-lib/lib/geo/euServiceAreas.d.ts.map +1 -0
  75. package/dist-lib/lib/geo/euServiceAreas.js +277 -0
  76. package/dist-lib/lib/invalidatePlanning.d.ts +11 -0
  77. package/dist-lib/lib/invalidatePlanning.d.ts.map +1 -0
  78. package/dist-lib/lib/invalidatePlanning.js +25 -0
  79. package/dist-lib/lib/pickDate.d.ts +21 -0
  80. package/dist-lib/lib/pickDate.d.ts.map +1 -0
  81. package/dist-lib/lib/pickDate.js +71 -0
  82. package/dist-lib/lib/pickstationI18n.d.ts +35 -0
  83. package/dist-lib/lib/pickstationI18n.d.ts.map +1 -0
  84. package/dist-lib/lib/pickstationI18n.js +171 -0
  85. package/dist-lib/lib/pickstationOrders.server.d.ts +9 -0
  86. package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -0
  87. package/dist-lib/lib/pickstationOrders.server.js +372 -0
  88. package/dist-lib/lib/pickstationTypes.d.ts +26 -0
  89. package/dist-lib/lib/pickstationTypes.d.ts.map +1 -0
  90. package/dist-lib/lib/pickstationTypes.js +1 -0
  91. package/dist-lib/lib/planning.d.ts +82 -0
  92. package/dist-lib/lib/planning.d.ts.map +1 -0
  93. package/dist-lib/lib/planning.js +243 -0
  94. package/package.json +12 -6
@@ -6,17 +6,21 @@ import { Link, useNavigate } from "@tanstack/react-router";
6
6
  import { useServerFn } from "@tanstack/react-start";
7
7
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
8
  import { getCarrier, upsertCarrier, } from "../../lib/carriers.functions";
9
+ import { DEFAULT_PICKUP_MOMENT } from "../../lib/planning";
9
10
  import { COUNTRIES, countryName } from "../../lib/countries";
11
+ import { ServiceAreaSelect } from "./ServiceAreaSelect";
10
12
  const EMPTY = {
11
13
  name: "",
12
14
  contact_person: "",
13
15
  email: "",
14
16
  phone: "",
15
17
  address: "",
16
- delivery_days: null,
17
- signup_time_1: "",
18
- signup_time_2: "",
19
18
  notes: "",
19
+ is_default: false,
20
+ pickup_moments: [DEFAULT_PICKUP_MOMENT],
21
+ service_countries: [],
22
+ service_provinces: [],
23
+ service_cities: [],
20
24
  };
21
25
  function FieldIcon({ icon: Icon }) {
22
26
  return (_jsx("div", { className: "flex items-center justify-center w-10 h-10 rounded-md bg-primary/10 text-primary shrink-0", children: _jsx(Icon, { className: "w-4 h-4" }) }));
@@ -38,6 +42,42 @@ function CountryPicker({ value, onChange, excluded, }) {
38
42
  setQuery("");
39
43
  }, className: "flex items-center justify-between w-full px-3 py-2 text-sm text-left hover:bg-muted", children: [_jsx("span", { children: c.name }), _jsx("span", { className: "text-xs text-muted-foreground", children: c.code })] }, c.code)))) })] })] }));
40
44
  }
45
+ function PickupMomentRow({ label, days, time, maxDays, onDaysChange, onTimeChange, }) {
46
+ return (_jsxs("div", { className: "grid grid-cols-[110px,1fr,1fr] items-center gap-3", children: [_jsx("div", { className: "text-sm font-medium", children: label }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Werkdagen v\u00F3\u00F3r levering" }), _jsx(IconInput, { icon: Calendar, type: "number", min: 0, max: maxDays, value: days, onChange: (e) => onDaysChange(Number(e.target.value) || 0) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Tijdstip" }), _jsx(IconInput, { icon: Clock, type: "time", value: time, onChange: (e) => onTimeChange(e.target.value) })] })] }));
47
+ }
48
+ // Normaliseer een ophaalmoment: aanmelden moet altijd vóór (of op) ophalen
49
+ // liggen, ophalen vóór (of op) bezorgen. Werkdagen-waardes zijn afstanden tot
50
+ // de bezorgdag, dus geldt: signup_days >= pickup_days >= delivery_days.
51
+ function normalizeMoment(next, prev) {
52
+ const m = { ...next };
53
+ if (next.signup_days !== prev.signup_days) {
54
+ // Aanmelden verlaagd → ophalen mag niet eerder zijn dan aanmelden
55
+ if (m.pickup_days > m.signup_days)
56
+ m.pickup_days = m.signup_days;
57
+ if (m.delivery_days > m.pickup_days)
58
+ m.delivery_days = m.pickup_days;
59
+ }
60
+ if (next.pickup_days !== prev.pickup_days) {
61
+ // Ophalen mag nooit vóór aanmelden plaatsvinden
62
+ if (m.pickup_days > m.signup_days)
63
+ m.pickup_days = m.signup_days;
64
+ if (m.delivery_days > m.pickup_days)
65
+ m.delivery_days = m.pickup_days;
66
+ }
67
+ if (next.delivery_days !== prev.delivery_days) {
68
+ if (m.delivery_days > m.pickup_days)
69
+ m.delivery_days = m.pickup_days;
70
+ }
71
+ return m;
72
+ }
73
+ function PickupMomentEditor({ index, value, canDelete, onChange, onDelete, }) {
74
+ const update = (patch) => {
75
+ const next = { ...value, ...patch };
76
+ onChange(normalizeMoment(next, value));
77
+ };
78
+ const pickupInvalid = value.pickup_days > value.signup_days;
79
+ return (_jsxs("div", { className: "rounded-md border bg-background p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "text-sm font-semibold", children: ["Ophaalmoment ", index + 1] }), canDelete ? (_jsxs("button", { type: "button", onClick: onDelete, className: "text-xs text-muted-foreground hover:text-destructive inline-flex items-center gap-1", "aria-label": "Ophaalmoment verwijderen", children: [_jsx(Trash2, { className: "w-3.5 h-3.5" }), "Verwijderen"] })) : null] }), _jsxs("div", { className: "space-y-3", children: [_jsx(PickupMomentRow, { label: "Aanmelden", days: value.signup_days, time: value.signup_time, onDaysChange: (n) => update({ signup_days: n }), onTimeChange: (v) => update({ signup_time: v }) }), _jsx(PickupMomentRow, { label: "Ophalen", days: value.pickup_days, time: value.pickup_time, maxDays: value.signup_days, onDaysChange: (n) => update({ pickup_days: Math.min(n, value.signup_days) }), onTimeChange: (v) => update({ pickup_time: v }) }), pickupInvalid ? (_jsx("p", { className: "text-xs text-destructive -mt-1 pl-[122px]", children: "Ophalen kan niet v\u00F3\u00F3r aanmelden plaatsvinden." })) : null, _jsx(PickupMomentRow, { label: "Bezorgen", days: value.delivery_days, time: value.delivery_time, maxDays: value.pickup_days, onDaysChange: (n) => update({ delivery_days: Math.min(n, value.pickup_days) }), onTimeChange: (v) => update({ delivery_time: v }) })] })] }));
80
+ }
41
81
  export function VervoerderForm({ carrierId }) {
42
82
  const isEdit = !!carrierId;
43
83
  const navigate = useNavigate();
@@ -63,10 +103,14 @@ export function VervoerderForm({ carrierId }) {
63
103
  email: c.email ?? "",
64
104
  phone: c.phone ?? "",
65
105
  address: c.address ?? "",
66
- delivery_days: c.delivery_days,
67
- signup_time_1: c.signup_time_1 ?? "",
68
- signup_time_2: c.signup_time_2 ?? "",
69
106
  notes: c.notes ?? "",
107
+ is_default: c.is_default ?? false,
108
+ pickup_moments: c.pickup_moments && c.pickup_moments.length > 0
109
+ ? c.pickup_moments
110
+ : [DEFAULT_PICKUP_MOMENT],
111
+ service_countries: c.service_countries ?? [],
112
+ service_provinces: c.service_provinces ?? [],
113
+ service_cities: c.service_cities ?? [],
70
114
  });
71
115
  setRates(c.rates);
72
116
  }
@@ -85,9 +129,10 @@ export function VervoerderForm({ carrierId }) {
85
129
  throw e;
86
130
  }
87
131
  },
88
- onSuccess: () => {
132
+ onSuccess: async () => {
89
133
  toast.success(isEdit ? "Vervoerder bijgewerkt" : "Vervoerder aangemaakt");
90
- queryClient.invalidateQueries({ queryKey: ["carriers"] });
134
+ const { invalidatePlanning } = await import("../../lib/invalidatePlanning");
135
+ await invalidatePlanning(queryClient);
91
136
  if (carrierId)
92
137
  queryClient.invalidateQueries({ queryKey: ["carrier", carrierId] });
93
138
  goBack();
@@ -126,10 +171,26 @@ export function VervoerderForm({ carrierId }) {
126
171
  }
127
172
  return (_jsxs("main", { className: "p-6 space-y-6 max-w-4xl", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/afhandeling/vervoerders", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar vervoerders"] }) }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(FieldIcon, { icon: Truck }), _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: isEdit ? "Vervoerder bewerken" : "Vervoerder aanmaken" }), _jsx("p", { className: "text-sm text-muted-foreground", children: isEdit
128
173
  ? "Wijzig de gegevens van de vervoerder."
129
- : "Voeg een nieuwe vervoerder toe en stel verzendtarieven in." })] })] }), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Algemene gegevens" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { children: ["Bedrijfsnaam ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(IconInput, { icon: Truck, value: form.name, onChange: (e) => setForm({ ...form, name: e.target.value }), placeholder: "Bijv. DHL Freight", required: true })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Contactpersoon" }), _jsx(IconInput, { icon: User, value: form.contact_person ?? "", onChange: (e) => setForm({ ...form, contact_person: e.target.value }) })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "E-mail" }), _jsx(IconInput, { icon: Mail, type: "email", value: form.email ?? "", onChange: (e) => setForm({ ...form, email: e.target.value }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Telefoonnummer" }), _jsx(IconInput, { icon: Phone, value: form.phone ?? "", onChange: (e) => setForm({ ...form, phone: e.target.value }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Adres" }), _jsx(IconInput, { icon: MapPin, value: form.address ?? "", onChange: (e) => setForm({ ...form, address: e.target.value }), placeholder: "Straat, postcode, plaats" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Levertijd (dagen)" }), _jsx(IconInput, { icon: Calendar, type: "number", min: 0, value: form.delivery_days ?? "", onChange: (e) => setForm({
130
- ...form,
131
- delivery_days: e.target.value === "" ? null : Number(e.target.value),
132
- }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aanmeldtijd 1" }), _jsx(IconInput, { icon: Clock, type: "time", value: form.signup_time_1 ?? "", onChange: (e) => setForm({ ...form, signup_time_1: e.target.value }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aanmeldtijd 2" }), _jsx(IconInput, { icon: Clock, type: "time", value: form.signup_time_2 ?? "", onChange: (e) => setForm({ ...form, signup_time_2: e.target.value }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Opmerkingen" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(FieldIcon, { icon: FileText }), _jsx(Textarea, { value: form.notes ?? "", onChange: (e) => setForm({ ...form, notes: e.target.value }), placeholder: "Extra informatie over deze vervoerder...", rows: 3, className: "flex-1" })] })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Verzendtarieven" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stel de verzendkosten ", _jsx("strong", { children: "per kwekerskar" }), " in voor elk land waar deze vervoerder naartoe verzendt."] })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-[1fr,160px,auto] gap-3", children: [_jsx(CountryPicker, { value: pendingCountry, onChange: setPendingCountry, excluded: rates.map((r) => r.country_code) }), _jsxs("div", { className: "flex items-stretch gap-2 rounded-md border bg-background h-10", children: [_jsx("div", { className: "flex items-center justify-center w-10 bg-primary/10 text-primary rounded-l-md text-sm font-medium", children: "\u20AC" }), _jsx("input", { type: "number", step: "0.01", min: 0, value: pendingPrice, onChange: (e) => setPendingPrice(e.target.value), placeholder: "Prijs", className: "flex-1 bg-transparent text-sm outline-none pr-3" })] }), _jsxs(Button, { type: "button", onClick: addRate, children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Toevoegen"] })] }), rates.length === 0 ? (_jsxs("div", { className: "border rounded-md py-10 text-center space-y-2", children: [_jsx(Globe, { className: "w-8 h-8 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen landen toegevoegd" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een land toe om verzendtarieven in te stellen." })] })) : (_jsx("div", { className: "border rounded-md divide-y", children: rates
174
+ : "Voeg een nieuwe vervoerder toe en stel verzendtarieven in." })] })] }), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Algemene gegevens" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { children: ["Bedrijfsnaam ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(IconInput, { icon: Truck, value: form.name, onChange: (e) => setForm({ ...form, name: e.target.value }), placeholder: "Bijv. DHL Freight", required: true })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Contactpersoon" }), _jsx(IconInput, { icon: User, value: form.contact_person ?? "", onChange: (e) => setForm({ ...form, contact_person: e.target.value }) })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "E-mail" }), _jsx(IconInput, { icon: Mail, type: "email", value: form.email ?? "", onChange: (e) => setForm({ ...form, email: e.target.value }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Telefoonnummer" }), _jsx(IconInput, { icon: Phone, value: form.phone ?? "", onChange: (e) => setForm({ ...form, phone: e.target.value }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Adres" }), _jsx(IconInput, { icon: MapPin, value: form.address ?? "", onChange: (e) => setForm({ ...form, address: e.target.value }), placeholder: "Straat, postcode, plaats" })] }), _jsxs("div", { className: "rounded-md border bg-muted/30 p-4 space-y-4", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Ophaalmomenten (transportplanning)" }), _jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: ["De bezorgdag is ", _jsx("strong", { children: "dag 0" }), ". Per ophaalmoment stel je in op hoeveel werkdagen v\u00F3\u00F3r bezorging \u00E9n op welk tijdstip elke stap plaatsvindt. Bij een nieuwe order kiest het systeem automatisch het eerstvolgende moment waarvan de aanmelddeadline nog niet is verstreken."] })] }), _jsx("div", { className: "space-y-3", children: form.pickup_moments.map((moment, idx) => (_jsx(PickupMomentEditor, { index: idx, value: moment, canDelete: form.pickup_moments.length > 1, onChange: (next) => {
175
+ const copy = [...form.pickup_moments];
176
+ copy[idx] = next;
177
+ setForm({ ...form, pickup_moments: copy });
178
+ }, onDelete: () => {
179
+ const copy = form.pickup_moments.filter((_, i) => i !== idx);
180
+ setForm({ ...form, pickup_moments: copy });
181
+ } }, idx))) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setForm({
182
+ ...form,
183
+ pickup_moments: [...form.pickup_moments, { ...DEFAULT_PICKUP_MOMENT }],
184
+ }), children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Ophaalmoment toevoegen"] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: form.is_default ?? false, onChange: (e) => setForm({ ...form, is_default: e.target.checked }), className: "h-4 w-4 rounded border" }), _jsxs("span", { children: ["Gebruik als ", _jsx("strong", { children: "standaard vervoerder" }), " voor de automatische planning (voor orders zonder eigen vervoerder)."] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Opmerkingen" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(FieldIcon, { icon: FileText }), _jsx(Textarea, { value: form.notes ?? "", onChange: (e) => setForm({ ...form, notes: e.target.value }), placeholder: "Extra informatie over deze vervoerder...", rows: 3, className: "flex-1" })] })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Bedieningsgebied" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Selecteer de landen, provincies en/of plaatsen waar deze vervoerder actief is. Een land dekt automatisch al zijn provincies en plaatsen." })] }), _jsx(CardContent, { children: _jsx(ServiceAreaSelect, { value: {
185
+ countries: form.service_countries ?? [],
186
+ provinces: form.service_provinces ?? [],
187
+ cities: form.service_cities ?? [],
188
+ }, onChange: (next) => setForm({
189
+ ...form,
190
+ service_countries: next.countries,
191
+ service_provinces: next.provinces,
192
+ service_cities: next.cities,
193
+ }) }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Verzendtarieven" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stel de verzendkosten ", _jsx("strong", { children: "per kwekerskar" }), " in voor elk land waar deze vervoerder naartoe verzendt."] })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-[1fr,160px,auto] gap-3", children: [_jsx(CountryPicker, { value: pendingCountry, onChange: setPendingCountry, excluded: rates.map((r) => r.country_code) }), _jsxs("div", { className: "flex items-stretch gap-2 rounded-md border bg-background h-10", children: [_jsx("div", { className: "flex items-center justify-center w-10 bg-primary/10 text-primary rounded-l-md text-sm font-medium", children: "\u20AC" }), _jsx("input", { type: "number", step: "0.01", min: 0, value: pendingPrice, onChange: (e) => setPendingPrice(e.target.value), placeholder: "Prijs", className: "flex-1 bg-transparent text-sm outline-none pr-3" })] }), _jsxs(Button, { type: "button", onClick: addRate, children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Toevoegen"] })] }), rates.length === 0 ? (_jsxs("div", { className: "border rounded-md py-10 text-center space-y-2", children: [_jsx(Globe, { className: "w-8 h-8 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen landen toegevoegd" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een land toe om verzendtarieven in te stellen." })] })) : (_jsx("div", { className: "border rounded-md divide-y", children: rates
133
194
  .slice()
134
195
  .sort((a, b) => countryName(a.country_code).localeCompare(countryName(b.country_code), "nl"))
135
196
  .map((r) => (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx(Globe, { className: "w-4 h-4 text-muted-foreground" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: countryName(r.country_code) }), _jsx("div", { className: "text-xs text-muted-foreground", children: r.country_code })] }), _jsxs("div", { className: "text-sm font-medium", children: ["\u20AC ", r.price_per_trolley.toFixed(2), " ", _jsx("span", { className: "text-muted-foreground font-normal", children: "/ kar" })] }), _jsx("button", { type: "button", onClick: () => removeRate(r.country_code), className: "text-muted-foreground hover:text-destructive", "aria-label": "Verwijderen", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }, r.country_code))) })), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["De prijs per kwekerskar wordt automatisch berekend", " ", _jsx("strong", { children: "bij het aanmaken van een bestelling" }), "."] })] })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "outline", onClick: goBack, children: [_jsx(X, { className: "w-4 h-4 mr-1" }), "Annuleren"] }), _jsx(Button, { type: "submit", disabled: mutation.isPending, children: mutation.isPending ? "Opslaan..." : "Opslaan" })] })] })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"VervoerdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerdersPage.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,eAAe,gCAgJ9B"}
1
+ {"version":3,"file":"VervoerdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/VervoerdersPage.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,eAAe,gCA6L9B"}
@@ -1,15 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, toast, } from "@flowselections/core";
4
- import { Plus, Pencil, Trash2, Truck } from "lucide-react";
4
+ import { Plus, Pencil, Trash2, Truck, ArrowUp, ArrowDown } from "lucide-react";
5
5
  import { Link } from "@tanstack/react-router";
6
6
  import { useServerFn } from "@tanstack/react-start";
7
7
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
- import { listCarriers, deleteCarrier } from "../../lib/carriers.functions";
8
+ import { listCarriers, deleteCarrier, reorderCarriers } from "../../lib/carriers.functions";
9
9
  export function VervoerdersPage() {
10
10
  const queryClient = useQueryClient();
11
11
  const fetchList = useServerFn(listCarriers);
12
12
  const removeCarrier = useServerFn(deleteCarrier);
13
+ const reorder = useServerFn(reorderCarriers);
13
14
  const [deletingId, setDeletingId] = useState(null);
14
15
  const { data, isLoading, error, refetch } = useQuery({
15
16
  queryKey: ["carriers"],
@@ -29,13 +30,30 @@ export function VervoerdersPage() {
29
30
  });
30
31
  const deleteMutation = useMutation({
31
32
  mutationFn: (id) => removeCarrier({ data: { id } }),
32
- onSuccess: () => {
33
+ onSuccess: async () => {
33
34
  toast.success("Vervoerder verwijderd");
34
- queryClient.invalidateQueries({ queryKey: ["carriers"] });
35
+ const { invalidatePlanning } = await import("../../lib/invalidatePlanning");
36
+ await invalidatePlanning(queryClient);
35
37
  setDeletingId(null);
36
38
  },
37
39
  onError: (err) => toast.error(err.message),
38
40
  });
41
+ const reorderMutation = useMutation({
42
+ mutationFn: (orderedIds) => reorder({ data: { orderedIds } }),
43
+ onSuccess: async () => {
44
+ const { invalidatePlanning } = await import("../../lib/invalidatePlanning");
45
+ await invalidatePlanning(queryClient);
46
+ },
47
+ onError: (err) => toast.error(err.message),
48
+ });
39
49
  const carriers = data?.carriers ?? [];
40
- return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Vervoerders" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer vervoerders en hun verzendtarieven per land." })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] }), _jsx("div", { className: "border rounded-md bg-card", children: isLoading ? (_jsx("div", { className: "p-12 text-center text-muted-foreground", children: "Laden..." })) : error ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsxs("div", { className: "text-destructive", children: ["Fout bij laden: ", error.message] }), _jsx(Button, { variant: "outline", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : carriers.length === 0 ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsx(Truck, { className: "w-10 h-10 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen vervoerders" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een vervoerder toe om verzendtarieven in te stellen." }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Bedrijfsnaam" }), _jsx(TableHead, { children: "Contactpersoon" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Telefoon" }), _jsx(TableHead, { children: "Levertijd" }), _jsx(TableHead, { children: "Landen" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: carriers.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: c.name }), _jsx(TableCell, { children: c.contact_person ?? "—" }), _jsx(TableCell, { children: c.email ?? "—" }), _jsx(TableCell, { children: c.phone ?? "—" }), _jsx(TableCell, { children: c.delivery_days != null ? `${c.delivery_days} dagen` : "—" }), _jsx(TableCell, { children: c.rate_count }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsx(Link, { to: "/afhandeling/vervoerders/$id/bewerken", params: { id: c.id }, children: _jsx(Pencil, { className: "w-4 h-4" }) }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDeletingId(c.id), className: "text-destructive hover:text-destructive", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }) })] }, c.id))) })] })) }), _jsx(AlertDialog, { open: !!deletingId, onOpenChange: (o) => !o && setDeletingId(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Vervoerder verwijderen?" }), _jsx(AlertDialogDescription, { children: "Deze actie kan niet ongedaan gemaakt worden. Alle verzendtarieven worden ook verwijderd." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => deletingId && deleteMutation.mutate(deletingId), className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Verwijderen" })] })] }) })] }));
50
+ const move = (index, direction) => {
51
+ const target = index + direction;
52
+ if (target < 0 || target >= carriers.length)
53
+ return;
54
+ const ids = carriers.map((c) => c.id);
55
+ [ids[index], ids[target]] = [ids[target], ids[index]];
56
+ reorderMutation.mutate(ids);
57
+ };
58
+ return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Vervoerders" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer vervoerders en hun verzendtarieven per land." })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] }), _jsx("div", { className: "border rounded-md bg-card", children: isLoading ? (_jsx("div", { className: "p-12 text-center text-muted-foreground", children: "Laden..." })) : error ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsxs("div", { className: "text-destructive", children: ["Fout bij laden: ", error.message] }), _jsx(Button, { variant: "outline", onClick: () => refetch(), children: "Opnieuw proberen" })] })) : carriers.length === 0 ? (_jsxs("div", { className: "p-12 text-center space-y-3", children: [_jsx(Truck, { className: "w-10 h-10 mx-auto text-muted-foreground" }), _jsx("div", { className: "font-medium", children: "Nog geen vervoerders" }), _jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg een vervoerder toe om verzendtarieven in te stellen." }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/afhandeling/vervoerders/nieuw", children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), "Vervoerder toevoegen"] }) })] })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-32", children: "Prioriteit" }), _jsx(TableHead, { children: "Bedrijfsnaam" }), _jsx(TableHead, { children: "Contactpersoon" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Telefoon" }), _jsx(TableHead, { children: "Levertijd" }), _jsx(TableHead, { children: "Landen" }), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: carriers.map((c, index) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "w-6 text-sm font-medium tabular-nums text-muted-foreground", children: index + 1 }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(index, -1), disabled: index === 0 || reorderMutation.isPending, "aria-label": "Hoger", children: _jsx(ArrowUp, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(index, 1), disabled: index === carriers.length - 1 || reorderMutation.isPending, "aria-label": "Lager", children: _jsx(ArrowDown, { className: "w-4 h-4" }) })] }) }), _jsx(TableCell, { className: "font-medium", children: c.name }), _jsx(TableCell, { children: c.contact_person ?? "—" }), _jsx(TableCell, { children: c.email ?? "—" }), _jsx(TableCell, { children: c.phone ?? "—" }), _jsx(TableCell, { children: c.delivery_days != null ? `${c.delivery_days} dagen` : "—" }), _jsx(TableCell, { children: c.rate_count }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsx(Link, { to: "/afhandeling/vervoerders/$id/bewerken", params: { id: c.id }, children: _jsx(Pencil, { className: "w-4 h-4" }) }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDeletingId(c.id), className: "text-destructive hover:text-destructive", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }) })] }, c.id))) })] })) }), _jsx(AlertDialog, { open: !!deletingId, onOpenChange: (o) => !o && setDeletingId(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Vervoerder verwijderen?" }), _jsx(AlertDialogDescription, { children: "Deze actie kan niet ongedaan gemaakt worden. Alle verzendtarieven worden ook verwijderd." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => deletingId && deleteMutation.mutate(deletingId), className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Verwijderen" })] })] }) })] }));
41
59
  }
@@ -0,0 +1,13 @@
1
+ import type { FloridayOrder } from "../../lib/floriday.functions";
2
+ import type { PlannedOrder } from "../../hooks/useOrderPlanning";
3
+ export type WeekAgendaMode = "delivery" | "pickup" | "transport-register" | "pick";
4
+ export interface WeekAgendaViewProps {
5
+ planned: PlannedOrder[];
6
+ mode: WeekAgendaMode;
7
+ isLoading?: boolean;
8
+ onOrderClick?: (order: FloridayOrder) => void;
9
+ onDayClick?: (date: Date) => void;
10
+ title?: string;
11
+ }
12
+ export declare function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayClick, title, }: WeekAgendaViewProps): import("react").JSX.Element;
13
+ //# sourceMappingURL=WeekAgendaView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4DD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,GACN,EAAE,mBAAmB,+BAwHrB"}
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Button, Badge } from "@flowselections/core";
4
+ import { ChevronLeft, ChevronRight, Package, Truck, Sprout, Clock, CheckCircle2, Scale } from "lucide-react";
5
+ import { isSameDay, startOfWeek, addDays } from "../../lib/pickDate";
6
+ const WEEKDAY_LABELS = ["MAANDAG", "DINSDAG", "WOENSDAG", "DONDERDAG", "VRIJDAG", "ZATERDAG", "ZONDAG"];
7
+ const MONTHS_NL_SHORT = ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"];
8
+ function isOrderReady(o) {
9
+ const s = (o.status ?? "").toLowerCase();
10
+ return s.includes("ready") || s.includes("verzendklaar") || s.includes("completed") || s.includes("delivered") || s.includes("afgerond");
11
+ }
12
+ function getISOWeek(d) {
13
+ const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
14
+ const dayNum = date.getUTCDay() || 7;
15
+ date.setUTCDate(date.getUTCDate() + 4 - dayNum);
16
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
17
+ return Math.ceil(((date.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
18
+ }
19
+ function dateForPlanned(p, mode) {
20
+ if (!p.planning)
21
+ return null;
22
+ switch (mode) {
23
+ case "delivery": return p.planning.deliveryDate;
24
+ case "pickup": return p.planning.pickupDate;
25
+ case "transport-register": return p.planning.transportRegisterDate;
26
+ case "pick": return p.planning.scheduledPickDate ?? p.planning.pickStartDate;
27
+ }
28
+ }
29
+ function isMovedEarlier(p, mode) {
30
+ if (mode !== "pick" || !p.planning)
31
+ return false;
32
+ return Boolean(p.planning.movedEarlier);
33
+ }
34
+ const MODE_LABELS = {
35
+ delivery: { summary: "Leveringen deze week" },
36
+ pickup: { summary: "Ophalingen deze week" },
37
+ "transport-register": { summary: "Aanmeldingen deze week" },
38
+ pick: { summary: "Te picken deze week" },
39
+ };
40
+ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayClick, title, }) {
41
+ const [cursor, setCursor] = useState(() => {
42
+ const d = new Date();
43
+ d.setHours(0, 0, 0, 0);
44
+ return startOfWeek(d);
45
+ });
46
+ const weekStart = useMemo(() => startOfWeek(cursor), [cursor]);
47
+ const weekEnd = useMemo(() => addDays(weekStart, 6), [weekStart]);
48
+ const weekNumber = useMemo(() => getISOWeek(weekStart), [weekStart]);
49
+ const days = useMemo(() => {
50
+ return Array.from({ length: 7 }, (_, i) => {
51
+ const date = addDays(weekStart, i);
52
+ const entries = [];
53
+ for (const p of planned) {
54
+ if (!p.planning)
55
+ continue;
56
+ if (mode === "pick") {
57
+ const assigns = p.planning.pickAssignments ?? [
58
+ { date: p.planning.scheduledPickDate ?? p.planning.pickStartDate, cartCount: p.order.cartCount ?? 0 },
59
+ ];
60
+ const total = assigns.length;
61
+ assigns.forEach((a, idx) => {
62
+ if (!isSameDay(a.date, date))
63
+ return;
64
+ const totalCarts = p.order.cartCount ?? 0;
65
+ const ratio = totalCarts > 0 ? a.cartCount / totalCarts : 1 / Math.max(total, 1);
66
+ entries.push({
67
+ order: p.order,
68
+ cartCount: a.cartCount,
69
+ plantCount: Math.round((p.order.plantCount ?? 0) * ratio),
70
+ movedEarlier: a.date.getTime() !== p.planning.pickStartDate.getTime(),
71
+ splitPart: total > 1 ? { index: idx + 1, total } : undefined,
72
+ });
73
+ });
74
+ }
75
+ else {
76
+ const d = dateForPlanned(p, mode);
77
+ if (!d || !isSameDay(d, date))
78
+ continue;
79
+ entries.push({
80
+ order: p.order,
81
+ cartCount: p.order.cartCount ?? 0,
82
+ plantCount: p.order.plantCount ?? 0,
83
+ movedEarlier: false,
84
+ });
85
+ }
86
+ }
87
+ const karren = entries.reduce((s, e) => s + e.cartCount, 0);
88
+ const planten = entries.reduce((s, e) => s + e.plantCount, 0);
89
+ const verzendklaar = entries.filter((e) => isOrderReady(e.order)).length;
90
+ const voorbereiding = entries.length - verzendklaar;
91
+ const movedEarlierOrderIds = new Set(entries.filter((e) => e.movedEarlier).map((e) => e.order.id));
92
+ return {
93
+ date,
94
+ entries,
95
+ movedEarlierOrderIds,
96
+ bestellingen: entries.length,
97
+ karren,
98
+ planten,
99
+ verzendklaar,
100
+ voorbereiding,
101
+ };
102
+ });
103
+ }, [planned, weekStart, mode]);
104
+ const totals = useMemo(() => {
105
+ const leveringen = days.reduce((s, d) => s + d.bestellingen, 0);
106
+ const verzendklaar = days.reduce((s, d) => s + d.verzendklaar, 0);
107
+ const voorbereiding = days.reduce((s, d) => s + d.voorbereiding, 0);
108
+ const totaalStuks = days.reduce((s, d) => s + d.planten, 0);
109
+ return { leveringen, verzendklaar, voorbereiding, totaalStuks };
110
+ }, [days]);
111
+ const headerLabel = `Week ${weekNumber} - ${weekStart.getDate()} ${MONTHS_NL_SHORT[weekStart.getMonth()]}. t/m ${weekEnd.getDate()} ${MONTHS_NL_SHORT[weekEnd.getMonth()]}. ${weekEnd.getFullYear()}`;
112
+ const goPrev = () => setCursor((c) => addDays(c, -7));
113
+ const goNext = () => setCursor((c) => addDays(c, 7));
114
+ const goToday = () => {
115
+ const d = new Date();
116
+ d.setHours(0, 0, 0, 0);
117
+ setCursor(startOfWeek(d));
118
+ };
119
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h2", { className: "text-xl font-semibold tracking-tight", children: title ?? headerLabel }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: goPrev, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), " Vorige week"] }), _jsx(Button, { variant: "outline", size: "sm", onClick: goToday, children: "Deze week" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: goNext, children: ["Volgende week ", _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: [_jsx(SummaryCard, { label: MODE_LABELS[mode].summary, value: totals.leveringen, tone: "default" }), _jsx(SummaryCard, { label: "Verzendklaar", value: totals.verzendklaar, tone: "success" }), _jsx(SummaryCard, { label: "In voorbereiding", value: totals.voorbereiding, tone: "muted" }), _jsx(SummaryCard, { label: "Totaal stuks", value: totals.totaalStuks, tone: "default" })] }), _jsx("div", { className: "space-y-2", children: isLoading ? (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : (days.map((day) => _jsx(DayRow, { day: day, onOrderClick: onOrderClick, onDayClick: onDayClick }, day.date.toISOString()))) })] }));
120
+ }
121
+ function SummaryCard({ label, value, tone, }) {
122
+ const valueClass = tone === "success" ? "text-green-600" : "text-foreground";
123
+ return (_jsxs("div", { className: "rounded-lg border bg-card p-4", children: [_jsx("div", { className: `text-3xl font-bold ${valueClass}`, children: value }), _jsx("div", { className: "mt-1 text-sm text-muted-foreground", children: label })] }));
124
+ }
125
+ function DayRow({ day, onOrderClick, onDayClick }) {
126
+ const weekdayIdx = (day.date.getDay() + 6) % 7;
127
+ const today = new Date();
128
+ today.setHours(0, 0, 0, 0);
129
+ const isToday = isSameDay(day.date, today);
130
+ return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6 px-4 py-4 sm:px-6", children: [_jsxs("div", { className: "w-20 shrink-0", children: [_jsx("div", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: WEEKDAY_LABELS[weekdayIdx] }), _jsx("div", { className: "mt-1 text-3xl font-bold leading-none", children: day.date.getDate() }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [MONTHS_NL_SHORT[day.date.getMonth()], "."] })] }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-6", children: [_jsx(Stat, { icon: _jsx(Package, { className: "h-4 w-4 text-slate-500" }), bg: "bg-slate-100", value: day.bestellingen, label: "bestellingen" }), _jsx(Stat, { icon: _jsx(Truck, { className: "h-4 w-4 text-amber-600" }), bg: "bg-amber-100", value: day.karren, label: "karren" }), _jsx(Stat, { icon: _jsx(Sprout, { className: "h-4 w-4 text-green-600" }), bg: "bg-green-100", value: day.planten, label: "planten" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [day.movedEarlierOrderIds.size > 0 && (_jsxs(Badge, { className: "bg-purple-500/10 text-purple-700 hover:bg-purple-500/10", variant: "secondary", title: "Aantal orders dat vervroegd is ingepland door Pickverdeling.", children: [_jsx(Scale, { className: "mr-1 h-3.5 w-3.5" }), day.movedEarlierOrderIds.size, " vervroegd"] })), day.verzendklaar > 0 && (_jsxs(Badge, { className: "bg-green-500/15 text-green-700 hover:bg-green-500/15", variant: "secondary", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), day.verzendklaar, " verzendklaar"] })), day.voorbereiding > 0 && (_jsxs(Badge, { className: "bg-blue-500/10 text-blue-700 hover:bg-blue-500/10", variant: "secondary", children: [_jsx(Clock, { className: "mr-1 h-3.5 w-3.5" }), day.voorbereiding, " in voorbereiding"] })), onDayClick && day.bestellingen > 0 && (_jsx(Button, { size: "sm", variant: "outline", onClick: () => onDayClick(day.date), children: "Dagdetails" }))] })] }), onOrderClick && day.entries.length > 0 && (_jsx("div", { className: "border-t px-4 py-2 sm:px-6", children: _jsx("div", { className: "flex flex-wrap gap-2", children: day.entries.map((e, idx) => {
131
+ const moved = e.movedEarlier;
132
+ const split = e.splitPart;
133
+ return (_jsxs("button", { onClick: () => onOrderClick(e.order), title: split
134
+ ? `Deel ${split.index}/${split.total} — ${e.cartCount} karren deze dag (verdeeld picken).`
135
+ : moved
136
+ ? "Deze order is eerder ingepland vanwege Pickverdeling."
137
+ : undefined, className: `flex items-center gap-1 rounded border px-2 py-1 text-xs hover:bg-muted ${moved || split ? "border-purple-300 bg-purple-50" : ""}`, children: [(moved || split) && _jsx(Scale, { className: "h-3 w-3 text-purple-600" }), _jsx("span", { className: "font-medium", children: e.order.orderNumber }), _jsxs("span", { className: "ml-1 text-muted-foreground", children: ["\u00B7 ", e.order.customerName] }), _jsxs("span", { className: "ml-1 text-purple-700", children: ["\u00B7 ", e.cartCount, " karren", split ? ` (deel ${split.index}/${split.total})` : ""] })] }, `${e.order.id}-${idx}`));
138
+ }) }) }))] }));
139
+ }
140
+ function Stat({ icon, bg, value, label, }) {
141
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `grid h-8 w-8 place-items-center rounded-full ${bg}`, children: icon }), _jsxs("div", { children: [_jsx("div", { className: "text-lg font-bold leading-none", children: value }), _jsx("div", { className: "text-xs text-muted-foreground", children: label })] })] }));
142
+ }
@@ -0,0 +1,5 @@
1
+ import { type PickstationLang } from "../../lib/pickstationI18n";
2
+ export declare function LanguagePicker({ onPick }: {
3
+ onPick: (lang: PickstationLang) => void;
4
+ }): import("react").JSX.Element;
5
+ //# sourceMappingURL=LanguagePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LanguagePicker.d.ts","sourceRoot":"","sources":["../../../src/components/pickstation/LanguagePicker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAExF,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;CAAE,+BAuBrF"}
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PICKSTATION_LANGUAGES } from "../../lib/pickstationI18n";
3
+ export function LanguagePicker({ onPick }) {
4
+ return (_jsx("div", { className: "fixed inset-0 z-50 bg-slate-900 text-white flex items-center justify-center p-6", children: _jsxs("div", { className: "w-full max-w-3xl", children: [_jsx("h1", { className: "text-4xl font-semibold mb-2 text-center", children: "Pickstation" }), _jsx("p", { className: "text-slate-400 text-center mb-10 text-lg", children: "Kies uw taal \u00B7 Wybierz j\u0119zyk \u00B7 Alege\u021Bi limba \u00B7 \u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0437\u0438\u043A \u00B7 \u041E\u0431\u0435\u0440\u0456\u0442\u044C \u043C\u043E\u0432\u0443 \u00B7 Choose your language" }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: PICKSTATION_LANGUAGES.map((lang) => (_jsxs("button", { onClick: () => onPick(lang.code), className: "bg-white text-slate-900 hover:bg-emerald-500 hover:text-white transition rounded-2xl py-6 px-6 text-left text-2xl font-semibold flex items-center gap-4 shadow-lg", children: [_jsx("span", { className: "text-4xl", children: lang.flag }), _jsx("span", { children: lang.label })] }, lang.code))) })] }) }));
5
+ }
@@ -0,0 +1,20 @@
1
+ import type { PickstationOrder } from "../../lib/pickstationTypes";
2
+ /** Returns a fully self-contained printable HTML document for one packing slip per cart. */
3
+ export declare function renderPakbonHtml(order: {
4
+ id: string;
5
+ orderNumber: string;
6
+ customerName: string;
7
+ country: string | null;
8
+ deliveryDate: string | null;
9
+ pickDate: string;
10
+ cartIndex: number;
11
+ cartTotal: number;
12
+ cratesOnCart: number | null;
13
+ lines: {
14
+ description: string;
15
+ crates: number | null;
16
+ unit: string | null;
17
+ }[];
18
+ }): string;
19
+ export type { PickstationOrder };
20
+ //# sourceMappingURL=PakbonTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PakbonTemplate.d.ts","sourceRoot":"","sources":["../../../src/components/pickstation/PakbonTemplate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;CAC9E,GAAG,MAAM,CAqET;AAWD,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,76 @@
1
+ /** Returns a fully self-contained printable HTML document for one packing slip per cart. */
2
+ export function renderPakbonHtml(order) {
3
+ const fmt = (d) => {
4
+ if (!d)
5
+ return "—";
6
+ const dt = new Date(d);
7
+ if (Number.isNaN(dt.getTime()))
8
+ return d;
9
+ return dt.toLocaleDateString("nl-NL", { day: "2-digit", month: "2-digit", year: "numeric" });
10
+ };
11
+ const rows = order.lines.length
12
+ ? order.lines
13
+ .map((l) => `
14
+ <tr>
15
+ <td>${escapeHtml(l.description)}</td>
16
+ <td style="text-align:right">${l.crates ?? ""}</td>
17
+ <td>${escapeHtml(l.unit ?? "")}</td>
18
+ </tr>`)
19
+ .join("")
20
+ : `<tr><td colspan="3" style="text-align:center;color:#666">Geen regels</td></tr>`;
21
+ const cartLabel = `Kar ${order.cartIndex} / ${order.cartTotal}`;
22
+ const fustenLabel = order.cratesOnCart != null ? `${order.cratesOnCart}` : "—";
23
+ return `<!doctype html>
24
+ <html lang="nl">
25
+ <head>
26
+ <meta charset="utf-8">
27
+ <title>Pakbon ${escapeHtml(order.orderNumber)} · ${escapeHtml(cartLabel)}</title>
28
+ <style>
29
+ @page { size: A4; margin: 16mm; }
30
+ body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; color:#111; }
31
+ h1 { margin: 0 0 4px; font-size: 26px; }
32
+ .sub { color:#444; margin-bottom: 18px; }
33
+ .cart-badge { display:inline-block; margin-left:8px; padding:2px 10px; border-radius:999px; background:#111; color:#fff; font-size:14px; vertical-align:middle; }
34
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; margin: 18px 0 22px; }
35
+ .grid div { font-size: 14px; }
36
+ .label { color:#666; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
37
+ .value { font-size: 16px; font-weight: 600; }
38
+ table { width:100%; border-collapse: collapse; font-size: 14px; }
39
+ th, td { padding: 8px 6px; border-bottom: 1px solid #ddd; vertical-align: top; }
40
+ th { text-align: left; background:#f5f5f5; }
41
+ .barcode-fallback { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 18px; letter-spacing: 4px; padding: 8px 12px; border: 2px solid #111; display: inline-block; margin-top: 18px; }
42
+ .footer { margin-top: 28px; font-size: 11px; color:#666; }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <h1>Pakbon <span class="cart-badge">${escapeHtml(cartLabel)}</span></h1>
47
+ <div class="sub">Order ${escapeHtml(order.orderNumber)}</div>
48
+
49
+ <div class="grid">
50
+ <div><div class="label">Klant</div><div class="value">${escapeHtml(order.customerName)}</div></div>
51
+ <div><div class="label">Land</div><div class="value">${escapeHtml(order.country ?? "—")}</div></div>
52
+ <div><div class="label">Leverdatum</div><div class="value">${fmt(order.deliveryDate)}</div></div>
53
+ <div><div class="label">Pickdatum</div><div class="value">${fmt(order.pickDate)}</div></div>
54
+ <div><div class="label">Kar</div><div class="value">${escapeHtml(cartLabel)}</div></div>
55
+ <div><div class="label">Fusten op deze kar</div><div class="value">${escapeHtml(fustenLabel)}</div></div>
56
+ </div>
57
+
58
+ <table>
59
+ <thead><tr><th>Omschrijving</th><th style="text-align:right;width:90px">Fusten</th><th style="width:90px">Eenheid</th></tr></thead>
60
+ <tbody>${rows}</tbody>
61
+ </table>
62
+
63
+ <div class="barcode-fallback">*${escapeHtml(order.orderNumber)}-${order.cartIndex}*</div>
64
+
65
+ <div class="footer">Geprint via Pickstation · ${new Date().toLocaleString("nl-NL")}</div>
66
+ </body>
67
+ </html>`;
68
+ }
69
+ function escapeHtml(s) {
70
+ return s
71
+ .replace(/&/g, "&amp;")
72
+ .replace(/</g, "&lt;")
73
+ .replace(/>/g, "&gt;")
74
+ .replace(/"/g, "&quot;")
75
+ .replace(/'/g, "&#39;");
76
+ }
@@ -0,0 +1,3 @@
1
+ export declare function printHtml(html: string): Promise<void>;
2
+ export declare function printPdfBase64(base64: string, mime?: string): Promise<void>;
3
+ //# sourceMappingURL=printDocuments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printDocuments.d.ts","sourceRoot":"","sources":["../../../src/components/pickstation/printDocuments.ts"],"names":[],"mappings":"AAqCA,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3D;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,SAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5F"}
@@ -0,0 +1,53 @@
1
+ // Browser-only helpers: print a HTML string and optionally a PDF (base64) via hidden iframes.
2
+ function makeFrame() {
3
+ const f = document.createElement("iframe");
4
+ f.style.position = "fixed";
5
+ f.style.right = "-9999px";
6
+ f.style.bottom = "-9999px";
7
+ f.style.width = "0";
8
+ f.style.height = "0";
9
+ f.style.border = "0";
10
+ document.body.appendChild(f);
11
+ return f;
12
+ }
13
+ function waitForFrame(f) {
14
+ return new Promise((resolve) => {
15
+ const handler = () => {
16
+ f.removeEventListener("load", handler);
17
+ resolve();
18
+ };
19
+ f.addEventListener("load", handler);
20
+ });
21
+ }
22
+ function callPrint(win) {
23
+ return new Promise((resolve) => {
24
+ if (!win)
25
+ return resolve();
26
+ try {
27
+ win.focus();
28
+ win.print();
29
+ }
30
+ catch {
31
+ // ignored
32
+ }
33
+ setTimeout(resolve, 800);
34
+ });
35
+ }
36
+ export async function printHtml(html) {
37
+ const f = makeFrame();
38
+ const load = waitForFrame(f);
39
+ f.srcdoc = html;
40
+ await load;
41
+ await callPrint(f.contentWindow);
42
+ setTimeout(() => f.remove(), 2000);
43
+ }
44
+ export async function printPdfBase64(base64, mime = "application/pdf") {
45
+ const f = makeFrame();
46
+ const load = waitForFrame(f);
47
+ f.src = `data:${mime};base64,${base64}`;
48
+ await load;
49
+ // Give the browser a moment to render the PDF.
50
+ await new Promise((r) => setTimeout(r, 600));
51
+ await callPrint(f.contentWindow);
52
+ setTimeout(() => f.remove(), 2000);
53
+ }
@@ -0,0 +1,2 @@
1
+ export declare function AfhandelingSettingsCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=AfhandelingSettingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AfhandelingSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AfhandelingSettingsCard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,uBAAuB,gCAoMtC"}