@open-mercato/core 0.6.6-develop.6385.1.9a81faa5f0 → 0.6.6-develop.6387.1.f4896ad565
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/attachments/lib/scope.js +26 -0
- package/dist/modules/attachments/lib/scope.js.map +7 -0
- package/dist/modules/catalog/commands/variants.js +4 -2
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- 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/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/dist/modules/messages/commands/messages.js +0 -1
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/lib/attachments.js +13 -7
- package/dist/modules/messages/lib/attachments.js.map +2 -2
- package/package.json +11 -7
- package/src/modules/attachments/lib/scope.ts +42 -0
- package/src/modules/catalog/commands/variants.ts +7 -2
- 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/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
- package/src/modules/messages/commands/messages.ts +0 -1
- package/src/modules/messages/lib/attachments.ts +15 -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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/AddressesSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall, apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { AddressesSection as SharedAddressesSection } from '@open-mercato/ui/backend/detail'\nimport type { AddressDataAdapter, AddressTypesAdapter, AddressFormatStrategy, SectionAction } from '@open-mercato/ui/backend/detail'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type AddressesSectionProps = {\n entityId: string | null\n emptyLabel: string\n addActionLabel: string\n emptyState: { title: string; actionLabel: string; description?: string }\n onActionChange?: (action: SectionAction | null) => void\n translator?: (key: string, fallback?: string, params?: Record<string, string | number>) => string\n onLoadingChange?: (isLoading: boolean) => void\n}\n\ntype ApiAddressPayload = Record<string, unknown>\n\nexport function AddressesSection({\n entityId,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n translator,\n onLoadingChange,\n}: AddressesSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo(() => createTranslatorWithFallback(tHook), [tHook])\n const t = translator ?? fallbackTranslator\n\n const dataAdapter = React.useMemo<AddressDataAdapter>(() => ({\n list: async ({ entityId: listEntityId }) => {\n if (!listEntityId) return []\n const params = new URLSearchParams({ entityId: listEntityId, pageSize: '100' })\n const payload = await readApiResultOrThrow<ApiAddressPayload>(\n `/api/customers/addresses?${params.toString()}`,\n undefined,\n { errorMessage: t('customers.people.detail.addresses.error') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items\n .map((item) => {\n if (!item || typeof item !== 'object') return null\n const record = item as Record<string, unknown>\n const rawId = record.id ?? record.address_id ?? null\n const id =\n typeof rawId === 'string'\n ? rawId\n : typeof rawId === 'number' || typeof rawId === 'bigint'\n ? String(rawId)\n : null\n if (!id) return null\n const addressLine1 = typeof record.address_line1 === 'string'\n ? record.address_line1\n : typeof record.addressLine1 === 'string'\n ? record.addressLine1\n : null\n if (!addressLine1) return null\n return {\n id,\n name: typeof record.name === 'string' ? record.name : null,\n purpose: typeof record.purpose === 'string' ? record.purpose : null,\n companyName: typeof record.company_name === 'string'\n ? record.company_name\n : typeof record.companyName === 'string'\n ? record.companyName\n : null,\n addressLine1,\n addressLine2: typeof record.address_line2 === 'string'\n ? record.address_line2\n : typeof record.addressLine2 === 'string'\n ? record.addressLine2\n : null,\n buildingNumber: typeof record.building_number === 'string'\n ? record.building_number\n : typeof record.buildingNumber === 'string'\n ? record.buildingNumber\n : null,\n flatNumber: typeof record.flat_number === 'string'\n ? record.flat_number\n : typeof record.flatNumber === 'string'\n ? record.flatNumber\n : null,\n city: typeof record.city === 'string' ? record.city : null,\n region: typeof record.region === 'string' ? record.region : null,\n postalCode: typeof record.postal_code === 'string'\n ? record.postal_code\n : typeof record.postalCode === 'string'\n ? record.postalCode\n : null,\n country: typeof record.country === 'string' ? record.country : null,\n isPrimary:\n typeof record.is_primary === 'boolean'\n ? record.is_primary\n : typeof record.isPrimary === 'boolean'\n ? record.isPrimary\n : false,\n updatedAt:\n typeof record.updated_at === 'string'\n ? record.updated_at\n : typeof record.updatedAt === 'string'\n ? record.updatedAt\n : null,\n }\n })\n .filter((value): value is NonNullable<typeof value> => value !== null)\n },\n create: async ({ entityId: targetId, payload }) => {\n const response = await apiCallOrThrow<Record<string, unknown>>(\n '/api/customers/addresses',\n {\n // optimistic-lock-exempt: address create-only (no prior version to compare)\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId: targetId,\n ...payload,\n country: payload.country ? payload.country.toUpperCase() : undefined,\n }),\n },\n { errorMessage: t('customers.people.detail.addresses.error') },\n )\n return response.result ?? {}\n },\n update: async ({ id, payload, updatedAt }) => {\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/addresses',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n id,\n ...payload,\n country: payload.country ? payload.country.toUpperCase() : undefined,\n }),\n },\n { errorMessage: t('customers.people.detail.addresses.error') },\n ),\n )\n } catch (err) {\n surfaceRecordConflict(err, t)\n throw err\n }\n },\n delete: async ({ id, updatedAt }) => {\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/addresses',\n {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n },\n { errorMessage: t('customers.people.detail.addresses.error') },\n ),\n )\n } catch (err) {\n surfaceRecordConflict(err, t)\n throw err\n }\n },\n }), [t])\n\n const addressTypesAdapter = React.useMemo<AddressTypesAdapter>(() => ({\n list: async () => {\n const call = await apiCall<Record<string, unknown>>('/api/customers/dictionaries/address-types', {\n method: 'GET',\n })\n const payload = call.result ?? {}\n const rawItems = (payload as { items?: unknown[] }).items\n const items = Array.isArray(rawItems) ? rawItems : []\n return items\n .map((item) => {\n if (!item || typeof item !== 'object') return null\n const record = item as Record<string, unknown>\n const value = typeof record.value === 'string' ? record.value : null\n if (!value) return null\n const label = typeof record.label === 'string' && record.label.trim().length ? record.label : value\n return { value, label }\n })\n .filter((entry): entry is { value: string; label: string } => !!entry)\n },\n create: async (value: string) => {\n const response = await apiCallOrThrow<Record<string, unknown>>(\n '/api/customers/dictionaries/address-types',\n {\n // optimistic-lock-exempt: address-type dictionary create-only\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ value }),\n },\n { errorMessage: t('customers.people.detail.addresses.types.saveError', 'Failed to save address type') },\n )\n const payload = response.result ?? {}\n const createdValue = typeof payload.value === 'string' ? payload.value : value\n const label = typeof payload.label === 'string' && payload.label.trim().length ? payload.label : createdValue\n return { value: createdValue, label }\n },\n manageHref: '/backend/config/customers',\n }), [t])\n\n const loadFormat = React.useCallback(async (): Promise<AddressFormatStrategy> => {\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 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 throw new Error(message)\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (value === 'street_first' || value === 'line_first') {\n return value\n }\n return 'line_first'\n }, [t])\n\n return (\n <SharedAddressesSection\n entityId={entityId}\n emptyLabel={emptyLabel}\n addActionLabel={addActionLabel}\n emptyState={emptyState}\n onActionChange={onActionChange}\n onLoadingChange={onLoadingChange}\n translator={t}\n dataAdapter={dataAdapter}\n addressTypesAdapter={addressTypesAdapter}\n loadFormat={loadFormat}\n />\n )\n}\n\nexport default AddressesSection\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall, apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { AddressesSection as SharedAddressesSection } from '@open-mercato/ui/backend/detail'\nimport type { AddressDataAdapter, AddressTypesAdapter, AddressFormatStrategy, SectionAction } from '@open-mercato/ui/backend/detail'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type AddressesSectionProps = {\n entityId: string | null\n emptyLabel: string\n addActionLabel: string\n emptyState: { title: string; actionLabel: string; description?: string }\n onActionChange?: (action: SectionAction | null) => void\n translator?: (key: string, fallback?: string, params?: Record<string, string | number>) => string\n onLoadingChange?: (isLoading: boolean) => void\n}\n\ntype ApiAddressPayload = Record<string, unknown>\n\nexport function AddressesSection({\n entityId,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n translator,\n onLoadingChange,\n}: AddressesSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo(() => createTranslatorWithFallback(tHook), [tHook])\n const t = translator ?? fallbackTranslator\n\n const dataAdapter = React.useMemo<AddressDataAdapter>(() => ({\n list: async ({ entityId: listEntityId }) => {\n if (!listEntityId) return []\n const params = new URLSearchParams({ entityId: listEntityId, pageSize: '100' })\n const payload = await readApiResultOrThrow<ApiAddressPayload>(\n `/api/customers/addresses?${params.toString()}`,\n undefined,\n { errorMessage: t('customers.people.detail.addresses.error') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items\n .map((item) => {\n if (!item || typeof item !== 'object') return null\n const record = item as Record<string, unknown>\n const rawId = record.id ?? record.address_id ?? null\n const id =\n typeof rawId === 'string'\n ? rawId\n : typeof rawId === 'number' || typeof rawId === 'bigint'\n ? String(rawId)\n : null\n if (!id) return null\n const addressLine1 = typeof record.address_line1 === 'string'\n ? record.address_line1\n : typeof record.addressLine1 === 'string'\n ? record.addressLine1\n : null\n if (!addressLine1) return null\n return {\n id,\n name: typeof record.name === 'string' ? record.name : null,\n purpose: typeof record.purpose === 'string' ? record.purpose : null,\n companyName: typeof record.company_name === 'string'\n ? record.company_name\n : typeof record.companyName === 'string'\n ? record.companyName\n : null,\n addressLine1,\n addressLine2: typeof record.address_line2 === 'string'\n ? record.address_line2\n : typeof record.addressLine2 === 'string'\n ? record.addressLine2\n : null,\n buildingNumber: typeof record.building_number === 'string'\n ? record.building_number\n : typeof record.buildingNumber === 'string'\n ? record.buildingNumber\n : null,\n flatNumber: typeof record.flat_number === 'string'\n ? record.flat_number\n : typeof record.flatNumber === 'string'\n ? record.flatNumber\n : null,\n city: typeof record.city === 'string' ? record.city : null,\n region: typeof record.region === 'string' ? record.region : null,\n postalCode: typeof record.postal_code === 'string'\n ? record.postal_code\n : typeof record.postalCode === 'string'\n ? record.postalCode\n : null,\n country: typeof record.country === 'string' ? record.country : null,\n latitude: typeof record.latitude === 'number' && Number.isFinite(record.latitude) ? record.latitude : null,\n longitude: typeof record.longitude === 'number' && Number.isFinite(record.longitude) ? record.longitude : null,\n isPrimary:\n typeof record.is_primary === 'boolean'\n ? record.is_primary\n : typeof record.isPrimary === 'boolean'\n ? record.isPrimary\n : false,\n updatedAt:\n typeof record.updated_at === 'string'\n ? record.updated_at\n : typeof record.updatedAt === 'string'\n ? record.updatedAt\n : null,\n }\n })\n .filter((value): value is NonNullable<typeof value> => value !== null)\n },\n create: async ({ entityId: targetId, payload }) => {\n const response = await apiCallOrThrow<Record<string, unknown>>(\n '/api/customers/addresses',\n {\n // optimistic-lock-exempt: address create-only (no prior version to compare)\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId: targetId,\n ...payload,\n country: payload.country ? payload.country.toUpperCase() : undefined,\n }),\n },\n { errorMessage: t('customers.people.detail.addresses.error') },\n )\n return response.result ?? {}\n },\n update: async ({ id, payload, updatedAt }) => {\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/addresses',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n id,\n ...payload,\n country: payload.country ? payload.country.toUpperCase() : undefined,\n }),\n },\n { errorMessage: t('customers.people.detail.addresses.error') },\n ),\n )\n } catch (err) {\n surfaceRecordConflict(err, t)\n throw err\n }\n },\n delete: async ({ id, updatedAt }) => {\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/addresses',\n {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n },\n { errorMessage: t('customers.people.detail.addresses.error') },\n ),\n )\n } catch (err) {\n surfaceRecordConflict(err, t)\n throw err\n }\n },\n }), [t])\n\n const addressTypesAdapter = React.useMemo<AddressTypesAdapter>(() => ({\n list: async () => {\n const call = await apiCall<Record<string, unknown>>('/api/customers/dictionaries/address-types', {\n method: 'GET',\n })\n const payload = call.result ?? {}\n const rawItems = (payload as { items?: unknown[] }).items\n const items = Array.isArray(rawItems) ? rawItems : []\n return items\n .map((item) => {\n if (!item || typeof item !== 'object') return null\n const record = item as Record<string, unknown>\n const value = typeof record.value === 'string' ? record.value : null\n if (!value) return null\n const label = typeof record.label === 'string' && record.label.trim().length ? record.label : value\n return { value, label }\n })\n .filter((entry): entry is { value: string; label: string } => !!entry)\n },\n create: async (value: string) => {\n const response = await apiCallOrThrow<Record<string, unknown>>(\n '/api/customers/dictionaries/address-types',\n {\n // optimistic-lock-exempt: address-type dictionary create-only\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ value }),\n },\n { errorMessage: t('customers.people.detail.addresses.types.saveError', 'Failed to save address type') },\n )\n const payload = response.result ?? {}\n const createdValue = typeof payload.value === 'string' ? payload.value : value\n const label = typeof payload.label === 'string' && payload.label.trim().length ? payload.label : createdValue\n return { value: createdValue, label }\n },\n manageHref: '/backend/config/customers',\n }), [t])\n\n const loadFormat = React.useCallback(async (): Promise<AddressFormatStrategy> => {\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 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 throw new Error(message)\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (value === 'street_first' || value === 'line_first') {\n return value\n }\n return 'line_first'\n }, [t])\n\n return (\n <SharedAddressesSection\n entityId={entityId}\n emptyLabel={emptyLabel}\n addActionLabel={addActionLabel}\n emptyState={emptyState}\n onActionChange={onActionChange}\n onLoadingChange={onLoadingChange}\n translator={t}\n dataAdapter={dataAdapter}\n addressTypesAdapter={addressTypesAdapter}\n loadFormat={loadFormat}\n showCoordinateFields\n />\n )\n}\n\nexport default AddressesSection\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -715,6 +715,7 @@ const createPersonFormFields = (t) => {
|
|
|
715
715
|
t,
|
|
716
716
|
emptyLabel: t("customers.people.detail.empty.addresses"),
|
|
717
717
|
gridClassName: "grid gap-4 min-[480px]:grid-cols-1 xl:grid-cols-2",
|
|
718
|
+
showCoordinateFields: true,
|
|
718
719
|
onCreate: async (payload) => {
|
|
719
720
|
const nextId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `tmp-${Math.random().toString(36).slice(2)}`;
|
|
720
721
|
const next = {
|
|
@@ -730,6 +731,8 @@ const createPersonFormFields = (t) => {
|
|
|
730
731
|
region: payload.region ?? void 0,
|
|
731
732
|
postalCode: payload.postalCode ?? void 0,
|
|
732
733
|
country: payload.country ?? void 0,
|
|
734
|
+
latitude: payload.latitude ?? void 0,
|
|
735
|
+
longitude: payload.longitude ?? void 0,
|
|
733
736
|
isPrimary: payload.isPrimary ?? false
|
|
734
737
|
};
|
|
735
738
|
const current = Array.isArray(addresses) ? addresses : [];
|
|
@@ -755,6 +758,8 @@ const createPersonFormFields = (t) => {
|
|
|
755
758
|
region: payload.region ?? null,
|
|
756
759
|
postalCode: payload.postalCode ?? null,
|
|
757
760
|
country: payload.country ?? null,
|
|
761
|
+
latitude: payload.latitude ?? null,
|
|
762
|
+
longitude: payload.longitude ?? null,
|
|
758
763
|
isPrimary: payload.isPrimary ?? false
|
|
759
764
|
};
|
|
760
765
|
});
|
|
@@ -977,6 +982,7 @@ const createCompanyFormFields = (t) => {
|
|
|
977
982
|
t,
|
|
978
983
|
emptyLabel: t("customers.companies.detail.empty.addresses"),
|
|
979
984
|
gridClassName: "grid gap-4 min-[480px]:grid-cols-1 xl:grid-cols-2",
|
|
985
|
+
showCoordinateFields: true,
|
|
980
986
|
onCreate: async (payload) => {
|
|
981
987
|
const nextId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `tmp-${Math.random().toString(36).slice(2)}`;
|
|
982
988
|
const next = {
|
|
@@ -992,6 +998,8 @@ const createCompanyFormFields = (t) => {
|
|
|
992
998
|
region: payload.region ?? void 0,
|
|
993
999
|
postalCode: payload.postalCode ?? void 0,
|
|
994
1000
|
country: payload.country ?? void 0,
|
|
1001
|
+
latitude: payload.latitude ?? void 0,
|
|
1002
|
+
longitude: payload.longitude ?? void 0,
|
|
995
1003
|
isPrimary: payload.isPrimary ?? false
|
|
996
1004
|
};
|
|
997
1005
|
const current = Array.isArray(addresses) ? addresses : [];
|
|
@@ -1017,6 +1025,8 @@ const createCompanyFormFields = (t) => {
|
|
|
1017
1025
|
region: payload.region ?? null,
|
|
1018
1026
|
postalCode: payload.postalCode ?? null,
|
|
1019
1027
|
country: payload.country ?? null,
|
|
1028
|
+
latitude: payload.latitude ?? null,
|
|
1029
|
+
longitude: payload.longitude ?? null,
|
|
1020
1030
|
isPrimary: payload.isPrimary ?? false
|
|
1021
1031
|
};
|
|
1022
1032
|
});
|