@flowselections/floriday-voorraad 1.0.15 → 1.0.17

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 (84) hide show
  1. package/dist-lib/components/voorraad/AdditionalPackagingDialog.d.ts +9 -0
  2. package/dist-lib/components/voorraad/AdditionalPackagingDialog.d.ts.map +1 -0
  3. package/dist-lib/components/voorraad/AdditionalPackagingDialog.js +51 -0
  4. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  5. package/dist-lib/components/voorraad/ArtikelWizard.js +411 -28
  6. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
  7. package/dist-lib/components/voorraad/EditArtikelPage.js +78 -4
  8. package/dist-lib/components/voorraad/ExcelExportButton.d.ts +2 -1
  9. package/dist-lib/components/voorraad/ExcelExportButton.d.ts.map +1 -1
  10. package/dist-lib/components/voorraad/ExcelExportButton.js +24 -5
  11. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +8 -0
  12. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/OrderHistoryCard.js +133 -0
  14. package/dist-lib/components/voorraad/PriceStatusBadge.d.ts +7 -0
  15. package/dist-lib/components/voorraad/PriceStatusBadge.d.ts.map +1 -0
  16. package/dist-lib/components/voorraad/PriceStatusBadge.js +67 -0
  17. package/dist-lib/components/voorraad/ProductLinkCard.d.ts +13 -0
  18. package/dist-lib/components/voorraad/ProductLinkCard.d.ts.map +1 -0
  19. package/dist-lib/components/voorraad/ProductLinkCard.js +159 -0
  20. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  21. package/dist-lib/components/voorraad/StockBulkButtons.js +88 -7
  22. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +2 -1
  23. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  24. package/dist-lib/components/voorraad/VoorraadStockCard.js +5 -4
  25. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  26. package/dist-lib/components/voorraad/VoorraadTable.js +6 -1
  27. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  28. package/dist-lib/hooks/useLocalTradeItems.js +4 -0
  29. package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -1
  30. package/dist-lib/hooks/useTradeItemSpec.js +4 -1
  31. package/dist-lib/hooks/useTradeItemStock.d.ts +14 -1
  32. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  33. package/dist-lib/hooks/useTradeItemStock.js +76 -17
  34. package/dist-lib/hooks/useTradeItemSupplyLine.d.ts +21 -0
  35. package/dist-lib/hooks/useTradeItemSupplyLine.d.ts.map +1 -0
  36. package/dist-lib/hooks/useTradeItemSupplyLine.js +49 -0
  37. package/dist-lib/hooks/useVoorraadData.d.ts +5 -4
  38. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  39. package/dist-lib/hooks/useVoorraadData.js +73 -7
  40. package/dist-lib/index.d.ts +1 -0
  41. package/dist-lib/index.d.ts.map +1 -1
  42. package/dist-lib/index.js +1 -0
  43. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1307 -94
  44. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  45. package/dist-lib/integrations/supabase/client.d.ts +1307 -94
  46. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/client.server.d.ts +1307 -94
  48. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  49. package/dist-lib/integrations/supabase/types.d.ts +1328 -87
  50. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  51. package/dist-lib/lib/ensure-article-codes.d.ts +8 -0
  52. package/dist-lib/lib/ensure-article-codes.d.ts.map +1 -0
  53. package/dist-lib/lib/ensure-article-codes.js +78 -0
  54. package/dist-lib/lib/floricode-required-features.functions.d.ts +1313 -94
  55. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  56. package/dist-lib/lib/floricode-required-features.functions.js +20 -2
  57. package/dist-lib/lib/floriday-client.d.ts +6 -0
  58. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  59. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +11264 -0
  60. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -0
  61. package/dist-lib/lib/floriday-customer-offer.functions.js +175 -0
  62. package/dist-lib/lib/floriday-gateway.functions.d.ts +16129 -17
  63. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  64. package/dist-lib/lib/floriday-gateway.functions.js +150 -0
  65. package/dist-lib/lib/floriday-gateway.server.d.ts +22 -0
  66. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  67. package/dist-lib/lib/floriday-gateway.server.js +3 -0
  68. package/dist-lib/lib/floriday-payload.d.ts +76 -8
  69. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  70. package/dist-lib/lib/floriday-payload.js +209 -39
  71. package/dist-lib/lib/floriday-supply-line.d.ts +53 -0
  72. package/dist-lib/lib/floriday-supply-line.d.ts.map +1 -0
  73. package/dist-lib/lib/floriday-supply-line.js +109 -0
  74. package/dist-lib/lib/floriday-warehouse.functions.d.ts +33638 -0
  75. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -0
  76. package/dist-lib/lib/floriday-warehouse.functions.js +98 -0
  77. package/dist-lib/lib/floriday-writes.functions.d.ts +2707 -281
  78. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  79. package/dist-lib/lib/floriday-writes.functions.js +39 -6
  80. package/dist-lib/lib/vrs-catalog.d.ts +27 -0
  81. package/dist-lib/lib/vrs-catalog.d.ts.map +1 -0
  82. package/dist-lib/lib/vrs-catalog.js +98 -0
  83. package/package.json +3 -2
  84. package/public/flowselections-assets/Voorraad/floriday-logo.png +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAWA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,2CA0DrD"}
