@flowselections/floriday-voorraad 1.0.20 → 1.0.21
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/ArtikelCompositionCard.d.ts +7 -0
- package/dist-lib/components/voorraad/ArtikelCompositionCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ArtikelCompositionCard.js +196 -0
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts +2 -1
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +259 -26
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/EditArtikelPage.js +28 -85
- package/dist-lib/components/voorraad/FloridayStatusBadge.d.ts +4 -0
- package/dist-lib/components/voorraad/FloridayStatusBadge.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FloridayStatusBadge.js +8 -0
- package/dist-lib/components/voorraad/StockBulkButtons.js +7 -6
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +5 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +26 -56
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts +4 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadDashboard.js +14 -6
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +11 -0
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +5 -12
- package/dist-lib/hooks/useFloridayContinuousStock.d.ts +13 -0
- package/dist-lib/hooks/useFloridayContinuousStock.d.ts.map +1 -0
- package/dist-lib/hooks/useFloridayContinuousStock.js +109 -0
- package/dist-lib/hooks/useFlorydayCustomers.d.ts +11 -4
- package/dist-lib/hooks/useFlorydayCustomers.d.ts.map +1 -1
- package/dist-lib/hooks/useFlorydayCustomers.js +85 -38
- package/dist-lib/hooks/useLocalTradeItems.d.ts +26 -2
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +119 -79
- package/dist-lib/hooks/useVoorraadData.d.ts +5 -5
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +127 -50
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +662 -168
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +662 -168
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +662 -168
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +678 -163
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +662 -168
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.d.ts +29 -12
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.js +36 -0
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +662 -168
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +3220 -750
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.d.ts +14 -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.map +1 -1
- package/dist-lib/lib/floriday-payload.js +32 -8
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +1932 -450
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +1333 -345
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/merge-products.d.ts +8 -0
- package/dist-lib/lib/merge-products.d.ts.map +1 -0
- package/dist-lib/lib/merge-products.js +17 -0
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ import { useFlorydayCustomers } from "../../hooks/useFlorydayCustomers";
|
|
|
8
8
|
import { floriday } from "../../lib/floriday-client";
|
|
9
9
|
import { listFloricode } from "../../lib/floricode-client";
|
|
10
10
|
import { supabase } from "../../integrations/supabase/client";
|
|
11
|
-
import { buildAddTradeItemPayload, buildUpdateTradeItemPayload, buildPrimaryPackingBase, emptyWizardData, floridayCacheToWizardData, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
|
|
11
|
+
import { buildAddTradeItemPayload, buildUpdateTradeItemPayload, buildPrimaryPackingBase, emptyWizardData, floridayCacheToWizardData, isValidGtin13, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
|
|
12
12
|
import { AdditionalPackagingDialog } from "./AdditionalPackagingDialog";
|
|
13
13
|
import { publishCustomerOfferFn } from "../../lib/floriday-customer-offer.functions";
|
|
14
14
|
import { upsertSupplyLineForTradeItem } from "../../lib/floriday-supply-line";
|
|
@@ -21,6 +21,7 @@ import { VoorraadStockCard } from "../../components/voorraad/VoorraadStockCard";
|
|
|
21
21
|
import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
|
|
22
22
|
import { PriceStatusBadge } from "../../components/voorraad/PriceStatusBadge";
|
|
23
23
|
import { OrderHistoryCard } from "../../components/voorraad/OrderHistoryCard";
|
|
24
|
+
import { ArtikelCompositionCard } from "../../components/voorraad/ArtikelCompositionCard";
|
|
24
25
|
import { stockKeyFor } from "../../hooks/useTradeItemStock";
|
|
25
26
|
const empty = emptyWizardData;
|
|
26
27
|
const LOAD_CARRIER_LABELS = {
|
|
@@ -36,12 +37,12 @@ const STEPS = [
|
|
|
36
37
|
{ key: "artikel", label: "Artikel" },
|
|
37
38
|
{ key: "paspoort", label: "Plantenpaspoort", onlyForPlants: true },
|
|
38
39
|
{ key: "eigenschappen", label: "Eigenschappen" },
|
|
39
|
-
{ key: "klanten", label: "Klanten" },
|
|
40
|
+
{ key: "klanten", label: "Klanten", onlyForCustomerSpecific: true },
|
|
40
41
|
{ key: "fotos", label: "Foto's" },
|
|
41
42
|
{ key: "warehouse", label: "Warehouse" },
|
|
42
43
|
{ key: "beladingen", label: "Beladingen" },
|
|
43
44
|
{ key: "verpakking", label: "Aanvullende verpakkingsmaterialen" },
|
|
44
|
-
{ key: "samenstelling", label: "Samenstelling"
|
|
45
|
+
{ key: "samenstelling", label: "Samenstelling" },
|
|
45
46
|
{ key: "beschikbaarheid", label: "Verwachte beschikbaarheid" },
|
|
46
47
|
{ key: "aanbieden", label: "Aanbieden & voorraad" },
|
|
47
48
|
];
|
|
@@ -50,6 +51,8 @@ function visibleSteps(d) {
|
|
|
50
51
|
const meta = s;
|
|
51
52
|
if (meta.onlyForPlants && d.productGroup === "CUT_FLOWER")
|
|
52
53
|
return false;
|
|
54
|
+
if (meta.onlyForCustomerSpecific && d.articleType !== "CUSTOMER_SPECIFIC")
|
|
55
|
+
return false;
|
|
53
56
|
return true;
|
|
54
57
|
});
|
|
55
58
|
}
|
|
@@ -67,7 +70,7 @@ function warehouseSubLabel(raw) {
|
|
|
67
70
|
const city = addr.city ?? "";
|
|
68
71
|
return [line, city].filter(Boolean).join(", ") || undefined;
|
|
69
72
|
}
|
|
70
|
-
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
73
|
+
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, } = {}) {
|
|
71
74
|
const navigate = useNavigate();
|
|
72
75
|
const { activeId, activeConnection, loading: connLoading } = useFloridayConnection();
|
|
73
76
|
const { customers: floridayCustomers } = useFlorydayCustomers(activeId);
|
|
@@ -79,11 +82,13 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
79
82
|
const [draftFloridayId, setDraftFloridayId] = useState(null);
|
|
80
83
|
const [draftLoading, setDraftLoading] = useState(Boolean(initialDraftId));
|
|
81
84
|
const [publishedLoading, setPublishedLoading] = useState(Boolean(tradeItemId));
|
|
85
|
+
const [productLoading, setProductLoading] = useState(Boolean(productId));
|
|
82
86
|
const [syncState, setSyncState] = useState("idle");
|
|
83
87
|
const [syncError, setSyncError] = useState(null);
|
|
84
88
|
const isEditingDraft = Boolean(initialDraftId);
|
|
85
89
|
const isEditingPublished = Boolean(tradeItemId);
|
|
86
|
-
const
|
|
90
|
+
const isEditingProduct = Boolean(productId);
|
|
91
|
+
const isEditing = isEditingDraft || isEditingPublished || isEditingProduct;
|
|
87
92
|
const saveWarehousePreference = useCallback(async (targetTradeItemId, warehouseId) => {
|
|
88
93
|
if (!activeId || !targetTradeItemId || !warehouseId)
|
|
89
94
|
return;
|
|
@@ -147,6 +152,87 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
147
152
|
cancelled = true;
|
|
148
153
|
};
|
|
149
154
|
}, [initialDraftId, activeId]);
|
|
155
|
+
// Load existing ERP product directly into the same Floriday field wizard.
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!productId)
|
|
158
|
+
return;
|
|
159
|
+
let cancelled = false;
|
|
160
|
+
setProductLoading(true);
|
|
161
|
+
(async () => {
|
|
162
|
+
try {
|
|
163
|
+
const { data: product, error } = await supabase
|
|
164
|
+
.from("products")
|
|
165
|
+
.select("id,product,barcode,article_number,quantity,sale_price,image_url,plants_per_crate,crates_per_layer,layers_per_cart,floriday_active,floriday_trade_item_id,floriday_payload")
|
|
166
|
+
.eq("id", productId)
|
|
167
|
+
.maybeSingle();
|
|
168
|
+
if (cancelled)
|
|
169
|
+
return;
|
|
170
|
+
if (error)
|
|
171
|
+
throw error;
|
|
172
|
+
if (!product) {
|
|
173
|
+
toast.error("Product niet gevonden");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const barcode = String(product.barcode ?? "").trim();
|
|
177
|
+
const validGtin = isValidGtin13(barcode);
|
|
178
|
+
const imageUrl = String(product.image_url ?? "").trim();
|
|
179
|
+
const ppc = Number(product.plants_per_crate) || 0;
|
|
180
|
+
const cpl = Number(product.crates_per_layer) || 0;
|
|
181
|
+
const lpc = Number(product.layers_per_cart) || 0;
|
|
182
|
+
const payload = (product.floriday_payload ?? {});
|
|
183
|
+
const payloadHasPacking = Array.isArray(payload.packingConfigurations) && payload.packingConfigurations.length > 0;
|
|
184
|
+
const seededPacking = payloadHasPacking
|
|
185
|
+
? payload.packingConfigurations
|
|
186
|
+
: ppc > 0 || cpl > 0 || lpc > 0
|
|
187
|
+
? [
|
|
188
|
+
{
|
|
189
|
+
id: typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
190
|
+
? crypto.randomUUID()
|
|
191
|
+
: `pk-${Date.now()}`,
|
|
192
|
+
source: "VBN",
|
|
193
|
+
loadCarrier: "DANISH_TROLLEY",
|
|
194
|
+
packagingType: "",
|
|
195
|
+
unitsPerPackage: ppc || 1,
|
|
196
|
+
packagesPerLayer: cpl || 1,
|
|
197
|
+
layersPerLoadCarrier: lpc || 1,
|
|
198
|
+
isPrimary: true,
|
|
199
|
+
isCustomerSpecific: false,
|
|
200
|
+
customerOrganizationIds: [],
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
: [];
|
|
204
|
+
setData({
|
|
205
|
+
...empty,
|
|
206
|
+
...payload,
|
|
207
|
+
name: product.product ?? payload.name ?? "",
|
|
208
|
+
articleCode: String(product.article_number ?? payload.articleCode ?? "").trim(),
|
|
209
|
+
gtin: validGtin ? barcode : (payload.gtin ?? ""),
|
|
210
|
+
salePricePerPiece: Number(product.sale_price ?? payload.salePricePerPiece) || 0,
|
|
211
|
+
saleStockPieces: Number(product.quantity ?? payload.saleStockPieces) || 0,
|
|
212
|
+
images: Array.isArray(payload.images) && payload.images.length > 0
|
|
213
|
+
? payload.images
|
|
214
|
+
: imageUrl
|
|
215
|
+
? [{ url: imageUrl, name: product.product ?? undefined, primary: true }]
|
|
216
|
+
: [],
|
|
217
|
+
linkedProductId: product.id,
|
|
218
|
+
packingConfigurations: seededPacking,
|
|
219
|
+
});
|
|
220
|
+
setDraftId(product.id);
|
|
221
|
+
setDraftFloridayId((product.floriday_trade_item_id ?? null));
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
if (!cancelled)
|
|
225
|
+
toast.error(`Product laden mislukt: ${e.message}`);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
if (!cancelled)
|
|
229
|
+
setProductLoading(false);
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
return () => {
|
|
233
|
+
cancelled = true;
|
|
234
|
+
};
|
|
235
|
+
}, [productId]);
|
|
150
236
|
// Load existing published trade item from the shared cache
|
|
151
237
|
useEffect(() => {
|
|
152
238
|
if (!tradeItemId || !activeId)
|
|
@@ -603,7 +689,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
603
689
|
// wijzigt (o.a. beladingen) opgeslagen worden — anders raken tussentijdse
|
|
604
690
|
// bewerkingen verloren zodra ze terug navigeren.
|
|
605
691
|
if (!draftId && !vbnValid) {
|
|
606
|
-
toast.error("Kies eerst een VBN-code voordat je een
|
|
692
|
+
toast.error("Kies eerst een VBN-code voordat je een product opslaat");
|
|
607
693
|
setStep(0);
|
|
608
694
|
return;
|
|
609
695
|
}
|
|
@@ -618,7 +704,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
618
704
|
await saveWarehousePreference(draftFloridayId, data.warehouseId);
|
|
619
705
|
}
|
|
620
706
|
setDraftId(newId);
|
|
621
|
-
toast.success(draftId ? "
|
|
707
|
+
toast.success(draftId ? "Artikel bijgewerkt" : "Artikel opgeslagen");
|
|
622
708
|
navigate({ to: "/voorraad" });
|
|
623
709
|
}
|
|
624
710
|
catch (e) {
|
|
@@ -638,7 +724,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
638
724
|
useEffect(() => {
|
|
639
725
|
if (!activeId)
|
|
640
726
|
return;
|
|
641
|
-
if (draftLoading || publishedLoading)
|
|
727
|
+
if (draftLoading || publishedLoading || productLoading)
|
|
642
728
|
return;
|
|
643
729
|
const pushTargetId = (isEditingPublished && tradeItemId) ? tradeItemId : (draftFloridayId || null);
|
|
644
730
|
if (!draftId && !pushTargetId)
|
|
@@ -705,30 +791,36 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
705
791
|
activeId,
|
|
706
792
|
draftLoading,
|
|
707
793
|
publishedLoading,
|
|
794
|
+
productLoading,
|
|
708
795
|
isEditingPublished,
|
|
709
796
|
saveWarehousePreference,
|
|
710
797
|
tradeItemId,
|
|
711
798
|
]);
|
|
712
|
-
if (connLoading || draftLoading || publishedLoading) {
|
|
799
|
+
if (connLoading || draftLoading || publishedLoading || productLoading) {
|
|
713
800
|
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" }), publishedLoading
|
|
714
801
|
? "Artikel laden..."
|
|
715
802
|
: draftLoading
|
|
716
803
|
? "Lokaal artikel laden..."
|
|
717
|
-
:
|
|
804
|
+
: productLoading
|
|
805
|
+
? "Product laden..."
|
|
806
|
+
: "Floriday-connectie laden..."] }) }));
|
|
718
807
|
}
|
|
719
808
|
if (!activeConnection) {
|
|
720
809
|
return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: "Geen actieve Floriday-connectie gevonden. Voeg er eerst een toe in Instellingen." }) }));
|
|
721
810
|
}
|
|
722
|
-
return (_jsxs("main", { className: "mx-auto w-full max-w-3xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || (!draftId && !vbnValid), children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })),
|
|
811
|
+
return (_jsxs("main", { className: "mx-auto w-full max-w-3xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || (!draftId && !vbnValid), children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })), "Product opslaan"] })), (isEditingDraft || isEditingProduct) && (_jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Product verwijderen?" }), _jsx(AlertDialogDescription, { children: "Dit product wordt permanent uit de voorraad verwijderd. Als het product actief op Floriday staat moet je het daar apart deactiveren." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: handleDeleteDraft, children: "Verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_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" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && (_jsxs(_Fragment, { children: [_jsx(ArtikelCompositionCard, { productId: linkedProductId, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
723
812
|
const isOpen = i === step;
|
|
724
813
|
const isValid = stepValidity[i];
|
|
725
814
|
const isVisited = i < step || isEditing;
|
|
726
|
-
// Status: valid
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
815
|
+
// Status: alleen valid tonen als de stap ook bezocht is; anders
|
|
816
|
+
// krijgen optioneel-gevalideerde stappen (jaarrond default, geen
|
|
817
|
+
// extra verpakking, samenstelling read-only) onterecht een vinkje
|
|
818
|
+
// voordat de gebruiker ze überhaupt heeft geopend.
|
|
819
|
+
const status = !isVisited
|
|
820
|
+
? "untouched"
|
|
821
|
+
: isValid
|
|
822
|
+
? "valid"
|
|
823
|
+
: "invalid";
|
|
732
824
|
const isLastStep = i === steps.length - 1;
|
|
733
825
|
const navDisabled = !isEditing && i > step && !firstStepUnlocked;
|
|
734
826
|
const summary = summaryFor(s.key, data);
|
|
@@ -1221,18 +1313,51 @@ function CharacteristicField({ field, value, onChange, onRemove, }) {
|
|
|
1221
1313
|
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Label, { children: [field.label, field.required && _jsx("span", { className: "ml-1 text-destructive", children: "*" }), " ", _jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: ["(", field.code, ")"] })] }), onRemove && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0 text-muted-foreground hover:text-destructive", onClick: onRemove, "aria-label": `Verwijder ${field.label}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), !hasSpecValues && codeListQuery.isLoading ? (_jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Waardelijst ", field.listName, " wordt geladen\u2026"] })) : values.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Select, { value: value || undefined, onValueChange: (v) => onChange(v === CLEAR ? "" : v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer een door Floriday toegestane waarde\u2026" }) }), _jsxs(SelectContent, { children: [value && (_jsx(SelectItem, { value: CLEAR, children: _jsx("span", { className: "text-muted-foreground", children: "\u2014 Wissen \u2014" }) })), values.slice(0, 1000).map((v) => (_jsxs(SelectItem, { value: v.code, children: [v.label, " ", _jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: ["(", v.code, ")"] })] }, v.code)))] })] }), invalidExistingValue && (_jsxs("p", { className: "text-xs text-destructive", children: ["Huidige waarde \"", value, "\" komt niet voor in de door Floriday toegestane lijst. Kies een geldige waarde."] }))] })) : (_jsx("p", { className: "rounded-md border border-dashed border-muted-foreground/30 bg-muted/40 p-2 text-xs text-muted-foreground", children: "Geen toegestane waarden bekend voor deze karakteristiek. Laat leeg \u2014 vrije invoer wordt door Floriday niet geaccepteerd." }))] }));
|
|
1222
1314
|
}
|
|
1223
1315
|
function StepKlanten({ data, update, floridayCustomers }) {
|
|
1316
|
+
const [search, setSearch] = useState("");
|
|
1317
|
+
const [onlySelected, setOnlySelected] = useState(false);
|
|
1318
|
+
const selectedSet = useMemo(() => new Set(data.customerOrganizationIds), [data.customerOrganizationIds]);
|
|
1319
|
+
const filtered = useMemo(() => {
|
|
1320
|
+
const q = search.trim().toLowerCase();
|
|
1321
|
+
return floridayCustomers.filter((c) => {
|
|
1322
|
+
if (onlySelected && !selectedSet.has(c.id))
|
|
1323
|
+
return false;
|
|
1324
|
+
if (!q)
|
|
1325
|
+
return true;
|
|
1326
|
+
const hay = [c.name, c.commercialName, c.city, c.country, c.gln, c.id]
|
|
1327
|
+
.filter(Boolean)
|
|
1328
|
+
.join(" ")
|
|
1329
|
+
.toLowerCase();
|
|
1330
|
+
return hay.includes(q);
|
|
1331
|
+
});
|
|
1332
|
+
}, [floridayCustomers, search, onlySelected, selectedSet]);
|
|
1224
1333
|
if (floridayCustomers.length === 0) {
|
|
1225
1334
|
return (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen klantorganisaties geladen vanuit Floriday. Controleer de connectie of probeer later opnieuw." }));
|
|
1226
1335
|
}
|
|
1227
1336
|
const toggle = (id) => {
|
|
1228
|
-
if (
|
|
1337
|
+
if (selectedSet.has(id)) {
|
|
1229
1338
|
update("customerOrganizationIds", data.customerOrganizationIds.filter((c) => c !== id));
|
|
1230
1339
|
}
|
|
1231
1340
|
else {
|
|
1232
1341
|
update("customerOrganizationIds", [...data.customerOrganizationIds, id]);
|
|
1233
1342
|
}
|
|
1234
1343
|
};
|
|
1235
|
-
|
|
1344
|
+
const initials = (name) => name
|
|
1345
|
+
.split(/\s+/)
|
|
1346
|
+
.filter(Boolean)
|
|
1347
|
+
.slice(0, 2)
|
|
1348
|
+
.map((w) => w[0]?.toUpperCase() ?? "")
|
|
1349
|
+
.join("") || "?";
|
|
1350
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[240px] flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, stad of GLN\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs("button", { type: "button", onClick: () => setOnlySelected((v) => !v), className: `rounded-md border px-3 py-1.5 text-xs ${onlySelected
|
|
1351
|
+
? "border-primary bg-primary/5 text-primary"
|
|
1352
|
+
: "hover:border-primary/40"}`, children: ["Alleen geselecteerd (", selectedSet.size, ")"] }), selectedSet.size > 0 && (_jsx("button", { type: "button", onClick: () => update("customerOrganizationIds", []), className: "rounded-md border px-3 py-1.5 text-xs text-destructive hover:border-destructive/40 hover:bg-destructive/5", children: "Selectie wissen" }))] }), _jsx("div", { className: "max-h-[420px] divide-y overflow-y-auto rounded-md border", children: filtered.length === 0 ? (_jsxs("div", { className: "p-4 text-center text-sm text-muted-foreground", children: ["Geen klanten gevonden voor \"", search, "\"."] })) : (filtered.map((c) => {
|
|
1353
|
+
const checked = selectedSet.has(c.id);
|
|
1354
|
+
const subtitle = [c.city, c.country].filter(Boolean).join(" · ");
|
|
1355
|
+
return (_jsxs("label", { className: `flex cursor-pointer items-center gap-3 px-3 py-2.5 text-sm transition-colors ${checked ? "bg-primary/5" : "hover:bg-muted/60"}`, children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => toggle(c.id), className: "h-4 w-4 shrink-0" }), c.logoUrl ? (
|
|
1356
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
1357
|
+
_jsx("img", { src: c.logoUrl, alt: "", className: "h-9 w-9 shrink-0 rounded-full border object-cover" })) : (_jsx("div", { className: "grid h-9 w-9 shrink-0 place-items-center rounded-full border bg-muted text-xs font-semibold text-muted-foreground", children: initials(c.name) })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium", children: c.name }), !c.hasRealName && (_jsx("span", { className: "shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground", children: "Onbekend" }))] }), (subtitle || c.commercialName) && (_jsx("div", { className: "truncate text-xs text-muted-foreground", children: c.commercialName && c.commercialName !== c.name
|
|
1358
|
+
? `${c.commercialName}${subtitle ? ` · ${subtitle}` : ""}`
|
|
1359
|
+
: subtitle }))] }), c.gln && (_jsxs("span", { className: "ml-auto hidden shrink-0 font-mono text-[11px] text-muted-foreground sm:inline", children: ["GLN ", c.gln] }))] }, c.id));
|
|
1360
|
+
})) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [filtered.length, " van ", floridayCustomers.length, " klanten getoond"] })] }));
|
|
1236
1361
|
}
|
|
1237
1362
|
function StepFotos({ data, update, activeId }) {
|
|
1238
1363
|
const inputRef = useRef(null);
|
|
@@ -1569,17 +1694,125 @@ function StepVerpakking({ data, update }) {
|
|
|
1569
1694
|
}
|
|
1570
1695
|
function StepBeschikbaarheid({ data, update }) {
|
|
1571
1696
|
const weeks = Array.from({ length: 52 }, (_, i) => i + 1);
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1697
|
+
// Drag-selectie: bij mousedown bepalen we of we in "select"- of "deselect"-
|
|
1698
|
+
// modus zitten (op basis van de startcel) en breiden we de selectie uit
|
|
1699
|
+
// terwijl de gebruiker over de weken sleept, zoals in Floriday.
|
|
1700
|
+
const dragStateRef = useRef(null);
|
|
1701
|
+
const [dragHover, setDragHover] = useState(null);
|
|
1702
|
+
const setSelection = (list) => {
|
|
1703
|
+
const unique = Array.from(new Set(list.filter((w) => w >= 1 && w <= 52)))
|
|
1704
|
+
.sort((a, b) => a - b);
|
|
1705
|
+
update("weeks", unique);
|
|
1706
|
+
};
|
|
1707
|
+
const applyDrag = (current) => {
|
|
1708
|
+
const state = dragStateRef.current;
|
|
1709
|
+
if (!state)
|
|
1710
|
+
return;
|
|
1711
|
+
const [a, b] = state.anchor <= current ? [state.anchor, current] : [current, state.anchor];
|
|
1712
|
+
const range = Array.from({ length: b - a + 1 }, (_, i) => a + i);
|
|
1713
|
+
if (state.mode === "select") {
|
|
1714
|
+
setSelection([...state.baseline, ...range]);
|
|
1575
1715
|
}
|
|
1576
1716
|
else {
|
|
1577
|
-
|
|
1717
|
+
const rm = new Set(range);
|
|
1718
|
+
setSelection(state.baseline.filter((w) => !rm.has(w)));
|
|
1578
1719
|
}
|
|
1579
1720
|
};
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1721
|
+
const onWeekMouseDown = (w, e) => {
|
|
1722
|
+
if (e.button !== 0)
|
|
1723
|
+
return;
|
|
1724
|
+
e.preventDefault();
|
|
1725
|
+
const mode = data.weeks.includes(w) ? "deselect" : "select";
|
|
1726
|
+
dragStateRef.current = { mode, anchor: w, baseline: data.weeks };
|
|
1727
|
+
setDragHover(w);
|
|
1728
|
+
applyDrag(w);
|
|
1729
|
+
};
|
|
1730
|
+
const onWeekMouseEnter = (w) => {
|
|
1731
|
+
if (!dragStateRef.current)
|
|
1732
|
+
return;
|
|
1733
|
+
setDragHover(w);
|
|
1734
|
+
applyDrag(w);
|
|
1735
|
+
};
|
|
1736
|
+
// Auto-scroll wanneer je tijdens het slepen dicht bij de linker- of
|
|
1737
|
+
// rechterrand van de scroll-container komt.
|
|
1738
|
+
const scrollRef = useRef(null);
|
|
1739
|
+
const autoScrollRafRef = useRef(null);
|
|
1740
|
+
const pointerXRef = useRef(null);
|
|
1741
|
+
useEffect(() => {
|
|
1742
|
+
const end = () => {
|
|
1743
|
+
dragStateRef.current = null;
|
|
1744
|
+
setDragHover(null);
|
|
1745
|
+
pointerXRef.current = null;
|
|
1746
|
+
if (autoScrollRafRef.current != null) {
|
|
1747
|
+
cancelAnimationFrame(autoScrollRafRef.current);
|
|
1748
|
+
autoScrollRafRef.current = null;
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
const onMove = (e) => {
|
|
1752
|
+
if (!dragStateRef.current)
|
|
1753
|
+
return;
|
|
1754
|
+
pointerXRef.current = e.clientX;
|
|
1755
|
+
};
|
|
1756
|
+
window.addEventListener("mouseup", end);
|
|
1757
|
+
window.addEventListener("mouseleave", end);
|
|
1758
|
+
window.addEventListener("mousemove", onMove);
|
|
1759
|
+
return () => {
|
|
1760
|
+
window.removeEventListener("mouseup", end);
|
|
1761
|
+
window.removeEventListener("mouseleave", end);
|
|
1762
|
+
window.removeEventListener("mousemove", onMove);
|
|
1763
|
+
};
|
|
1764
|
+
}, []);
|
|
1765
|
+
const startAutoScrollLoop = () => {
|
|
1766
|
+
if (autoScrollRafRef.current != null)
|
|
1767
|
+
return;
|
|
1768
|
+
const EDGE = 60; // px vanaf de rand waar auto-scroll begint
|
|
1769
|
+
const MAX_SPEED = 18; // px per frame
|
|
1770
|
+
const step = () => {
|
|
1771
|
+
const el = scrollRef.current;
|
|
1772
|
+
const state = dragStateRef.current;
|
|
1773
|
+
const x = pointerXRef.current;
|
|
1774
|
+
if (!el || !state || x == null) {
|
|
1775
|
+
autoScrollRafRef.current = null;
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
const rect = el.getBoundingClientRect();
|
|
1779
|
+
let delta = 0;
|
|
1780
|
+
if (x < rect.left + EDGE) {
|
|
1781
|
+
const t = Math.min(1, (rect.left + EDGE - x) / EDGE);
|
|
1782
|
+
delta = -MAX_SPEED * t;
|
|
1783
|
+
}
|
|
1784
|
+
else if (x > rect.right - EDGE) {
|
|
1785
|
+
const t = Math.min(1, (x - (rect.right - EDGE)) / EDGE);
|
|
1786
|
+
delta = MAX_SPEED * t;
|
|
1787
|
+
}
|
|
1788
|
+
if (delta !== 0) {
|
|
1789
|
+
el.scrollLeft += delta;
|
|
1790
|
+
// Bepaal welke week nu onder de cursor ligt en pas de selectie aan
|
|
1791
|
+
// zodat het bereik meegroeit terwijl er wordt gescrolld.
|
|
1792
|
+
const target = document.elementFromPoint(x, rect.top + rect.height / 2);
|
|
1793
|
+
const weekAttr = target?.closest?.("[data-week]");
|
|
1794
|
+
const w = weekAttr ? Number(weekAttr.getAttribute("data-week")) : NaN;
|
|
1795
|
+
if (Number.isFinite(w)) {
|
|
1796
|
+
setDragHover(w);
|
|
1797
|
+
applyDrag(w);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
autoScrollRafRef.current = requestAnimationFrame(step);
|
|
1801
|
+
};
|
|
1802
|
+
autoScrollRafRef.current = requestAnimationFrame(step);
|
|
1803
|
+
};
|
|
1804
|
+
const onWeekMouseDownWithScroll = (w, e) => {
|
|
1805
|
+
onWeekMouseDown(w, e);
|
|
1806
|
+
pointerXRef.current = e.clientX;
|
|
1807
|
+
startAutoScrollLoop();
|
|
1808
|
+
};
|
|
1809
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx("button", { type: "button", onClick: () => update("yearRound", true), className: `rounded-md border px-3 py-1.5 text-sm ${data.yearRound ? "border-primary bg-primary/5" : "hover:border-primary/40"}`, children: "Jaarrond" }), _jsx("button", { type: "button", onClick: () => update("yearRound", false), className: `rounded-md border px-3 py-1.5 text-sm ${!data.yearRound ? "border-primary bg-primary/5" : "hover:border-primary/40"}`, children: "Specifieke periode" })] }), !data.yearRound && (_jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", children: [_jsx(Label, { className: "shrink-0", children: "Selecteer weken" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: ["Sleep om meerdere weken te selecteren", data.weeks.length > 0 && ` — ${data.weeks.length} week/weken`] }), _jsx("button", { type: "button", onClick: () => setSelection([]), disabled: data.weeks.length === 0, className: "rounded-md border px-2.5 py-1 text-xs text-destructive hover:border-destructive/40 hover:bg-destructive/5 disabled:cursor-not-allowed disabled:opacity-40", children: "Selectie leegmaken" })] })] }), _jsx("div", { ref: scrollRef, className: "overflow-x-auto pb-2", children: _jsx("div", { className: "flex select-none gap-1 min-w-max", children: weeks.map((w) => {
|
|
1810
|
+
const selected = data.weeks.includes(w);
|
|
1811
|
+
const isHover = dragHover === w;
|
|
1812
|
+
return (_jsx("button", { type: "button", "data-week": w, onMouseDown: (e) => onWeekMouseDownWithScroll(w, e), onMouseEnter: () => onWeekMouseEnter(w), className: `h-9 w-11 shrink-0 rounded border text-xs transition-colors ${selected
|
|
1813
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
1814
|
+
: "hover:border-primary/40"} ${isHover ? "ring-2 ring-primary/40" : ""}`, children: w }, w));
|
|
1815
|
+
}) }) })] }))] }));
|
|
1583
1816
|
}
|
|
1584
1817
|
// ------------------------------------------------------------
|
|
1585
1818
|
// Stap "Aanbieden & voorraad" — Direct Sale via CustomerOffer
|
|
@@ -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":"AAWA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,2CA4ErD"}
|
|
@@ -1,33 +1,40 @@
|
|
|
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";
|
|
4
3
|
import { Card } from "@flowselections/core";
|
|
5
4
|
import { Loader2 } from "lucide-react";
|
|
6
5
|
import { ArtikelWizard } from "./ArtikelWizard";
|
|
7
6
|
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";
|
|
11
7
|
export function EditArtikelPage({ id }) {
|
|
12
8
|
const [resolved, setResolved] = useState(null);
|
|
13
|
-
const [promoting, setPromoting] = useState(false);
|
|
14
|
-
const { activeId } = useFloridayConnection();
|
|
15
|
-
const navigate = useNavigate();
|
|
16
9
|
useEffect(() => {
|
|
17
10
|
let cancelled = false;
|
|
18
11
|
setResolved(null);
|
|
19
12
|
(async () => {
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
// 1. Volwaardig product (canonieke bron sinds de dedup-migratie).
|
|
14
|
+
const prodRes = await supabase
|
|
15
|
+
.from("products")
|
|
22
16
|
.select("id")
|
|
23
17
|
.eq("id", id)
|
|
24
18
|
.maybeSingle();
|
|
25
19
|
if (cancelled)
|
|
26
20
|
return;
|
|
27
|
-
if (
|
|
28
|
-
setResolved({ kind: "
|
|
21
|
+
if (prodRes.data) {
|
|
22
|
+
setResolved({ kind: "product", id });
|
|
29
23
|
return;
|
|
30
24
|
}
|
|
25
|
+
// 2. Product met dit Floriday-artikel-ID.
|
|
26
|
+
const byFloriday = await supabase
|
|
27
|
+
.from("products")
|
|
28
|
+
.select("id")
|
|
29
|
+
.eq("floriday_trade_item_id", id)
|
|
30
|
+
.maybeSingle();
|
|
31
|
+
if (cancelled)
|
|
32
|
+
return;
|
|
33
|
+
if (byFloriday.data?.id) {
|
|
34
|
+
setResolved({ kind: "product", id: byFloriday.data.id });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 3. Alleen nog in de Floriday-cache aanwezig (nog niet lokaal gespiegeld).
|
|
31
38
|
const pubRes = await supabase
|
|
32
39
|
.from("floriday_trade_items_cache")
|
|
33
40
|
.select("floriday_id")
|
|
@@ -39,87 +46,23 @@ export function EditArtikelPage({ id }) {
|
|
|
39
46
|
setResolved({ kind: "published", id });
|
|
40
47
|
return;
|
|
41
48
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
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
|
-
}
|
|
49
|
+
setResolved({
|
|
50
|
+
kind: "notfound",
|
|
51
|
+
reason: "Dit artikel bestaat niet (meer) in de voorraad.",
|
|
52
|
+
});
|
|
110
53
|
})();
|
|
111
54
|
return () => {
|
|
112
55
|
cancelled = true;
|
|
113
56
|
};
|
|
114
|
-
}, [id
|
|
115
|
-
if (!resolved
|
|
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" }),
|
|
57
|
+
}, [id]);
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
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 laden\u2026"] }) }));
|
|
117
60
|
}
|
|
118
61
|
if (resolved.kind === "notfound") {
|
|
119
|
-
return (_jsx("main", { className: "p-6", children:
|
|
62
|
+
return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 text-sm space-y-1", children: [_jsx("div", { children: "Artikel niet gevonden." }), resolved.reason && (_jsx("div", { className: "text-muted-foreground", children: resolved.reason }))] }) }));
|
|
120
63
|
}
|
|
121
|
-
if (resolved.kind === "
|
|
122
|
-
return _jsx(ArtikelWizard, {
|
|
64
|
+
if (resolved.kind === "product") {
|
|
65
|
+
return _jsx(ArtikelWizard, { productId: resolved.id });
|
|
123
66
|
}
|
|
124
67
|
return _jsx(ArtikelWizard, { tradeItemId: resolved.id });
|
|
125
68
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloridayStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayStatusBadge.tsx"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,2CAa9E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "@flowselections/core";
|
|
3
|
+
export function FloridayStatusBadge({ active }) {
|
|
4
|
+
if (active) {
|
|
5
|
+
return (_jsx(Badge, { className: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: "Actief op Floriday" }));
|
|
6
|
+
}
|
|
7
|
+
return (_jsx(Badge, { className: "bg-slate-500/15 text-slate-700 border-slate-500/30", children: "Niet actief op Floriday" }));
|
|
8
|
+
}
|