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