1
+ {"version":3,"file":"EditArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/EditArtikelPage.tsx"],"names":[],"mappings":"AAeA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,2CAyHrD"}
@@ -1,11 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
+ import { useNavigate } from "@tanstack/react-router";
3
4
  import { Card } from "@flowselections/core";
4
5
  import { Loader2 } from "lucide-react";
5
6
  import { ArtikelWizard } from "./ArtikelWizard";
6
7
  import { supabase } from "../../integrations/supabase/client";
8
+ import { useFloridayConnection } from "../../hooks/useFloridayConnection";
9
+ import { upsertLocalTradeItem } from "../../hooks/useLocalTradeItems";
10
+ import { emptyWizardData, isValidGtin13 } from "../../lib/floriday-payload";
7
11
  export function EditArtikelPage({ id }) {
8
12
  const [resolved, setResolved] = useState(null);
13
+ const [promoting, setPromoting] = useState(false);
14
+ const { activeId } = useFloridayConnection();
15
+ const navigate = useNavigate();
9
16
  useEffect(() => {
10
17
  let cancelled = false;
11
18
  setResolved(null);
@@ -32,14 +39,81 @@ export function EditArtikelPage({ id }) {
32
39
  setResolved({ kind: "published", id });
33
40
  return;
34
41
  }
35
- setResolved({ kind: "notfound" });
42
+ // Fallback: ERP-only product → promote to editable draft transparently.
43
+ const prodRes = await supabase
44
+ .from("products")
45
+ .select("id,product,barcode,quantity,sale_price")
46
+ .eq("id", id)
47
+ .maybeSingle();
48
+ if (cancelled)
49
+ return;
50
+ if (!prodRes.data) {
51
+ setResolved({ kind: "notfound" });
52
+ return;
53
+ }
54
+ if (!activeId) {
55
+ // No Floriday connection yet — cannot create a draft (connection_id required).
56
+ setResolved({ kind: "notfound" });
57
+ return;
58
+ }
59
+ setPromoting(true);
60
+ try {
61
+ // Look for an existing draft already linked to this product.
62
+ const existing = await supabase
63
+ .from("trade_item_drafts")
64
+ .select("id,data")
65
+ .eq("connection_id", activeId)
66
+ .contains("data", { linkedProductId: id })
67
+ .maybeSingle();
68
+ if (cancelled)
69
+ return;
70
+ if (existing.data) {
71
+ navigate({
72
+ to: "/voorraad/$id",
73
+ params: { id: existing.data.id },
74
+ replace: true,
75
+ });
76
+ return;
77
+ }
78
+ const prod = prodRes.data;
79
+ const barcode = String(prod.barcode ?? "").trim();
80
+ const validGtin = isValidGtin13(barcode);
81
+ const seed = {
82
+ ...emptyWizardData,
83
+ name: prod.product ?? "",
84
+ articleCode: validGtin ? "" : barcode,
85
+ gtin: validGtin ? barcode : "",
86
+ salePricePerPiece: Number(prod.sale_price) || 0,
87
+ saleStockPieces: Number(prod.quantity) || 0,
88
+ linkedProductId: id,
89
+ };
90
+ const newId = await upsertLocalTradeItem({
91
+ connectionId: activeId,
92
+ data: seed,
93
+ });
94
+ if (cancelled)
95
+ return;
96
+ navigate({
97
+ to: "/voorraad/$id",
98
+ params: { id: newId },
99
+ replace: true,
100
+ });
101
+ }
102
+ catch {
103
+ if (!cancelled)
104
+ setResolved({ kind: "notfound" });
105
+ }
106
+ finally {
107
+ if (!cancelled)
108
+ setPromoting(false);
109
+ }
36
110
  })();
37
111
  return () => {
38
112
  cancelled = true;
39
113
  };
40
- }, [id]);
41
- if (!resolved) {
42
- return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Artikel zoeken..."] }) }));
114
+ }, [id, activeId, navigate]);
115
+ if (!resolved || promoting) {
116
+ return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), promoting ? "Artikel voorbereiden voor bewerken..." : "Artikel zoeken..."] }) }));
43
117
  }
