@flowselections/actie-mail-kwekers-erp-mathot 1.0.7 → 1.0.9

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 (29) hide show
  1. package/dist-lib/components/AanbiedingsmailtjePage.d.ts.map +1 -1
  2. package/dist-lib/components/AanbiedingsmailtjePage.js +135 -24
  3. package/dist-lib/components/ActiePage.d.ts.map +1 -1
  4. package/dist-lib/components/ActiePage.js +37 -8
  5. package/dist-lib/components/CampagneDetail.d.ts.map +1 -1
  6. package/dist-lib/components/CampagneDetail.js +10 -1
  7. package/dist-lib/index.d.ts +1 -0
  8. package/dist-lib/index.d.ts.map +1 -1
  9. package/dist-lib/index.js +1 -2
  10. package/dist-lib/integrations/supabase/auth-middleware.d.ts +9550 -1645
  11. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  12. package/dist-lib/integrations/supabase/client.d.ts +9550 -1645
  13. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/client.server.d.ts +9550 -1645
  15. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/types.d.ts +9771 -1655
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/types.js +20 -1
  19. package/dist-lib/lib/campaigns.functions.d.ts +48026 -8501
  20. package/dist-lib/lib/campaigns.functions.d.ts.map +1 -1
  21. package/dist-lib/lib/campaigns.functions.js +28 -27
  22. package/dist-lib/lib/public-offer.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/public-offer.functions.js +37 -21
  24. package/dist-lib/lib/stats.functions.d.ts +19140 -3330
  25. package/dist-lib/lib/stats.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/voorraad-service.d.ts +23 -0
  27. package/dist-lib/lib/voorraad-service.d.ts.map +1 -0
  28. package/dist-lib/lib/voorraad-service.js +31 -0
  29. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"AanbiedingsmailtjePage.d.ts","sourceRoot":"","sources":["../../src/components/AanbiedingsmailtjePage.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,sBAAsB,gCA+drC"}
1
+ {"version":3,"file":"AanbiedingsmailtjePage.d.ts","sourceRoot":"","sources":["../../src/components/AanbiedingsmailtjePage.tsx"],"names":[],"mappings":"AA0CA,wBAAgB,sBAAsB,gCAylBrC"}
@@ -5,6 +5,7 @@ import { useServerFn } from "@tanstack/react-start";
5
5
  import { useNavigate } from "@tanstack/react-router";
6
6
  import { Mail, Send, Search, X, Package, Users, Percent, Tag, List, BarChart3 } from "lucide-react";
7
7
  import { createCampaign, sendCampaign } from "../lib/campaigns.functions";
