@flowselections/floriday-voorraad 1.0.38 → 1.0.40

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 (63) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +34 -3
  3. package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/FustenPage.js +7 -1
  5. package/dist-lib/components/voorraad/InventorySettingsDrawer.d.ts +10 -0
  6. package/dist-lib/components/voorraad/InventorySettingsDrawer.d.ts.map +1 -0
  7. package/dist-lib/components/voorraad/InventorySettingsDrawer.js +99 -0
  8. package/dist-lib/components/voorraad/InventoryStatusBadge.d.ts +6 -0
  9. package/dist-lib/components/voorraad/InventoryStatusBadge.d.ts.map +1 -0
  10. package/dist-lib/components/voorraad/InventoryStatusBadge.js +43 -0
  11. package/dist-lib/components/voorraad/StockHistoryChart.d.ts +4 -0
  12. package/dist-lib/components/voorraad/StockHistoryChart.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/StockHistoryChart.js +53 -0
  14. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  15. package/dist-lib/components/voorraad/VoorraadPage.js +29 -1
  16. package/dist-lib/components/voorraad/VoorraadTable.d.ts +9 -1
  17. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  18. package/dist-lib/components/voorraad/VoorraadTable.js +17 -6
  19. package/dist-lib/hooks/useInventorySettings.d.ts +29 -0
  20. package/dist-lib/hooks/useInventorySettings.d.ts.map +1 -0
  21. package/dist-lib/hooks/useInventorySettings.js +62 -0
  22. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  23. package/dist-lib/hooks/useTradeItemStock.js +1 -0
  24. package/dist-lib/integrations/supabase/auth-middleware.d.ts +3833 -1196
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  26. package/dist-lib/integrations/supabase/client.d.ts +3833 -1196
  27. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.server.d.ts +3833 -1196
  29. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.d.ts +3987 -1324
  31. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/types.js +1 -0
  33. package/dist-lib/lib/floricode-required-features.functions.d.ts +3833 -1196
  34. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/floriday-2026v1.server.d.ts +77 -0
  36. package/dist-lib/lib/floriday-2026v1.server.d.ts.map +1 -0
  37. package/dist-lib/lib/floriday-2026v1.server.js +178 -0
  38. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +3833 -1196
  39. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/floriday-env.functions.d.ts +6593 -1319
  41. package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-gateway.functions.d.ts +19621 -3799
  43. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday-stock-supply.server.d.ts +18 -7
  45. package/dist-lib/lib/floriday-stock-supply.server.d.ts.map +1 -1
  46. package/dist-lib/lib/floriday-stock-supply.server.js +70 -68
  47. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +6593 -1319
  48. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
  49. package/dist-lib/lib/floriday-warehouse.functions.d.ts +9850 -1939
  50. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/floriday-writes.functions.d.ts +6593 -1319
  52. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  53. package/dist-lib/lib/fusten.d.ts +1 -0
  54. package/dist-lib/lib/fusten.d.ts.map +1 -1
  55. package/dist-lib/lib/fusten.js +1 -1
  56. package/dist-lib/lib/inventory-settings.functions.d.ts +51709 -0
  57. package/dist-lib/lib/inventory-settings.functions.d.ts.map +1 -0
  58. package/dist-lib/lib/inventory-settings.functions.js +97 -0
  59. package/dist-lib/lib/module-health.functions.d.ts +6593 -1319
  60. package/dist-lib/lib/module-health.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/stock-sync.functions.d.ts +6593 -1319
  62. package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
  63. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AAqMA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,2CAs7DL"}
1
+ {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AAqMA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,2CAs9DL"}
@@ -959,10 +959,41 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
959
959
  });
960
960
  return;
961
961
  }
962
+ // Zijn de productregels (schema) nog niet binnen? Dan publiceren we niet,
963
+ // maar bewaren we de wijziging ALTIJD lokaal en markeren we hem als
964
+ // "nog niet doorgegeven aan Floriday".
962
965
  if (wantsPublish && !schemaQuery.isSuccess) {
963
- toast.error("De productregels worden nog opgehaald", {
964
- description: "Wacht heel even en probeer het opnieuw.",
965
- });
966
+ setSubmitting(true);
967
+ try {
968
+ const localId = await upsertLocalTradeItem({
969
+ id: draftId,
970
+ connectionId: activeId,
971
+ data,
972
+ floridayTradeItemId: (isEditingPublished && tradeItemId) ? tradeItemId : (draftFloridayId || null),
973
+ sync: {
974
+ dirty: true,
975
+ last_attempt_at: new Date().toISOString(),
976
+ last_error: schemaQuery.error instanceof Error
977
+ ? schemaQuery.error.message
978
+ : "De productregels van Floriday konden nog niet worden opgehaald.",
979
+ },
980
+ });
981
+ if (localId && localId !== draftId)
982
+ setDraftId(localId);
983
+ setPendingSync({
984
+ error: "De productregels van Floriday konden nog niet worden opgehaald.",
985
+ at: new Date().toISOString(),
986
+ });
987
+ toast.success("Wijziging opgeslagen", {
988
+ description: "Nog niet doorgegeven aan Floriday — je kunt het straks opnieuw proberen.",
989
+ });
990
+ }
991
+ catch (e) {
992
+ toast.error(`Opslaan mislukt: ${e.message}`);
993
+ }
994
+ finally {
995
+ setSubmitting(false);
996
+ }
966
997
  return;
967
998
  }
