@flowselections/floriday-voorraad 1.0.13 → 1.0.15
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.
- package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts +5 -0
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ArtikelWizard.js +1229 -0
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts +4 -0
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/EditArtikelPage.js +51 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts +2 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +5 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts +7 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -0
- package/dist-lib/components/voorraad/StockBulkButtons.js +356 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +7 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +87 -0
- package/dist-lib/components/voorraad/ValidationPage.d.ts +2 -0
- package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ValidationPage.js +12 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts +6 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.js +42 -0
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +70 -11
- package/dist-lib/components/voorraad/VoorraadPagination.d.ts +10 -0
- package/dist-lib/components/voorraad/VoorraadPagination.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadPagination.js +12 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +8 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.js +33 -0
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +16 -5
- package/dist-lib/hooks/useFloriday.d.ts +55 -0
- package/dist-lib/hooks/useFloriday.d.ts.map +1 -0
- package/dist-lib/hooks/useFloriday.js +298 -0
- package/dist-lib/hooks/useLocalTradeItems.d.ts +17 -1
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +54 -10
- package/dist-lib/hooks/useTradeItemSpec.d.ts +42 -0
- package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -0
- package/dist-lib/hooks/useTradeItemSpec.js +448 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts +19 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -0
- package/dist-lib/hooks/useTradeItemStock.js +78 -0
- package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts +3 -0
- package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts.map +1 -0
- package/dist-lib/hooks/useVbnRegulatoryFeatures.js +19 -0
- package/dist-lib/hooks/useVoorraadData.d.ts +2 -1
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +39 -11
- package/dist-lib/index.d.ts +4 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +6 -5
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +8346 -3367
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +8346 -3367
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +8346 -3367
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +8460 -3341
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +10 -1
- package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/floricode-required-features.test.js +186 -0
- package/dist-lib/lib/floricode-client.d.ts +15 -1
- package/dist-lib/lib/floricode-client.d.ts.map +1 -1
- package/dist-lib/lib/floricode-client.js +42 -4
- package/dist-lib/lib/floricode-required-features.functions.d.ts +10026 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -0
- package/dist-lib/lib/floricode-required-features.functions.js +170 -0
- package/dist-lib/lib/floriday-client.d.ts +36 -8
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.js +206 -25
- package/dist-lib/lib/floriday-env.d.ts +29 -0
- package/dist-lib/lib/floriday-env.d.ts.map +1 -0
- package/dist-lib/lib/floriday-env.js +42 -0
- package/dist-lib/lib/floriday-env.server.d.ts +15 -0
- package/dist-lib/lib/floriday-env.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-env.server.js +13 -0
- package/dist-lib/lib/floriday-field-sources.d.ts +18 -0
- package/dist-lib/lib/floriday-field-sources.d.ts.map +1 -0
- package/dist-lib/lib/floriday-field-sources.js +45 -0
- package/dist-lib/lib/floriday-gateway-entities.d.ts +44 -0
- package/dist-lib/lib/floriday-gateway-entities.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway-entities.js +51 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts +39999 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway.functions.js +68 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts +122 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway.server.js +230 -0
- package/dist-lib/lib/floriday-import-validator.d.ts +26 -0
- package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -0
- package/dist-lib/lib/floriday-import-validator.js +41 -0
- package/dist-lib/lib/floriday-payload.d.ts +47 -3
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +179 -7
- package/dist-lib/lib/floriday-schema-to-zod.d.ts +9 -0
- package/dist-lib/lib/floriday-schema-to-zod.d.ts.map +1 -0
- package/dist-lib/lib/floriday-schema-to-zod.js +53 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +19998 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-writes.functions.js +74 -0
- package/dist-lib/lib/image-resize.d.ts +43 -0
- package/dist-lib/lib/image-resize.d.ts.map +1 -0
- package/dist-lib/lib/image-resize.js +212 -0
- package/dist-lib/lib/normalize-errors-middleware.d.ts +2 -0
- package/dist-lib/lib/normalize-errors-middleware.d.ts.map +1 -0
- package/dist-lib/lib/normalize-errors-middleware.js +24 -0
- package/dist-lib/lib/wizard-step-fields.d.ts +15 -0
- package/dist-lib/lib/wizard-step-fields.d.ts.map +1 -0
- package/dist-lib/lib/wizard-step-fields.js +51 -0
- package/package.json +35 -14
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Card } from "@flowselections/core";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
5
|
+
import { ArtikelWizard } from "./ArtikelWizard";
|
|
6
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
7
|
+
export function EditArtikelPage({ id }) {
|
|
8
|
+
const [resolved, setResolved] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let cancelled = false;
|
|
11
|
+
setResolved(null);
|
|
12
|
+
(async () => {
|
|
13
|
+
const draftRes = await supabase
|
|
14
|
+
.from("trade_item_drafts")
|
|
15
|
+
.select("id")
|
|
16
|
+
.eq("id", id)
|
|
17
|
+
.maybeSingle();
|
|
18
|
+
if (cancelled)
|
|
19
|
+
return;
|
|
20
|
+
if (draftRes.data) {
|
|
21
|
+
setResolved({ kind: "draft", id });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const pubRes = await supabase
|
|
25
|
+
.from("floriday_trade_items_cache")
|
|
26
|
+
.select("floriday_id")
|
|
27
|
+
.eq("floriday_id", id)
|
|
28
|
+
.maybeSingle();
|
|
29
|
+
if (cancelled)
|
|
30
|
+
return;
|
|
31
|
+
if (pubRes.data) {
|
|
32
|
+
setResolved({ kind: "published", id });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setResolved({ kind: "notfound" });
|
|
36
|
+
})();
|
|
37
|
+
return () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
};
|
|
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..."] }) }));
|
|
43
|
+
}
|
|
44
|
+
if (resolved.kind === "notfound") {
|
|
45
|
+
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." }) }));
|
|
46
|
+
}
|
|
47
|
+
if (resolved.kind === "draft") {
|
|
48
|
+
return _jsx(ArtikelWizard, { draftId: resolved.id });
|
|
49
|
+
}
|
|
50
|
+
return _jsx(ArtikelWizard, { tradeItemId: resolved.id });
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,4CAE/B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TradeItem } from "../../lib/floriday-client";
|
|
2
|
+
export declare function StockBulkButtons({ items, activeConnectionId, onImported, }: {
|
|
3
|
+
items: TradeItem[];
|
|
4
|
+
activeConnectionId: string | null;
|
|
5
|
+
onImported: () => void | Promise<void>;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=StockBulkButtons.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useRef, useState } from "react";
|
|
3
|
+
import * as XLSX from "xlsx";
|
|
4
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Checkbox, Label, toast, } from "@flowselections/core";
|
|
5
|
+
import { Download, FileDown, Upload, Loader2 } from "lucide-react";
|
|
6
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
7
|
+
import { emptyWizardData } from "../../lib/floriday-payload";
|
|
8
|
+
import { upsertLocalTradeItem, updateDraftMeta } from "../../hooks/useLocalTradeItems";
|
|
9
|
+
import { validateDraftForPublish } from "../../lib/floriday-import-validator";
|
|
10
|
+
import { floriday } from "../../lib/floriday-client";
|
|
11
|
+
const COLUMNS = [
|
|
12
|
+
"Artikelcode",
|
|
13
|
+
"Artikelnaam",
|
|
14
|
+
"VBN-code",
|
|
15
|
+
"Botanische naam",
|
|
16
|
+
"Land van oorsprong",
|
|
17
|
+
"GTIN",
|
|
18
|
+
"Liggende voorraad",
|
|
19
|
+
"Minimum voorraad",
|
|
20
|
+
"Gereserveerde voorraad",
|
|
21
|
+
"Inkomende voorraad",
|
|
22
|
+
];
|
|
23
|
+
function toKey(item) {
|
|
24
|
+
return item.localId ? `draft:${item.localId}` : item.id;
|
|
25
|
+
}
|
|
26
|
+
function parseNumber(val) {
|
|
27
|
+
if (val === undefined || val === null || val === "")
|
|
28
|
+
return { value: null };
|
|
29
|
+
const n = typeof val === "number" ? val : Number(String(val).replace(",", "."));
|
|
30
|
+
if (!Number.isFinite(n))
|
|
31
|
+
return { value: null, error: "geen numerieke waarde" };
|
|
32
|
+
return { value: n };
|
|
33
|
+
}
|
|
34
|
+
function productGroupForVbn(vbn) {
|
|
35
|
+
const s = vbn.trim();
|
|
36
|
+
if (s.startsWith("10"))
|
|
37
|
+
return "CUT_FLOWER";
|
|
38
|
+
if (s.startsWith("20"))
|
|
39
|
+
return "POT_PLANT";
|
|
40
|
+
if (s.startsWith("30"))
|
|
41
|
+
return "GARDEN_PLANT";
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
45
|
+
const fileRef = useRef(null);
|
|
46
|
+
const [open, setOpen] = useState(false);
|
|
47
|
+
const [importing, setImporting] = useState(false);
|
|
48
|
+
const [progress, setProgress] = useState(0);
|
|
49
|
+
const allowNegative = true;
|
|
50
|
+
const [autoPublish, setAutoPublish] = useState(false);
|
|
51
|
+
const [result, setResult] = useState(null);
|
|
52
|
+
const byCode = useMemo(() => {
|
|
53
|
+
const map = new Map();
|
|
54
|
+
for (const it of items) {
|
|
55
|
+
const code = (it.articleNumber ?? "").trim();
|
|
56
|
+
if (code)
|
|
57
|
+
map.set(code, it);
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
}, [items]);
|
|
61
|
+
/** Published Floriday items only (voor auto-koppeling van nieuwe artikelen). */
|
|
62
|
+
const publishedByCode = useMemo(() => {
|
|
63
|
+
const map = new Map();
|
|
64
|
+
for (const it of items) {
|
|
65
|
+
if (!it.published)
|
|
66
|
+
continue;
|
|
67
|
+
const code = (it.articleNumber ?? "").trim();
|
|
68
|
+
if (!code)
|
|
69
|
+
continue;
|
|
70
|
+
const arr = map.get(code) ?? [];
|
|
71
|
+
arr.push(it);
|
|
72
|
+
map.set(code, arr);
|
|
73
|
+
}
|
|
74
|
+
return map;
|
|
75
|
+
}, [items]);
|
|
76
|
+
const downloadTemplate = () => {
|
|
77
|
+
const rows = items.map((i) => {
|
|
78
|
+
const d = i.raw?.data;
|
|
79
|
+
return {
|
|
80
|
+
Artikelcode: i.articleNumber ?? "",
|
|
81
|
+
Artikelnaam: i.name,
|
|
82
|
+
"VBN-code": d?.vbnCode ?? i.raw?.vbnProductCode ?? "",
|
|
83
|
+
"Botanische naam": d?.botanicalName ?? "",
|
|
84
|
+
"Land van oorsprong": d?.countryOfOrigin ?? "",
|
|
85
|
+
GTIN: d?.gtin ?? "",
|
|
86
|
+
"Liggende voorraad": i.quantity,
|
|
87
|
+
"Minimum voorraad": i.minQuantity,
|
|
88
|
+
"Gereserveerde voorraad": i.reservedQuantity,
|
|
89
|
+
"Inkomende voorraad": i.incomingQuantity,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
const ws = XLSX.utils.json_to_sheet(rows, { header: [...COLUMNS] });
|
|
93
|
+
ws["!cols"] = COLUMNS.map((c) => ({
|
|
94
|
+
wch: c === "Artikelnaam" ? 40 : c === "Artikelcode" ? 18 : 20,
|
|
95
|
+
}));
|
|
96
|
+
const wb = XLSX.utils.book_new();
|
|
97
|
+
XLSX.utils.book_append_sheet(wb, ws, "Voorraad");
|
|
98
|
+
const stamp = new Date().toISOString().slice(0, 10);
|
|
99
|
+
XLSX.writeFile(wb, `voorraad-template-${stamp}.xlsx`);
|
|
100
|
+
};
|
|
101
|
+
const downloadErrors = () => {
|
|
102
|
+
if (!result?.errors.length)
|
|
103
|
+
return;
|
|
104
|
+
const rows = result.errors.map((e) => ({
|
|
105
|
+
Regel: e.row,
|
|
106
|
+
Artikelcode: e.articleCode,
|
|
107
|
+
Artikelnaam: e.articleName,
|
|
108
|
+
Fout: e.reden,
|
|
109
|
+
}));
|
|
110
|
+
const ws = XLSX.utils.json_to_sheet(rows);
|
|
111
|
+
const wb = XLSX.utils.book_new();
|
|
112
|
+
XLSX.utils.book_append_sheet(wb, ws, "Fouten");
|
|
113
|
+
XLSX.writeFile(wb, `voorraad-import-fouten-${new Date().toISOString().slice(0, 10)}.xlsx`);
|
|
114
|
+
};
|
|
115
|
+
const handleFile = async (file) => {
|
|
116
|
+
if (!activeConnectionId) {
|
|
117
|
+
toast.error("Geen actieve Floriday-connectie");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
setImporting(true);
|
|
121
|
+
setProgress(0);
|
|
122
|
+
setResult(null);
|
|
123
|
+
const errors = [];
|
|
124
|
+
let updated = 0;
|
|
125
|
+
let created = 0;
|
|
126
|
+
let linked = 0;
|
|
127
|
+
let published = 0;
|
|
128
|
+
let waiting = 0;
|
|
129
|
+
try {
|
|
130
|
+
const buf = await file.arrayBuffer();
|
|
131
|
+
const wb = XLSX.read(buf, { type: "array" });
|
|
132
|
+
const sheet = wb.Sheets[wb.SheetNames[0]];
|
|
133
|
+
const rows = XLSX.utils.sheet_to_json(sheet, {
|
|
134
|
+
defval: "",
|
|
135
|
+
});
|
|
136
|
+
const userRes = await supabase.auth.getUser();
|
|
137
|
+
const userId = userRes.data.user?.id;
|
|
138
|
+
if (!userId)
|
|
139
|
+
throw new Error("Niet ingelogd");
|
|
140
|
+
// "Nieuw sinds laatste import" — timestamp opslaan
|
|
141
|
+
try {
|
|
142
|
+
localStorage.setItem(`voorraad_last_import_${activeConnectionId}`, new Date().toISOString());
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* ignore quota */
|
|
146
|
+
}
|
|
147
|
+
const stockBatch = [];
|
|
148
|
+
for (let idx = 0; idx < rows.length; idx++) {
|
|
149
|
+
const row = rows[idx];
|
|
150
|
+
const excelRow = idx + 2;
|
|
151
|
+
const code = String(row["Artikelcode"] ?? "").trim();
|
|
152
|
+
const name = String(row["Artikelnaam"] ?? "").trim();
|
|
153
|
+
if (!code) {
|
|
154
|
+
errors.push({
|
|
155
|
+
row: excelRow,
|
|
156
|
+
articleCode: "",
|
|
157
|
+
articleName: name,
|
|
158
|
+
reden: "Artikelcode ontbreekt",
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Voorraadvelden parsen
|
|
163
|
+
const stockFields = [
|
|
164
|
+
["physical_stock", "Liggende voorraad"],
|
|
165
|
+
["minimum_stock", "Minimum voorraad"],
|
|
166
|
+
["reserved_stock", "Gereserveerde voorraad"],
|
|
167
|
+
["incoming_stock", "Inkomende voorraad"],
|
|
168
|
+
];
|
|
169
|
+
const stockValues = {};
|
|
170
|
+
let stockError = null;
|
|
171
|
+
for (const [key, col] of stockFields) {
|
|
172
|
+
const parsed = parseNumber(row[col]);
|
|
173
|
+
if (parsed.error) {
|
|
174
|
+
stockError = `${col}: ${parsed.error}`;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (parsed.value !== null && !allowNegative && parsed.value < 0) {
|
|
178
|
+
stockError = `${col}: negatieve waarde niet toegestaan`;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
stockValues[key] = parsed.value;
|
|
182
|
+
}
|
|
183
|
+
if (stockError) {
|
|
184
|
+
errors.push({
|
|
185
|
+
row: excelRow,
|
|
186
|
+
articleCode: code,
|
|
187
|
+
articleName: name,
|
|
188
|
+
reden: stockError,
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const existing = byCode.get(code);
|
|
193
|
+
// === Bestaand artikel: alleen stock bijwerken ===
|
|
194
|
+
if (existing) {
|
|
195
|
+
const applied = {
|
|
196
|
+
physical_stock: stockValues.physical_stock ?? existing.quantity,
|
|
197
|
+
minimum_stock: stockValues.minimum_stock ?? existing.minQuantity,
|
|
198
|
+
reserved_stock: stockValues.reserved_stock ?? existing.reservedQuantity,
|
|
199
|
+
incoming_stock: stockValues.incoming_stock ?? existing.incomingQuantity,
|
|
200
|
+
};
|
|
201
|
+
const connId = existing.raw?.connection_id ?? activeConnectionId;
|
|
202
|
+
stockBatch.push({
|
|
203
|
+
user_id: userId,
|
|
204
|
+
connection_id: connId,
|
|
205
|
+
trade_item_key: toKey(existing),
|
|
206
|
+
...applied,
|
|
207
|
+
});
|
|
208
|
+
updated++;
|
|
209
|
+
setProgress(Math.round(((idx + 1) / rows.length) * 100));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
// === Nieuw artikel aanmaken ===
|
|
213
|
+
const vbnCode = String(row["VBN-code"] ?? "").trim();
|
|
214
|
+
const botanical = String(row["Botanische naam"] ?? "").trim();
|
|
215
|
+
const country = String(row["Land van oorsprong"] ?? "")
|
|
216
|
+
.trim()
|
|
217
|
+
.toUpperCase();
|
|
218
|
+
const gtin = String(row["GTIN"] ?? "").trim();
|
|
219
|
+
const data = {
|
|
220
|
+
...emptyWizardData,
|
|
221
|
+
articleCode: code,
|
|
222
|
+
name: name || code,
|
|
223
|
+
vbnCode,
|
|
224
|
+
vbnName: "",
|
|
225
|
+
productGroup: productGroupForVbn(vbnCode),
|
|
226
|
+
botanicalName: botanical,
|
|
227
|
+
countryOfOrigin: country,
|
|
228
|
+
gtin,
|
|
229
|
+
};
|
|
230
|
+
// Zoek in cache van gepubliceerde Floriday-items
|
|
231
|
+
const matches = publishedByCode.get(code) ?? [];
|
|
232
|
+
let linkStatus = "unlinked";
|
|
233
|
+
let floridayId = null;
|
|
234
|
+
let publishError = null;
|
|
235
|
+
if (matches.length === 1) {
|
|
236
|
+
linkStatus = "linked";
|
|
237
|
+
floridayId = matches[0].id;
|
|
238
|
+
linked++;
|
|
239
|
+
}
|
|
240
|
+
else if (matches.length > 1) {
|
|
241
|
+
linkStatus = "ambiguous_match";
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Geen match — evalueer validatie
|
|
245
|
+
const validation = validateDraftForPublish(data);
|
|
246
|
+
if (validation.ok && autoPublish) {
|
|
247
|
+
// Poging tot publiceren
|
|
248
|
+
try {
|
|
249
|
+
const { buildAddTradeItemPayload } = await import("../../lib/floriday-payload");
|
|
250
|
+
const payload = buildAddTradeItemPayload(data);
|
|
251
|
+
const res = await floriday.createTradeItem(activeConnectionId, payload);
|
|
252
|
+
floridayId = res?.tradeItemId ?? null;
|
|
253
|
+
linkStatus = "published";
|
|
254
|
+
published++;
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
publishError = e?.message ?? String(e);
|
|
258
|
+
linkStatus = "publish_failed";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (validation.ok) {
|
|
262
|
+
linkStatus = "ready_to_publish";
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
linkStatus = "waiting_for_data";
|
|
266
|
+
waiting++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Maak draft aan
|
|
270
|
+
try {
|
|
271
|
+
const validation = validateDraftForPublish(data);
|
|
272
|
+
const draftId = await upsertLocalTradeItem({
|
|
273
|
+
connectionId: activeConnectionId,
|
|
274
|
+
data,
|
|
275
|
+
meta: {
|
|
276
|
+
floridayLinkStatus: linkStatus,
|
|
277
|
+
floridayId,
|
|
278
|
+
floridayLinkError: publishError,
|
|
279
|
+
missingRequiredFields: validation.missing,
|
|
280
|
+
lastFloridaySyncAt: new Date().toISOString(),
|
|
281
|
+
source: "excel_import",
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
created++;
|
|
285
|
+
// Stock koppelen aan de nieuwe draft
|
|
286
|
+
const anyStock = stockValues.physical_stock !== null ||
|
|
287
|
+
stockValues.minimum_stock !== null ||
|
|
288
|
+
stockValues.reserved_stock !== null ||
|
|
289
|
+
stockValues.incoming_stock !== null;
|
|
290
|
+
if (anyStock) {
|
|
291
|
+
stockBatch.push({
|
|
292
|
+
user_id: userId,
|
|
293
|
+
connection_id: activeConnectionId,
|
|
294
|
+
trade_item_key: `draft:${draftId}`,
|
|
295
|
+
physical_stock: stockValues.physical_stock ?? 0,
|
|
296
|
+
minimum_stock: stockValues.minimum_stock ?? 0,
|
|
297
|
+
reserved_stock: stockValues.reserved_stock ?? 0,
|
|
298
|
+
incoming_stock: stockValues.incoming_stock ?? 0,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
errors.push({
|
|
304
|
+
row: excelRow,
|
|
305
|
+
articleCode: code,
|
|
306
|
+
articleName: name,
|
|
307
|
+
reden: `Aanmaken mislukt: ${e?.message ?? e}`,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
setProgress(Math.round(((idx + 1) / rows.length) * 100));
|
|
311
|
+
}
|
|
312
|
+
// Stock batch upserten
|
|
313
|
+
const CHUNK = 500;
|
|
314
|
+
for (let i = 0; i < stockBatch.length; i += CHUNK) {
|
|
315
|
+
const slice = stockBatch.slice(i, i + CHUNK);
|
|
316
|
+
const { error } = await supabase
|
|
317
|
+
.from("trade_item_stock")
|
|
318
|
+
.upsert(slice, { onConflict: "connection_id,trade_item_key" });
|
|
319
|
+
if (error) {
|
|
320
|
+
errors.push({
|
|
321
|
+
row: 0,
|
|
322
|
+
articleCode: "(voorraad-batch)",
|
|
323
|
+
articleName: "",
|
|
324
|
+
reden: `Database-fout: ${error.message}`,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
setResult({ updated, created, linked, published, waiting, errors });
|
|
329
|
+
if (updated + created > 0) {
|
|
330
|
+
toast.success(`${updated} bijgewerkt · ${created} nieuw`);
|
|
331
|
+
await onImported();
|
|
332
|
+
}
|
|
333
|
+
if (errors.length && updated + created === 0) {
|
|
334
|
+
toast.error("Geen artikelen verwerkt");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
toast.error(`Import mislukt: ${e.message}`);
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
setImporting(false);
|
|
342
|
+
if (fileRef.current)
|
|
343
|
+
fileRef.current.value = "";
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
// Silence unused-import warning if updateDraftMeta remains for later use
|
|
347
|
+
void updateDraftMeta;
|
|
348
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: downloadTemplate, disabled: items.length === 0, children: [_jsx(FileDown, { className: "mr-2 h-4 w-4" }), "Template downloaden"] }), _jsxs(Button, { variant: "outline", onClick: () => setOpen(true), children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), "Voorraad importeren"] }), _jsx(Dialog, { open: open, onOpenChange: (o) => !importing && setOpen(o), children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Voorraad importeren" }), _jsxs(DialogDescription, { children: ["Upload een Excel-bestand (op basis van de template). Bestaande artikelen worden gematcht op ", _jsx("strong", { children: "Artikelcode" }), " en alleen de voorraadvelden worden bijgewerkt. Onbekende artikelcodes worden als nieuwe artikelen aangemaakt en waar mogelijk automatisch gekoppeld aan Floriday."] })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "auto-publish", checked: autoPublish, onCheckedChange: (v) => setAutoPublish(Boolean(v)), disabled: importing }), _jsx(Label, { htmlFor: "auto-publish", className: "text-sm font-normal", children: "Nieuwe producten automatisch naar Floriday publiceren (indien alle verplichte gegevens aanwezig zijn)" })] }), _jsx("input", { ref: fileRef, type: "file", accept: ".xlsx,.xls", className: "block w-full text-sm file:mr-3 file:rounded-md file:border-0 file:bg-primary file:px-3 file:py-2 file:text-primary-foreground hover:file:bg-primary/90 disabled:opacity-50", disabled: importing, onChange: (e) => {
|
|
349
|
+
const f = e.target.files?.[0];
|
|
350
|
+
if (f)
|
|
351
|
+
void handleFile(f);
|
|
352
|
+
} }), importing && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded bg-muted", children: _jsx("div", { className: "h-full bg-primary transition-all", style: { width: `${progress}%` } }) }), _jsxs("p", { className: "text-xs text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Bezig met importeren\u2026 ", progress, "%"] })] })), result && (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm space-y-2", children: [_jsx("div", { className: "font-medium", children: "Importoverzicht" }), _jsxs("ul", { className: "text-xs space-y-1", children: [_jsxs("li", { children: ["Bestaande artikelen bijgewerkt:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.updated })] }), _jsxs("li", { children: ["Nieuwe artikelen aangemaakt:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.created })] }), _jsxs("li", { children: ["Automatisch gekoppeld met Floriday:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.linked })] }), _jsxs("li", { children: ["Gepubliceerd naar Floriday:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.published })] }), _jsxs("li", { children: ["Wacht op Floriday-publicatie (gegevens incompleet):", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.waiting })] }), _jsxs("li", { children: ["Fouten:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.errors.length })] })] }), result.errors.length > 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "max-h-40 overflow-y-auto rounded border bg-background p-2 text-xs space-y-1", children: [result.errors.slice(0, 20).map((e, i) => (_jsxs("div", { className: "text-destructive", children: ["Regel ", e.row, " \u2014 ", e.articleCode || "(geen code)", " \u2014", " ", e.reden] }, i))), result.errors.length > 20 && (_jsxs("div", { className: "text-muted-foreground", children: ["\u2026 en ", result.errors.length - 20, " meer. Download het foutbestand voor de volledige lijst."] }))] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: downloadErrors, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), "Download foutbestand"] })] }))] }))] }), _jsx(DialogFooter, { children: _jsx(Button, { variant: "ghost", onClick: () => {
|
|
353
|
+
setOpen(false);
|
|
354
|
+
setResult(null);
|
|
355
|
+
}, disabled: importing, children: "Sluiten" }) })] }) })] }));
|
|
356
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TradeItem } from "../../lib/floriday-client";
|
|
2
|
+
export declare function ValidationOverviewTable({ items, connectionId, onChanged, }: {
|
|
3
|
+
items: TradeItem[];
|
|
4
|
+
connectionId: string | null;
|
|
5
|
+
onChanged: () => void | Promise<void>;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=ValidationOverviewTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,2BAA2B,CAAC;AAwB/E,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,2CA0IA"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
4
|
+
import { Badge, Button } from "@flowselections/core";
|
|
5
|
+
import { Pencil, RefreshCw, Send } from "lucide-react";
|
|
6
|
+
import { validateDraftForPublish } from "../../lib/floriday-import-validator";
|
|
7
|
+
import { updateDraftMeta } from "../../hooks/useLocalTradeItems";
|
|
8
|
+
import { floriday } from "../../lib/floriday-client";
|
|
9
|
+
import { toast } from "@flowselections/core";
|
|
10
|
+
function statusBadge(s) {
|
|
11
|
+
switch (s) {
|
|
12
|
+
case "linked":
|
|
13
|
+
case "published":
|
|
14
|
+
return _jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Gekoppeld" });
|
|
15
|
+
case "ambiguous_match":
|
|
16
|
+
return _jsx(Badge, { className: "bg-amber-500/15 text-amber-700 border-amber-500/30", children: "Meerdere matches" });
|
|
17
|
+
case "waiting_for_data":
|
|
18
|
+
return _jsx(Badge, { className: "bg-amber-500/15 text-amber-700 border-amber-500/30", children: "Wacht op gegevens" });
|
|
19
|
+
case "ready_to_publish":
|
|
20
|
+
return _jsx(Badge, { className: "bg-blue-500/15 text-blue-700 border-blue-500/30", children: "Klaar voor publicatie" });
|
|
21
|
+
case "publish_failed":
|
|
22
|
+
return _jsx(Badge, { variant: "destructive", children: "Publicatie mislukt" });
|
|
23
|
+
default:
|
|
24
|
+
return _jsx(Badge, { variant: "outline", children: "Niet gekoppeld" });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
28
|
+
const navigate = useNavigate();
|
|
29
|
+
const rows = useMemo(() => items.filter((i) => !i.published &&
|
|
30
|
+
i.linkStatus !== "linked" &&
|
|
31
|
+
i.linkStatus !== "published"), [items]);
|
|
32
|
+
const revalidate = async (item) => {
|
|
33
|
+
if (!item.localId)
|
|
34
|
+
return;
|
|
35
|
+
const d = item.raw?.data ?? {};
|
|
36
|
+
const v = validateDraftForPublish(d);
|
|
37
|
+
try {
|
|
38
|
+
await updateDraftMeta(item.localId, {
|
|
39
|
+
missingRequiredFields: v.missing,
|
|
40
|
+
floridayLinkStatus: v.ok ? "ready_to_publish" : "waiting_for_data",
|
|
41
|
+
lastFloridaySyncAt: new Date().toISOString(),
|
|
42
|
+
});
|
|
43
|
+
toast.success("Validatie bijgewerkt");
|
|
44
|
+
await onChanged();
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
toast.error(e?.message ?? "Bijwerken mislukt");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const republish = async (item) => {
|
|
51
|
+
if (!item.localId || !connectionId)
|
|
52
|
+
return;
|
|
53
|
+
const d = item.raw?.data;
|
|
54
|
+
const v = validateDraftForPublish(d);
|
|
55
|
+
if (!v.ok) {
|
|
56
|
+
toast.error("Nog niet compleet: " + v.missing.join(", "));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const { buildAddTradeItemPayload } = await import("../../lib/floriday-payload");
|
|
61
|
+
const payload = buildAddTradeItemPayload(d);
|
|
62
|
+
const res = await floriday.createTradeItem(connectionId, payload);
|
|
63
|
+
await updateDraftMeta(item.localId, {
|
|
64
|
+
floridayLinkStatus: "published",
|
|
65
|
+
floridayId: res?.tradeItemId ?? null,
|
|
66
|
+
floridayLinkError: null,
|
|
67
|
+
lastFloridaySyncAt: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
toast.success("Gepubliceerd naar Floriday");
|
|
70
|
+
await onChanged();
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
await updateDraftMeta(item.localId, {
|
|
74
|
+
floridayLinkStatus: "publish_failed",
|
|
75
|
+
floridayLinkError: e?.message ?? String(e),
|
|
76
|
+
lastFloridaySyncAt: new Date().toISOString(),
|
|
77
|
+
});
|
|
78
|
+
toast.error(e?.message ?? "Publicatie mislukt");
|
|
79
|
+
await onChanged();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
if (rows.length === 0) {
|
|
83
|
+
return (_jsx("div", { className: "p-8 text-center text-sm text-muted-foreground", children: "Alle artikelen zijn gekoppeld aan Floriday." }));
|
|
84
|
+
}
|
|
85
|
+
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", children: "Artikelcode" }), _jsx("th", { className: "p-3", children: "Artikelnaam" }), _jsx("th", { className: "p-3", children: "Status" }), _jsx("th", { className: "p-3", children: "Ontbrekende gegevens" }), _jsx("th", { className: "p-3", children: "Laatste sync" }), _jsx("th", { className: "p-3", children: "Foutmelding" }), _jsx("th", { className: "p-3 text-right", children: "Acties" })] }) }), _jsx("tbody", { children: rows.map((it) => (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-3 font-mono text-xs", children: it.articleNumber || "—" }), _jsx("td", { className: "p-3", children: it.name }), _jsx("td", { className: "p-3", children: statusBadge(it.linkStatus) }), _jsx("td", { className: "p-3", children: (it.missingFields ?? []).length === 0 ? (_jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })) : (_jsx("div", { className: "flex flex-wrap gap-1", children: (it.missingFields ?? []).map((m) => (_jsx(Badge, { variant: "outline", className: "text-[10px]", children: m }, m))) })) }), _jsx("td", { className: "p-3 text-xs text-muted-foreground", children: it.lastSyncAt ? new Date(it.lastSyncAt).toLocaleString("nl-NL") : "—" }), _jsx("td", { className: "p-3 text-xs text-destructive max-w-[240px] truncate", children: it.linkError ?? "—" }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => it.localId &&
|
|
86
|
+
navigate({ to: "/voorraad/$id", params: { id: it.localId } }), children: [_jsx(Pencil, { className: "h-3 w-3 mr-1" }), "Bewerken"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => revalidate(it), children: [_jsx(RefreshCw, { className: "h-3 w-3 mr-1" }), "Valideer"] }), _jsxs(Button, { size: "sm", onClick: () => republish(it), children: [_jsx(Send, { className: "h-3 w-3 mr-1" }), "Publiceer"] })] }) })] }, it.id))) })] }) }));
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ValidationPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationPage.tsx"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,4CAiC7B"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from "@tanstack/react-router";
|
|
3
|
+
import { Card } from "@flowselections/core";
|
|
4
|
+
import { ArrowLeft } from "lucide-react";
|
|
5
|
+
import { useFloridayConnection } from "../../hooks/useFloridayConnection";
|
|
6
|
+
import { useVoorraadData } from "../../hooks/useVoorraadData";
|
|
7
|
+
import { ValidationOverviewTable } from "./ValidationOverviewTable";
|
|
8
|
+
export function ValidationPage() {
|
|
9
|
+
const { activeId } = useFloridayConnection();
|
|
10
|
+
const { items, loading, reload } = useVoorraadData(activeId);
|
|
11
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Floriday-koppeling \u2014 validatie" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alle artikelen die nog niet succesvol met Floriday gekoppeld zijn." })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(ValidationOverviewTable, { items: items, connectionId: activeId, onChanged: reload })) })] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TradeItem } from "../../lib/floriday-client";
|
|
2
|
+
export declare function VoorraadDashboard({ items, connectionId, }: {
|
|
3
|
+
items: TradeItem[];
|
|
4
|
+
connectionId: string | null;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=VoorraadDashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VoorraadDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadDashboard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AA2B3D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,YAAY,GACb,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,2CA4DA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from "@tanstack/react-router";
|
|
3
|
+
import { Card } from "@flowselections/core";
|
|
4
|
+
function Stat({ label, value, tone = "default", }) {
|
|
5
|
+
const toneCls = tone === "danger"
|
|
6
|
+
? "text-destructive"
|
|
7
|
+
: tone === "warn"
|
|
8
|
+
? "text-amber-600"
|
|
9
|
+
: tone === "good"
|
|
10
|
+
? "text-emerald-600"
|
|
11
|
+
: "text-foreground";
|
|
12
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: label }), _jsx("div", { className: `text-2xl font-semibold tabular-nums ${toneCls}`, children: value })] }));
|
|
13
|
+
}
|
|
14
|
+
export function VoorraadDashboard({ items, connectionId, }) {
|
|
15
|
+
const total = items.length;
|
|
16
|
+
const linked = items.filter((i) => i.published).length +
|
|
17
|
+
items.filter((i) => !i.published && i.linkStatus === "linked").length;
|
|
18
|
+
const waiting = items.filter((i) => !i.published && i.linkStatus === "waiting_for_data").length;
|
|
19
|
+
const validationErrors = items.filter((i) => !i.published && (i.missingFields?.length ?? 0) > 0 && i.linkStatus !== "linked").length;
|
|
20
|
+
const publishErrors = items.filter((i) => !i.published && i.linkStatus === "publish_failed").length;
|
|
21
|
+
// Nieuw sinds laatste import
|
|
22
|
+
let lastImport = null;
|
|
23
|
+
try {
|
|
24
|
+
if (connectionId) {
|
|
25
|
+
const s = localStorage.getItem(`voorraad_last_import_${connectionId}`);
|
|
26
|
+
if (s)
|
|
27
|
+
lastImport = new Date(s);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* ignore */
|
|
32
|
+
}
|
|
33
|
+
const newSinceImport = lastImport
|
|
34
|
+
? items.filter((i) => {
|
|
35
|
+
const raw = i.raw;
|
|
36
|
+
const src = raw?.source ?? raw?.data?.source;
|
|
37
|
+
const created = raw?.created_at ? new Date(raw.created_at) : null;
|
|
38
|
+
return src === "excel_import" && created && created >= lastImport;
|
|
39
|
+
}).length
|
|
40
|
+
: 0;
|
|
41
|
+
return (_jsxs(Card, { className: "p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Floriday-koppeling overzicht" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Status van alle artikelen ten opzichte van Floriday" })] }), _jsx(Link, { to: "/voorraad/validatie", className: "text-xs text-primary hover:underline", children: "Openstaande artikelen bekijken \u2192" })] }), _jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4", children: [_jsx(Stat, { label: "Artikelen", value: total }), _jsx(Stat, { label: "Gekoppeld", value: linked, tone: "good" }), _jsx(Stat, { label: "Wacht op koppeling", value: waiting, tone: "warn" }), _jsx(Stat, { label: "Validatiefouten", value: validationErrors, tone: "warn" }), _jsx(Stat, { label: "Publicatiefouten", value: publishErrors, tone: "danger" }), _jsx(Stat, { label: "Nieuw sinds import", value: newSinceImport })] })] }));
|
|
42
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,YAAY,4CAiM3B"}
|