diff --git a/src/actions/ai-import.ts b/src/actions/ai-import.ts index a062d87..ed6a807 100644 --- a/src/actions/ai-import.ts +++ b/src/actions/ai-import.ts @@ -259,10 +259,12 @@ function fallbackParse( }; const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const keywordPatterns: Record = {}; - for (const [etype, kws] of Object.entries(keywordMaps)) { - keywordPatterns[etype] = new RegExp(kws.map(escapeRegex).join("|")); - } + const keywordPatterns: Record = { + products: new RegExp(productKeywords.map(escapeRegex).join("|")), + orders: new RegExp(orderKeywords.map(escapeRegex).join("|")), + contacts: new RegExp(contactKeywords.map(escapeRegex).join("|")), + stops: new RegExp(stopKeywords.map(escapeRegex).join("|")), + }; for (const header of h) { for (const [etype, pattern] of Object.entries(keywordPatterns)) { @@ -314,10 +316,11 @@ function fallbackParse( notes: ["notes", "note", "special_instructions", "comments", "memo", "instruction"], }; - const semanticPatterns: Record = {}; - for (const [field, kws] of Object.entries(semanticMap)) { - semanticPatterns[field] = new RegExp(kws.map(escapeRegex).join("|")); - } + const buildPattern = (kws: readonly string[]) => + new RegExp(kws.map(escapeRegex).join("|")); + const semanticPatterns: Record = Object.fromEntries( + Object.entries(semanticMap).map(([field, kws]) => [field, buildPattern(kws)]), + ); for (let i = 0; i < h.length; i++) { const header = h[i]; diff --git a/src/app/admin/settings/ai/AIClient.tsx b/src/app/admin/settings/ai/AIClient.tsx index c5967e5..37611cf 100644 --- a/src/app/admin/settings/ai/AIClient.tsx +++ b/src/app/admin/settings/ai/AIClient.tsx @@ -897,12 +897,12 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) { {/* Price Tiers */}
- +
{priceTiers.map((tier, i) => (
- updateTier(i, "tier", e.target.value)} @@ -931,7 +931,7 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) { {/* Historical Sales */}
- +
@@ -943,7 +943,7 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) {
{historicalSales.map((sale, i) => (
- updateSale(i, "date", e.target.value)} @@ -1405,7 +1405,7 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
- +
@@ -1649,7 +1649,7 @@ function DemandForecastTool({ brandId }: { brandId: string }) {
{historicalData.map((row, i) => (
- updateRow(i, "date", e.target.value)} diff --git a/src/app/admin/settings/integrations/IntegrationsClient.tsx b/src/app/admin/settings/integrations/IntegrationsClient.tsx index 8c050c0..6d66335 100644 --- a/src/app/admin/settings/integrations/IntegrationsClient.tsx +++ b/src/app/admin/settings/integrations/IntegrationsClient.tsx @@ -308,15 +308,16 @@ function IntegrationCard({

{integration.description}

-
{/* Sync options */} @@ -393,7 +394,7 @@ function IntegrationCard({ {/* Environment toggle */}
- +

Environment

{["sandbox", "production"].map((e) => (
- + setSearchQuery(e.target.value)} @@ -1653,7 +1653,7 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: { - 0} onChange={toggleAll} className="rounded" /> + 0} onChange={toggleAll} className="rounded" /> Invoice Customer @@ -2112,7 +2112,7 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }: {/* Team Notification Recipients */}
- +

These team members receive all wholesale notifications for this brand — new orders, deposits, fulfillments, price sheets, and pickup reminders. If no recipients are @@ -2131,7 +2131,7 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }:

{form.notificationRecipients.map((r: NotificationRecipient, idx: number) => (
- toggleRecipient(idx)} className="rounded border-[var(--admin-border)] mt-0.5" title={r.active ? "Active — receives notifications" : "Inactive"} />
@@ -2162,7 +2162,7 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }:
{/* Webhook Settings */}
- +

Send order events to external systems (Harvest Point, ERPs, etc.). Payload is signed with HMAC-SHA256. Enable and configure the URL and secret below. diff --git a/src/app/wholesale/portal/WholesalePortalClient.tsx b/src/app/wholesale/portal/WholesalePortalClient.tsx index 7ccd312..87eb62d 100644 --- a/src/app/wholesale/portal/WholesalePortalClient.tsx +++ b/src/app/wholesale/portal/WholesalePortalClient.tsx @@ -697,11 +697,11 @@ export default function WholesalePortalClient({

-
-
-
- - {STATUS_LABELS[cart.status]?.en ?? cart.status} - -
-

{cart.contact_name ?? "—"}

-

{cart.contact_email}

-

Step: {STEP_LABELS[cart.sequence_step] ?? cart.sequence_step}

-
-
- -
-

Cart Items

- - - - - - - - - - {cart.cart_snapshot.items.map((item, i) => ( - - ))} - - - - - - - -
ItemQtyPrice
Total${Number(cart.cart_snapshot.subtotal).toFixed(2)}
-
- - {cart.last_email_sent_at && ( -

Last email sent: {new Date(cart.last_email_sent_at).toLocaleString()}

- )} - {cart.next_email_at && ( -

Next email: {new Date(cart.next_email_at).toLocaleString()}

- )} -
-
- - )} + setSelectedCart(null)} />
); +} + +function CartDetailModal({ cart, onClose }: { cart: AbandonedCart | null; onClose: () => void }) { + const dialogRef = useRef(null); + useEffect(() => { + const dialog = dialogRef.current; + if (!dialog) return; + if (cart && !dialog.open) dialog.showModal(); + if (!cart && dialog.open) dialog.close(); + const onCancel = (e: Event) => { + e.preventDefault(); + onClose(); + }; + dialog.addEventListener("cancel", onCancel); + return () => { + dialog.removeEventListener("cancel", onCancel); + }; + }, [cart, onClose]); + + if (!cart) return null; + + return ( + +
+
+
+
+

Abandoned Cart

+

{cart.contact_email}

+
+ +
+
+
+ + {STATUS_LABELS[cart.status]?.en ?? cart.status} + +
+

{cart.contact_name ?? "—"}

+

{cart.contact_email}

+

Step: {STEP_LABELS[cart.sequence_step] ?? cart.sequence_step}

+
+
+ +
+

Cart Items

+ + + + + + + + + + {cart.cart_snapshot.items.map((item, i) => ( + + ))} + + + + + + + +
ItemQtyPrice
Total${Number(cart.cart_snapshot.subtotal).toFixed(2)}
+
+ + {cart.last_email_sent_at && ( +

Last email sent: {new Date(cart.last_email_sent_at).toLocaleString()}

+ )} + {cart.next_email_at && ( +

Next email: {new Date(cart.next_email_at).toLocaleString()}

+ )} +
+
+
+
+ ); } \ No newline at end of file diff --git a/src/components/admin/AddStopModal.tsx b/src/components/admin/AddStopModal.tsx index aba249e..8a7b9de 100644 --- a/src/components/admin/AddStopModal.tsx +++ b/src/components/admin/AddStopModal.tsx @@ -318,9 +318,9 @@ export default function AddStopModal({ isOpen, onClose, brandId, duplicateFrom, {/* Row 5 — Status: segmented control (compact, replaces two giant buttons) */}
-