968
999
  // Strikte controle van alle stappen voor verzending.
@@ -1 +1 @@
1
- {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AA2JA,wBAAgB,UAAU,4CAkMzB"}
1
+ {"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AAiKA,wBAAgB,UAAU,4CAkMzB"}
@@ -53,7 +53,13 @@ function MutationHistory({ fust }) {
53
53
  }
54
54
  };
55
55
  return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: toggle, className: "text-xs text-primary hover:underline", children: open ? "Historie verbergen" : "Historie" }), open && (_jsxs("div", { className: "mt-2 rounded-md border border-border bg-muted/30 p-2 text-xs", children: [rows === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), rows !== null && rows.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Nog geen mutaties." })), rows !== null &&
56
- rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: new Date(m.created_at).toLocaleString("nl-NL") }), _jsxs("span", { className: "flex-1 truncate", children: [m.reason === "sale" ? "Verkoop" : "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
56
+ rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: new Date(m.created_at).toLocaleString("nl-NL") }), _jsxs("span", { className: "flex-1 truncate", children: [m.reason === "sale"
57
+ ? "Verkoop"
58
+ : m.reason === "sale_cancelled"
59
+ ? "Order geannuleerd"
60
+ : m.reason === "manual_sale"
61
+ ? "Handmatige verkoop"
62
+ : "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
57
63
  }
58
64
  export function FustenPage() {
59
65
  const { activeId } = useFloridayConnection();
@@ -0,0 +1,10 @@
1
+ import type { InventorySetting } from "../../hooks/useInventorySettings";
2
+ export declare function InventorySettingsDrawer({ open, onOpenChange, connectionId, productIds, current, onSaved, }: {
3
+ open: boolean;
4
+ onOpenChange: (v: boolean) => void;
5
+ connectionId: string | null;
6
+ productIds: string[];
7
+ current?: InventorySetting | undefined;
8
+ onSaved?: () => void;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=InventorySettingsDrawer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InventorySettingsDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/InventorySettingsDrawer.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,gBAAgB,EAAa,MAAM,kCAAkC,CAAC;AAWpF,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,OAAO,EACP,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,2CA0LA"}
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Instellingen voor de Floriday-voorraadsturing van één of meerdere producten.
4
+ * Bij meerdere producten (bulk) worden alleen de ingevulde velden toegepast.
5
+ */
6
+ import { useEffect, useState } from "react";
7
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
8
+ import { useServerFn } from "@tanstack/react-start";
9
+ import { toast } from "sonner";
10
+ import { saveInventorySettings, republishInventory, } from "../../lib/inventory-settings.functions";
11
+ import { MODE_LABELS } from "./InventoryStatusBadge";
12
+ const MODE_HELP = {
13
+ FOLLOW_ERP: "Publiceert de beschikbare voorraad uit dit systeem, minus de buffer.",
14
+ MAX: "Volgt het ERP, maar publiceert nooit meer dan het opgegeven maximum.",
15
+ FIXED: "Publiceert altijd hetzelfde aantal, ongeacht de ERP-voorraad.",
16
+ MANUAL: "Publiceert het aantal dat je hier zelf invult; het ERP wordt genegeerd.",
17
+ DISABLED: "Er wordt niets meer naar Floriday gestuurd voor dit artikel.",
18
+ };
19
+ export function InventorySettingsDrawer({ open, onOpenChange, connectionId, productIds, current, onSaved, }) {
20
+ const save = useServerFn(saveInventorySettings);
21
+ const republish = useServerFn(republishInventory);
22
+ const bulk = productIds.length > 1;
23
+ const [mode, setMode] = useState("");
24
+ const [buffer, setBuffer] = useState("");
25
+ const [maximum, setMaximum] = useState("");
26
+ const [fixed, setFixed] = useState("");
27
+ const [manual, setManual] = useState("");
28
+ const [busy, setBusy] = useState(false);
29
+ useEffect(() => {
30
+ if (!open)
31
+ return;
32
+ if (bulk || !current) {
33
+ setMode("");
34
+ setBuffer("");
35
+ setMaximum("");
36
+ setFixed("");
37
+ setManual("");
38
+ return;
39
+ }
40
+ setMode((current.stock_mode ?? "FOLLOW_ERP"));
41
+ setBuffer(String(current.buffer_quantity ?? 0));
42
+ setMaximum(current.maximum_quantity === null ? "" : String(current.maximum_quantity));
43
+ setFixed(current.fixed_quantity === null ? "" : String(current.fixed_quantity));
44
+ setManual(current.manual_quantity === null ? "" : String(current.manual_quantity));
45
+ }, [open, bulk, current]);
46
+ const num = (v) => {
47
+ if (v.trim() === "")
48
+ return undefined;
49
+ const n = Number(v);
50
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined;
51
+ };
52
+ const onSave = async () => {
53
+ if (!connectionId || productIds.length === 0)
54
+ return;
55
+ setBusy(true);
56
+ try {
57
+ const res = await save({
58
+ data: {
59
+ connectionId,
60
+ productIds,
61
+ ...(mode ? { stockMode: mode } : {}),
62
+ ...(num(buffer) !== undefined ? { bufferQuantity: num(buffer) } : {}),
63
+ ...(maximum.trim() !== "" ? { maximumQuantity: num(maximum) ?? null } : {}),
64
+ ...(fixed.trim() !== "" ? { fixedQuantity: num(fixed) ?? null } : {}),
65
+ ...(manual.trim() !== "" ? { manualQuantity: num(manual) ?? null } : {}),
66
+ },
67
+ });
68
+ toast.success(`Voorraadsturing bijgewerkt voor ${res.updated} artikel(en).`);
69
+ onSaved?.();
70
+ onOpenChange(false);
71
+ }
72
+ catch (e) {
73
+ toast.error(e.message);
74
+ }
75
+ finally {
76
+ setBusy(false);
77
+ }
78
+ };
79
+ const onRepublish = async () => {
80
+ if (!connectionId || productIds.length === 0)
81
+ return;
82
+ setBusy(true);
83
+ try {
84
+ const res = await republish({ data: { connectionId, productIds } });
85
+ toast.success(`${res.queued} artikel(en) opnieuw ingepland voor Floriday.`);
86
+ onSaved?.();
87
+ }
88
+ catch (e) {
89
+ toast.error(e.message);
90
+ }
91
+ finally {
92
+ setBusy(false);
93
+ }
94
+ };
95
+ const activeMode = (mode || current?.stock_mode || "");
96
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Voorraadsturing Floriday" }), _jsx(DialogDescription, { children: bulk
97
+ ? `${productIds.length} artikelen. Alleen de velden die je invult worden aangepast.`
98
+ : "Bepaal welk aantal er naar Floriday wordt gepubliceerd." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Modus" }), _jsxs(Select, { value: mode, onValueChange: (v) => setMode(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: bulk ? "Niet wijzigen" : "Kies een modus" }) }), _jsx(SelectContent, { children: Object.keys(MODE_LABELS).map((m) => (_jsx(SelectItem, { value: m, children: MODE_LABELS[m] }, m))) })] }), activeMode && (_jsx("p", { className: "text-xs text-muted-foreground", children: MODE_HELP[activeMode] }))] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Buffer" }), _jsx(Input, { inputMode: "numeric", value: buffer, onChange: (e) => setBuffer(e.target.value), placeholder: bulk ? "Niet wijzigen" : "0" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aantal dat je achterhoudt en dus niet aanbiedt." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Maximum" }), _jsx(Input, { inputMode: "numeric", value: maximum, onChange: (e) => setMaximum(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "MAX" && !bulk })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Vast aantal" }), _jsx(Input, { inputMode: "numeric", value: fixed, onChange: (e) => setFixed(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "FIXED" && !bulk })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Handmatig aantal" }), _jsx(Input, { inputMode: "numeric", value: manual, onChange: (e) => setManual(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "MANUAL" && !bulk })] })] }), !bulk && current && (_jsxs("div", { className: "rounded-md border bg-muted/40 p-3 text-xs text-muted-foreground space-y-1", children: [_jsxs("div", { children: ["Gewenst: ", _jsx("strong", { className: "text-foreground", children: current.desired_quantity }), " ", "\u00B7 In Floriday:", " ", _jsx("strong", { className: "text-foreground", children: current.remote_quantity ?? "onbekend" })] }), current.last_published_at && (_jsxs("div", { children: ["Laatst verstuurd:", " ", new Date(current.last_published_at).toLocaleString("nl-NL")] })), current.last_sync_error && (_jsxs("div", { className: "text-destructive", children: ["Laatste fout: ", current.last_sync_error] }))] }))] }), _jsxs(DialogFooter, { className: "gap-2", children: [_jsx(Button, { variant: "outline", onClick: onRepublish, disabled: busy, children: "Opnieuw versturen" }), _jsx(Button, { onClick: onSave, disabled: busy, children: busy ? "Opslaan..." : "Opslaan" })] })] }) }));
99
+ }
@@ -0,0 +1,6 @@
1
+ import type { InventorySetting, StockMode } from "../../hooks/useInventorySettings";
2
+ export declare const MODE_LABELS: Record<StockMode, string>;
3
+ export declare function InventoryStatusBadge({ setting }: {
4
+ setting: InventorySetting | undefined;
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=InventoryStatusBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InventoryStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/InventoryStatusBadge.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAEpF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMjD,CAAC;AA4BF,wBAAgB,oBAAoB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAA;CAAE,2CAyB1F"}
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge } from "@flowselections/core";
3
+ export const MODE_LABELS = {
4
+ FOLLOW_ERP: "Volgt ERP",
5
+ MAX: "Max",
6
+ FIXED: "Vast",
7
+ MANUAL: "Handmatig",
8
+ DISABLED: "Uit",
9
+ };
10
+ const MODE_CLASSES = {
11
+ FOLLOW_ERP: "bg-primary/10 text-primary border-primary/30",
12
+ MAX: "bg-sky-500/15 text-sky-700 border-sky-500/30",
13
+ FIXED: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30",
14
+ MANUAL: "bg-amber-500/15 text-amber-800 border-amber-500/30",
15
+ DISABLED: "bg-muted text-muted-foreground border-border",
16
+ };
17
+ /** Bolletje: klopt de stand in Floriday met wat wij willen? */
18
+ function syncDot(status) {
19
+ switch (status) {
20
+ case "IN_SYNC":
21
+ return { cls: "bg-emerald-500", title: "Gelijk aan Floriday" };
22
+ case "PENDING":
23
+ return { cls: "bg-amber-500", title: "Wordt doorgezet naar Floriday" };
24
+ case "DRIFT":
25
+ return { cls: "bg-orange-500", title: "Wijkt af van Floriday" };
26
+ case "ERROR":
27
+ return { cls: "bg-destructive", title: "Doorzetten mislukt" };
28
+ case "DISABLED":
29
+ return { cls: "bg-muted-foreground/40", title: "Sturing staat uit" };
30
+ default:
31
+ return { cls: "bg-muted-foreground/40", title: "Nog niet gecontroleerd" };
32
+ }
33
+ }
34
+ export function InventoryStatusBadge({ setting }) {
35
+ if (!setting) {
36
+ return _jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" });
37
+ }
38
+ const mode = (setting.stock_mode ?? "FOLLOW_ERP");
39
+ const dot = syncDot(setting.sync_status ?? "UNKNOWN");
40
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { className: MODE_CLASSES[mode] ?? MODE_CLASSES.FOLLOW_ERP, children: MODE_LABELS[mode] ?? mode }), _jsx("span", { className: `h-2 w-2 shrink-0 rounded-full ${dot.cls}`, title: setting.last_sync_error
41
+ ? `${dot.title}: ${setting.last_sync_error}`
42
+ : dot.title, "aria-label": dot.title }), _jsx("span", { className: "tabular-nums text-xs text-muted-foreground", children: setting.desired_quantity ?? 0 })] }));
43
+ }
@@ -0,0 +1,4 @@
1
+ export declare function StockHistoryChart({ connectionId }: {
2
+ connectionId: string | null;
3
+ }): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=StockHistoryChart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StockHistoryChart.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockHistoryChart.tsx"],"names":[],"mappings":"AA0BA,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,2CAoGlF"}
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Historie van de voorraadsturing: wat had het ERP beschikbaar, wat wilden we
4
+ * in Floriday hebben, en wat stond er werkelijk. De punten komen uit
5
+ * `inventory_stock_snapshots` en worden door de database gecompacteerd.
6
+ */
7
+ import { useEffect, useState } from "react";
8
+ import { Card } from "@flowselections/core";
9
+ import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts";
10
+ import { supabase } from "../../integrations/supabase/client";
11
+ export function StockHistoryChart({ connectionId }) {
12
+ const [points, setPoints] = useState([]);
13
+ const [loading, setLoading] = useState(false);
14
+ useEffect(() => {
15
+ let cancelled = false;
16
+ if (!connectionId) {
17
+ setPoints([]);
18
+ return;
19
+ }
20
+ setLoading(true);
21
+ const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
22
+ supabase
23
+ .from("inventory_stock_snapshots")
24
+ .select("snapshot_at, erp_available_quantity, floriday_desired_quantity, floriday_remote_quantity")
25
+ .eq("connection_id", connectionId)
26
+ .gte("snapshot_at", since)
27
+ .order("snapshot_at", { ascending: true })
28
+ .limit(1000)
29
+ .then(({ data }) => {
30
+ if (cancelled)
31
+ return;
32
+ setPoints((data ?? []).map((r) => ({
33
+ t: r.snapshot_at,
34
+ label: new Date(r.snapshot_at).toLocaleString("nl-NL", {
35
+ day: "2-digit",
36
+ month: "2-digit",
37
+ hour: "2-digit",
38
+ minute: "2-digit",
39
+ }),
40
+ erp: Number(r.erp_available_quantity ?? 0),
41
+ gewenst: Number(r.floriday_desired_quantity ?? 0),
42
+ floriday: r.floriday_remote_quantity === null
43
+ ? null
44
+ : Number(r.floriday_remote_quantity),
45
+ })));
46
+ setLoading(false);
47
+ });
48
+ return () => {
49
+ cancelled = true;
50
+ };
51
+ }, [connectionId]);
52
+ return (_jsxs(Card, { className: "p-4", children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-semibold", children: "Voorraadverloop (30 dagen)" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "ERP beschikbaar \u00B7 gewenst in Floriday \u00B7 werkelijk in Floriday" })] }), _jsx("div", { className: "mt-4 h-56", children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Historie laden..." })) : points.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen historie beschikbaar. De eerste meetpunten verschijnen binnen een uur." })) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(LineChart, { data: points, margin: { top: 4, right: 8, bottom: 0, left: -16 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", className: "stroke-border" }), _jsx(XAxis, { dataKey: "label", tick: { fontSize: 11 }, minTickGap: 32 }), _jsx(YAxis, { tick: { fontSize: 11 }, allowDecimals: false }), _jsx(Tooltip, {}), _jsx(Line, { type: "monotone", dataKey: "erp", name: "ERP beschikbaar", stroke: "hsl(var(--muted-foreground))", dot: false, strokeWidth: 2 }), _jsx(Line, { type: "monotone", dataKey: "gewenst", name: "Gewenst", stroke: "hsl(var(--primary))", dot: false, strokeWidth: 2 }), _jsx(Line, { type: "monotone", dataKey: "floriday", name: "In Floriday", stroke: "hsl(142 71% 40%)", dot: false, strokeWidth: 2, connectNulls: true })] }) })) })] }));
53
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAiGA,wBAAgB,YAAY,4CA+O3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAqGA,wBAAgB,YAAY,4CAiT3B"}
@@ -11,6 +11,9 @@ import { ExcelExportButton } from "./ExcelExportButton";
11
11
  import { StockBulkButtons } from "./StockBulkButtons";