8
+ import { createVoorraadStockMap, voorraadAvailableStock } from "../lib/voorraad-service";
8
9
  export function AanbiedingsmailtjePage() {
9
10
  const { user } = useAuth();
10
11
  const navigate = useNavigate();
@@ -49,18 +50,130 @@ export function AanbiedingsmailtjePage() {
49
50
  (async () => {
50
51
  setLoading(true);
51
52
  const sb = supabase;
52
- const [{ data: ps }, { data: cs }, { data: settings }] = await Promise.all([
53
- sb.from("products").select("id, product, sale_price, quantity, image_url, barcode").order("product").limit(500),
53
+ // 1) Bepaal actieve Floriday-koppeling
54
+ const { data: activeConn } = await sb.rpc("get_active_floriday_connection_public");
55
+ const activeId = Array.isArray(activeConn) && activeConn.length > 0
56
+ ? (activeConn[0]?.id ?? activeConn[0]?.connection_id ?? null)
57
+ : (activeConn?.id ?? activeConn?.connection_id ?? null);
58
+ // 2) Producten uit Floriday (gepubliceerd + concepten), klanten + instellingen
59
+ const cachePromise = activeId
60
+ ? sb.from("floriday_trade_items_cache")
61
+ .select("floriday_id, data")
62
+ .eq("connection_id", activeId)
63
+ .eq("is_deleted", false)
64
+ .limit(1000)
65
+ : Promise.resolve({ data: [] });
66
+ const draftPromise = activeId
67
+ ? sb.from("trade_item_drafts")
68
+ .select("id, data, updated_at")
69
+ .eq("connection_id", activeId)
70
+ .order("updated_at", { ascending: false })
71
+ .limit(1000)
72
+ : Promise.resolve({ data: [] });
73
+ const stockPromise = activeId
74
+ ? sb.from("trade_item_stock")
75
+ .select("trade_item_key, physical_stock, reserved_stock, updated_at")
76
+ .eq("connection_id", activeId)
77
+ .limit(5000)
78
+ : Promise.resolve({ data: [] });
79
+ const [{ data: cacheRows }, { data: draftRows }, { data: stockRows }, { data: cs }, { data: extCs }, { data: settings }] = await Promise.all([
80
+ cachePromise,
81
+ draftPromise,
82
+ stockPromise,
54
83
  sb.from("customers").select("id, company_name, email, customer_class, country").not("email", "is", null).order("company_name").limit(1000),
55
- sb.from("offer_settings").select("from_name, from_email, default_subject, default_intro, default_sticker_cost, show_customer_classes").maybeSingle(),
84
+ sb.from("external_customers_grouped").select("group_key, display_name, email, country, is_active").not("email", "is", null).eq("is_active", true).limit(2000),
85
+ sb.from("offer_settings").select("default_subject, default_intro, default_sticker_cost, show_customer_classes").maybeSingle(),
56
86
  ]);
57
- setProducts((ps ?? []));
58
- setCustomers((cs ?? []));
87
+ const stockMap = createVoorraadStockMap((Array.isArray(stockRows) ? stockRows : []));
88
+ const stockFor = (key) => {
89
+ const s = stockMap.get(key);
90
+ return voorraadAvailableStock(s);
91
+ };
92
+ const pickName = (n) => {
93
+ if (!n)
94
+ return "";
95
+ if (typeof n === "string")
96
+ return n;
97
+ return n.nl || n.en || n.de || Object.values(n)[0] || "";
98
+ };
99
+ const pickImage = (d) => {
100
+ const photos = Array.isArray(d?.photos) ? d.photos : [];
101
+ const primary = photos.find((p) => p?.primary || p?.isPrimary) ?? photos[0];
102
+ if (primary?.url)
103
+ return primary.url;
104
+ if (Array.isArray(d?.images) && d.images[0])
105
+ return d.images[0].url ?? d.images[0].previewUrl ?? null;
106
+ if (Array.isArray(d?.imageUrls) && d.imageUrls[0])
107
+ return d.imageUrls[0];
108
+ return null;
109
+ };
110
+ const pickPacking = (d) => {
111
+ const cfgs = Array.isArray(d?.packingConfigurations) ? d.packingConfigurations : [];
112
+ const primary = cfgs.find((c) => c?.primary) ?? cfgs[0];
113
+ if (!primary)
114
+ return { plants_per_crate: null, belading: null };
115
+ const ppc = Number(primary.piecesPerPackage);
116
+ const perLayer = Number(primary.packagesPerLayer);
117
+ const layers = Number(primary.layersPerLoadCarrier);
118
+ const parts = [ppc, perLayer, layers].filter((n) => Number.isFinite(n) && n > 0);
119
+ return {
120
+ plants_per_crate: Number.isFinite(ppc) && ppc > 0 ? ppc : null,
121
+ belading: parts.length === 3 ? `${ppc}x${perLayer}x${layers}` : null,
122
+ };
123
+ };
124
+ const published = (cacheRows ?? []).map((r) => {
125
+ const d = r.data ?? {};
126
+ const packing = pickPacking(d);
127
+ return {
128
+ id: r.floriday_id,
129
+ product: pickName(d.tradeItemName) || pickName(d.name) || pickName(d.vbnName) || (d.botanicalNames?.[0] ?? "(naamloos)"),
130
+ sale_price: null,
131
+ quantity: stockFor(r.floriday_id),
132
+ image_url: pickImage(d),
133
+ barcode: d.supplierArticleCode ?? d.articleCode ?? d.gtin ?? d.articleGtin ?? d.tradeItemReference ?? "",
134
+ plants_per_crate: packing.plants_per_crate,
135
+ belading: packing.belading,
136
+ is_draft: false,
137
+ };
138
+ });
139
+ const drafts = (draftRows ?? []).map((r) => {
140
+ const d = r.data ?? {};
141
+ const firstImage = Array.isArray(d.images) && d.images[0] ? d.images[0] : null;
142
+ const packing = pickPacking(d);
143
+ return {
144
+ id: `local:${r.id}`,
145
+ product: d.name || d.vbnName || "(naamloos)",
146
+ sale_price: null,
147
+ quantity: stockFor(`draft:${r.id}`),
148
+ image_url: firstImage?.url ?? firstImage?.previewUrl ?? null,
149
+ barcode: d.articleCode ?? d.gtin ?? "",
150
+ plants_per_crate: packing.plants_per_crate,
151
+ belading: packing.belading,
152
+ is_draft: true,
153
+ };
154
+ });
155
+ const merged = [
156
+ ...drafts,
157
+ ...published.sort((a, b) => a.product.localeCompare(b.product)),
158
+ ];
159
+ setProducts(merged);
160
+ const localList = (cs ?? []).map((c) => ({
161
+ id: c.id, company_name: c.company_name, email: c.email, customer_class: c.customer_class, country: c.country, source: "local",
162
+ }));
163
+ const seenEmails = new Set(localList.map((c) => (c.email ?? "").toLowerCase()).filter(Boolean));
164
+ const extList = (extCs ?? [])
165
+ .filter((r) => r.email && !seenEmails.has(String(r.email).toLowerCase()))
166
+ .map((r) => ({
167
+ id: `ext:${r.group_key}`,
168
+ company_name: r.display_name ?? "(naamloos)",
169
+ email: r.email,
170
+ customer_class: null,
171
+ country: r.country ?? null,
172
+ source: "ext",
173
+ }));
174
+ const mergedCustomers = [...localList, ...extList].sort((a, b) => (a.company_name ?? "").localeCompare(b.company_name ?? ""));
175
+ setCustomers(mergedCustomers);
59
176
  if (settings) {
60
- if (settings.from_name)
61
- setFromName(settings.from_name);
62
- if (settings.from_email)
63
- setFromEmail(settings.from_email);
64
177
  if (settings.default_subject)
65
178
  setSubject(settings.default_subject);
66
179
  if (settings.default_intro)
@@ -71,28 +184,30 @@ export function AanbiedingsmailtjePage() {
71
184
  if (typeof settings.show_customer_classes === "boolean")
72
185
  setShowCustomerClasses(settings.show_customer_classes);
73
186
  }
187
+ if (!activeId) {
188
+ toast.error("Geen actieve Floriday-koppeling — stel deze in via de Voorraad Module");
189
+ }
74
190
  setLoading(false);
75
191
  })();
76
192
  }, []);
77
193
  const addProduct = (p) => {
78
194
  if (selProducts.find((sp) => sp.product_id === p.id))
79
195
  return;
80
- const price = Number(p.sale_price ?? 0);
81
196
  setSelProducts((prev) => [...prev, {
82
197
  product_id: p.id,
83
198
  product_name: p.product,
84
199
  description: "",
85
200
  image_url: p.image_url,
86
- normal_price: price,
87
- discount_price: price,
201
+ normal_price: 0,
202
+ discount_price: 0,
88
203
  available_stock: p.quantity ?? 0,
89
- plants_per_crate: 10,
90
- belading: "",
204
+ plants_per_crate: p.plants_per_crate ?? 10,
205
+ belading: p.belading ?? "",
91
206
  barcode: p.barcode ?? "",
92
207
  crate_price: 0,
93
208
  sticker_cost: null,
94
209
  sticker_available: true,
95
- tiers: [{ min_crates: 1, price_per_crate: price }],
210
+ tiers: [{ min_crates: 1, price_per_crate: 0 }],
96
211
  }]);
97
212
  setProductSearch("");
98
213
  };
@@ -162,16 +277,12 @@ export function AanbiedingsmailtjePage() {
162
277
  toast.error("Selecteer minimaal één klant");
163
278
  return;
164
279
  }
165
- if (!fromEmail) {
166
- toast.error("Vul een afzender e-mailadres in");
167
- return;
168
- }
169
280
  setBusy(true);
170
281
  try {
171
282
  const recipients = customers
172
283
  .filter((c) => selCustomers.has(c.id) && c.email)
173
284
  .map((c) => ({
174
- customer_id: c.id,
285
+ customer_id: c.id.startsWith("ext:") ? null : c.id,
175
286
  recipient_name: c.company_name,
176
287
  recipient_email: c.email,
177
288
  }));
@@ -197,10 +308,10 @@ export function AanbiedingsmailtjePage() {
197
308
  setBusy(false);
198
309
  }
199
310
  };
200
- const filteredProducts = products.filter((p) => Number(p.quantity ?? 0) > 0 && p.product.toLowerCase().includes(productSearch.toLowerCase()));
311
+ const filteredProducts = products.filter((p) => p.product.toLowerCase().includes(productSearch.toLowerCase()));
201
312
  if (loading)
202
313
  return _jsxs("div", { className: "p-6 space-y-4", children: [_jsx(Skeleton, { className: "h-32 w-full" }), _jsx(Skeleton, { className: "h-64 w-full" })] });
203
- return (_jsxs("main", { className: "p-6 pb-28 space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tag, { className: "h-6 w-6 text-primary" }), _jsx("h1", { className: "text-2xl font-bold", children: "Acties & Promoties" })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Maak promoties en verstuur naar klanten" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/aanbiedingsmailtje/overzicht" }), children: [_jsx(List, { className: "h-4 w-4 mr-2" }), "Overzicht"] }), _jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/aanbiedingsmailtje/rapportage" }), children: [_jsx(BarChart3, { className: "h-4 w-4 mr-2" }), "Rapportage"] })] })] }), _jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Producten Selecteren" })] }), _jsx(CardDescription, { children: "Kies producten en stel actieprijzen in" })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek product...", value: productSearch, onChange: (e) => setProductSearch(e.target.value) })] }), productSearch && (_jsx("div", { className: "max-h-40 overflow-y-auto border rounded-md", children: filteredProducts.slice(0, 20).map((p) => (_jsxs("button", { onClick: () => addProduct(p), className: "w-full text-left px-3 py-2 hover:bg-muted text-sm", children: [p.product, " ", _jsxs("span", { className: "text-muted-foreground", children: ["\u20AC ", Number(p.sale_price ?? 0).toFixed(2), " \u00B7 voorraad ", p.quantity ?? 0] })] }, p.id))) })), _jsxs("div", { className: "text-sm font-medium", children: ["Geselecteerde producten (", selProducts.length, ")"] }), selProducts.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground py-6", children: "Geen producten geselecteerd" })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Snelle korting:" }), [10, 15, 20, 25, 30].map((p) => (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => applyDiscount(p), children: [_jsx(Percent, { className: "h-3 w-3 mr-1" }), p, "%"] }, p)))] }), _jsxs("div", { className: "flex flex-wrap gap-2 items-center rounded-md border bg-muted/30 p-2", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Stickeropdruk \u2014 \u00E9\u00E9n prijs voor \u00E1lle producten: \u20AC" }), _jsx(Input, { type: "number", step: "0.01", placeholder: "0,00", className: "w-28 h-8", value: bulkPrice, onChange: (e) => setBulkPrice(e.target.value) }), _jsxs("div", { className: "flex items-center gap-2 ml-2", children: [_jsx(Switch, { id: "bulk-sticker-toggle", checked: bulkStickerOn, onCheckedChange: (checked) => {
314
+ return (_jsxs("main", { className: "p-6 pb-28 space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tag, { className: "h-6 w-6 text-primary" }), _jsx("h1", { className: "text-2xl font-bold", children: "Acties & Promoties" })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Maak promoties en verstuur naar klanten" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/aanbiedingsmailtje/overzicht" }), children: [_jsx(List, { className: "h-4 w-4 mr-2" }), "Overzicht"] }), _jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/aanbiedingsmailtje/rapportage" }), children: [_jsx(BarChart3, { className: "h-4 w-4 mr-2" }), "Rapportage"] })] })] }), _jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Producten Selecteren" })] }), _jsx(CardDescription, { children: "Kies producten en stel actieprijzen in" })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek product...", value: productSearch, onChange: (e) => setProductSearch(e.target.value) })] }), productSearch && (_jsx("div", { className: "max-h-40 overflow-y-auto border rounded-md", children: filteredProducts.slice(0, 20).map((p) => (_jsxs("button", { onClick: () => addProduct(p), className: "w-full text-left px-3 py-2 hover:bg-muted text-sm", children: [p.product, " ", _jsx("span", { className: "text-muted-foreground", children: p.is_draft ? "· concept" : `· voorraad ${p.quantity ?? 0}` })] }, p.id))) })), _jsxs("div", { className: "text-sm font-medium", children: ["Geselecteerde producten (", selProducts.length, ")"] }), selProducts.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground py-6", children: "Geen producten geselecteerd" })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Snelle korting:" }), [10, 15, 20, 25, 30].map((p) => (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => applyDiscount(p), children: [_jsx(Percent, { className: "h-3 w-3 mr-1" }), p, "%"] }, p)))] }), _jsxs("div", { className: "flex flex-wrap gap-2 items-center rounded-md border bg-muted/30 p-2", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Stickeropdruk \u2014 \u00E9\u00E9n prijs voor \u00E1lle producten: \u20AC" }), _jsx(Input, { type: "number", step: "0.01", placeholder: "0,00", className: "w-28 h-8", value: bulkPrice, onChange: (e) => setBulkPrice(e.target.value) }), _jsxs("div", { className: "flex items-center gap-2 ml-2", children: [_jsx(Switch, { id: "bulk-sticker-toggle", checked: bulkStickerOn, onCheckedChange: (checked) => {
204
315
  setBulkStickerOn(checked);
205
316
  if (!checked) {
206
317
  setSelProducts((prev) => prev.map((p) => ({ ...p, sticker_cost: null })));
@@ -228,12 +339,12 @@ export function AanbiedingsmailtjePage() {
228
339
  return (_jsxs("div", { className: "border rounded-md p-3 space-y-3 bg-muted/30", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium text-sm", children: p.product_name }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Originele prijs: \u20AC ", p.normal_price.toFixed(2)] })] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => removeProduct(p.product_id), children: _jsx(X, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-24 shrink-0", children: "Planten/fust:" }), _jsx(Input, { type: "number", value: p.plants_per_crate, onChange: (e) => updateProd(p.product_id, { plants_per_crate: Number(e.target.value) }) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-20 shrink-0", children: "Belading:" }), _jsx(Input, { placeholder: "AxBxC", value: p.belading, onChange: (e) => updateProd(p.product_id, { belading: e.target.value }) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-24 shrink-0", children: "Barcode:" }), _jsx(Input, { value: p.barcode, onChange: (e) => updateProd(p.product_id, { barcode: e.target.value }) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-20 shrink-0", children: "Fustprijs: \u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: p.crate_price, onChange: (e) => updateProd(p.product_id, { crate_price: Number(e.target.value) }) })] })] }), _jsxs("div", { className: "flex items-center gap-2 border-t pt-2", children: [_jsx(Label, { className: "text-xs", children: "Stickeropdruk beschikbaar:" }), _jsx(Button, { type: "button", size: "sm", variant: p.sticker_available ? "default" : "outline", onClick: () => updateProd(p.product_id, { sticker_available: true }), children: "Ja" }), _jsx(Button, { type: "button", size: "sm", variant: !p.sticker_available ? "default" : "outline", onClick: () => updateProd(p.product_id, { sticker_available: false }), children: "Nee" })] }), _jsxs("div", { className: "space-y-2 border-t pt-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs font-medium", children: "Prijs aftuin ex. Fust vanaf .." }), _jsx(Button, { variant: "outline", size: "sm", onClick: addTier, children: "+ Staffel" })] }), p.tiers.map((t, idx) => {
229
340
  const pct = p.normal_price > 0 ? Math.round((1 - t.price_per_crate / p.normal_price) * 100) : 0;
230
341
  return (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Vanaf" }), _jsx(Input, { type: "number", className: "w-20", value: t.min_crates, onChange: (e) => updateTier(idx, { min_crates: Number(e.target.value) }) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "fust(en): \u20AC" }), _jsx(Input, { type: "number", step: "0.01", className: "w-24", value: t.price_per_crate, onChange: (e) => updateTier(idx, { price_per_crate: Number(e.target.value) }) }), _jsxs("span", { className: `text-xs font-medium ${pct > 0 ? "text-destructive" : "text-muted-foreground"}`, children: ["(", pct, "%)"] }), p.tiers.length > 1 && (_jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => removeTier(idx), children: _jsx(X, { className: "h-3 w-3" }) }))] }, idx));
231
- })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2 border-t pt-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Actieprijs \u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: p.discount_price, onChange: (e) => updateProd(p.product_id, { discount_price: Number(e.target.value) }) })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Voorraad" }), _jsx(Input, { type: "number", value: p.available_stock, onChange: (e) => updateProd(p.product_id, { available_stock: Number(e.target.value) }) })] })] }), _jsx(Textarea, { placeholder: "Omschrijving (optioneel)", rows: 2, value: p.description, onChange: (e) => updateProd(p.product_id, { description: e.target.value }) })] }, p.product_id));
342
+ })] }), _jsx(Textarea, { placeholder: "Omschrijving (optioneel)", rows: 2, value: p.description, onChange: (e) => updateProd(p.product_id, { description: e.target.value }) })] }, p.product_id));
232
343
  }) })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Klanten Selecteren" })] }), _jsx(CardDescription, { children: "Kies klanten om de actie naar te versturen" })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek klant...", value: customerSearch, onChange: (e) => setCustomerSearch(e.target.value) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: allVisibleSelected, onChange: toggleAllVisible }), "Selecteer alle klanten met email"] }), _jsxs(Badge, { variant: "secondary", children: [selCustomers.size, " geselecteerd"] })] }), showCustomerClasses && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Selecteer op klantgroep" }), _jsx("div", { className: "flex flex-wrap gap-2", children: classGroups.map((g) => {