44
118
  if (resolved.kind === "notfound") {
45
119
  return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6 text-sm", children: "Artikel niet gevonden. Het is mogelijk verwijderd of nog niet gesynchroniseerd vanuit Floriday." }) }));
@@ -1,6 +1,7 @@
1
1
  import type { TradeItem } from "../../lib/floriday-client";
2
- export declare function ExcelExportButton({ items, filename, }: {
2
+ export declare function ExcelExportButton({ items, filename, onExported, }: {
3
3
  items: TradeItem[];
4
4
  filename?: string;
5
+ onExported?: () => void | Promise<void>;
5
6
  }): import("react/jsx-runtime").JSX.Element;
6
7
  //# sourceMappingURL=ExcelExportButton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExcelExportButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ExcelExportButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAqB,GACtB,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,2CAyBA"}
1
+ {"version":3,"file":"ExcelExportButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ExcelExportButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAG3D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAqB,EACrB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC,2CAgDA"}
@@ -1,11 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as XLSX from "xlsx";
3
- import { Button } from "@flowselections/core";
3
+ import { Button, toast } from "@flowselections/core";
4
4
  import { Download } from "lucide-react";
5
- export function ExcelExportButton({ items, filename = "voorraad", }) {
6
- const handle = () => {
7
- const rows = items.map((i) => ({
8
- Artikelnummer: i.articleNumber,
5
+ import { ensureArticleCodes } from "../../lib/ensure-article-codes";
6
+ export function ExcelExportButton({ items, filename = "voorraad", onExported, }) {
7
+ const handle = async () => {
8
+ let exportItems;
9
+ try {
10
+ exportItems = await ensureArticleCodes(items);
11
+ }
12
+ catch (e) {
13
+ toast.error(`Artikelcodes genereren mislukt: ${e.message}`);
14
+ return;
15
+ }
16
+ const missing = exportItems.filter((i) => !(i.articleNumber ?? "").trim());
17
+ if (missing.length > 0) {
18
+ toast.error(`${missing.length} artikel(en) missen nog een artikelcode; export afgebroken.`);
19
+ return;
20
+ }
21
+ const generatedCount = exportItems.filter((i, idx) => i.articleNumber !== items[idx].articleNumber).length;
22
+ const rows = exportItems.map((i) => ({
23
+ Artikelcode: i.articleNumber,
9
24
  Productnaam: i.name,
10
25
  "Liggende voorraad": i.quantity,
11
26
  Minimum: i.minQuantity,
@@ -19,6 +34,10 @@ export function ExcelExportButton({ items, filename = "voorraad", }) {
19
34
  XLSX.utils.book_append_sheet(wb, ws, "Voorraad");
20
35
  const stamp = new Date().toISOString().slice(0, 10);
21
36
  XLSX.writeFile(wb, `${filename}-${stamp}.xlsx`);
37
+ if (generatedCount > 0) {
38
+ toast.success(`${generatedCount} artikel(en) kregen automatisch een nieuwe artikelcode.`);
39
+ await onExported?.();
40
+ }
22
41
  };
23
42
  return (_jsxs(Button, { variant: "outline", onClick: handle, disabled: items.length === 0, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), "Excel export"] }));
24
43
  }
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ tradeItemKey: string | null;
3
+ productName: string | null;
4
+ productId?: string | null;
5
+ }
6
+ export declare function OrderHistoryCard({ tradeItemKey, productName, productId }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=OrderHistoryCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrderHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/OrderHistoryCard.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAgDD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,KAAK,2CAoK/E"}
@@ -0,0 +1,133 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Card, CardContent, Badge } from "@flowselections/core";
4
+ import { Loader2, ClipboardList, ExternalLink } from "lucide-react";
5
+ import { supabase } from "../../integrations/supabase/client";
6
+ const STATUS_TONE = {
7
+ "nog te picken": "bg-amber-100 text-amber-800 border-amber-200",
8
+ "klaar voor verzending": "bg-blue-100 text-blue-800 border-blue-200",
9
+ verzonden: "bg-emerald-100 text-emerald-800 border-emerald-200",
10
+ geannuleerd: "bg-rose-100 text-rose-800 border-rose-200",
11
+ concept: "bg-slate-100 text-slate-700 border-slate-200",
12
+ };
13
+ // Legacy waarden mappen naar de nieuwe fases zolang de backfill/trigger
14
+ // nog niet elke rij heeft omgezet.
15
+ function normalizeStatus(raw) {
16
+ const s = (raw ?? "").trim().toLowerCase();
17
+ if (!s)
18
+ return "Nog te picken";
19
+ if (["verzonden", "geleverd", "shipped", "delivered"].includes(s))
20
+ return "Verzonden";
21
+ if (["nieuw", "open", "new", "pending"].includes(s))
22
+ return "Nog te picken";
23
+ if (["ready", "klaar", "klaar voor verzending"].includes(s))
24
+ return "Klaar voor verzending";
25
+ if (["geannuleerd", "cancelled", "canceled"].includes(s))
26
+ return "geannuleerd";
27
+ if (s === "concept")
28
+ return "concept";
29
+ if (s === "nog te picken")
30
+ return "Nog te picken";
31
+ return raw ?? "Nog te picken";
32
+ }
33
+ function formatDate(v) {
34
+ if (!v)
35
+ return "—";
36
+ try {
37
+ const d = new Date(v);
38
+ if (Number.isNaN(d.getTime()))
39
+ return v;
40
+ return d.toLocaleDateString("nl-NL", { day: "2-digit", month: "short", year: "numeric" });
41
+ }
42
+ catch {
43
+ return v;
44
+ }
45
+ }
46
+ export function OrderHistoryCard({ tradeItemKey, productName, productId }) {
47
+ const [rows, setRows] = useState(null);
48
+ const [error, setError] = useState(null);
49
+ useEffect(() => {
50
+ let cancelled = false;
51
+ const load = async () => {
52
+ setError(null);
53
+ try {
54
+ // Resolve all local product ids that map to this trade item key.
55
+ const productIds = new Set();
56
+ if (productId)
57
+ productIds.add(productId);
58
+ if (tradeItemKey) {
59
+ const { data: prods } = await supabase
60
+ .from("products")
61
+ .select("id")
62
+ .eq("trade_item_key", tradeItemKey)
63
+ .limit(50);
64
+ for (const p of (prods ?? []))
65
+ productIds.add(p.id);
66
+ }
67
+ // Build the OR filter: match on product_id OR on product_name for
68
+ // legacy rows that weren't linked to a product yet.
69
+ const orFilters = [];
70
+ if (productIds.size > 0) {
71
+ orFilters.push(`product_id.in.(${[...productIds].join(",")})`);
72
+ }
73
+ if (productName) {
74
+ const safe = productName.replace(/[\\%,()]/g, " ").trim();
75
+ if (safe)
76
+ orFilters.push(`product_name.eq.${safe}`);
77
+ }
78
+ if (orFilters.length === 0) {
79
+ if (!cancelled)
80
+ setRows([]);
81
+ return;
82
+ }
83
+ const { data, error } = await supabase
84
+ .from("order_items")
85
+ .select("order_id,quantity,unit,orders!inner(id,order_number,customer_name,order_date,created_at,status,order_source,deleted_at)")
86
+ .or(orFilters.join(","))
87
+ .is("orders.deleted_at", null)
88
+ .order("created_at", { ascending: false, foreignTable: "orders" })
89
+ .limit(100);
90
+ if (error)
91
+ throw error;
92
+ if (cancelled)
93
+ return;
94
+ const mapped = (data ?? [])
95
+ .map((r) => ({
96
+ order_id: r.order_id,
97
+ order_number: r.orders?.order_number ?? "—",
98
+ customer_name: r.orders?.customer_name ?? null,
99
+ quantity: r.quantity ?? 0,
100
+ unit: r.unit ?? null,
101
+ order_date: r.orders?.order_date ?? null,
102
+ created_at: r.orders?.created_at ?? "",
103
+ status: r.orders?.status ?? null,
104
+ order_source: r.orders?.order_source ?? null,
105
+ }))
106
+ .sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
107
+ setRows(mapped);
108
+ }
109
+ catch (e) {
110
+ if (!cancelled)
111
+ setError(e.message);
112
+ }
113
+ };
114
+ load();
115
+ // Realtime: refresh when any order or order_item mutates.
116
+ const channel = supabase
117
+ .channel(`order-history-${tradeItemKey ?? "none"}`)
118
+ .on("postgres_changes", { event: "*", schema: "public", table: "order_items" }, load)
119
+ .on("postgres_changes", { event: "*", schema: "public", table: "orders" }, load)
120
+ .subscribe();
121
+ return () => {
122
+ cancelled = true;
123
+ supabase.removeChannel(channel);
124
+ };
125
+ }, [tradeItemKey, productName, productId]);
126
+ return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon bestelgeschiedenis niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bestellingen laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen bestellingen voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Ordernummer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Klant" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Fusten" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Besteldatum" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Status" }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { children: rows.map((r) => {
127
+ const label = normalizeStatus(r.status);
128
+ const tone = STATUS_TONE[label.toLowerCase()] ??
129
+ "bg-slate-100 text-slate-700 border-slate-200";
130
+ const href = `/floriday-verkoop/verkooporders/${r.order_id}`;
131
+ return (_jsxs("tr", { className: "border-t hover:bg-muted/40 cursor-pointer", onClick: () => { window.location.href = href; }, children: [_jsx("td", { className: "px-3 py-2 font-medium", children: _jsx("a", { href: href, className: "text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: r.order_number }) }), _jsx("td", { className: "px-3 py-2", children: r.customer_name ?? "—" }), _jsxs("td", { className: "px-3 py-2 text-right tabular-nums", children: [r.quantity, r.unit ? _jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: r.unit }) : null] }), _jsx("td", { className: "px-3 py-2", children: formatDate(r.order_date ?? r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${tone}`, children: label }) }), _jsx("td", { className: "px-3 py-2 text-right text-muted-foreground", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5 inline" }) })] }, r.order_id));
132
+ }) })] }) }))] })] }));
133
+ }
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ tradeItemId: string | null | undefined;
3
+ offerForSale: boolean;
4
+ };
5
+ export declare function PriceStatusBadge({ tradeItemId, offerForSale }: Props): import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
7
+ //# sourceMappingURL=PriceStatusBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PriceStatusBadge.tsx"],"names":[],"mappings":"AAGA,KAAK,KAAK,GAAG;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAeF,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,KAAK,kDAkEpE"}
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CircleDollarSign, Clock, MinusCircle, Loader2 } from "lucide-react";
3
+ import { useTradeItemSupplyLine } from "../../hooks/useTradeItemSupplyLine";
4
+ function formatDate(iso) {
5
+ if (!iso)
6
+ return "—";
7
+ try {
8
+ return new Date(iso).toLocaleDateString("nl-NL", {
9
+ day: "2-digit",
10
+ month: "short",
11
+ year: "numeric",
12
+ });
13
+ }
14
+ catch {
15
+ return iso;
16
+ }
17
+ }
18
+ export function PriceStatusBadge({ tradeItemId, offerForSale }) {
19
+ const { data: supplyLine, isLoading } = useTradeItemSupplyLine(tradeItemId);
20
+ if (!tradeItemId)
21
+ return null;
22
+ if (isLoading) {
23
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Prijsstatus laden\u2026"] }));
24
+ }
25
+ const now = Date.now();
26
+ const endMs = supplyLine?.period_end ? new Date(supplyLine.period_end).getTime() : null;
27
+ const qty = supplyLine?.available_quantity ?? 0;
28
+ const isExpired = endMs !== null && endMs < now;
29
+ const isDrained = supplyLine !== null && qty <= 0;
30
+ let label;
31
+ let Icon = CircleDollarSign;
32
+ let cls = "";
33
+ let variant;
34
+ if (!supplyLine || !offerForSale) {
35
+ variant = "none";
36
+ label = "Geen prijs actief";
37
+ Icon = MinusCircle;
38
+ cls = "bg-muted text-muted-foreground";
39
+ }
40
+ else if (isExpired || isDrained) {
41
+ variant = "expired";
42
+ label = isExpired ? "Prijs verlopen" : "Voorraad op";
43
+ Icon = Clock;
44
+ cls = "bg-amber-100 text-amber-800";
45
+ }
46
+ else {
47
+ variant = "live";
48
+ label = "Prijs live in catalogus";
49
+ Icon = CircleDollarSign;
50
+ cls = "bg-emerald-100 text-emerald-700";
51
+ }
52
+ const title = supplyLine
53
+ ? [
54
+ `Prijs: ${supplyLine.price_per_piece != null
55
+ ? `${supplyLine.currency ?? "EUR"} ${supplyLine.price_per_piece.toFixed(2)} / stuk`
56
+ : "—"}`,
57
+ `Beschikbaar: ${qty}`,
58
+ `Periode: ${formatDate(supplyLine.period_start)} → ${formatDate(supplyLine.period_end)}`,
59
+ variant === "none" && !offerForSale
60
+ ? '"Te koop aanbieden" staat uit.'
61
+ : null,
62
+ ]
63
+ .filter(Boolean)
64
+ .join("\n")
65
+ : "Nog geen prijs naar Floriday-catalogus gepusht.";
66
+ return (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full cursor-help ${cls}`, title: title, children: [_jsx(Icon, { className: "h-3 w-3" }), " ", label] }));
67
+ }
@@ -0,0 +1,13 @@
1
+ interface Props {
2
+ /** trade_item_key for this stock row (usually the Floriday id or `draft:<uuid>`). */
3
+ tradeItemKey: string | null;
4
+ /** Optional product name to power auto-linking suggestions. */
5
+ productName?: string | null;
6
+ }
7
+ /**
8
+ * Koppelt ERP-`products` rijen aan één Floriday-voorraadrij (`trade_item_key`).
9
+ * Zonder koppeling doen bestel-triggers niets voor mailbox/manual-orders.
10
+ */
11
+ export declare function ProductLinkCard({ tradeItemKey, productName }: Props): import("react/jsx-runtime").JSX.Element;
12
+ export {};
13
+ //# sourceMappingURL=ProductLinkCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductLinkCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductLinkCard.tsx"],"names":[],"mappings":"AAWA,UAAU,KAAK;IACb,qFAAqF;IACrF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AASD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,2CA4OnE"}
@@ -0,0 +1,159 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { Button, Card, CardContent, Input, toast, } from "@flowselections/core";
4
+ import { Link2, Loader2, RefreshCw, Trash2, Wand2 } from "lucide-react";
5
+ import { supabase } from "../../integrations/supabase/client";
6
+ /**
7
+ * Koppelt ERP-`products` rijen aan één Floriday-voorraadrij (`trade_item_key`).
8
+ * Zonder koppeling doen bestel-triggers niets voor mailbox/manual-orders.
9
+ */
10
+ export function ProductLinkCard({ tradeItemKey, productName }) {
11
+ const [linked, setLinked] = useState([]);
12
+ const [candidates, setCandidates] = useState([]);
13
+ const [query, setQuery] = useState("");
14
+ const [loading, setLoading] = useState(false);
15
+ const [busy, setBusy] = useState(false);
16
+ const reload = useCallback(async () => {
17
+ if (!tradeItemKey)
18
+ return;
19
+ setLoading(true);
20
+ try {
21
+ const { data: linkedRows } = await supabase
22
+ .from("products")
23
+ .select("id, product, barcode, trade_item_key")
24
+ .eq("trade_item_key", tradeItemKey)
25
+ .order("product");
26
+ setLinked(linkedRows ?? []);
27
+ }
28
+ finally {
29
+ setLoading(false);
30
+ }
31
+ }, [tradeItemKey]);
32
+ useEffect(() => {
33
+ void reload();
34
+ }, [reload]);
35
+ useEffect(() => {
36
+ let cancelled = false;
37
+ const q = query.trim();
38
+ if (q.length < 2) {
39
+ setCandidates([]);
40
+ return;
41
+ }
42
+ (async () => {
43
+ const { data } = await supabase
44
+ .from("products")
45
+ .select("id, product, barcode, trade_item_key")
46
+ .or(`product.ilike.%${q}%,barcode.ilike.%${q}%`)
47
+ .is("trade_item_key", null)
48
+ .limit(20);
49
+ if (!cancelled)
50
+ setCandidates(data ?? []);
51
+ })();
52
+ return () => {
53
+ cancelled = true;
54
+ };
55
+ }, [query]);
56
+ const linkProduct = async (id) => {
57
+ if (!tradeItemKey)
58
+ return;
59
+ setBusy(true);
60
+ try {
61
+ const { error } = await supabase
62
+ .from("products")
63
+ .update({ trade_item_key: tradeItemKey })
64
+ .eq("id", id);
65
+ if (error)
66
+ throw error;
67
+ toast.success("Product gekoppeld");
68
+ setQuery("");
69
+ setCandidates([]);
70
+ await reload();
71
+ }
72
+ catch (e) {
73
+ toast.error(`Koppelen mislukt: ${e.message}`);
74
+ }
75
+ finally {
76
+ setBusy(false);
77
+ }
78
+ };
79
+ const unlinkProduct = async (id) => {
80
+ setBusy(true);
81
+ try {
82
+ const { error } = await supabase
83
+ .from("products")
84
+ .update({ trade_item_key: null })
85
+ .eq("id", id);
86
+ if (error)
87
+ throw error;
88
+ toast.success("Koppeling verwijderd");
89
+ await reload();
90
+ }
91
+ catch (e) {
92
+ toast.error(`Ontkoppelen mislukt: ${e.message}`);
93
+ }
94
+ finally {
95
+ setBusy(false);
96
+ }
97
+ };
98
+ const autoLinkByName = async () => {
99
+ if (!tradeItemKey || !productName)
100
+ return;
101
+ setBusy(true);
102
+ try {
103
+ const { data, error } = await supabase
104
+ .from("products")
105
+ .select("id, product")
106
+ .ilike("product", productName)
107
+ .is("trade_item_key", null);
108
+ if (error)
109
+ throw error;
110
+ const matches = data ?? [];
111
+ if (matches.length === 0) {
112
+ toast.info(`Geen niet-gekoppelde producten met naam "${productName}" gevonden.`);
113
+ return;
114
+ }
115
+ const ids = matches.map((m) => m.id);
116
+ const { error: updErr } = await supabase
117
+ .from("products")
118
+ .update({ trade_item_key: tradeItemKey })
119
+ .in("id", ids);
120
+ if (updErr)
121
+ throw updErr;
122
+ toast.success(`${ids.length} product(en) gekoppeld op naam`);
123
+ await reload();
124
+ }
125
+ catch (e) {
126
+ toast.error(`Auto-koppelen mislukt: ${e.message}`);
127
+ }
128
+ finally {
129
+ setBusy(false);
130
+ }
131
+ };
132
+ const backfillReservations = async () => {
133
+ setBusy(true);
134
+ try {
135
+ const { data, error } = await supabase.rpc("backfill_open_order_reservations");
136
+ if (error)
137
+ throw error;
138
+ const rows = data ?? [];
139
+ const n = rows[0]?.processed_items ?? 0;
140
+ toast.success(`Backfill klaar: ${n} orderregel(s) verwerkt`);
141
+ }
142
+ catch (e) {
143
+ toast.error(`Backfill mislukt: ${e.message}`);
144
+ }
145
+ finally {
146
+ setBusy(false);
147
+ }
148
+ };
149
+ const helpText = useMemo(() => {
150
+ if (!tradeItemKey) {
151
+ return "Sla het artikel eerst op om producten te kunnen koppelen.";
152
+ }
153
+ if (linked.length === 0) {
154
+ return "Nog geen ERP-product gekoppeld. Zonder koppeling worden mailbox-orders niet automatisch van de voorraad afgeschreven.";
155
+ }
156
+ return "Bestellingen op deze ERP-producten reserveren automatisch op deze voorraadrij.";
157
+ }, [tradeItemKey, linked.length]);
158
+ return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Link2, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium", children: "Gekoppelde ERP-producten" }), _jsx("div", { className: "text-xs text-muted-foreground", children: helpText })] }), _jsxs("div", { className: "flex gap-2", children: [productName && (_jsxs(Button, { variant: "outline", size: "sm", onClick: autoLinkByName, disabled: !tradeItemKey || busy, title: "Zoek ERP-producten met exact deze naam en koppel ze", children: [_jsx(Wand2, { className: "h-4 w-4 mr-1" }), "Auto op naam"] })), _jsxs(Button, { variant: "outline", size: "sm", onClick: backfillReservations, disabled: busy, title: "Reserveer openstaande orders retroactief", children: [_jsx(RefreshCw, { className: "h-4 w-4 mr-1" }), "Backfill"] })] })] }), _jsx(CardContent, { className: "pt-0 space-y-3", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden\u2026"] })) : (_jsxs(_Fragment, { children: [linked.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border", children: linked.map((p) => (_jsxs("li", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: p.product ?? "(zonder naam)" }), p.barcode && (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Barcode: ", p.barcode] }))] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => unlinkProduct(p.id), disabled: busy, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))) })), _jsxs("div", { className: "space-y-2", children: [_jsx(Input, { placeholder: "Zoek ERP-product op naam of SKU\u2026", value: query, onChange: (e) => setQuery(e.target.value), disabled: !tradeItemKey || busy }), candidates.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border max-h-56 overflow-auto", children: candidates.map((p) => (_jsxs("li", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate", children: p.product ?? "(zonder naam)" }), p.barcode && (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Barcode: ", p.barcode] }))] }), _jsx(Button, { size: "sm", onClick: () => linkProduct(p.id), disabled: busy, children: "Koppel" })] }, p.id))) }))] })] })) })] }));
159
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAsD3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,2CA8dA"}
1
+ {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAoF3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,2CAikBA"}