@open-mercato/core 0.6.6-develop.6386.1.391a1afb9e → 0.6.6-develop.6388.1.b9b9f5e778
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/customers/api/deals/map/route.js +433 -0
- package/dist/modules/customers/api/deals/map/route.js.map +7 -0
- package/dist/modules/customers/backend/customers/companies/create/page.js +2 -0
- package/dist/modules/customers/backend/customers/companies/create/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/map/components/DealsLocationPanel.js +159 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsLocationPanel.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvas.js +26 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvas.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvasImpl.js +321 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvasImpl.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +533 -0
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/map/page.js +55 -0
- package/dist/modules/customers/backend/customers/deals/map/page.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/map/page.meta.js +38 -0
- package/dist/modules/customers/backend/customers/deals/map/page.meta.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DealCard.js +38 -47
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DealCard.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.js +3 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +22 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/create/page.js +2 -0
- package/dist/modules/customers/backend/customers/people/create/page.js.map +2 -2
- package/dist/modules/customers/components/AddressEditor.js +34 -2
- package/dist/modules/customers/components/AddressEditor.js.map +2 -2
- package/dist/modules/customers/components/AddressTiles.js +43 -3
- package/dist/modules/customers/components/AddressTiles.js.map +2 -2
- package/dist/modules/customers/components/detail/AddressesSection.js +4 -1
- package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +10 -0
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/validators.js +3 -2
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/dealsMapLocation.js +75 -0
- package/dist/modules/customers/lib/dealsMapLocation.js.map +7 -0
- package/package.json +11 -7
- package/src/modules/customers/api/deals/map/route.ts +549 -0
- package/src/modules/customers/backend/customers/companies/create/page.tsx +2 -0
- package/src/modules/customers/backend/customers/deals/map/components/DealsLocationPanel.tsx +213 -0
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapCanvas.tsx +79 -0
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapCanvasImpl.tsx +407 -0
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +667 -0
- package/src/modules/customers/backend/customers/deals/map/page.meta.ts +36 -0
- package/src/modules/customers/backend/customers/deals/map/page.tsx +77 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/DealCard.tsx +57 -42
- package/src/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.tsx +3 -3
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +22 -1
- package/src/modules/customers/backend/customers/people/create/page.tsx +2 -0
- package/src/modules/customers/components/AddressEditor.tsx +35 -0
- package/src/modules/customers/components/AddressTiles.tsx +51 -1
- package/src/modules/customers/components/detail/AddressesSection.tsx +3 -0
- package/src/modules/customers/components/formConfig.tsx +10 -0
- package/src/modules/customers/data/validators.ts +13 -2
- package/src/modules/customers/i18n/de.json +24 -0
- package/src/modules/customers/i18n/en.json +24 -0
- package/src/modules/customers/i18n/es.json +24 -0
- package/src/modules/customers/i18n/pl.json +24 -0
- package/src/modules/customers/lib/dealsMapLocation.ts +125 -0
|
@@ -33,7 +33,8 @@ function AddressEditor({
|
|
|
33
33
|
disabled = false,
|
|
34
34
|
errors = {},
|
|
35
35
|
hidePrimaryToggle = false,
|
|
36
|
-
showFormatHint = true
|
|
36
|
+
showFormatHint = true,
|
|
37
|
+
showCoordinateFields = false
|
|
37
38
|
}) {
|
|
38
39
|
const pathname = usePathname();
|
|
39
40
|
const searchParams = useSearchParams();
|
|
@@ -61,6 +62,7 @@ function AddressEditor({
|
|
|
61
62
|
region: value.region ?? "",
|
|
62
63
|
postalCode: value.postalCode ?? "",
|
|
63
64
|
country: value.country ?? "",
|
|
65
|
+
...showCoordinateFields ? { latitude: value.latitude ?? "", longitude: value.longitude ?? "" } : {},
|
|
64
66
|
isPrimary: value.isPrimary ?? false
|
|
65
67
|
};
|
|
66
68
|
const update = React.useCallback(
|
|
@@ -388,7 +390,37 @@ function AddressEditor({
|
|
|
388
390
|
]
|
|
389
391
|
}
|
|
390
392
|
),
|
|
391
|
-
errors.country ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errors.country }) : null
|
|
393
|
+
errors.country ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errors.country }) : null,
|
|
394
|
+
showCoordinateFields ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
395
|
+
/* @__PURE__ */ jsx(
|
|
396
|
+
Input,
|
|
397
|
+
{
|
|
398
|
+
className: inputClass("latitude"),
|
|
399
|
+
placeholder: t("customers.people.detail.addresses.fields.latitude", "Latitude"),
|
|
400
|
+
"aria-label": t("customers.people.detail.addresses.fields.latitude", "Latitude"),
|
|
401
|
+
inputMode: "decimal",
|
|
402
|
+
value: current.latitude ?? "",
|
|
403
|
+
onChange: (evt) => update("latitude", evt.target.value),
|
|
404
|
+
disabled,
|
|
405
|
+
"aria-invalid": errors.latitude ? "true" : void 0
|
|
406
|
+
}
|
|
407
|
+
),
|
|
408
|
+
errors.latitude ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errors.latitude }) : null,
|
|
409
|
+
/* @__PURE__ */ jsx(
|
|
410
|
+
Input,
|
|
411
|
+
{
|
|
412
|
+
className: inputClass("longitude"),
|
|
413
|
+
placeholder: t("customers.people.detail.addresses.fields.longitude", "Longitude"),
|
|
414
|
+
"aria-label": t("customers.people.detail.addresses.fields.longitude", "Longitude"),
|
|
415
|
+
inputMode: "decimal",
|
|
416
|
+
value: current.longitude ?? "",
|
|
417
|
+
onChange: (evt) => update("longitude", evt.target.value),
|
|
418
|
+
disabled,
|
|
419
|
+
"aria-invalid": errors.longitude ? "true" : void 0
|
|
420
|
+
}
|
|
421
|
+
),
|
|
422
|
+
errors.longitude ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errors.longitude }) : null
|
|
423
|
+
] }) : null
|
|
392
424
|
] }),
|
|
393
425
|
!hidePrimaryToggle ? /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
|
|
394
426
|
/* @__PURE__ */ jsx(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/AddressEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { buildCountryOptions } from '@open-mercato/shared/lib/location/countries'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport type { AddressFormatStrategy } from '../utils/addressFormat'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type AddressEditorDraft = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\nexport type AddressEditorField =\n | 'name'\n | 'purpose'\n | 'companyName'\n | 'addressLine1'\n | 'addressLine2'\n | 'buildingNumber'\n | 'flatNumber'\n | 'city'\n | 'region'\n | 'postalCode'\n | 'country'\n | 'isPrimary'\n\ntype AddressEditorProps = {\n value: AddressEditorDraft\n onChange: (next: AddressEditorDraft) => void\n format: AddressFormatStrategy\n t: Translator\n disabled?: boolean\n errors?: Partial<Record<AddressEditorField, string>>\n hidePrimaryToggle?: boolean\n showFormatHint?: boolean\n}\n\nexport function AddressEditor({\n value,\n onChange,\n format,\n t,\n disabled = false,\n errors = {},\n hidePrimaryToggle = false,\n showFormatHint = true,\n}: AddressEditorProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const { options: addressTypes, loading: addressTypesLoading, error: addressTypeError, createType } = useAddressTypes(t)\n const [typeDialogOpen, setTypeDialogOpen] = React.useState(false)\n const [typeValue, setTypeValue] = React.useState('')\n const [typeFormError, setTypeFormError] = React.useState<string | null>(null)\n const [countryDialogOpen, setCountryDialogOpen] = React.useState(false)\n const [countryQuery, setCountryQuery] = React.useState('')\n\n const countryOptions = React.useMemo(\n () =>\n buildCountryOptions({\n transformLabel: (code, fallback) => t(`customers.countries.${code.toLowerCase()}`, fallback ?? code),\n }),\n [t],\n )\n\n const current: AddressEditorDraft = {\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n isPrimary: value.isPrimary ?? false,\n }\n\n const update = React.useCallback(\n (key: keyof AddressEditorDraft, nextValue: string | boolean) => {\n onChange({ ...current, [key]: nextValue })\n },\n [current, onChange],\n )\n\n const filteredCountryOptions = React.useMemo(() => {\n const query = countryQuery.trim().toLowerCase()\n if (!query.length) return countryOptions\n return countryOptions.filter(\n (option) => option.label.toLowerCase().includes(query) || option.code.toLowerCase().includes(query),\n )\n }, [countryOptions, countryQuery])\n\n const selectedCountry = React.useMemo(() => {\n const code = (current.country ?? '').toUpperCase()\n if (!code.length) return null\n return countryOptions.find((option) => option.code === code) ?? null\n }, [countryOptions, current.country])\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageAddressTypesHref = React.useMemo(\n () => buildHrefWithReturnTo('/backend/config/customers', returnTo),\n [returnTo],\n )\n\n const handleTypeSubmit = React.useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n const trimmed = typeValue.trim()\n if (!trimmed.length) {\n setTypeFormError(t('customers.people.detail.addresses.types.emptyError', 'Please provide a value'))\n return\n }\n setTypeFormError(null)\n await createType(trimmed)\n setTypeDialogOpen(false)\n setTypeValue('')\n },\n [createType, t, typeValue],\n )\n\n const inputClass = (field: AddressEditorField) =>\n [\n 'w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',\n errors[field] ? 'border-status-error-border focus:ring-status-error-border' : 'border-input bg-background',\n ].join(' ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('name')}\n placeholder={t('customers.people.detail.addresses.fields.label', 'Label')}\n value={current.name}\n onChange={(evt) => update('name', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.name ? 'true' : undefined}\n />\n <div className=\"flex gap-2\">\n <Select\n value={current.purpose || undefined}\n onValueChange={(next) => update('purpose', next ?? '')}\n disabled={disabled}\n >\n <SelectTrigger\n className={errors.purpose ? 'border-destructive' : undefined}\n aria-invalid={errors.purpose ? 'true' : undefined}\n >\n <SelectValue\n placeholder={\n addressTypesLoading\n ? t('customers.people.detail.addresses.types.loading', 'Loading\u2026')\n : t('customers.people.detail.addresses.types.placeholder', 'Address type')\n }\n />\n </SelectTrigger>\n <SelectContent>\n {addressTypes.map((entry) => (\n <SelectItem key={entry.value} value={entry.value}>\n {entry.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Dialog open={typeDialogOpen} onOpenChange={setTypeDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"icon\" className=\"shrink-0\" disabled={disabled}>\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.types.add', 'Add address type')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.types.addHint', 'Create a new address type for reuse.')}\n </DialogDescription>\n </DialogHeader>\n <form className=\"space-y-3\" onSubmit={handleTypeSubmit}>\n <Input\n autoFocus\n value={typeValue}\n onChange={(evt) => {\n setTypeValue(evt.target.value)\n if (typeFormError) setTypeFormError(null)\n }}\n placeholder={t('customers.people.detail.addresses.types.placeholder', 'Address type')}\n disabled={disabled}\n aria-invalid={typeFormError ? 'true' : undefined}\n />\n {typeFormError ? <p className=\"text-sm text-destructive\">{typeFormError}</p> : null}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTypeDialogOpen(false)} disabled={disabled}>\n {t('customers.people.detail.addresses.types.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled || !typeValue.trim()}>\n {t('customers.people.detail.addresses.types.save', 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n <Button\n asChild\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n disabled={disabled}\n title={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Link\n href={manageAddressTypesHref}\n aria-label={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Settings className=\"h-4 w-4\" />\n </Link>\n </Button>\n </div>\n </div>\n {errors.purpose ? <p className=\"text-xs text-destructive\">{errors.purpose}</p> : null}\n {addressTypeError ? <p className=\"text-xs text-destructive\">{addressTypeError}</p> : null}\n <Input\n className={inputClass('companyName')}\n placeholder={t('customers.people.detail.addresses.fields.companyName', 'Company name')}\n value={current.companyName}\n onChange={(evt) => update('companyName', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.companyName ? 'true' : undefined}\n />\n {showFormatHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {format === 'street_first'\n ? t('customers.people.detail.addresses.streetFormatHint', 'Street-first layout is active.')\n : t('customers.people.detail.addresses.lineFormatHint', 'Address-line layout is active.')}\n </p>\n ) : null}\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('addressLine1')}\n placeholder={\n format === 'street_first'\n ? t('customers.people.detail.addresses.fields.street', 'Street')\n : t('customers.people.detail.addresses.fields.line1', 'Address line 1')\n }\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n {errors.addressLine1 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine1}</p> : null}\n {format === 'street_first' ? (\n <>\n <Input\n className={inputClass('buildingNumber')}\n placeholder={t('customers.people.detail.addresses.fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={t('customers.people.detail.addresses.fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.streetExtra', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n ) : (\n <>\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.line2', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n )}\n <Input\n className={inputClass('city')}\n placeholder={t('customers.people.detail.addresses.fields.city', 'City')}\n value={current.city}\n onChange={(evt) => update('city', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.city ? 'true' : undefined}\n />\n {errors.city ? <p className=\"text-xs text-destructive\">{errors.city}</p> : null}\n <Input\n className={inputClass('region')}\n placeholder={t('customers.people.detail.addresses.fields.region', 'Region/state')}\n value={current.region}\n onChange={(evt) => update('region', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.region ? 'true' : undefined}\n />\n {errors.region ? <p className=\"text-xs text-destructive\">{errors.region}</p> : null}\n <Input\n className={inputClass('postalCode')}\n placeholder={t('customers.people.detail.addresses.fields.postalCode', 'Postal code')}\n value={current.postalCode}\n onChange={(evt) => update('postalCode', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.postalCode ? 'true' : undefined}\n />\n {errors.postalCode ? <p className=\"text-xs text-destructive\">{errors.postalCode}</p> : null}\n <Dialog\n open={countryDialogOpen}\n onOpenChange={(open) => {\n setCountryDialogOpen(open)\n if (!open) setCountryQuery('')\n }}\n >\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n className={`${inputClass('country')} h-10 w-full justify-between`}\n disabled={disabled}\n aria-invalid={errors.country ? 'true' : undefined}\n >\n <span className=\"truncate text-left\">\n {selectedCountry\n ? `${selectedCountry.label}`\n : t('customers.people.detail.addresses.countryPlaceholder', 'Select country')}\n </span>\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {selectedCountry ? selectedCountry.code : null}\n </span>\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.countryDialogTitle', 'Select country')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.countryDialogDescription', 'Search and choose an ISO country code.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <Input\n placeholder={t('customers.people.detail.addresses.countrySearch', 'Search country')}\n value={countryQuery}\n onChange={(evt) => setCountryQuery(evt.target.value)}\n />\n <div className=\"max-h-64 overflow-y-auto rounded border divide-y\">\n {filteredCountryOptions.length === 0 ? (\n <p className=\"px-3 py-2 text-sm text-muted-foreground\">\n {t('customers.people.detail.addresses.countryEmpty', 'No matches found')}\n </p>\n ) : (\n filteredCountryOptions.map((option) => (\n <Button\n key={option.code}\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between rounded-none font-normal\"\n onClick={() => {\n update('country', option.code)\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n >\n <span className=\"truncate\">{option.label}</span>\n <span className=\"text-xs text-muted-foreground\">{option.code}</span>\n </Button>\n ))\n )}\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n update('country', '')\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n disabled={disabled}\n >\n {t('customers.people.detail.addresses.countryClear', 'Clear')}\n </Button>\n <Button type=\"button\" onClick={() => setCountryDialogOpen(false)}>\n {t('customers.people.detail.addresses.countryClose', 'Done')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n {errors.country ? <p className=\"text-xs text-destructive\">{errors.country}</p> : null}\n </div>\n {!hidePrimaryToggle ? (\n <label className=\"inline-flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={current.isPrimary}\n onChange={(evt) => update('isPrimary', evt.target.checked)}\n disabled={disabled}\n aria-invalid={errors.isPrimary ? 'true' : undefined}\n />\n <span>{t('customers.people.detail.addresses.fields.primary', 'Set as primary')}</span>\n </label>\n ) : null}\n {errors.isPrimary ? <p className=\"text-xs text-destructive\">{errors.isPrimary}</p> : null}\n </div>\n )\n}\n\nexport default AddressEditor\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { buildCountryOptions } from '@open-mercato/shared/lib/location/countries'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport type { AddressFormatStrategy } from '../utils/addressFormat'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type AddressEditorDraft = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n latitude?: string\n longitude?: string\n isPrimary: boolean\n}\n\nexport type AddressEditorField =\n | 'name'\n | 'purpose'\n | 'companyName'\n | 'addressLine1'\n | 'addressLine2'\n | 'buildingNumber'\n | 'flatNumber'\n | 'city'\n | 'region'\n | 'postalCode'\n | 'country'\n | 'latitude'\n | 'longitude'\n | 'isPrimary'\n\ntype AddressEditorProps = {\n value: AddressEditorDraft\n onChange: (next: AddressEditorDraft) => void\n format: AddressFormatStrategy\n t: Translator\n disabled?: boolean\n errors?: Partial<Record<AddressEditorField, string>>\n hidePrimaryToggle?: boolean\n showFormatHint?: boolean\n showCoordinateFields?: boolean\n}\n\nexport function AddressEditor({\n value,\n onChange,\n format,\n t,\n disabled = false,\n errors = {},\n hidePrimaryToggle = false,\n showFormatHint = true,\n showCoordinateFields = false,\n}: AddressEditorProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const { options: addressTypes, loading: addressTypesLoading, error: addressTypeError, createType } = useAddressTypes(t)\n const [typeDialogOpen, setTypeDialogOpen] = React.useState(false)\n const [typeValue, setTypeValue] = React.useState('')\n const [typeFormError, setTypeFormError] = React.useState<string | null>(null)\n const [countryDialogOpen, setCountryDialogOpen] = React.useState(false)\n const [countryQuery, setCountryQuery] = React.useState('')\n\n const countryOptions = React.useMemo(\n () =>\n buildCountryOptions({\n transformLabel: (code, fallback) => t(`customers.countries.${code.toLowerCase()}`, fallback ?? code),\n }),\n [t],\n )\n\n const current: AddressEditorDraft = {\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n ...(showCoordinateFields\n ? { latitude: value.latitude ?? '', longitude: value.longitude ?? '' }\n : {}),\n isPrimary: value.isPrimary ?? false,\n }\n\n const update = React.useCallback(\n (key: keyof AddressEditorDraft, nextValue: string | boolean) => {\n onChange({ ...current, [key]: nextValue })\n },\n [current, onChange],\n )\n\n const filteredCountryOptions = React.useMemo(() => {\n const query = countryQuery.trim().toLowerCase()\n if (!query.length) return countryOptions\n return countryOptions.filter(\n (option) => option.label.toLowerCase().includes(query) || option.code.toLowerCase().includes(query),\n )\n }, [countryOptions, countryQuery])\n\n const selectedCountry = React.useMemo(() => {\n const code = (current.country ?? '').toUpperCase()\n if (!code.length) return null\n return countryOptions.find((option) => option.code === code) ?? null\n }, [countryOptions, current.country])\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageAddressTypesHref = React.useMemo(\n () => buildHrefWithReturnTo('/backend/config/customers', returnTo),\n [returnTo],\n )\n\n const handleTypeSubmit = React.useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n const trimmed = typeValue.trim()\n if (!trimmed.length) {\n setTypeFormError(t('customers.people.detail.addresses.types.emptyError', 'Please provide a value'))\n return\n }\n setTypeFormError(null)\n await createType(trimmed)\n setTypeDialogOpen(false)\n setTypeValue('')\n },\n [createType, t, typeValue],\n )\n\n const inputClass = (field: AddressEditorField) =>\n [\n 'w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',\n errors[field] ? 'border-status-error-border focus:ring-status-error-border' : 'border-input bg-background',\n ].join(' ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('name')}\n placeholder={t('customers.people.detail.addresses.fields.label', 'Label')}\n value={current.name}\n onChange={(evt) => update('name', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.name ? 'true' : undefined}\n />\n <div className=\"flex gap-2\">\n <Select\n value={current.purpose || undefined}\n onValueChange={(next) => update('purpose', next ?? '')}\n disabled={disabled}\n >\n <SelectTrigger\n className={errors.purpose ? 'border-destructive' : undefined}\n aria-invalid={errors.purpose ? 'true' : undefined}\n >\n <SelectValue\n placeholder={\n addressTypesLoading\n ? t('customers.people.detail.addresses.types.loading', 'Loading\u2026')\n : t('customers.people.detail.addresses.types.placeholder', 'Address type')\n }\n />\n </SelectTrigger>\n <SelectContent>\n {addressTypes.map((entry) => (\n <SelectItem key={entry.value} value={entry.value}>\n {entry.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Dialog open={typeDialogOpen} onOpenChange={setTypeDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"icon\" className=\"shrink-0\" disabled={disabled}>\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.types.add', 'Add address type')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.types.addHint', 'Create a new address type for reuse.')}\n </DialogDescription>\n </DialogHeader>\n <form className=\"space-y-3\" onSubmit={handleTypeSubmit}>\n <Input\n autoFocus\n value={typeValue}\n onChange={(evt) => {\n setTypeValue(evt.target.value)\n if (typeFormError) setTypeFormError(null)\n }}\n placeholder={t('customers.people.detail.addresses.types.placeholder', 'Address type')}\n disabled={disabled}\n aria-invalid={typeFormError ? 'true' : undefined}\n />\n {typeFormError ? <p className=\"text-sm text-destructive\">{typeFormError}</p> : null}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTypeDialogOpen(false)} disabled={disabled}>\n {t('customers.people.detail.addresses.types.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled || !typeValue.trim()}>\n {t('customers.people.detail.addresses.types.save', 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n <Button\n asChild\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n disabled={disabled}\n title={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Link\n href={manageAddressTypesHref}\n aria-label={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Settings className=\"h-4 w-4\" />\n </Link>\n </Button>\n </div>\n </div>\n {errors.purpose ? <p className=\"text-xs text-destructive\">{errors.purpose}</p> : null}\n {addressTypeError ? <p className=\"text-xs text-destructive\">{addressTypeError}</p> : null}\n <Input\n className={inputClass('companyName')}\n placeholder={t('customers.people.detail.addresses.fields.companyName', 'Company name')}\n value={current.companyName}\n onChange={(evt) => update('companyName', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.companyName ? 'true' : undefined}\n />\n {showFormatHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {format === 'street_first'\n ? t('customers.people.detail.addresses.streetFormatHint', 'Street-first layout is active.')\n : t('customers.people.detail.addresses.lineFormatHint', 'Address-line layout is active.')}\n </p>\n ) : null}\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('addressLine1')}\n placeholder={\n format === 'street_first'\n ? t('customers.people.detail.addresses.fields.street', 'Street')\n : t('customers.people.detail.addresses.fields.line1', 'Address line 1')\n }\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n {errors.addressLine1 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine1}</p> : null}\n {format === 'street_first' ? (\n <>\n <Input\n className={inputClass('buildingNumber')}\n placeholder={t('customers.people.detail.addresses.fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={t('customers.people.detail.addresses.fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.streetExtra', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n ) : (\n <>\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.line2', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n )}\n <Input\n className={inputClass('city')}\n placeholder={t('customers.people.detail.addresses.fields.city', 'City')}\n value={current.city}\n onChange={(evt) => update('city', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.city ? 'true' : undefined}\n />\n {errors.city ? <p className=\"text-xs text-destructive\">{errors.city}</p> : null}\n <Input\n className={inputClass('region')}\n placeholder={t('customers.people.detail.addresses.fields.region', 'Region/state')}\n value={current.region}\n onChange={(evt) => update('region', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.region ? 'true' : undefined}\n />\n {errors.region ? <p className=\"text-xs text-destructive\">{errors.region}</p> : null}\n <Input\n className={inputClass('postalCode')}\n placeholder={t('customers.people.detail.addresses.fields.postalCode', 'Postal code')}\n value={current.postalCode}\n onChange={(evt) => update('postalCode', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.postalCode ? 'true' : undefined}\n />\n {errors.postalCode ? <p className=\"text-xs text-destructive\">{errors.postalCode}</p> : null}\n <Dialog\n open={countryDialogOpen}\n onOpenChange={(open) => {\n setCountryDialogOpen(open)\n if (!open) setCountryQuery('')\n }}\n >\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n className={`${inputClass('country')} h-10 w-full justify-between`}\n disabled={disabled}\n aria-invalid={errors.country ? 'true' : undefined}\n >\n <span className=\"truncate text-left\">\n {selectedCountry\n ? `${selectedCountry.label}`\n : t('customers.people.detail.addresses.countryPlaceholder', 'Select country')}\n </span>\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {selectedCountry ? selectedCountry.code : null}\n </span>\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.countryDialogTitle', 'Select country')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.countryDialogDescription', 'Search and choose an ISO country code.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <Input\n placeholder={t('customers.people.detail.addresses.countrySearch', 'Search country')}\n value={countryQuery}\n onChange={(evt) => setCountryQuery(evt.target.value)}\n />\n <div className=\"max-h-64 overflow-y-auto rounded border divide-y\">\n {filteredCountryOptions.length === 0 ? (\n <p className=\"px-3 py-2 text-sm text-muted-foreground\">\n {t('customers.people.detail.addresses.countryEmpty', 'No matches found')}\n </p>\n ) : (\n filteredCountryOptions.map((option) => (\n <Button\n key={option.code}\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between rounded-none font-normal\"\n onClick={() => {\n update('country', option.code)\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n >\n <span className=\"truncate\">{option.label}</span>\n <span className=\"text-xs text-muted-foreground\">{option.code}</span>\n </Button>\n ))\n )}\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n update('country', '')\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n disabled={disabled}\n >\n {t('customers.people.detail.addresses.countryClear', 'Clear')}\n </Button>\n <Button type=\"button\" onClick={() => setCountryDialogOpen(false)}>\n {t('customers.people.detail.addresses.countryClose', 'Done')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n {errors.country ? <p className=\"text-xs text-destructive\">{errors.country}</p> : null}\n {showCoordinateFields ? (\n <>\n <Input\n className={inputClass('latitude')}\n placeholder={t('customers.people.detail.addresses.fields.latitude', 'Latitude')}\n aria-label={t('customers.people.detail.addresses.fields.latitude', 'Latitude')}\n inputMode=\"decimal\"\n value={current.latitude ?? ''}\n onChange={(evt) => update('latitude', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.latitude ? 'true' : undefined}\n />\n {errors.latitude ? <p className=\"text-xs text-destructive\">{errors.latitude}</p> : null}\n <Input\n className={inputClass('longitude')}\n placeholder={t('customers.people.detail.addresses.fields.longitude', 'Longitude')}\n aria-label={t('customers.people.detail.addresses.fields.longitude', 'Longitude')}\n inputMode=\"decimal\"\n value={current.longitude ?? ''}\n onChange={(evt) => update('longitude', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.longitude ? 'true' : undefined}\n />\n {errors.longitude ? <p className=\"text-xs text-destructive\">{errors.longitude}</p> : null}\n </>\n ) : null}\n </div>\n {!hidePrimaryToggle ? (\n <label className=\"inline-flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={current.isPrimary}\n onChange={(evt) => update('isPrimary', evt.target.checked)}\n disabled={disabled}\n aria-invalid={errors.isPrimary ? 'true' : undefined}\n />\n <span>{t('customers.people.detail.addresses.fields.primary', 'Set as primary')}</span>\n </label>\n ) : null}\n {errors.isPrimary ? <p className=\"text-xs text-destructive\">{errors.isPrimary}</p> : null}\n </div>\n )\n}\n\nexport default AddressEditor\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,6 +8,7 @@ import { TabEmptyState } from "@open-mercato/ui/backend/detail";
|
|
|
8
8
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
9
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
10
10
|
import { AddressView, formatAddressJson, formatAddressString } from "../utils/addressFormat.js";
|
|
11
|
+
import { normalizeCoordinateInput, validateCoordinateInput } from "@open-mercato/shared/lib/location/coordinates";
|
|
11
12
|
import AddressEditor from "./AddressEditor.js";
|
|
12
13
|
import { useAddressTypes } from "./detail/hooks/useAddressTypes.js";
|
|
13
14
|
import { useQueryClient } from "@tanstack/react-query";
|
|
@@ -23,6 +24,8 @@ const defaultDraft = {
|
|
|
23
24
|
region: "",
|
|
24
25
|
postalCode: "",
|
|
25
26
|
country: "",
|
|
27
|
+
latitude: "",
|
|
28
|
+
longitude: "",
|
|
26
29
|
isPrimary: false
|
|
27
30
|
};
|
|
28
31
|
const serverFieldMap = {
|
|
@@ -37,6 +40,8 @@ const serverFieldMap = {
|
|
|
37
40
|
region: "region",
|
|
38
41
|
postalCode: "postalCode",
|
|
39
42
|
country: "country",
|
|
43
|
+
latitude: "latitude",
|
|
44
|
+
longitude: "longitude",
|
|
40
45
|
isPrimary: "isPrimary"
|
|
41
46
|
};
|
|
42
47
|
function normalizeOptional(value) {
|
|
@@ -86,7 +91,8 @@ function CustomerAddressTiles({
|
|
|
86
91
|
hideAddButton = false,
|
|
87
92
|
onAddActionChange,
|
|
88
93
|
emptyStateTitle,
|
|
89
|
-
emptyStateActionLabel
|
|
94
|
+
emptyStateActionLabel,
|
|
95
|
+
showCoordinateFields = false
|
|
90
96
|
}) {
|
|
91
97
|
const scopeVersion = useOrganizationScopeVersion();
|
|
92
98
|
const queryClient = useQueryClient();
|
|
@@ -114,6 +120,8 @@ function CustomerAddressTiles({
|
|
|
114
120
|
region: t("customers.people.detail.addresses.fields.region"),
|
|
115
121
|
postalCode: t("customers.people.detail.addresses.fields.postalCode"),
|
|
116
122
|
country: t("customers.people.detail.addresses.fields.country"),
|
|
123
|
+
latitude: t("customers.people.detail.addresses.fields.latitude", "Latitude"),
|
|
124
|
+
longitude: t("customers.people.detail.addresses.fields.longitude", "Longitude"),
|
|
117
125
|
isPrimary: t("customers.people.detail.addresses.fields.primary")
|
|
118
126
|
}),
|
|
119
127
|
[t]
|
|
@@ -182,6 +190,8 @@ function CustomerAddressTiles({
|
|
|
182
190
|
region: address.region ?? "",
|
|
183
191
|
postalCode: address.postalCode ?? "",
|
|
184
192
|
country: address.country ? address.country.toUpperCase() : "",
|
|
193
|
+
latitude: address.latitude != null ? String(address.latitude) : "",
|
|
194
|
+
longitude: address.longitude != null ? String(address.longitude) : "",
|
|
185
195
|
isPrimary: address.isPrimary ?? false
|
|
186
196
|
});
|
|
187
197
|
setFieldErrors({});
|
|
@@ -207,6 +217,28 @@ function CustomerAddressTiles({
|
|
|
207
217
|
setGeneralError(message);
|
|
208
218
|
return;
|
|
209
219
|
}
|
|
220
|
+
const coordinateErrors = {};
|
|
221
|
+
for (const key of ["latitude", "longitude"]) {
|
|
222
|
+
const result = validateCoordinateInput(key, draft[key]);
|
|
223
|
+
if (result.status === "invalid") {
|
|
224
|
+
coordinateErrors[key] = t(
|
|
225
|
+
"customers.people.detail.addresses.validation.invalid",
|
|
226
|
+
void 0,
|
|
227
|
+
{ field: fieldLabels[key] }
|
|
228
|
+
);
|
|
229
|
+
} else if (result.status === "outOfRange") {
|
|
230
|
+
coordinateErrors[key] = t(
|
|
231
|
+
"customers.people.detail.addresses.validation.coordinateRange",
|
|
232
|
+
void 0,
|
|
233
|
+
{ field: fieldLabels[key], min: result.min, max: result.max }
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (Object.keys(coordinateErrors).length) {
|
|
238
|
+
setFieldErrors((prev) => ({ ...prev, ...coordinateErrors }));
|
|
239
|
+
setGeneralError(Object.values(coordinateErrors)[0] ?? null);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
210
242
|
const payload = {
|
|
211
243
|
addressLine1: trimmedLine1,
|
|
212
244
|
isPrimary: draft.isPrimary
|
|
@@ -231,6 +263,12 @@ function CustomerAddressTiles({
|
|
|
231
263
|
if (postal !== void 0) payload.postalCode = postal;
|
|
232
264
|
const country = normalizeOptional(draft.country);
|
|
233
265
|
if (country !== void 0) payload.country = country.toUpperCase();
|
|
266
|
+
const latitude = normalizeCoordinateInput(draft.latitude);
|
|
267
|
+
if (latitude !== void 0) payload.latitude = latitude;
|
|
268
|
+
else if (editingId) payload.latitude = null;
|
|
269
|
+
const longitude = normalizeCoordinateInput(draft.longitude);
|
|
270
|
+
if (longitude !== void 0) payload.longitude = longitude;
|
|
271
|
+
else if (editingId) payload.longitude = null;
|
|
234
272
|
setSaving(true);
|
|
235
273
|
setGeneralError(null);
|
|
236
274
|
setFieldErrors({});
|
|
@@ -327,7 +365,7 @@ function CustomerAddressTiles({
|
|
|
327
365
|
{
|
|
328
366
|
value: draft,
|
|
329
367
|
onChange: (next) => {
|
|
330
|
-
setDraft(next);
|
|
368
|
+
setDraft({ ...next, latitude: next.latitude ?? "", longitude: next.longitude ?? "" });
|
|
331
369
|
if (Object.keys(fieldErrors).length) {
|
|
332
370
|
const nextErrors = { ...fieldErrors };
|
|
333
371
|
Object.keys(nextErrors).forEach((key2) => {
|
|
@@ -343,7 +381,8 @@ function CustomerAddressTiles({
|
|
|
343
381
|
t,
|
|
344
382
|
disabled: disableActions,
|
|
345
383
|
errors: fieldErrors,
|
|
346
|
-
showFormatHint: !formatLoading
|
|
384
|
+
showFormatHint: !formatLoading,
|
|
385
|
+
showCoordinateFields
|
|
347
386
|
}
|
|
348
387
|
),
|
|
349
388
|
generalError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: generalError }) : null,
|
|
@@ -370,6 +409,7 @@ function CustomerAddressTiles({
|
|
|
370
409
|
handleSave,
|
|
371
410
|
generalError,
|
|
372
411
|
saving,
|
|
412
|
+
showCoordinateFields,
|
|
373
413
|
t
|
|
374
414
|
]
|
|
375
415
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/AddressTiles.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { AddressView, formatAddressJson, formatAddressString, type AddressFormatStrategy } from '../utils/addressFormat'\nimport AddressEditor from './AddressEditor'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { ensureCustomerDictionary, invalidateCustomerDictionary } from './detail/hooks/useCustomerDictionary'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type CustomerAddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n isPrimary?: boolean\n}\n\nexport type CustomerAddressValue = CustomerAddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n}\n\ntype CustomerAddressTilesProps = {\n addresses: CustomerAddressValue[]\n onCreate: (payload: CustomerAddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: CustomerAddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator): string {\n switch (detail.code) {\n case 'invalid_type':\n return t('customers.people.detail.addresses.validation.invalid', undefined, { field: fieldLabel })\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return t('customers.people.detail.addresses.validation.required', undefined, { field: fieldLabel })\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return t(\n 'customers.people.detail.addresses.validation.tooLong',\n undefined,\n {\n field: fieldLabel,\n max: detail.maximum,\n }\n )\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n default:\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n }\n}\n\nexport function CustomerAddressTiles({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid gap-4 min-[480px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n}: CustomerAddressTilesProps) {\n const scopeVersion = useOrganizationScopeVersion()\n const queryClient = useQueryClient()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\n const { map: addressTypeMap } = useAddressTypes(t)\n\n const fieldLabels = React.useMemo(\n () => ({\n name: t('customers.people.detail.addresses.fields.label'),\n purpose: t('customers.people.detail.addresses.fields.type'),\n companyName: t('customers.people.detail.addresses.fields.companyName', 'Company name'),\n addressLine1: t('customers.people.detail.addresses.fields.line1'),\n addressLine2: t('customers.people.detail.addresses.fields.line2'),\n street: t('customers.people.detail.addresses.fields.street', 'Street'),\n buildingNumber: t('customers.people.detail.addresses.fields.buildingNumber', 'Building number'),\n flatNumber: t('customers.people.detail.addresses.fields.flatNumber', 'Flat number'),\n city: t('customers.people.detail.addresses.fields.city'),\n region: t('customers.people.detail.addresses.fields.region'),\n postalCode: t('customers.people.detail.addresses.fields.postalCode'),\n country: t('customers.people.detail.addresses.fields.country'),\n isPrimary: t('customers.people.detail.addresses.fields.primary'),\n }),\n [t]\n )\n const line1FieldLabel = React.useMemo(\n () => (format === 'street_first' ? fieldLabels.street : fieldLabels.addressLine1),\n [fieldLabels.addressLine1, fieldLabels.street, format],\n )\n\n\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormat() {\n setFormatLoading(true)\n try {\n const call = await apiCall<{ addressFormat?: string; error?: string }>(\n '/api/customers/settings/address-format',\n )\n const payload = (call.result ?? {}) as Record<string, unknown>\n if (!call.ok) {\n if (!cancelled) {\n const message =\n typeof (payload as Record<string, unknown>)?.error === 'string'\n ? (payload as Record<string, unknown>).error as string\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n return\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n loadFormat().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [scopeVersion, t])\n\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const openEditForm = React.useCallback(\n (address: CustomerAddressValue) => {\n setDraft({\n name: address.name ?? '',\n purpose: address.purpose ?? '',\n companyName: address.companyName ?? '',\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? '',\n buildingNumber: address.buildingNumber ?? '',\n flatNumber: address.flatNumber ?? '',\n city: address.city ?? '',\n region: address.region ?? '',\n postalCode: address.postalCode ?? '',\n country: address.country ? address.country.toUpperCase() : '',\n isPrimary: address.isPrimary ?? false,\n })\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(address.id)\n setIsFormOpen(true)\n },\n []\n )\n\n const handleCancel = React.useCallback(() => {\n setIsFormOpen(false)\n resetForm()\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n const trimmedLine1 = draft.addressLine1.trim()\n if (!trimmedLine1.length) {\n const message = t(\n 'customers.people.detail.addresses.validation.required',\n undefined,\n { field: line1FieldLabel }\n )\n setFieldErrors((prev) => ({ ...prev, addressLine1: message }))\n setGeneralError(message)\n return\n }\n\n const payload: CustomerAddressInput = {\n addressLine1: trimmedLine1,\n isPrimary: draft.isPrimary,\n }\n\n const purpose = normalizeOptional(draft.purpose)\n if (purpose !== undefined) payload.purpose = purpose\n const name = normalizeOptional(draft.name)\n if (name !== undefined) payload.name = name\n const companyName = normalizeOptional(draft.companyName)\n if (companyName !== undefined) payload.companyName = companyName\n const line2 = normalizeOptional(draft.addressLine2)\n if (line2 !== undefined) payload.addressLine2 = line2\n const buildingNumber = normalizeOptional(draft.buildingNumber)\n if (buildingNumber !== undefined) payload.buildingNumber = buildingNumber\n const flatNumber = normalizeOptional(draft.flatNumber)\n if (flatNumber !== undefined) payload.flatNumber = flatNumber\n const city = normalizeOptional(draft.city)\n if (city !== undefined) payload.city = city\n const region = normalizeOptional(draft.region)\n if (region !== undefined) payload.region = region\n const postal = normalizeOptional(draft.postalCode)\n if (postal !== undefined) payload.postalCode = postal\n const country = normalizeOptional(draft.country)\n if (country !== undefined) payload.country = country.toUpperCase()\n\n setSaving(true)\n setGeneralError(null)\n setFieldErrors({})\n try {\n if (editingId && onUpdate) await onUpdate(editingId, payload)\n else await onCreate(payload)\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n for (const detail of details) {\n const path = Array.isArray(detail.path) ? detail.path : []\n const targetKey = path.length ? serverFieldMap[String(path[0])] : undefined\n if (!targetKey) continue\n const message = resolveFieldMessage(\n detail,\n targetKey === 'addressLine1' ? line1FieldLabel : fieldLabels[targetKey],\n t,\n )\n if (message) nextErrors[targetKey] = message\n }\n if (Object.keys(nextErrors).length) {\n setFieldErrors(nextErrors)\n setGeneralError(Object.values(nextErrors)[0] ?? null)\n setSaving(false)\n return\n }\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, editingId, fieldLabels, line1FieldLabel, onCreate, onUpdate, resetForm, t])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, onDelete, resetForm, t]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? t('customers.people.detail.addresses.add')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/30 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? t('customers.people.detail.addresses.editTitle')\n : t('customers.people.detail.addresses.addTitle')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.detail.addresses.formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft(next)\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n />\n {generalError ? <p className=\"text-xs text-status-error-text\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {t('customers.people.detail.addresses.cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? t('customers.people.detail.addresses.updating')\n : t('customers.people.detail.addresses.saving')}\n </>\n ) : editingId ? (\n t('customers.people.detail.addresses.update')\n ) : (\n t('customers.people.detail.addresses.save')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n saving,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('customers.people.detail.addresses.add')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(address.id)\n }\n const formattedJson = formatAddressJson(address, format)\n const formattedString = formatAddressString(address, format)\n\n return (\n <div\n key={address.id}\n className=\"rounded-lg border bg-background p-4 text-sm shadow-sm\"\n title={formattedString}\n data-address-json={JSON.stringify(formattedJson)}\n data-address-string={formattedString}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <span className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {address.name ||\n (address.purpose ? addressTypeMap.get(address.purpose) ?? address.purpose : null) ||\n t('customers.people.detail.address')}\n </span>\n {address.isPrimary ? (\n <span className=\"mt-1 inline-flex w-fit rounded bg-status-success-bg px-2 py-0.5 text-overline font-semibold text-status-success-text\">\n {t('customers.people.detail.primary')}\n </span>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => openEditForm(address)}\n disabled={disableActions}\n aria-label={t('customers.people.detail.addresses.editAction')}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-destructive hover:text-destructive focus-visible:text-destructive\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions || !onDelete}\n aria-label={t('customers.people.detail.addresses.deleteAction')}\n >\n {deletingId === address.id ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n <div className=\"mt-2 space-y-1\">\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {addressTypeMap.get(address.purpose) ?? address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"space-y-1\" lineClassName=\"text-sm\" />\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('__new') : null}\n </div>\n ) : isFormOpen && !editingId ? (\n <div className={gridClassName}>{renderFormTile('__new')}</div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { AddressView, formatAddressJson, formatAddressString, type AddressFormatStrategy } from '../utils/addressFormat'\nimport { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'\nimport AddressEditor from './AddressEditor'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { ensureCustomerDictionary, invalidateCustomerDictionary } from './detail/hooks/useCustomerDictionary'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type CustomerAddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n latitude?: number | null\n longitude?: number | null\n isPrimary?: boolean\n}\n\nexport type CustomerAddressValue = CustomerAddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype CustomerAddressTilesProps = {\n addresses: CustomerAddressValue[]\n onCreate: (payload: CustomerAddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: CustomerAddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n showCoordinateFields?: boolean\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n latitude: string\n longitude: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n latitude: '',\n longitude: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator): string {\n switch (detail.code) {\n case 'invalid_type':\n return t('customers.people.detail.addresses.validation.invalid', undefined, { field: fieldLabel })\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return t('customers.people.detail.addresses.validation.required', undefined, { field: fieldLabel })\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return t(\n 'customers.people.detail.addresses.validation.tooLong',\n undefined,\n {\n field: fieldLabel,\n max: detail.maximum,\n }\n )\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n default:\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n }\n}\n\nexport function CustomerAddressTiles({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid gap-4 min-[480px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n showCoordinateFields = false,\n}: CustomerAddressTilesProps) {\n const scopeVersion = useOrganizationScopeVersion()\n const queryClient = useQueryClient()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\n const { map: addressTypeMap } = useAddressTypes(t)\n\n const fieldLabels = React.useMemo(\n () => ({\n name: t('customers.people.detail.addresses.fields.label'),\n purpose: t('customers.people.detail.addresses.fields.type'),\n companyName: t('customers.people.detail.addresses.fields.companyName', 'Company name'),\n addressLine1: t('customers.people.detail.addresses.fields.line1'),\n addressLine2: t('customers.people.detail.addresses.fields.line2'),\n street: t('customers.people.detail.addresses.fields.street', 'Street'),\n buildingNumber: t('customers.people.detail.addresses.fields.buildingNumber', 'Building number'),\n flatNumber: t('customers.people.detail.addresses.fields.flatNumber', 'Flat number'),\n city: t('customers.people.detail.addresses.fields.city'),\n region: t('customers.people.detail.addresses.fields.region'),\n postalCode: t('customers.people.detail.addresses.fields.postalCode'),\n country: t('customers.people.detail.addresses.fields.country'),\n latitude: t('customers.people.detail.addresses.fields.latitude', 'Latitude'),\n longitude: t('customers.people.detail.addresses.fields.longitude', 'Longitude'),\n isPrimary: t('customers.people.detail.addresses.fields.primary'),\n }),\n [t]\n )\n const line1FieldLabel = React.useMemo(\n () => (format === 'street_first' ? fieldLabels.street : fieldLabels.addressLine1),\n [fieldLabels.addressLine1, fieldLabels.street, format],\n )\n\n\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormat() {\n setFormatLoading(true)\n try {\n const call = await apiCall<{ addressFormat?: string; error?: string }>(\n '/api/customers/settings/address-format',\n )\n const payload = (call.result ?? {}) as Record<string, unknown>\n if (!call.ok) {\n if (!cancelled) {\n const message =\n typeof (payload as Record<string, unknown>)?.error === 'string'\n ? (payload as Record<string, unknown>).error as string\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n return\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n loadFormat().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [scopeVersion, t])\n\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const openEditForm = React.useCallback(\n (address: CustomerAddressValue) => {\n setDraft({\n name: address.name ?? '',\n purpose: address.purpose ?? '',\n companyName: address.companyName ?? '',\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? '',\n buildingNumber: address.buildingNumber ?? '',\n flatNumber: address.flatNumber ?? '',\n city: address.city ?? '',\n region: address.region ?? '',\n postalCode: address.postalCode ?? '',\n country: address.country ? address.country.toUpperCase() : '',\n latitude: address.latitude != null ? String(address.latitude) : '',\n longitude: address.longitude != null ? String(address.longitude) : '',\n isPrimary: address.isPrimary ?? false,\n })\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(address.id)\n setIsFormOpen(true)\n },\n []\n )\n\n const handleCancel = React.useCallback(() => {\n setIsFormOpen(false)\n resetForm()\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n const trimmedLine1 = draft.addressLine1.trim()\n if (!trimmedLine1.length) {\n const message = t(\n 'customers.people.detail.addresses.validation.required',\n undefined,\n { field: line1FieldLabel }\n )\n setFieldErrors((prev) => ({ ...prev, addressLine1: message }))\n setGeneralError(message)\n return\n }\n\n const coordinateErrors: Partial<Record<DraftFieldKey, string>> = {}\n for (const key of ['latitude', 'longitude'] as const) {\n const result = validateCoordinateInput(key, draft[key])\n if (result.status === 'invalid') {\n coordinateErrors[key] = t(\n 'customers.people.detail.addresses.validation.invalid',\n undefined,\n { field: fieldLabels[key] }\n )\n } else if (result.status === 'outOfRange') {\n coordinateErrors[key] = t(\n 'customers.people.detail.addresses.validation.coordinateRange',\n undefined,\n { field: fieldLabels[key], min: result.min, max: result.max }\n )\n }\n }\n if (Object.keys(coordinateErrors).length) {\n setFieldErrors((prev) => ({ ...prev, ...coordinateErrors }))\n setGeneralError(Object.values(coordinateErrors)[0] ?? null)\n return\n }\n\n const payload: CustomerAddressInput = {\n addressLine1: trimmedLine1,\n isPrimary: draft.isPrimary,\n }\n\n const purpose = normalizeOptional(draft.purpose)\n if (purpose !== undefined) payload.purpose = purpose\n const name = normalizeOptional(draft.name)\n if (name !== undefined) payload.name = name\n const companyName = normalizeOptional(draft.companyName)\n if (companyName !== undefined) payload.companyName = companyName\n const line2 = normalizeOptional(draft.addressLine2)\n if (line2 !== undefined) payload.addressLine2 = line2\n const buildingNumber = normalizeOptional(draft.buildingNumber)\n if (buildingNumber !== undefined) payload.buildingNumber = buildingNumber\n const flatNumber = normalizeOptional(draft.flatNumber)\n if (flatNumber !== undefined) payload.flatNumber = flatNumber\n const city = normalizeOptional(draft.city)\n if (city !== undefined) payload.city = city\n const region = normalizeOptional(draft.region)\n if (region !== undefined) payload.region = region\n const postal = normalizeOptional(draft.postalCode)\n if (postal !== undefined) payload.postalCode = postal\n const country = normalizeOptional(draft.country)\n if (country !== undefined) payload.country = country.toUpperCase()\n // On edit, an emptied coordinate must be sent as `null` to clear the stored value; omitting it\n // makes the partial-update handler keep the old coordinate. On create there is nothing to clear.\n const latitude = normalizeCoordinateInput(draft.latitude)\n if (latitude !== undefined) payload.latitude = latitude\n else if (editingId) payload.latitude = null\n const longitude = normalizeCoordinateInput(draft.longitude)\n if (longitude !== undefined) payload.longitude = longitude\n else if (editingId) payload.longitude = null\n\n setSaving(true)\n setGeneralError(null)\n setFieldErrors({})\n try {\n if (editingId && onUpdate) await onUpdate(editingId, payload)\n else await onCreate(payload)\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n for (const detail of details) {\n const path = Array.isArray(detail.path) ? detail.path : []\n const targetKey = path.length ? serverFieldMap[String(path[0])] : undefined\n if (!targetKey) continue\n const message = resolveFieldMessage(\n detail,\n targetKey === 'addressLine1' ? line1FieldLabel : fieldLabels[targetKey],\n t,\n )\n if (message) nextErrors[targetKey] = message\n }\n if (Object.keys(nextErrors).length) {\n setFieldErrors(nextErrors)\n setGeneralError(Object.values(nextErrors)[0] ?? null)\n setSaving(false)\n return\n }\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, editingId, fieldLabels, line1FieldLabel, onCreate, onUpdate, resetForm, t])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, onDelete, resetForm, t]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? t('customers.people.detail.addresses.add')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/30 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? t('customers.people.detail.addresses.editTitle')\n : t('customers.people.detail.addresses.addTitle')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.detail.addresses.formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft({ ...next, latitude: next.latitude ?? '', longitude: next.longitude ?? '' })\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n showCoordinateFields={showCoordinateFields}\n />\n {generalError ? <p className=\"text-xs text-status-error-text\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {t('customers.people.detail.addresses.cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? t('customers.people.detail.addresses.updating')\n : t('customers.people.detail.addresses.saving')}\n </>\n ) : editingId ? (\n t('customers.people.detail.addresses.update')\n ) : (\n t('customers.people.detail.addresses.save')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n saving,\n showCoordinateFields,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('customers.people.detail.addresses.add')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(address.id)\n }\n const formattedJson = formatAddressJson(address, format)\n const formattedString = formatAddressString(address, format)\n\n return (\n <div\n key={address.id}\n className=\"rounded-lg border bg-background p-4 text-sm shadow-sm\"\n title={formattedString}\n data-address-json={JSON.stringify(formattedJson)}\n data-address-string={formattedString}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <span className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {address.name ||\n (address.purpose ? addressTypeMap.get(address.purpose) ?? address.purpose : null) ||\n t('customers.people.detail.address')}\n </span>\n {address.isPrimary ? (\n <span className=\"mt-1 inline-flex w-fit rounded bg-status-success-bg px-2 py-0.5 text-overline font-semibold text-status-success-text\">\n {t('customers.people.detail.primary')}\n </span>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => openEditForm(address)}\n disabled={disableActions}\n aria-label={t('customers.people.detail.addresses.editAction')}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-destructive hover:text-destructive focus-visible:text-destructive\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions || !onDelete}\n aria-label={t('customers.people.detail.addresses.deleteAction')}\n >\n {deletingId === address.id ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n <div className=\"mt-2 space-y-1\">\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {addressTypeMap.get(address.purpose) ?? address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"space-y-1\" lineClassName=\"text-sm\" />\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('__new') : null}\n </div>\n ) : isFormOpen && !editingId ? (\n <div className={gridClassName}>{renderFormTile('__new')}</div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["message", "key"]
|
|
7
7
|
}
|
|
@@ -50,6 +50,8 @@ function AddressesSection({
|
|
|
50
50
|
region: typeof record.region === "string" ? record.region : null,
|
|
51
51
|
postalCode: typeof record.postal_code === "string" ? record.postal_code : typeof record.postalCode === "string" ? record.postalCode : null,
|
|
52
52
|
country: typeof record.country === "string" ? record.country : null,
|
|
53
|
+
latitude: typeof record.latitude === "number" && Number.isFinite(record.latitude) ? record.latitude : null,
|
|
54
|
+
longitude: typeof record.longitude === "number" && Number.isFinite(record.longitude) ? record.longitude : null,
|
|
53
55
|
isPrimary: typeof record.is_primary === "boolean" ? record.is_primary : typeof record.isPrimary === "boolean" ? record.isPrimary : false,
|
|
54
56
|
updatedAt: typeof record.updated_at === "string" ? record.updated_at : typeof record.updatedAt === "string" ? record.updatedAt : null
|
|
55
57
|
};
|
|
@@ -178,7 +180,8 @@ function AddressesSection({
|
|
|
178
180
|
translator: t,
|
|
179
181
|
dataAdapter,
|
|
180
182
|
addressTypesAdapter,
|
|
181
|
-
loadFormat
|
|
183
|
+
loadFormat,
|
|
184
|
+
showCoordinateFields: true
|
|
182
185
|
}
|
|
183
186
|
);
|
|
184
187
|
}
|