@open-mercato/core 0.6.6-develop.6184.1.b7e55f8d61 → 0.6.6-develop.6201.1.8ceb502c4b
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/frontend/login.js +5 -5
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +3 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +26 -14
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +169 -149
- package/dist/modules/customers/api/deals/[id]/route.js.map +3 -3
- package/dist/modules/customers/components/detail/InlineEditors.js +9 -4
- package/dist/modules/customers/components/detail/InlineEditors.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +13 -9
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +81 -37
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/detail-page-refresh.js +17 -0
- package/dist/modules/integrations/backend/integrations/detail-page-refresh.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/page.js +45 -24
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/dist/modules/notifications/api/[id]/action/route.js +18 -3
- package/dist/modules/notifications/api/[id]/action/route.js.map +2 -2
- package/dist/modules/notifications/api/[id]/restore/route.js +19 -3
- package/dist/modules/notifications/api/[id]/restore/route.js.map +2 -2
- package/dist/modules/notifications/api/mark-all-read/route.js +18 -4
- package/dist/modules/notifications/api/mark-all-read/route.js.map +2 -2
- package/dist/modules/notifications/api/route.js +17 -4
- package/dist/modules/notifications/api/route.js.map +2 -2
- package/dist/modules/notifications/api/settings/route.js +26 -5
- package/dist/modules/notifications/api/settings/route.js.map +2 -2
- package/dist/modules/notifications/lib/routeHelpers.js +64 -5
- package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +299 -297
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/workflows/components/EdgeEditDialog.js +120 -141
- package/dist/modules/workflows/components/EdgeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +114 -118
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +155 -260
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/frontend/login.tsx +5 -5
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +27 -17
- package/src/modules/customers/api/deals/[id]/route.ts +208 -174
- package/src/modules/customers/components/detail/InlineEditors.tsx +6 -10
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +13 -9
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +78 -35
- package/src/modules/integrations/backend/integrations/detail-page-refresh.ts +36 -0
- package/src/modules/integrations/backend/integrations/page.tsx +42 -21
- package/src/modules/notifications/api/[id]/action/route.ts +17 -2
- package/src/modules/notifications/api/[id]/restore/route.ts +19 -3
- package/src/modules/notifications/api/mark-all-read/route.ts +18 -4
- package/src/modules/notifications/api/route.ts +16 -3
- package/src/modules/notifications/api/settings/route.ts +26 -5
- package/src/modules/notifications/lib/routeHelpers.ts +101 -4
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +351 -327
- package/src/modules/workflows/components/EdgeEditDialog.tsx +132 -157
- package/src/modules/workflows/components/NodeEditDialog.tsx +116 -121
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +172 -239
- package/src/modules/workflows/i18n/de.json +2 -0
- package/src/modules/workflows/i18n/en.json +2 -0
- package/src/modules/workflows/i18n/es.json +2 -0
- package/src/modules/workflows/i18n/pl.json +2 -0
|
@@ -300,6 +300,302 @@ function normalizeAddressDraft(draft) {
|
|
|
300
300
|
assign("isPrimary", "isPrimary");
|
|
301
301
|
return Object.keys(normalized).length ? normalized : null;
|
|
302
302
|
}
|
|
303
|
+
function DocumentNumberField({ value, setValue, values, t }) {
|
|
304
|
+
const formValues = values ?? {};
|
|
305
|
+
const kind = formValues.documentKind === "order" ? "order" : "quote";
|
|
306
|
+
const [loading, setLoading] = React.useState(false);
|
|
307
|
+
const [error, setError] = React.useState(null);
|
|
308
|
+
const autoValueRef = React.useRef(null);
|
|
309
|
+
const lastKindRef = React.useRef(null);
|
|
310
|
+
const requestNumber = React.useCallback(async () => {
|
|
311
|
+
setLoading(true);
|
|
312
|
+
setError(null);
|
|
313
|
+
try {
|
|
314
|
+
const call = await apiCall("/api/sales/document-numbers", {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: { "content-type": "application/json" },
|
|
317
|
+
body: JSON.stringify({ kind })
|
|
318
|
+
});
|
|
319
|
+
const nextNumber = typeof call.result?.number === "string" ? call.result.number : null;
|
|
320
|
+
if (call.ok && nextNumber) {
|
|
321
|
+
autoValueRef.current = nextNumber;
|
|
322
|
+
lastKindRef.current = kind;
|
|
323
|
+
setValue(nextNumber);
|
|
324
|
+
} else {
|
|
325
|
+
setError(call.result?.error || t("sales.documents.form.errors.numberGenerate", "Could not generate a document number."));
|
|
326
|
+
}
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error("sales.documents.generateNumber", err);
|
|
329
|
+
setError(t("sales.documents.form.errors.numberGenerate", "Could not generate a document number."));
|
|
330
|
+
} finally {
|
|
331
|
+
setLoading(false);
|
|
332
|
+
}
|
|
333
|
+
}, [kind, setValue, t]);
|
|
334
|
+
React.useEffect(() => {
|
|
335
|
+
const current = typeof value === "string" ? value.trim() : "";
|
|
336
|
+
if (!current.length && !lastKindRef.current) {
|
|
337
|
+
void requestNumber();
|
|
338
|
+
} else {
|
|
339
|
+
lastKindRef.current = kind;
|
|
340
|
+
}
|
|
341
|
+
}, [kind, requestNumber, value]);
|
|
342
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
343
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2 md:flex-row md:items-center md:gap-3", children: [
|
|
344
|
+
/* @__PURE__ */ jsx(
|
|
345
|
+
Input,
|
|
346
|
+
{
|
|
347
|
+
value: typeof value === "string" ? value : "",
|
|
348
|
+
onChange: (event) => setValue(event.target.value),
|
|
349
|
+
disabled: loading,
|
|
350
|
+
spellCheck: false,
|
|
351
|
+
className: "w-full md:flex-1"
|
|
352
|
+
}
|
|
353
|
+
),
|
|
354
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: requestNumber, disabled: loading, children: loading ? t("sales.documents.form.numberLoading", "Generating\u2026") : t("sales.documents.form.numberRefresh", "Generate") })
|
|
355
|
+
] }),
|
|
356
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: kind === "order" ? t("sales.documents.form.numberHintOrder", "Format applies to orders and uses the configured counter.") : t("sales.documents.form.numberHintQuote", "Format applies to quotes and uses the configured counter.") }),
|
|
357
|
+
error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null
|
|
358
|
+
] });
|
|
359
|
+
}
|
|
360
|
+
function BillingAddressSectionField({ values, setFormValue, t, addressesLoading, addressOptions, addressFormat }) {
|
|
361
|
+
const formValues = values ?? {};
|
|
362
|
+
const updateValue = setFormValue ?? (() => {
|
|
363
|
+
});
|
|
364
|
+
const useCustom = formValues.useCustomBilling === true;
|
|
365
|
+
const selectedId = typeof formValues.billingAddressId === "string" ? formValues.billingAddressId : "";
|
|
366
|
+
const draft = formValues.billingAddressDraft ?? {};
|
|
367
|
+
const customerRequired = !formValues.customerEntityId;
|
|
368
|
+
const sameAsShipping = formValues.sameAsShipping !== false;
|
|
369
|
+
const shippingId = typeof formValues.shippingAddressId === "string" ? formValues.shippingAddressId : null;
|
|
370
|
+
const shippingDraft = formValues.shippingAddressDraft ?? {};
|
|
371
|
+
const shippingDraftKey = JSON.stringify(shippingDraft);
|
|
372
|
+
const billingDraftKey = JSON.stringify(draft);
|
|
373
|
+
const useCustomShipping = formValues.useCustomShipping === true;
|
|
374
|
+
React.useEffect(() => {
|
|
375
|
+
if (!sameAsShipping) return;
|
|
376
|
+
if ((formValues.billingAddressId ?? null) !== shippingId) {
|
|
377
|
+
updateValue("billingAddressId", shippingId);
|
|
378
|
+
}
|
|
379
|
+
if (useCustomShipping !== (formValues.useCustomBilling === true)) {
|
|
380
|
+
updateValue("useCustomBilling", useCustomShipping);
|
|
381
|
+
}
|
|
382
|
+
if (useCustomShipping && shippingDraftKey !== billingDraftKey) {
|
|
383
|
+
updateValue("billingAddressDraft", shippingDraft);
|
|
384
|
+
}
|
|
385
|
+
}, [
|
|
386
|
+
billingDraftKey,
|
|
387
|
+
sameAsShipping,
|
|
388
|
+
updateValue,
|
|
389
|
+
shippingDraft,
|
|
390
|
+
shippingDraftKey,
|
|
391
|
+
shippingId,
|
|
392
|
+
useCustomShipping,
|
|
393
|
+
formValues.billingAddressId,
|
|
394
|
+
formValues.useCustomBilling
|
|
395
|
+
]);
|
|
396
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
397
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 md:flex-row md:items-center md:justify-between", children: [
|
|
398
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
399
|
+
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: t("sales.documents.form.billing.title", "Billing address") }),
|
|
400
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: sameAsShipping ? t("sales.documents.form.address.sameAsShippingHint", "Billing will mirror the shipping address. Uncheck to edit.") : t("sales.documents.form.billing.hint", "Select an address or define a new one.") })
|
|
401
|
+
] }),
|
|
402
|
+
/* @__PURE__ */ jsx(
|
|
403
|
+
SwitchField,
|
|
404
|
+
{
|
|
405
|
+
label: t("sales.documents.form.address.sameAsShipping", "Same as shipping"),
|
|
406
|
+
flip: true,
|
|
407
|
+
checked: sameAsShipping,
|
|
408
|
+
onCheckedChange: (checked) => {
|
|
409
|
+
updateValue("sameAsShipping", checked);
|
|
410
|
+
if (checked) {
|
|
411
|
+
updateValue("useCustomBilling", useCustomShipping);
|
|
412
|
+
updateValue("billingAddressId", shippingId);
|
|
413
|
+
updateValue("billingAddressDraft", shippingDraft);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
] }),
|
|
419
|
+
!sameAsShipping ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
420
|
+
!useCustom ? /* @__PURE__ */ jsxs(
|
|
421
|
+
Select,
|
|
422
|
+
{
|
|
423
|
+
value: selectedId || void 0,
|
|
424
|
+
onValueChange: (value) => updateValue("billingAddressId", value || null),
|
|
425
|
+
disabled: addressesLoading || customerRequired,
|
|
426
|
+
children: [
|
|
427
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(
|
|
428
|
+
SelectValue,
|
|
429
|
+
{
|
|
430
|
+
placeholder: addressesLoading ? t("sales.documents.form.address.loading", "Loading addresses\u2026") : t("sales.documents.form.address.placeholder", "Select address")
|
|
431
|
+
}
|
|
432
|
+
) }),
|
|
433
|
+
/* @__PURE__ */ jsx(SelectContent, { children: addressOptions.map((addr) => {
|
|
434
|
+
const optionLabel = addr.summary ? `${addr.label} \u2014 ${addr.summary}` : addr.label;
|
|
435
|
+
return /* @__PURE__ */ jsx(SelectItem, { value: addr.id, children: optionLabel }, addr.id);
|
|
436
|
+
}) })
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
) : null,
|
|
440
|
+
/* @__PURE__ */ jsx(
|
|
441
|
+
SwitchField,
|
|
442
|
+
{
|
|
443
|
+
label: t("sales.documents.form.shipping.custom", "Define new address"),
|
|
444
|
+
flip: true,
|
|
445
|
+
checked: useCustom,
|
|
446
|
+
onCheckedChange: (checked) => updateValue("useCustomBilling", checked),
|
|
447
|
+
disabled: false
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
useCustom ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
451
|
+
/* @__PURE__ */ jsx(
|
|
452
|
+
AddressEditor,
|
|
453
|
+
{
|
|
454
|
+
value: draft,
|
|
455
|
+
format: addressFormat,
|
|
456
|
+
t,
|
|
457
|
+
onChange: (next) => updateValue("billingAddressDraft", next),
|
|
458
|
+
hidePrimaryToggle: true
|
|
459
|
+
}
|
|
460
|
+
),
|
|
461
|
+
/* @__PURE__ */ jsx(
|
|
462
|
+
SwitchField,
|
|
463
|
+
{
|
|
464
|
+
containerClassName: "col-span-2",
|
|
465
|
+
label: t("sales.documents.form.address.saveToCustomer", "Save this address to the customer"),
|
|
466
|
+
flip: true,
|
|
467
|
+
checked: formValues.saveBillingAddress === true,
|
|
468
|
+
onCheckedChange: (checked) => updateValue("saveBillingAddress", checked)
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
] }) : null
|
|
472
|
+
] }) : null
|
|
473
|
+
] });
|
|
474
|
+
}
|
|
475
|
+
function CustomerGroupComponent({ values, setValue, t, customers, setCustomers, customerQuerySetter, loadAddresses, loadCustomers, fetchCustomerEmail, resetAddressFormState }) {
|
|
476
|
+
const emailValue = typeof values.customerEmail === "string" ? values.customerEmail : "";
|
|
477
|
+
const { duplicate, checking } = useEmailDuplicateCheck(emailValue, {
|
|
478
|
+
disabled: false,
|
|
479
|
+
debounceMs: 400,
|
|
480
|
+
matchMode: "prefix"
|
|
481
|
+
});
|
|
482
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
483
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsx(
|
|
484
|
+
LookupSelect,
|
|
485
|
+
{
|
|
486
|
+
value: typeof values.customerEntityId === "string" ? values.customerEntityId : null,
|
|
487
|
+
onChange: (next) => {
|
|
488
|
+
if (next !== values.customerEntityId) {
|
|
489
|
+
resetAddressFormState(setValue);
|
|
490
|
+
}
|
|
491
|
+
setValue("customerEntityId", next);
|
|
492
|
+
loadAddresses(next).then((addrs) => {
|
|
493
|
+
const primary = addrs.find((addr) => addr.isPrimary);
|
|
494
|
+
if (primary) {
|
|
495
|
+
setValue("shippingAddressId", primary.id);
|
|
496
|
+
}
|
|
497
|
+
}).catch((err) => {
|
|
498
|
+
console.error("sales.documents.autoSelectAddress", err);
|
|
499
|
+
});
|
|
500
|
+
if (next) {
|
|
501
|
+
const match = customers.find((entry) => entry.id === next);
|
|
502
|
+
const possibleEmail = typeof match?.primaryEmail === "string" && match.primaryEmail.length ? match.primaryEmail : null;
|
|
503
|
+
if (possibleEmail) {
|
|
504
|
+
setValue("customerEmail", possibleEmail);
|
|
505
|
+
} else {
|
|
506
|
+
fetchCustomerEmail(next, match?.kind).then((email) => {
|
|
507
|
+
if (email) setValue("customerEmail", email);
|
|
508
|
+
}).catch(() => {
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
fetchItems: async (query) => {
|
|
514
|
+
const options = await loadCustomers(query);
|
|
515
|
+
return options.map((opt) => ({
|
|
516
|
+
id: opt.id,
|
|
517
|
+
title: opt.label,
|
|
518
|
+
subtitle: opt.subtitle ?? void 0,
|
|
519
|
+
icon: opt.kind === "person" ? /* @__PURE__ */ jsx(UserRound, { className: "h-5 w-5 text-muted-foreground" }) : /* @__PURE__ */ jsx(Building2, { className: "h-5 w-5 text-muted-foreground" })
|
|
520
|
+
}));
|
|
521
|
+
},
|
|
522
|
+
actionSlot: /* @__PURE__ */ jsx(
|
|
523
|
+
CustomerQuickCreate,
|
|
524
|
+
{
|
|
525
|
+
t,
|
|
526
|
+
onCreated: ({ id, email, label, kind, subtitle }) => {
|
|
527
|
+
customerQuerySetter.current?.(label);
|
|
528
|
+
setCustomers((prev) => {
|
|
529
|
+
const exists = prev.some((entry) => entry.id === id);
|
|
530
|
+
if (exists) return prev;
|
|
531
|
+
const next = {
|
|
532
|
+
id,
|
|
533
|
+
label,
|
|
534
|
+
subtitle: subtitle ?? void 0,
|
|
535
|
+
kind,
|
|
536
|
+
primaryEmail: email ?? null
|
|
537
|
+
};
|
|
538
|
+
return [next, ...prev];
|
|
539
|
+
});
|
|
540
|
+
setValue("customerEntityId", id);
|
|
541
|
+
resetAddressFormState(setValue);
|
|
542
|
+
loadAddresses(id).then((addrs) => {
|
|
543
|
+
const primary = addrs.find((addr) => addr.isPrimary);
|
|
544
|
+
if (primary) {
|
|
545
|
+
setValue("shippingAddressId", primary.id);
|
|
546
|
+
}
|
|
547
|
+
}).catch((err) => {
|
|
548
|
+
console.error("sales.documents.autoSelectAddress", err);
|
|
549
|
+
});
|
|
550
|
+
if (email && !values.customerEmail) {
|
|
551
|
+
setValue("customerEmail", email);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
),
|
|
556
|
+
onReady: ({ setQuery }) => {
|
|
557
|
+
customerQuerySetter.current = setQuery;
|
|
558
|
+
},
|
|
559
|
+
searchPlaceholder: t("sales.documents.form.customer.placeholder", "Search customers\u2026"),
|
|
560
|
+
loadingLabel: t("sales.documents.form.customer.loading", "Loading customers\u2026"),
|
|
561
|
+
emptyLabel: t("sales.documents.form.customer.empty", "No customers found."),
|
|
562
|
+
selectedHintLabel: (id) => t("sales.documents.form.customer.selected", "Selected customer: {{id}}", { id })
|
|
563
|
+
}
|
|
564
|
+
) }),
|
|
565
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
566
|
+
/* @__PURE__ */ jsx(
|
|
567
|
+
EmailInput,
|
|
568
|
+
{
|
|
569
|
+
value: emailValue,
|
|
570
|
+
onChange: (event) => setValue("customerEmail", event.target.value),
|
|
571
|
+
placeholder: t("sales.documents.form.email.placeholder", "Email used for the document"),
|
|
572
|
+
spellCheck: false
|
|
573
|
+
}
|
|
574
|
+
),
|
|
575
|
+
duplicate ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border bg-muted px-3 py-2 text-xs text-muted-foreground", children: [
|
|
576
|
+
/* @__PURE__ */ jsx("span", { children: t("customers.people.form.emailDuplicateNotice", void 0, { name: duplicate.displayName }) }),
|
|
577
|
+
/* @__PURE__ */ jsx(
|
|
578
|
+
Button,
|
|
579
|
+
{
|
|
580
|
+
size: "sm",
|
|
581
|
+
variant: "secondary",
|
|
582
|
+
className: "px-4",
|
|
583
|
+
type: "button",
|
|
584
|
+
disabled: values.customerEntityId === duplicate.id,
|
|
585
|
+
"aria-disabled": values.customerEntityId === duplicate.id,
|
|
586
|
+
onClick: () => {
|
|
587
|
+
setValue("customerEntityId", duplicate.id);
|
|
588
|
+
resetAddressFormState(setValue);
|
|
589
|
+
loadAddresses(duplicate.id);
|
|
590
|
+
},
|
|
591
|
+
children: values.customerEntityId === duplicate.id ? t("sales.documents.form.email.alreadySelected", "Selected customer") : t("sales.documents.form.email.selectCustomer", "Select customer")
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
] }) : null,
|
|
595
|
+
!duplicate && checking ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customers.people.form.emailChecking") }) : null
|
|
596
|
+
] })
|
|
597
|
+
] });
|
|
598
|
+
}
|
|
303
599
|
function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind, inboxPreFill }) {
|
|
304
600
|
const t = useT();
|
|
305
601
|
const [customers, setCustomers] = React.useState([]);
|
|
@@ -596,63 +892,6 @@ function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind, inbox
|
|
|
596
892
|
},
|
|
597
893
|
[]
|
|
598
894
|
);
|
|
599
|
-
function DocumentNumberField({ value, setValue, values }) {
|
|
600
|
-
const formValues = values ?? {};
|
|
601
|
-
const kind = formValues.documentKind === "order" ? "order" : "quote";
|
|
602
|
-
const [loading, setLoading] = React.useState(false);
|
|
603
|
-
const [error, setError] = React.useState(null);
|
|
604
|
-
const autoValueRef = React.useRef(null);
|
|
605
|
-
const lastKindRef = React.useRef(null);
|
|
606
|
-
const requestNumber = React.useCallback(async () => {
|
|
607
|
-
setLoading(true);
|
|
608
|
-
setError(null);
|
|
609
|
-
try {
|
|
610
|
-
const call = await apiCall("/api/sales/document-numbers", {
|
|
611
|
-
method: "POST",
|
|
612
|
-
headers: { "content-type": "application/json" },
|
|
613
|
-
body: JSON.stringify({ kind })
|
|
614
|
-
});
|
|
615
|
-
const nextNumber = typeof call.result?.number === "string" ? call.result.number : null;
|
|
616
|
-
if (call.ok && nextNumber) {
|
|
617
|
-
autoValueRef.current = nextNumber;
|
|
618
|
-
lastKindRef.current = kind;
|
|
619
|
-
setValue(nextNumber);
|
|
620
|
-
} else {
|
|
621
|
-
setError(call.result?.error || t("sales.documents.form.errors.numberGenerate", "Could not generate a document number."));
|
|
622
|
-
}
|
|
623
|
-
} catch (err) {
|
|
624
|
-
console.error("sales.documents.generateNumber", err);
|
|
625
|
-
setError(t("sales.documents.form.errors.numberGenerate", "Could not generate a document number."));
|
|
626
|
-
} finally {
|
|
627
|
-
setLoading(false);
|
|
628
|
-
}
|
|
629
|
-
}, [kind, setValue, t]);
|
|
630
|
-
React.useEffect(() => {
|
|
631
|
-
const current = typeof value === "string" ? value.trim() : "";
|
|
632
|
-
if (!current.length && !lastKindRef.current) {
|
|
633
|
-
void requestNumber();
|
|
634
|
-
} else {
|
|
635
|
-
lastKindRef.current = kind;
|
|
636
|
-
}
|
|
637
|
-
}, [kind, requestNumber, value]);
|
|
638
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
639
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2 md:flex-row md:items-center md:gap-3", children: [
|
|
640
|
-
/* @__PURE__ */ jsx(
|
|
641
|
-
Input,
|
|
642
|
-
{
|
|
643
|
-
value: typeof value === "string" ? value : "",
|
|
644
|
-
onChange: (event) => setValue(event.target.value),
|
|
645
|
-
disabled: loading,
|
|
646
|
-
spellCheck: false,
|
|
647
|
-
className: "w-full md:flex-1"
|
|
648
|
-
}
|
|
649
|
-
),
|
|
650
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: requestNumber, disabled: loading, children: loading ? t("sales.documents.form.numberLoading", "Generating\u2026") : t("sales.documents.form.numberRefresh", "Generate") })
|
|
651
|
-
] }),
|
|
652
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: kind === "order" ? t("sales.documents.form.numberHintOrder", "Format applies to orders and uses the configured counter.") : t("sales.documents.form.numberHintQuote", "Format applies to quotes and uses the configured counter.") }),
|
|
653
|
-
error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null
|
|
654
|
-
] });
|
|
655
|
-
}
|
|
656
895
|
const fields = React.useMemo(() => [
|
|
657
896
|
{
|
|
658
897
|
id: "documentKind",
|
|
@@ -688,7 +927,7 @@ function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind, inbox
|
|
|
688
927
|
label: t("sales.documents.form.number", "Document number"),
|
|
689
928
|
type: "custom",
|
|
690
929
|
required: true,
|
|
691
|
-
component: DocumentNumberField
|
|
930
|
+
component: (props) => /* @__PURE__ */ jsx(DocumentNumberField, { ...props, t })
|
|
692
931
|
},
|
|
693
932
|
{
|
|
694
933
|
id: "currencyCode",
|
|
@@ -826,121 +1065,7 @@ function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind, inbox
|
|
|
826
1065
|
id: "billingAddressSection",
|
|
827
1066
|
label: "",
|
|
828
1067
|
type: "custom",
|
|
829
|
-
component:
|
|
830
|
-
const formValues = values ?? {};
|
|
831
|
-
const updateValue = setFormValue ?? (() => {
|
|
832
|
-
});
|
|
833
|
-
const useCustom = formValues.useCustomBilling === true;
|
|
834
|
-
const selectedId = typeof formValues.billingAddressId === "string" ? formValues.billingAddressId : "";
|
|
835
|
-
const draft = formValues.billingAddressDraft ?? {};
|
|
836
|
-
const customerRequired = !formValues.customerEntityId;
|
|
837
|
-
const sameAsShipping = formValues.sameAsShipping !== false;
|
|
838
|
-
const shippingId = typeof formValues.shippingAddressId === "string" ? formValues.shippingAddressId : null;
|
|
839
|
-
const shippingDraft = formValues.shippingAddressDraft ?? {};
|
|
840
|
-
const shippingDraftKey = JSON.stringify(shippingDraft);
|
|
841
|
-
const billingDraftKey = JSON.stringify(draft);
|
|
842
|
-
const useCustomShipping = formValues.useCustomShipping === true;
|
|
843
|
-
React.useEffect(() => {
|
|
844
|
-
if (!sameAsShipping) return;
|
|
845
|
-
if ((formValues.billingAddressId ?? null) !== shippingId) {
|
|
846
|
-
updateValue("billingAddressId", shippingId);
|
|
847
|
-
}
|
|
848
|
-
if (useCustomShipping !== (formValues.useCustomBilling === true)) {
|
|
849
|
-
updateValue("useCustomBilling", useCustomShipping);
|
|
850
|
-
}
|
|
851
|
-
if (useCustomShipping && shippingDraftKey !== billingDraftKey) {
|
|
852
|
-
updateValue("billingAddressDraft", shippingDraft);
|
|
853
|
-
}
|
|
854
|
-
}, [
|
|
855
|
-
billingDraftKey,
|
|
856
|
-
sameAsShipping,
|
|
857
|
-
updateValue,
|
|
858
|
-
shippingDraft,
|
|
859
|
-
shippingDraftKey,
|
|
860
|
-
shippingId,
|
|
861
|
-
useCustomShipping,
|
|
862
|
-
formValues.billingAddressId,
|
|
863
|
-
formValues.useCustomBilling
|
|
864
|
-
]);
|
|
865
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
866
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 md:flex-row md:items-center md:justify-between", children: [
|
|
867
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
868
|
-
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: t("sales.documents.form.billing.title", "Billing address") }),
|
|
869
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: sameAsShipping ? t("sales.documents.form.address.sameAsShippingHint", "Billing will mirror the shipping address. Uncheck to edit.") : t("sales.documents.form.billing.hint", "Select an address or define a new one.") })
|
|
870
|
-
] }),
|
|
871
|
-
/* @__PURE__ */ jsx(
|
|
872
|
-
SwitchField,
|
|
873
|
-
{
|
|
874
|
-
label: t("sales.documents.form.address.sameAsShipping", "Same as shipping"),
|
|
875
|
-
flip: true,
|
|
876
|
-
checked: sameAsShipping,
|
|
877
|
-
onCheckedChange: (checked) => {
|
|
878
|
-
updateValue("sameAsShipping", checked);
|
|
879
|
-
if (checked) {
|
|
880
|
-
updateValue("useCustomBilling", useCustomShipping);
|
|
881
|
-
updateValue("billingAddressId", shippingId);
|
|
882
|
-
updateValue("billingAddressDraft", shippingDraft);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
)
|
|
887
|
-
] }),
|
|
888
|
-
!sameAsShipping ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
889
|
-
!useCustom ? /* @__PURE__ */ jsxs(
|
|
890
|
-
Select,
|
|
891
|
-
{
|
|
892
|
-
value: selectedId || void 0,
|
|
893
|
-
onValueChange: (value) => updateValue("billingAddressId", value || null),
|
|
894
|
-
disabled: addressesLoading || customerRequired,
|
|
895
|
-
children: [
|
|
896
|
-
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(
|
|
897
|
-
SelectValue,
|
|
898
|
-
{
|
|
899
|
-
placeholder: addressesLoading ? t("sales.documents.form.address.loading", "Loading addresses\u2026") : t("sales.documents.form.address.placeholder", "Select address")
|
|
900
|
-
}
|
|
901
|
-
) }),
|
|
902
|
-
/* @__PURE__ */ jsx(SelectContent, { children: addressOptions.map((addr) => {
|
|
903
|
-
const optionLabel = addr.summary ? `${addr.label} \u2014 ${addr.summary}` : addr.label;
|
|
904
|
-
return /* @__PURE__ */ jsx(SelectItem, { value: addr.id, children: optionLabel }, addr.id);
|
|
905
|
-
}) })
|
|
906
|
-
]
|
|
907
|
-
}
|
|
908
|
-
) : null,
|
|
909
|
-
/* @__PURE__ */ jsx(
|
|
910
|
-
SwitchField,
|
|
911
|
-
{
|
|
912
|
-
label: t("sales.documents.form.shipping.custom", "Define new address"),
|
|
913
|
-
flip: true,
|
|
914
|
-
checked: useCustom,
|
|
915
|
-
onCheckedChange: (checked) => updateValue("useCustomBilling", checked),
|
|
916
|
-
disabled: false
|
|
917
|
-
}
|
|
918
|
-
),
|
|
919
|
-
useCustom ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
920
|
-
/* @__PURE__ */ jsx(
|
|
921
|
-
AddressEditor,
|
|
922
|
-
{
|
|
923
|
-
value: draft,
|
|
924
|
-
format: addressFormat,
|
|
925
|
-
t,
|
|
926
|
-
onChange: (next) => updateValue("billingAddressDraft", next),
|
|
927
|
-
hidePrimaryToggle: true
|
|
928
|
-
}
|
|
929
|
-
),
|
|
930
|
-
/* @__PURE__ */ jsx(
|
|
931
|
-
SwitchField,
|
|
932
|
-
{
|
|
933
|
-
containerClassName: "col-span-2",
|
|
934
|
-
label: t("sales.documents.form.address.saveToCustomer", "Save this address to the customer"),
|
|
935
|
-
flip: true,
|
|
936
|
-
checked: formValues.saveBillingAddress === true,
|
|
937
|
-
onCheckedChange: (checked) => updateValue("saveBillingAddress", checked)
|
|
938
|
-
}
|
|
939
|
-
)
|
|
940
|
-
] }) : null
|
|
941
|
-
] }) : null
|
|
942
|
-
] });
|
|
943
|
-
}
|
|
1068
|
+
component: (props) => /* @__PURE__ */ jsx(BillingAddressSectionField, { ...props, t, addressesLoading, addressOptions, addressFormat })
|
|
944
1069
|
},
|
|
945
1070
|
{
|
|
946
1071
|
id: "comments",
|
|
@@ -973,130 +1098,7 @@ function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind, inbox
|
|
|
973
1098
|
title: "",
|
|
974
1099
|
column: 1,
|
|
975
1100
|
fields: [],
|
|
976
|
-
component:
|
|
977
|
-
const emailValue = typeof values.customerEmail === "string" ? values.customerEmail : "";
|
|
978
|
-
const { duplicate, checking } = useEmailDuplicateCheck(emailValue, {
|
|
979
|
-
disabled: false,
|
|
980
|
-
debounceMs: 400,
|
|
981
|
-
matchMode: "prefix"
|
|
982
|
-
});
|
|
983
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
984
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsx(
|
|
985
|
-
LookupSelect,
|
|
986
|
-
{
|
|
987
|
-
value: typeof values.customerEntityId === "string" ? values.customerEntityId : null,
|
|
988
|
-
onChange: (next) => {
|
|
989
|
-
if (next !== values.customerEntityId) {
|
|
990
|
-
resetAddressFormState(setValue);
|
|
991
|
-
}
|
|
992
|
-
setValue("customerEntityId", next);
|
|
993
|
-
loadAddresses(next).then((addrs) => {
|
|
994
|
-
const primary = addrs.find((addr) => addr.isPrimary);
|
|
995
|
-
if (primary) {
|
|
996
|
-
setValue("shippingAddressId", primary.id);
|
|
997
|
-
}
|
|
998
|
-
}).catch((err) => {
|
|
999
|
-
console.error("sales.documents.autoSelectAddress", err);
|
|
1000
|
-
});
|
|
1001
|
-
if (next) {
|
|
1002
|
-
const match = customers.find((entry) => entry.id === next);
|
|
1003
|
-
const possibleEmail = typeof match?.primaryEmail === "string" && match.primaryEmail.length ? match.primaryEmail : null;
|
|
1004
|
-
if (possibleEmail) {
|
|
1005
|
-
setValue("customerEmail", possibleEmail);
|
|
1006
|
-
} else {
|
|
1007
|
-
fetchCustomerEmail(next, match?.kind).then((email) => {
|
|
1008
|
-
if (email) setValue("customerEmail", email);
|
|
1009
|
-
}).catch(() => {
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
},
|
|
1014
|
-
fetchItems: async (query) => {
|
|
1015
|
-
const options = await loadCustomers(query);
|
|
1016
|
-
return options.map((opt) => ({
|
|
1017
|
-
id: opt.id,
|
|
1018
|
-
title: opt.label,
|
|
1019
|
-
subtitle: opt.subtitle ?? void 0,
|
|
1020
|
-
icon: opt.kind === "person" ? /* @__PURE__ */ jsx(UserRound, { className: "h-5 w-5 text-muted-foreground" }) : /* @__PURE__ */ jsx(Building2, { className: "h-5 w-5 text-muted-foreground" })
|
|
1021
|
-
}));
|
|
1022
|
-
},
|
|
1023
|
-
actionSlot: /* @__PURE__ */ jsx(
|
|
1024
|
-
CustomerQuickCreate,
|
|
1025
|
-
{
|
|
1026
|
-
t,
|
|
1027
|
-
onCreated: ({ id, email, label, kind, subtitle }) => {
|
|
1028
|
-
customerQuerySetter.current?.(label);
|
|
1029
|
-
setCustomers((prev) => {
|
|
1030
|
-
const exists = prev.some((entry) => entry.id === id);
|
|
1031
|
-
if (exists) return prev;
|
|
1032
|
-
const next = {
|
|
1033
|
-
id,
|
|
1034
|
-
label,
|
|
1035
|
-
subtitle: subtitle ?? void 0,
|
|
1036
|
-
kind,
|
|
1037
|
-
primaryEmail: email ?? null
|
|
1038
|
-
};
|
|
1039
|
-
return [next, ...prev];
|
|
1040
|
-
});
|
|
1041
|
-
setValue("customerEntityId", id);
|
|
1042
|
-
resetAddressFormState(setValue);
|
|
1043
|
-
loadAddresses(id).then((addrs) => {
|
|
1044
|
-
const primary = addrs.find((addr) => addr.isPrimary);
|
|
1045
|
-
if (primary) {
|
|
1046
|
-
setValue("shippingAddressId", primary.id);
|
|
1047
|
-
}
|
|
1048
|
-
}).catch((err) => {
|
|
1049
|
-
console.error("sales.documents.autoSelectAddress", err);
|
|
1050
|
-
});
|
|
1051
|
-
if (email && !values.customerEmail) {
|
|
1052
|
-
setValue("customerEmail", email);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
),
|
|
1057
|
-
onReady: ({ setQuery }) => {
|
|
1058
|
-
customerQuerySetter.current = setQuery;
|
|
1059
|
-
},
|
|
1060
|
-
searchPlaceholder: t("sales.documents.form.customer.placeholder", "Search customers\u2026"),
|
|
1061
|
-
loadingLabel: t("sales.documents.form.customer.loading", "Loading customers\u2026"),
|
|
1062
|
-
emptyLabel: t("sales.documents.form.customer.empty", "No customers found."),
|
|
1063
|
-
selectedHintLabel: (id) => t("sales.documents.form.customer.selected", "Selected customer: {{id}}", { id })
|
|
1064
|
-
}
|
|
1065
|
-
) }),
|
|
1066
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1067
|
-
/* @__PURE__ */ jsx(
|
|
1068
|
-
EmailInput,
|
|
1069
|
-
{
|
|
1070
|
-
value: emailValue,
|
|
1071
|
-
onChange: (event) => setValue("customerEmail", event.target.value),
|
|
1072
|
-
placeholder: t("sales.documents.form.email.placeholder", "Email used for the document"),
|
|
1073
|
-
spellCheck: false
|
|
1074
|
-
}
|
|
1075
|
-
),
|
|
1076
|
-
duplicate ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border bg-muted px-3 py-2 text-xs text-muted-foreground", children: [
|
|
1077
|
-
/* @__PURE__ */ jsx("span", { children: t("customers.people.form.emailDuplicateNotice", void 0, { name: duplicate.displayName }) }),
|
|
1078
|
-
/* @__PURE__ */ jsx(
|
|
1079
|
-
Button,
|
|
1080
|
-
{
|
|
1081
|
-
size: "sm",
|
|
1082
|
-
variant: "secondary",
|
|
1083
|
-
className: "px-4",
|
|
1084
|
-
type: "button",
|
|
1085
|
-
disabled: values.customerEntityId === duplicate.id,
|
|
1086
|
-
"aria-disabled": values.customerEntityId === duplicate.id,
|
|
1087
|
-
onClick: () => {
|
|
1088
|
-
setValue("customerEntityId", duplicate.id);
|
|
1089
|
-
resetAddressFormState(setValue);
|
|
1090
|
-
loadAddresses(duplicate.id);
|
|
1091
|
-
},
|
|
1092
|
-
children: values.customerEntityId === duplicate.id ? t("sales.documents.form.email.alreadySelected", "Selected customer") : t("sales.documents.form.email.selectCustomer", "Select customer")
|
|
1093
|
-
}
|
|
1094
|
-
)
|
|
1095
|
-
] }) : null,
|
|
1096
|
-
!duplicate && checking ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customers.people.form.emailChecking") }) : null
|
|
1097
|
-
] })
|
|
1098
|
-
] });
|
|
1099
|
-
}
|
|
1101
|
+
component: (ctx) => /* @__PURE__ */ jsx(CustomerGroupComponent, { ...ctx, t, customers, setCustomers, customerQuerySetter, loadAddresses, loadCustomers, fetchCustomerEmail, resetAddressFormState })
|
|
1100
1102
|
},
|
|
1101
1103
|
{ id: "channels-comments", title: "", column: 1, fields: ["channelId", "comments"] },
|
|
1102
1104
|
{ id: "currency", title: "", column: 2, fields: ["currencyCode"] },
|