233
344
  const allSel = g.customers.length > 0 && g.customers.every((c) => selCustomers.has(c.id));
234
345
  return (_jsxs(Button, { type: "button", variant: allSel ? "default" : "outline", size: "sm", onClick: () => toggleClass(g.letter), className: "gap-2", children: ["Klasse ", g.letter, _jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-xs", children: g.customers.length })] }, g.letter));
235
346
  }) })] })), _jsx(Separator, {}), _jsx("div", { className: "max-h-[500px] overflow-y-auto space-y-2 pr-1", children: filteredCustomers.slice(0, 500).map((c) => {
236
347
  const checked = selCustomers.has(c.id);
237
- return (_jsxs("button", { onClick: () => toggleCustomer(c.id), className: `w-full text-left p-3 rounded-md border hover:bg-muted text-sm flex items-center gap-3 ${checked ? "bg-primary/5 border-primary/40" : ""}`, children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: c.company_name }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c.email })] }), c.customer_class && _jsx(Badge, { variant: "secondary", children: c.customer_class })] }, c.id));
348
+ return (_jsxs("button", { onClick: () => toggleCustomer(c.id), className: `w-full text-left p-3 rounded-md border hover:bg-muted text-sm flex items-center gap-3 ${checked ? "bg-primary/5 border-primary/40" : ""}`, children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [_jsx("span", { className: "truncate", children: c.company_name }), c.source === "ext" && _jsx(Badge, { variant: "outline", className: "text-[10px] px-1.5 py-0", children: "Floriday" })] }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c.email })] }), c.customer_class && _jsx(Badge, { variant: "secondary", children: c.customer_class })] }, c.id));
238
349
  }) })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Email Inhoud" })] }), _jsx(CardDescription, { children: "Pas het onderwerp en de boodschap aan" })] }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Onderwerp" }), _jsx(Input, { value: subject, onChange: (e) => setSubject(e.target.value) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bericht" }), _jsx(Textarea, { value: intro, onChange: (e) => setIntro(e.target.value), rows: 3 })] }), _jsxs("div", { className: "space-y-2 md:col-span-2 max-w-xs", children: [_jsx(Label, { children: "Kosten opdrukken eigen sticker (\u20AC per stuk)" }), _jsx(Input, { type: "number", step: "0.01", placeholder: "0,00", value: bulkPrice, onChange: (e) => setBulkPrice(e.target.value) })] }), _jsxs("div", { className: "space-y-2 md:col-span-2 hidden", children: [_jsx(Label, { children: "Naam (intern)" }), _jsx(Input, { value: name, onChange: (e) => setName(e.target.value) }), _jsx(Label, { children: "Afzendernaam" }), _jsx(Input, { value: fromName, onChange: (e) => setFromName(e.target.value) }), _jsx(Label, { children: "Afzender e-mail" }), _jsx(Input, { type: "email", value: fromEmail, onChange: (e) => setFromEmail(e.target.value) })] })] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 z-30 border-t bg-background/95 backdrop-blur px-6 py-3", children: _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold", children: "Samenvatting" }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [selProducts.length, " product(en) \u2022 ", selCustomers.size, " klant(en)"] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => handleSave(false), children: "Opslaan als concept" }), _jsxs(Button, { disabled: busy || selProducts.length === 0 || selCustomers.size === 0, onClick: () => handleSave(true), children: [_jsx(Send, { className: "h-4 w-4 mr-2" }), busy ? "Bezig..." : "Actie Versturen"] })] })] }) })] }));
239
350
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ActiePage.d.ts","sourceRoot":"","sources":["../../src/components/ActiePage.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,SAAS,gCAqbxB"}
1
+ {"version":3,"file":"ActiePage.d.ts","sourceRoot":"","sources":["../../src/components/ActiePage.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,SAAS,gCAoexB"}
@@ -83,19 +83,46 @@ export function ActiePage() {
83
83
  }
84
84
  const safeProducts = Array.isArray(data?.products) ? data.products : [];
85
85
  const lineFor = (p) => {
86
- const stockPlants = Math.max(0, Number(p.live_stock ?? p.available_stock ?? 0));
86
+ const stockPlants = Math.max(0, Number(p.live_stock ?? 0));
87
87
  const ppc = Math.max(1, Math.floor(Number(p.plants_per_crate ?? 1) || 1));
88
88
  const maxCrates = Math.floor(stockPlants / ppc);
89
89
  const soldOut = maxCrates <= 0;
90
90
  const crates = Math.max(0, Math.floor(qty[p.id] ?? 0));
91
91
  const plants = crates * ppc;
92
- const unit = Number(p.discount_price ?? 0);
92
+ // Prijsprioriteit: (1) tier-prijs uit actie-e-mail, (2) discount_price uit e-mail,
93
+ // (3) standaardprijs uit artikelbeheer. De e-mailprijs is ALTIJD leidend.
94
+ const tiers = Array.isArray(p.tiers)
95
+ ? [...p.tiers].map((t) => ({ min_crates: Number(t.min_crates), price_per_crate: Number(t.price_per_crate) }))
96
+ .sort((a, b) => a.min_crates - b.min_crates)
97
+ : [];
98
+ let unit = 0;
99
+ let cratePrice = 0;
100
+ let priceSource = "normal";
101
+ if (tiers.length > 0) {
102
+ const applicable = [...tiers].reverse().find((t) => crates >= t.min_crates) ?? tiers[0];
103
+ cratePrice = applicable.price_per_crate;
104
+ unit = cratePrice / ppc;
105
+ priceSource = "tier";
106
+ }
107
+ else if (Number(p.discount_price) > 0) {
108
+ unit = Number(p.discount_price);
109
+ cratePrice = unit * ppc;
110
+ priceSource = "discount";
111
+ }
112
+ else {
113
+ unit = Number(p.normal_price ?? 0);
114
+ cratePrice = unit * ppc;
115
+ priceSource = "normal";
116
+ }
117
+ const priceSourceLabel = priceSource === "tier" ? "Prijs uit actie-e-mail"
118
+ : priceSource === "discount" ? "Actieprijs uit e-mail"
119
+ : "Standaardprijs artikelbeheer";
93
120
  const stickerCost = Number(p.sticker_cost ?? 0);
94
121
  const stickerAvail = p.sticker_available !== false;
95
122
  const stickerOn = stickerAvail && !!sticker[p.id];
96
123
  const productSubtotal = plants * unit;
97
124
  const stickerSubtotal = stickerOn ? plants * stickerCost : 0;
98
- return { q: crates, plants, ppc, unit, stickerCost, stickerAvail, stickerOn, productSubtotal, stickerSubtotal, total: productSubtotal + stickerSubtotal, stock: stockPlants, maxCrates, soldOut };
125
+ return { q: crates, plants, ppc, unit, cratePrice, priceSource, priceSourceLabel, tiers, stickerCost, stickerAvail, stickerOn, productSubtotal, stickerSubtotal, total: productSubtotal + stickerSubtotal, stock: stockPlants, maxCrates, soldOut };
99
126
  };
100
127
  const totalAmount = safeProducts.reduce((s, p) => s + lineFor(p).total, 0);
101
128
  const hasItems = safeProducts.some((p) => (qty[p.id] ?? 0) > 0);
@@ -108,7 +135,7 @@ export function ActiePage() {
108
135
  return {
109
136
  product_id: p.id,
110
137
  product_name: p.product_name,
111
- quantity: l.q,
138
+ quantity: l.plants,
112
139
  unit_price: l.unit,
113
140
  sticker: l.stickerOn,
114
141
  sticker_unit_price: l.stickerOn ? l.stickerCost : 0,
@@ -157,12 +184,14 @@ export function ActiePage() {
157
184
  safeProducts.forEach((p) => { next[p.id] = false; });
158
185
  setSticker(next);
159
186
  }, className: "text-xs font-semibold bg-white text-blue-700 border border-blue-300 rounded px-3 py-1.5 hover:bg-blue-50", children: "Alles uit" })] })] })), _jsx("div", { className: "space-y-4", children: safeProducts.map((p) => {
160
- const normal = Number(p.normal_price ?? 0);
161
- const price = Number(p.discount_price ?? 0);
162
- const showStrike = normal > price;
163
187
  const l = lineFor(p);
188
+ const normal = Number(p.normal_price ?? 0);
189
+ const price = l.cratePrice > 0 ? l.cratePrice : Number(p.discount_price ?? 0);
190
+ const showStrike = normal > 0 && normal > price && price > 0;
164
191
  const currentQty = l.q;
165
- return (_jsxs("div", { className: `bg-white rounded-xl border shadow-sm overflow-hidden ${l.soldOut ? "border-slate-200 opacity-75" : "border-slate-200"}`, children: [_jsxs("div", { className: "p-4 flex gap-4", children: [_jsxs("div", { className: "shrink-0 relative", children: [p.image_url ? (_jsx("img", { src: p.image_url, alt: p.product_name, className: `w-24 h-24 rounded-lg object-cover border border-slate-200 bg-slate-50 ${l.soldOut ? "grayscale" : ""}` })) : (_jsx("div", { className: `w-24 h-24 rounded-lg border border-slate-200 bg-green-50 grid place-items-center overflow-hidden ${l.soldOut ? "grayscale" : ""}`, children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 96 96", className: "w-full h-full", children: [_jsx("path", { d: "M48 60 Q46 75 44 86", stroke: "#16a34a", strokeWidth: "2.5", fill: "none", strokeLinecap: "round" }), _jsx("path", { d: "M48 72 Q40 70 36 64 Q42 66 48 72", fill: "#22c55e" }), _jsx("path", { d: "M48 78 Q56 76 60 70 Q54 72 48 78", fill: "#22c55e" }), _jsxs("g", { transform: "translate(48 40)", children: [_jsx("ellipse", { cx: "0", cy: "-14", rx: "9", ry: "14", fill: "#f9a8d4" }), _jsx("ellipse", { cx: "13.3", cy: "-4.3", rx: "9", ry: "14", fill: "#f472b6", transform: "rotate(72)" }), _jsx("ellipse", { cx: "8.2", cy: "11.3", rx: "9", ry: "14", fill: "#ec4899", transform: "rotate(144)" }), _jsx("ellipse", { cx: "-8.2", cy: "11.3", rx: "9", ry: "14", fill: "#f472b6", transform: "rotate(216)" }), _jsx("ellipse", { cx: "-13.3", cy: "-4.3", rx: "9", ry: "14", fill: "#f9a8d4", transform: "rotate(288)" }), _jsx("circle", { cx: "0", cy: "0", r: "6", fill: "#fbbf24" }), _jsx("circle", { cx: "0", cy: "0", r: "3", fill: "#f59e0b" })] })] }) })), l.soldOut && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx("span", { className: "bg-red-600 text-white text-xs font-bold px-2 py-1 rounded rotate-[-8deg] shadow", children: "UITVERKOCHT" }) }))] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-bold text-slate-900 truncate", children: p.product_name }), p.plants_per_crate ? (_jsxs("div", { className: "text-sm text-slate-500", children: [p.plants_per_crate, " planten/fust"] })) : null, _jsxs("div", { className: "mt-2 flex items-baseline gap-2 flex-wrap", children: [showStrike && (_jsx("span", { className: "text-slate-400 line-through text-sm", children: fmtEuro(normal) })), _jsx("span", { className: "text-green-700 font-bold text-lg", children: fmtEuro(price) })] }), _jsx("div", { className: "mt-2", children: l.soldOut ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs font-semibold bg-red-100 text-red-800 border border-red-200 rounded px-2 py-1", children: "\u25CF Uitverkocht" })) : l.stock <= 10 ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-semibold bg-amber-100 text-amber-800 border border-amber-200 rounded px-2 py-1", children: ["\u25CF Nog ", l.stock, " op voorraad"] })) : (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-semibold bg-green-100 text-green-800 border border-green-200 rounded px-2 py-1", children: ["\u25CF Op voorraad: ", l.stock, " stuks"] })) }), !l.soldOut && l.stickerAvail && (_jsxs("label", { className: "mt-2 flex items-start gap-2 text-xs text-blue-900 bg-blue-50 border-l-4 border-blue-400 rounded px-2 py-2 cursor-pointer select-none", children: [_jsx("input", { type: "checkbox", className: "mt-0.5", checked: l.stickerOn, onChange: (e) => setSticker({ ...sticker, [p.id]: e.target.checked }) }), _jsxs("span", { children: ["Stickeropdruk toevoegen", l.stickerCost > 0 ? _jsxs(_Fragment, { children: [" \u2014 ", _jsx("strong", { children: fmtEuro(l.stickerCost) }), " per stuk"] }) : _jsxs(_Fragment, { children: [" \u2014 ", _jsx("strong", { children: "geen meerprijs" })] })] })] })), !l.soldOut && !l.stickerAvail && (_jsx("div", { className: "mt-2 text-xs text-slate-500 bg-slate-50 border-l-4 border-slate-300 rounded px-2 py-2", children: "Stickeropdruk niet beschikbaar voor dit product" })), !l.soldOut && currentQty > 0 && (_jsxs("div", { className: "mt-3 text-xs text-slate-600 space-y-0.5 border-t border-slate-100 pt-2", children: [_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Productprijs (", l.plants, " \u00D7 ", fmtEuro(l.unit), ")"] }), _jsx("span", { children: fmtEuro(l.productSubtotal) })] }), l.stickerOn && (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Stickeropdruk (", l.plants, " \u00D7 ", fmtEuro(l.stickerCost), ")"] }), _jsx("span", { children: fmtEuro(l.stickerSubtotal) })] })), _jsxs("div", { className: "flex justify-between font-bold text-slate-900 pt-0.5", children: [_jsx("span", { children: "Totaalprijs" }), _jsx("span", { className: "text-green-700", children: fmtEuro(l.total) })] })] }))] })] }), l.soldOut ? (_jsx("div", { className: "border-t border-slate-100 px-4 py-3 text-center text-sm text-red-700 bg-red-50 font-medium", children: "Dit product is niet meer beschikbaar" })) : (_jsxs("div", { className: "border-t border-slate-100 px-4 py-3 flex flex-wrap items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm text-slate-600", children: ["Aantal fusten:", currentQty > 0 && (_jsxs("span", { className: "ml-1 text-slate-500", children: ["= ", l.plants.toLocaleString("nl-NL"), " planten"] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", onClick: () => setQ(p.id, currentQty - 1, l.maxCrates), className: "h-9 w-9 rounded-md border border-slate-200 grid place-items-center text-slate-600 hover:bg-slate-50 disabled:opacity-40", disabled: currentQty <= 0, "aria-label": "Minder", children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { type: "number", inputMode: "numeric", step: 1, min: 0, max: l.maxCrates, value: currentQty, onChange: (e) => setQ(p.id, parseInt(e.target.value, 10), l.maxCrates), onKeyDown: (e) => {
192
+ return (_jsxs("div", { className: `bg-white rounded-xl border shadow-sm overflow-hidden ${l.soldOut ? "border-slate-200 opacity-75" : "border-slate-200"}`, children: [_jsxs("div", { className: "p-4 flex gap-4", children: [_jsxs("div", { className: "shrink-0 relative", children: [p.image_url ? (_jsx("img", { src: p.image_url, alt: p.product_name, className: `w-24 h-24 rounded-lg object-cover border border-slate-200 bg-slate-50 ${l.soldOut ? "grayscale" : ""}` })) : (_jsx("div", { className: `w-24 h-24 rounded-lg border border-slate-200 bg-green-50 grid place-items-center overflow-hidden ${l.soldOut ? "grayscale" : ""}`, children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 96 96", className: "w-full h-full", children: [_jsx("path", { d: "M48 60 Q46 75 44 86", stroke: "#16a34a", strokeWidth: "2.5", fill: "none", strokeLinecap: "round" }), _jsx("path", { d: "M48 72 Q40 70 36 64 Q42 66 48 72", fill: "#22c55e" }), _jsx("path", { d: "M48 78 Q56 76 60 70 Q54 72 48 78", fill: "#22c55e" }), _jsxs("g", { transform: "translate(48 40)", children: [_jsx("ellipse", { cx: "0", cy: "-14", rx: "9", ry: "14", fill: "#f9a8d4" }), _jsx("ellipse", { cx: "13.3", cy: "-4.3", rx: "9", ry: "14", fill: "#f472b6", transform: "rotate(72)" }), _jsx("ellipse", { cx: "8.2", cy: "11.3", rx: "9", ry: "14", fill: "#ec4899", transform: "rotate(144)" }), _jsx("ellipse", { cx: "-8.2", cy: "11.3", rx: "9", ry: "14", fill: "#f472b6", transform: "rotate(216)" }), _jsx("ellipse", { cx: "-13.3", cy: "-4.3", rx: "9", ry: "14", fill: "#f9a8d4", transform: "rotate(288)" }), _jsx("circle", { cx: "0", cy: "0", r: "6", fill: "#fbbf24" }), _jsx("circle", { cx: "0", cy: "0", r: "3", fill: "#f59e0b" })] })] }) })), l.soldOut && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx("span", { className: "bg-red-600 text-white text-xs font-bold px-2 py-1 rounded rotate-[-8deg] shadow", children: "UITVERKOCHT" }) }))] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-bold text-slate-900 truncate", children: p.product_name }), p.plants_per_crate ? (_jsxs("div", { className: "text-sm text-slate-500", children: [p.plants_per_crate, " planten/fust"] })) : null, _jsxs("div", { className: "mt-2 flex items-baseline gap-2 flex-wrap", children: [showStrike && (_jsx("span", { className: "text-slate-400 line-through text-sm", children: fmtEuro(normal) })), _jsx("span", { className: "text-green-700 font-bold text-lg", children: fmtEuro(price) }), _jsx("span", { className: "text-xs text-slate-500", children: "per fust" })] }), l.tiers.length > 0 && (_jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: l.tiers.map((t) => (_jsxs("span", { className: `text-[11px] px-2 py-0.5 rounded border ${l.cratePrice === t.price_per_crate && l.priceSource === "tier"
193
+ ? "bg-green-100 border-green-300 text-green-800 font-semibold"
194
+ : "bg-slate-50 border-slate-200 text-slate-600"}`, children: ["\u2265 ", t.min_crates, " fust: ", fmtEuro(t.price_per_crate)] }, t.min_crates))) })), _jsx("div", { className: "mt-2", children: l.soldOut ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs font-semibold bg-red-100 text-red-800 border border-red-200 rounded px-2 py-1", children: "\u25CF Uitverkocht" })) : l.stock <= 10 ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-semibold bg-amber-100 text-amber-800 border border-amber-200 rounded px-2 py-1", children: ["\u25CF Nog ", l.stock, " op voorraad"] })) : (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-semibold bg-green-100 text-green-800 border border-green-200 rounded px-2 py-1", children: ["\u25CF Op voorraad: ", l.stock, " stuks"] })) }), !l.soldOut && l.stickerAvail && (_jsxs("label", { className: "mt-2 flex items-start gap-2 text-xs text-blue-900 bg-blue-50 border-l-4 border-blue-400 rounded px-2 py-2 cursor-pointer select-none", children: [_jsx("input", { type: "checkbox", className: "mt-0.5", checked: l.stickerOn, onChange: (e) => setSticker({ ...sticker, [p.id]: e.target.checked }) }), _jsxs("span", { children: ["Stickeropdruk toevoegen", l.stickerCost > 0 ? _jsxs(_Fragment, { children: [" \u2014 ", _jsx("strong", { children: fmtEuro(l.stickerCost) }), " per stuk"] }) : _jsxs(_Fragment, { children: [" \u2014 ", _jsx("strong", { children: "geen meerprijs" })] })] })] })), !l.soldOut && !l.stickerAvail && (_jsx("div", { className: "mt-2 text-xs text-slate-500 bg-slate-50 border-l-4 border-slate-300 rounded px-2 py-2", children: "Stickeropdruk niet beschikbaar voor dit product" })), !l.soldOut && currentQty > 0 && (_jsxs("div", { className: "mt-3 text-xs text-slate-600 space-y-0.5 border-t border-slate-100 pt-2", children: [_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Productprijs (", l.plants, " \u00D7 ", fmtEuro(l.unit), ")"] }), _jsx("span", { children: fmtEuro(l.productSubtotal) })] }), l.stickerOn && (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Stickeropdruk (", l.plants, " \u00D7 ", fmtEuro(l.stickerCost), ")"] }), _jsx("span", { children: fmtEuro(l.stickerSubtotal) })] })), _jsxs("div", { className: "flex justify-between font-bold text-slate-900 pt-0.5", children: [_jsx("span", { children: "Totaalprijs" }), _jsx("span", { className: "text-green-700", children: fmtEuro(l.total) })] }), _jsxs("div", { className: "pt-1 text-[11px] text-slate-500 italic", children: ["Herkomst prijs: ", l.priceSourceLabel] })] }))] })] }), l.soldOut ? (_jsx("div", { className: "border-t border-slate-100 px-4 py-3 text-center text-sm text-red-700 bg-red-50 font-medium", children: "Dit product is niet meer beschikbaar" })) : (_jsxs("div", { className: "border-t border-slate-100 px-4 py-3 flex flex-wrap items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm text-slate-600", children: ["Aantal fusten:", currentQty > 0 && (_jsxs("span", { className: "ml-1 text-slate-500", children: ["= ", l.plants.toLocaleString("nl-NL"), " planten"] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", onClick: () => setQ(p.id, currentQty - 1, l.maxCrates), className: "h-9 w-9 rounded-md border border-slate-200 grid place-items-center text-slate-600 hover:bg-slate-50 disabled:opacity-40", disabled: currentQty <= 0, "aria-label": "Minder", children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { type: "number", inputMode: "numeric", step: 1, min: 0, max: l.maxCrates, value: currentQty, onChange: (e) => setQ(p.id, parseInt(e.target.value, 10), l.maxCrates), onKeyDown: (e) => {
166
195
  if (["e", "E", "+", "-", ".", ","].includes(e.key))
167
196
  e.preventDefault();
168
197
  }, className: "w-28 text-center" }), _jsx("button", { type: "button", onClick: () => setQ(p.id, currentQty + 1, l.maxCrates), className: "h-9 w-9 rounded-md border border-slate-200 grid place-items-center text-slate-600 hover:bg-slate-50 disabled:opacity-40", disabled: currentQty >= l.maxCrates, "aria-label": "Meer", children: _jsx(Plus, { className: "h-4 w-4" }) })] })] }))] }, p.id));
@@ -1 +1 @@
1
- {"version":3,"file":"CampagneDetail.d.ts","sourceRoot":"","sources":["../../src/components/CampagneDetail.tsx"],"names":[],"mappings":"AASA,wBAAgB,cAAc,gCA+G7B"}
1
+ {"version":3,"file":"CampagneDetail.d.ts","sourceRoot":"","sources":["../../src/components/CampagneDetail.tsx"],"names":[],"mappings":"AASA,wBAAgB,cAAc,gCA0H7B"}
@@ -34,7 +34,16 @@ export function CampagneDetail() {
34
34
  setBusy(true);
35
35
  try {
36
36
  const r = await sendFn({ data: { campaignId } });
37
- toast.success(`Verzonden naar ${r.sent} ontvangers`);
37
+ const firstError = r.errors?.[0]?.error;
38
+ if (r.sent > 0 && r.failed === 0) {
39
+ toast.success(`Verzonden naar ${r.sent} ontvangers`);
40
+ }
41
+ else if (r.sent > 0 && r.failed > 0) {
42
+ toast.warning(`Verzonden naar ${r.sent}, ${r.failed} mislukt${firstError ? `: ${firstError}` : ""}`);
43
+ }
44
+ else {
45
+ toast.error(`Verzending mislukt voor alle ${r.failed} ontvangers${firstError ? `: ${firstError}` : ""}`);
46
+ }
38
47
  await reload();
39
48
  }
40
49
  catch (e) {
@@ -5,5 +5,6 @@ export { CampagneOverzicht } from "./components/CampagneOverzicht";
5
5
  export { CampagneDetail } from "./components/CampagneDetail";
6
6
  export { RapportagePage } from "./components/RapportagePage";
7
7
  export { ActiePage } from "./components/ActiePage";
8
+ export * from "./lib/voorraad-service";
8
9
  export declare const actiemailKwekersErpMathot: FlowModule;
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAUnD,eAAO,MAAM,yBAAyB,EAAE,UAiBvC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,cAAc,wBAAwB,CAAC;AASvC,eAAO,MAAM,yBAAyB,EAAE,UAgBvC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -5,7 +5,7 @@ export { CampagneOverzicht } from "./components/CampagneOverzicht";
5
5
  export { CampagneDetail } from "./components/CampagneDetail";
6
6
  export { RapportagePage } from "./components/RapportagePage";
7
7
  export { ActiePage } from "./components/ActiePage";
8
- import { AfzenderSettingsCard } from "./components/settings/AfzenderSettingsCard";
8
+ export * from "./lib/voorraad-service";
9
9
  import { HuisstijlSettingsCard } from "./components/settings/HuisstijlSettingsCard";
10
10
  import { TrackingSettingsCard } from "./components/settings/TrackingSettingsCard";
11
11
  import { UitsluitingenSettingsCard } from "./components/settings/UitsluitingenSettingsCard";
@@ -21,7 +21,6 @@ export const actiemailKwekersErpMathot = {
21
21
  icon: Mail,
22
22
  },
23
23
  settingsCards: [
24
- { component: AfzenderSettingsCard, order: 10 },
25
24
  { component: HuisstijlSettingsCard, order: 20 },
26
25
  { component: TrackingSettingsCard, order: 30 },
27
26
  { component: StickerSettingsCard, order: 35 },