12
12
  import { VoorraadTable } from "./VoorraadTable";
13
13
  import { VoorraadPagination } from "./VoorraadPagination";
14
+ import { StockHistoryChart } from "./StockHistoryChart";
15
+ import { InventorySettingsDrawer } from "./InventorySettingsDrawer";
16
+ import { useInventorySettings } from "../../hooks/useInventorySettings";
14
17
  function isLinkedToFloriday(i) {
15
18
  return Boolean(i.floridayId) || i.source === "published";
16
19
  }
@@ -37,12 +40,32 @@ function availableOf(i) {
37
40
  export function VoorraadPage() {
38
41
  const { activeId, error: connError } = useFloridayConnection();
39
42
  const { items, loading, error, reload } = useVoorraadData(activeId);
43
+ const { byKey: settingsByKey, reload: reloadSettings } = useInventorySettings(activeId);
40
44
  const [search, setSearch] = useState("");
41
45
  const [stockFilter, setStockFilter] = useState("all");
42
46
  const [floridayFilter, setFloridayFilter] = useState("all");
43
47
  const [typeFilter, setTypeFilter] = useState("all");
44
48
  const [page, setPage] = useState(1);
45
49
  const [pageSize, setPageSize] = useState(25);
50
+ const [selected, setSelected] = useState(new Set());
51
+ const [drawerIds, setDrawerIds] = useState([]);
52
+ const [drawerOpen, setDrawerOpen] = useState(false);
53
+ const toggleSelect = (id) => setSelected((cur) => {
54
+ const next = new Set(cur);
55
+ if (next.has(id))
56
+ next.delete(id);
57
+ else
58
+ next.add(id);
59
+ return next;
60
+ });
61
+ const toggleAll = (ids) => setSelected((cur) => {
62
+ const all = ids.length > 0 && ids.every((id) => cur.has(id));
63
+ return all ? new Set() : new Set(ids);
64
+ });
65
+ const openSettings = (ids) => {
66
+ setDrawerIds(ids);
67
+ setDrawerOpen(true);
68
+ };
46
69
  const counts = useMemo(() => {
47
70
  let inStock = 0;
48
71
  let outOfStock = 0;
@@ -99,5 +122,10 @@ export function VoorraadPage() {
99
122
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
100
123
  ? "text-foreground"
101
124
  : "text-muted-foreground hover:text-foreground"}`, children: [t.label, _jsx("span", { className: `ml-2 text-xs ${active ? "text-primary" : "text-muted-foreground/70"}`, children: t.count }), active && (_jsx("span", { className: "absolute left-2 right-2 -bottom-px h-0.5 bg-primary rounded-full" }))] }, t.id));
102
- }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[220px] max-w-md", children: [_jsx(Search, { className: "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op product of artikelnummer...", className: "pl-9", value: search, onChange: (e) => setSearch(e.target.value) })] }), _jsxs(Select, { value: floridayFilter, onValueChange: (v) => setFloridayFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[190px]", children: _jsx(SelectValue, { placeholder: "Floriday" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle (Floriday)" }), _jsx(SelectItem, { value: "linked", children: "Gekoppeld aan Floriday" }), _jsx(SelectItem, { value: "unlinked", children: "Niet gekoppeld" })] })] }), _jsxs(Select, { value: typeFilter, onValueChange: (v) => setTypeFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[170px]", children: _jsx(SelectValue, { placeholder: "Type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle types" }), _jsx(SelectItem, { value: "artikel", children: "Artikel" }), _jsx(SelectItem, { value: "productgroep", children: "Productgroep" }), _jsx(SelectItem, { value: "samenstelling", children: "Samenstelling" })] })] }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsx(ExcelExportButton, { items: filtered }), _jsx(StockBulkButtons, { items: items, activeConnectionId: activeId, onImported: reload })] })] }), _jsx("div", { className: "overflow-x-auto", children: _jsx(VoorraadTable, { items: paged, loading: loading }) }), _jsx(VoorraadPagination, { total: filtered.length, page: page, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] })] }));
125
+ }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[220px] max-w-md", children: [_jsx(Search, { className: "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op product of artikelnummer...", className: "pl-9", value: search, onChange: (e) => setSearch(e.target.value) })] }), _jsxs(Select, { value: floridayFilter, onValueChange: (v) => setFloridayFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[190px]", children: _jsx(SelectValue, { placeholder: "Floriday" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle (Floriday)" }), _jsx(SelectItem, { value: "linked", children: "Gekoppeld aan Floriday" }), _jsx(SelectItem, { value: "unlinked", children: "Niet gekoppeld" })] })] }), _jsxs(Select, { value: typeFilter, onValueChange: (v) => setTypeFilter(v), children: [_jsx(SelectTrigger, { className: "h-9 w-[170px]", children: _jsx(SelectValue, { placeholder: "Type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle types" }), _jsx(SelectItem, { value: "artikel", children: "Artikel" }), _jsx(SelectItem, { value: "productgroep", children: "Productgroep" }), _jsx(SelectItem, { value: "samenstelling", children: "Samenstelling" })] })] }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsx(ExcelExportButton, { items: filtered }), _jsx(StockBulkButtons, { items: items, activeConnectionId: activeId, onImported: reload })] })] }), selected.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b bg-primary/5 px-4 py-2 text-sm", children: [_jsxs("span", { className: "font-medium", children: [selected.size, " geselecteerd"] }), _jsx(Button, { size: "sm", onClick: () => openSettings([...selected]), children: "Voorraadsturing instellen" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setSelected(new Set()), children: "Selectie wissen" })] })), _jsx("div", { className: "overflow-x-auto", children: _jsx(VoorraadTable, { items: paged, loading: loading, settingsByKey: settingsByKey, selected: selected, onToggleSelect: toggleSelect, onToggleAll: toggleAll, onOpenSettings: (id) => openSettings([id]) }) }), _jsx(VoorraadPagination, { total: filtered.length, page: page, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }), _jsx(StockHistoryChart, { connectionId: activeId ?? null }), _jsx(InventorySettingsDrawer, { open: drawerOpen, onOpenChange: setDrawerOpen, connectionId: activeId ?? null, productIds: drawerIds, current: drawerIds.length === 1
126
+ ? [...settingsByKey.values()].find((s) => s.product_id === drawerIds[0])
127
+ : undefined, onSaved: () => {
128
+ reloadSettings();
129
+ reload();
130
+ } })] }));
103
131
  }
@@ -1,6 +1,14 @@
1
1
  import type { TradeItem } from "../../lib/floriday-client";
2
- export declare function VoorraadTable({ items, loading, }: {
2
+ import type { InventorySetting } from "../../hooks/useInventorySettings";
3
+ /** Sleutel waarmee een rij aan zijn Floriday-voorraadinstelling wordt gekoppeld. */
4
+ export declare function settingKeyOf(item: TradeItem): string | null;
5
+ export declare function VoorraadTable({ items, loading, settingsByKey, selected, onToggleSelect, onToggleAll, onOpenSettings, }: {
3
6
  items: TradeItem[];
4
7
  loading: boolean;
8
+ settingsByKey?: Map<string, InventorySetting>;
9
+ selected?: Set<string>;
10
+ onToggleSelect?: (productId: string) => void;
11
+ onToggleAll?: (productIds: string[]) => void;
12
+ onOpenSettings?: (productId: string) => void;
5
13
  }): import("react/jsx-runtime").JSX.Element;
6
14
  //# sourceMappingURL=VoorraadTable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAmB3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,2CAmKA"}
1
+ {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAiBzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,2CAoNA"}
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useNavigate } from "@tanstack/react-router";
3
- import { Package } from "lucide-react";
4
- import { Badge } from "@flowselections/core";
3
+ import { Package, Settings2 } from "lucide-react";
4
+ import { Badge, Button, Checkbox } from "@flowselections/core";
5
5
  import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields";
6
6
  import { formatCustomValue } from "../../lib/voorraad-custom-fields";
7
+ import { InventoryStatusBadge } from "./InventoryStatusBadge";
7
8
  const columns = [
8
9
  { key: "quantity", label: "Liggend" },
9
10
  { key: "minQuantity", label: "Minimum" },
@@ -16,17 +17,25 @@ function customValue(item, key) {
16
17
  const cf = item.raw?.custom_fields;
17
18
  return cf && typeof cf === "object" ? cf[key] : undefined;
18
19
  }
19
- export function VoorraadTable({ items, loading, }) {
20
+ /** Sleutel waarmee een rij aan zijn Floriday-voorraadinstelling wordt gekoppeld. */
21
+ export function settingKeyOf(item) {
22
+ return item.productId ?? item.floridayId ?? (item.source === "published" ? item.id : null);
23
+ }
24
+ export function VoorraadTable({ items, loading, settingsByKey, selected, onToggleSelect, onToggleAll, onOpenSettings, }) {
20
25
  const navigate = useNavigate();
21
26
  const { fields } = useVoorraadCustomFields();
22
27
  const extraColumns = fields.filter((f) => f.show_in_columns);
28
+ const selectableIds = items
29
+ .map((i) => settingsByKey?.get(settingKeyOf(i) ?? "")?.product_id ?? null)
30
+ .filter((v) => Boolean(v));
31
+ const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selected?.has(id));
23
32
  if (loading) {
24
33
  return _jsx("p", { className: "text-sm text-muted-foreground p-6", children: "Voorraad laden..." });
25
34
  }
26
35
  if (items.length === 0) {
27
36
  return (_jsx("p", { className: "text-sm text-muted-foreground p-6", children: "Geen producten gevonden voor deze connectie." }));
28
37
  }
29
- return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3 w-14" }), _jsx("th", { className: "p-3", children: "Product" }), _jsx("th", { className: "p-3", children: "Type" }), _jsx("th", { className: "p-3", children: "Artikelnummer" }), columns.map((c) => (_jsx("th", { className: "p-3 text-right whitespace-nowrap", children: c.label }, c.key))), extraColumns.map((f) => (_jsx("th", { className: "p-3 whitespace-nowrap", children: f.field_label }, f.id)))] }) }), _jsx("tbody", { children: items.map((item) => {
38
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [onToggleSelect && (_jsx("th", { className: "p-3 w-10", children: _jsx(Checkbox, { checked: allSelected, onCheckedChange: () => onToggleAll?.(selectableIds), "aria-label": "Alles selecteren" }) })), _jsx("th", { className: "p-3 w-14" }), _jsx("th", { className: "p-3", children: "Product" }), _jsx("th", { className: "p-3", children: "Floriday-sturing" }), _jsx("th", { className: "p-3", children: "Type" }), _jsx("th", { className: "p-3", children: "Artikelnummer" }), columns.map((c) => (_jsx("th", { className: "p-3 text-right whitespace-nowrap", children: c.label }, c.key))), extraColumns.map((f) => (_jsx("th", { className: "p-3 whitespace-nowrap", children: f.field_label }, f.id)))] }) }), _jsx("tbody", { children: items.map((item) => {
30
39
  const low = item.minQuantity > 0 &&
31
40
  item.availableQuantity < item.minQuantity;
32
41
  const linkId = item.productId ?? item.localId ?? item.id;
@@ -37,6 +46,8 @@ export function VoorraadTable({ items, loading, }) {
37
46
  };
38
47
  const floriday = item.floriday ?? null;
39
48
  const thumbUrl = floriday?.photoUrl || item.imageUrl;
49
+ const setting = settingsByKey?.get(settingKeyOf(item) ?? "");
50
+ const settingProductId = setting?.product_id ?? null;
40
51
  return (_jsxs("tr", { className: `border-t hover:bg-muted/30 ${clickable ? "cursor-pointer" : ""}`, onClick: clickable ? open : undefined, role: clickable ? "button" : undefined, tabIndex: clickable ? 0 : undefined, onKeyDown: clickable
41
52
  ? (e) => {
42
53
  if (e.key === "Enter" || e.key === " ") {
@@ -44,11 +55,11 @@ export function VoorraadTable({ items, loading, }) {
44
55
  open();
45
56
  }
46
57
  }
47
- : undefined, "aria-label": clickable ? `Bewerk ${item.name}` : undefined, children: [_jsx("td", { className: "p-2", children: thumbUrl ? (_jsx("img", { src: thumbUrl, alt: item.name, className: "h-10 w-10 rounded object-cover", loading: "lazy" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })) }), _jsxs("td", { className: "p-3", children: [_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx("span", { className: "font-medium text-foreground", children: item.name }), _jsxs("div", { role: "status", "aria-label": floriday
58
+ : undefined, "aria-label": clickable ? `Bewerk ${item.name}` : undefined, children: [onToggleSelect && (_jsx("td", { className: "p-3", onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: settingProductId ? selected?.has(settingProductId) : false, disabled: !settingProductId, onCheckedChange: () => settingProductId && onToggleSelect(settingProductId), "aria-label": `Selecteer ${item.name}` }) })), _jsx("td", { className: "p-2", children: thumbUrl ? (_jsx("img", { src: thumbUrl, alt: item.name, className: "h-10 w-10 rounded object-cover", loading: "lazy" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })) }), _jsxs("td", { className: "p-3", children: [_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx("span", { className: "font-medium text-foreground", children: item.name }), _jsxs("div", { role: "status", "aria-label": floriday
48
59
  ? "Online in Floriday"
49
60
  : "Niet online in Floriday", title: floriday
50
61
  ? "Online in Floriday"
51
62
  : "Niet online in Floriday", className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2 py-0.5 text-[11px] font-medium leading-none text-muted-foreground", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full " +
52
- (floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), Boolean(customValue(item, "floriday_sync")?.dirty) && (_jsx("div", { className: "mt-1 inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium leading-none text-amber-800", title: "Opgeslagen in dit systeem, wacht nog op doorgifte naar Floriday", children: "Wacht op Floriday" })), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" }))] }), _jsx("td", { className: "p-3", children: item.itemType === "productgroep" ? (_jsx(Badge, { className: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30", children: "Productgroep" })) : item.itemType === "samenstelling" ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Samenstelling" })) : (_jsx(Badge, { variant: "secondary", children: "Artikel" })) }), _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), columns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key))), extraColumns.map((f) => (_jsx("td", { className: "p-3 text-muted-foreground whitespace-nowrap", children: formatCustomValue(customValue(item, f.field_key)) }, f.id)))] }, item.id));
63
+ (floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), Boolean(customValue(item, "floriday_sync")?.dirty) && (_jsx("div", { className: "mt-1 inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium leading-none text-amber-800", title: "Opgeslagen in dit systeem, wacht nog op doorgifte naar Floriday", children: "Wacht op Floriday" })), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" }))] }), _jsx("td", { className: "p-3", onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InventoryStatusBadge, { setting: setting }), settingProductId && onOpenSettings && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => onOpenSettings(settingProductId), "aria-label": `Voorraadsturing van ${item.name}`, children: _jsx(Settings2, { className: "h-4 w-4" }) }))] }) }), _jsx("td", { className: "p-3", children: item.itemType === "productgroep" ? (_jsx(Badge, { className: "bg-indigo-500/15 text-indigo-700 border-indigo-500/30", children: "Productgroep" })) : item.itemType === "samenstelling" ? (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Samenstelling" })) : (_jsx(Badge, { variant: "secondary", children: "Artikel" })) }), _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), columns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: Number(item[c.key] ?? 0) }, c.key))), extraColumns.map((f) => (_jsx("td", { className: "p-3 text-muted-foreground whitespace-nowrap", children: formatCustomValue(customValue(item, f.field_key)) }, f.id)))] }, item.id));
53
64
  }) })] }) }));
54
65
  }
@@ -0,0 +1,29 @@
1
+ export type StockMode = "FOLLOW_ERP" | "MAX" | "FIXED" | "MANUAL" | "DISABLED";
2
+ export type InventorySetting = {
3
+ id: string;
4
+ connection_id: string;
5
+ product_id: string | null;
6
+ floriday_trade_item_id: string | null;
7
+ stock_mode: StockMode;
8
+ buffer_quantity: number;
9
+ maximum_quantity: number | null;
10
+ fixed_quantity: number | null;
11
+ manual_quantity: number | null;
12
+ desired_quantity: number;
13
+ desired_availability: boolean;
14
+ remote_quantity: number | null;
15
+ remote_available: boolean | null;
16
+ remote_checked_at: string | null;
17
+ sync_status: string;
18
+ last_sync_error: string | null;
19
+ last_published_at: string | null;
20
+ last_confirmed_at: string | null;
21
+ };
22
+ export declare function useInventorySettings(connectionId: string | null): {
23
+ rows: InventorySetting[];
24
+ byKey: Map<string, InventorySetting>;
25
+ loading: boolean;
26
+ error: string | null;
27
+ reload: () => Promise<void>;
28
+ };
29
+ //# sourceMappingURL=useInventorySettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInventorySettings.d.ts","sourceRoot":"","sources":["../../src/hooks/useInventorySettings.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE/E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,UAAU,EAAE,SAAS,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAAC;AAKF,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;;EA0D/D"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Leest de Floriday-voorraadsturing per product. De rijen worden door de
3
+ * database zelf aangemaakt en bijgewerkt (`floriday_recompute_inventory`);
4
+ * de UI leest ze alleen en schrijft via server-functies.
5
+ */
6
+ import { useCallback, useEffect, useState } from "react";
7
+ import { supabase } from "../integrations/supabase/client";
8
+ const SELECT = "id, connection_id, product_id, floriday_trade_item_id, stock_mode, buffer_quantity, maximum_quantity, fixed_quantity, manual_quantity, desired_quantity, desired_availability, remote_quantity, remote_available, remote_checked_at, sync_status, last_sync_error, last_published_at, last_confirmed_at";
9
+ export function useInventorySettings(connectionId) {
10
+ const [rows, setRows] = useState([]);
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState(null);
13
+ const load = useCallback(async () => {
14
+ if (!connectionId) {
15
+ setRows([]);
16
+ return;
17
+ }
18
+ setLoading(true);
19
+ const { data, error: err } = await supabase
20
+ .from("floriday_inventory_settings")
21
+ .select(SELECT)
22
+ .eq("connection_id", connectionId)
23
+ .limit(2000);
24
+ if (err)
25
+ setError(err.message);
26
+ else {
27
+ setError(null);
28
+ setRows((data ?? []));
29
+ }
30
+ setLoading(false);
31
+ }, [connectionId]);
32
+ useEffect(() => {
33
+ load();
34
+ }, [load]);
35
+ useEffect(() => {
36
+ if (!connectionId)
37
+ return;
38
+ const channel = supabase
39
+ .channel(`inventory-settings-${connectionId}`)
40
+ .on("postgres_changes", {
41
+ event: "*",
42
+ schema: "public",
43
+ table: "floriday_inventory_settings",
44
+ filter: `connection_id=eq.${connectionId}`,
45
+ }, () => {
46
+ load();
47
+ })
48
+ .subscribe();
49
+ return () => {
50
+ supabase.removeChannel(channel);
51
+ };
52
+ }, [connectionId, load]);
53
+ /** Zowel op product-id als op Floriday trade-item-id opzoekbaar. */
54
+ const byKey = new Map();
55
+ for (const r of rows) {
56
+ if (r.product_id)
57
+ byKey.set(r.product_id, r);
58
+ if (r.floriday_trade_item_id)
59
+ byKey.set(r.floriday_trade_item_id, r);
60
+ }
61
+ return { rows, byKey, loading, error, reload: load };
62
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;;;mBA4ElB,iBAAiB;EAmEnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
1
+ {"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;;;mBA4ElB,iBAAiB;EAoEnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
@@ -152,6 +152,7 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
152
152
  connectionId,
153
153
  tradeItemKey: primaryKey,
154
154
  piecesSold: sold,
155
+ reason: "manual_sale",
155
156
  });
156
157
  }
157
158
  catch {