@open-mercato/core 0.6.8-develop.7088.1.3873d81f42 → 0.6.8-develop.7090.1.4dbca7b350
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
- package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
- package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +6 -5
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +5 -5
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
- package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +14 -7
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +8 -7
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +9 -8
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
- package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +5 -4
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
- package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
- package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
- package/src/modules/customers/components/detail/utils.ts +15 -5
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
- package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
- package/src/modules/sales/components/documents/ItemsSection.tsx +8 -1
- package/src/modules/sales/components/documents/LineItemDialog.tsx +9 -8
- package/src/modules/sales/components/documents/PaymentsSection.tsx +9 -8
- package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
- package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +5 -4
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
- package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/components/documents/ShipmentsSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Pencil, Plus, Trash2, Truck } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { ErrorMessage, LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n emitSalesDocumentTotalsRefresh,\n subscribeSalesDocumentTotalsRefresh,\n} from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'\nimport type { SectionAction } from '@open-mercato/core/modules/customers/components/detail/types'\nimport { generateTempId } from '@open-mercato/core/modules/customers/lib/detailHelpers'\nimport { formatAddressString, type AddressValue } from '@open-mercato/core/modules/customers/utils/addressFormat'\nimport { ShipmentDialog } from './ShipmentDialog'\nimport { handleSectionMutationError, readRowUpdatedAt } from './optimisticLock'\nimport { extractCustomFieldValues } from './customFieldHelpers'\nimport type { OrderLine, ShipmentRow, ShipmentItem } from './shipmentTypes'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\nconst ADDRESS_SNAPSHOT_KEY = 'shipmentAddressSnapshot'\nconst ADDRESS_FORMAT: 'line_first' = 'line_first'\n\ntype SalesShipmentsSectionProps = {\n orderId: string\n currencyCode?: string | null\n shippingAddressSnapshot?: Record<string, unknown> | null\n organizationId?: string | null\n tenantId?: string | null\n documentUpdatedAt?: string | null\n onActionChange?: (action: SectionAction | null) => void\n onAddComment?: (body: string) => Promise<void>\n}\n\nfunction formatDisplayDate(value: string | null | undefined): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)\n}\n\nconst formatShipmentAddress = (metadata?: Record<string, unknown> | null): string | null => {\n if (!metadata || typeof metadata !== 'object') return null\n const snapshot = (metadata as any)[ADDRESS_SNAPSHOT_KEY]\n if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) return null\n const read = (key: string): string | null => {\n const value = (snapshot as any)[key]\n return typeof value === 'string' && value.trim().length ? value.trim() : null\n }\n const addressValue: AddressValue = {\n addressLine1: read('addressLine1') ?? read('address_line1'),\n addressLine2: read('addressLine2') ?? read('address_line2'),\n buildingNumber: read('buildingNumber') ?? read('building_number'),\n flatNumber: read('flatNumber') ?? read('flat_number'),\n city: read('city'),\n region: read('region'),\n postalCode: read('postalCode') ?? read('postal_code'),\n country: read('country'),\n companyName: read('companyName') ?? read('company_name'),\n }\n const summary = formatAddressString(addressValue, ADDRESS_FORMAT)\n return summary && summary.trim().length ? summary : null\n}\n\nconst resolveLineThumbnail = (item: Record<string, unknown>): string | null => {\n const pickThumbnail = (...candidates: Array<unknown>): string | null => {\n const found = candidates.find(\n (candidate): candidate is string =>\n typeof candidate === 'string' && candidate.trim().length > 0,\n )\n return found ?? null\n }\n\n const metadata =\n typeof (item as any).metadata === 'object' && (item as any).metadata !== null\n ? ((item as any).metadata as Record<string, unknown>)\n : null\n const snapshot =\n typeof (item as any).catalog_snapshot === 'object' && (item as any).catalog_snapshot !== null\n ? ((item as any).catalog_snapshot as Record<string, unknown>)\n : null\n const productSnapshot =\n snapshot && typeof (snapshot as any).product === 'object' ? ((snapshot as any).product as Record<string, unknown>) : null\n const variantSnapshot =\n snapshot && typeof (snapshot as any).variant === 'object' ? ((snapshot as any).variant as Record<string, unknown>) : null\n\n const productThumb = pickThumbnail(\n metadata ? (metadata as any).productThumbnail : null,\n productSnapshot ? (productSnapshot as any).thumbnailUrl : null,\n productSnapshot ? (productSnapshot as any).thumbnail_url : null,\n )\n const variantThumb = pickThumbnail(\n metadata ? (metadata as any).variantThumbnail : null,\n variantSnapshot ? (variantSnapshot as any).thumbnailUrl : null,\n variantSnapshot ? (variantSnapshot as any).thumbnail_url : null,\n )\n\n const snapshotThumb = pickThumbnail(\n snapshot ? (snapshot as any).thumbnailUrl : null,\n snapshot ? (snapshot as any).thumbnail_url : null,\n )\n\n return variantThumb ?? productThumb ?? snapshotThumb ?? null\n}\n\nexport function SalesShipmentsSection({\n orderId,\n currencyCode,\n shippingAddressSnapshot,\n organizationId: organizationIdProp,\n tenantId: tenantIdProp,\n documentUpdatedAt,\n onActionChange,\n onAddComment,\n}: SalesShipmentsSectionProps) {\n const t = useT()\n const { organizationId, tenantId } = useOrganizationScopeDetail()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const resolvedOrganizationId = organizationIdProp ?? organizationId ?? null\n const resolvedTenantId = tenantIdProp ?? tenantId ?? null\n const addShipmentLabel = React.useMemo(\n () => t('sales.documents.shipments.add', 'Add shipment'),\n [t]\n )\n const [shipments, setShipments] = React.useState<ShipmentRow[]>([])\n const [lines, setLines] = React.useState<OrderLine[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [dialogState, setDialogState] = React.useState<{ mode: 'create' | 'edit'; shipment: ShipmentRow | null } | null>(null)\n\n const lineMap = React.useMemo(() => new Map(lines.map((line) => [line.id, line])), [lines])\n\n const shippedTotals = React.useMemo(() => {\n const totals = new Map<string, number>()\n shipments.forEach((shipment) => {\n shipment.items.forEach((item) => {\n const current = totals.get(item.orderLineId) ?? 0\n totals.set(item.orderLineId, current + (Number.isFinite(item.quantity) ? item.quantity : 0))\n })\n })\n return totals\n }, [shipments])\n\n const computeAvailable = React.useCallback(\n (lineId: string, excludeShipmentId?: string | null) => {\n const line = lineMap.get(lineId)\n if (!line) return 0\n const shipped = shippedTotals.get(lineId) ?? 0\n const editingShipment = excludeShipmentId ? shipments.find((entry) => entry.id === excludeShipmentId) : null\n const editingQty =\n editingShipment?.items\n .filter((item) => item.orderLineId === lineId)\n .reduce((acc, item) => acc + (Number.isFinite(item.quantity) ? item.quantity : 0), 0) ?? 0\n const remaining = line.quantity - (shipped - editingQty)\n return remaining < 0 ? 0 : remaining\n },\n [lineMap, shipments, shippedTotals]\n )\n\n const loadLines = React.useCallback(async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/order-lines?${params.toString()}`,\n undefined,\n { fallback: { items: [] } }\n )\n const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : []\n const mapped: OrderLine[] = items\n .map((item) => {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const name =\n typeof item.name === 'string'\n ? item.name\n : typeof (item as any).catalog_snapshot === 'object' &&\n (item as any).catalog_snapshot &&\n typeof (item as any).catalog_snapshot.name === 'string'\n ? (item as any).catalog_snapshot.name\n : null\n const lineNumber =\n typeof (item as any).line_number === 'number'\n ? (item as any).line_number\n : typeof (item as any).lineNumber === 'number'\n ? (item as any).lineNumber\n : null\n const quantity =\n typeof item.quantity === 'number'\n ? item.quantity\n : typeof (item as any).quantity === 'string'\n ? Number((item as any).quantity)\n : 0\n return {\n id,\n title: name ?? id,\n lineNumber,\n quantity: Number.isFinite(quantity) ? quantity : 0,\n thumbnail: resolveLineThumbnail(item as Record<string, unknown>),\n }\n })\n .filter((entry): entry is OrderLine => Boolean(entry?.id))\n setLines(mapped)\n }, [orderId])\n\n const loadShipments = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/shipments?${params.toString()}`,\n undefined,\n { fallback: { items: [] } }\n )\n const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : []\n const mapped: ShipmentRow[] = items\n .map((item) => {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const snapshotItemsRaw = Array.isArray((item as any).items_snapshot)\n ? ((item as any).items_snapshot as Array<Record<string, unknown>>)\n : []\n const responseItems = Array.isArray((item as any).items)\n ? ((item as any).items as Array<Record<string, unknown>>)\n : []\n const itemsRaw = responseItems.length ? responseItems : snapshotItemsRaw\n const shipmentItems: ShipmentItem[] = itemsRaw\n .map((entry) => {\n const lineId =\n typeof entry.orderLineId === 'string'\n ? entry.orderLineId\n : typeof (entry as any).order_line_id === 'string'\n ? (entry as any).order_line_id\n : null\n if (!lineId) return null\n const quantity =\n typeof entry.quantity === 'number'\n ? entry.quantity\n : typeof (entry as any).quantity === 'string'\n ? Number((entry as any).quantity)\n : 0\n return {\n id: typeof entry.id === 'string' ? entry.id : generateTempId(),\n orderLineId: lineId,\n orderLineName:\n typeof entry.orderLineName === 'string'\n ? entry.orderLineName\n : typeof (entry as any).order_line_name === 'string'\n ? (entry as any).order_line_name\n : null,\n orderLineNumber:\n typeof entry.orderLineNumber === 'number'\n ? entry.orderLineNumber\n : typeof (entry as any).order_line_number === 'number'\n ? (entry as any).order_line_number\n : null,\n quantity: Number.isFinite(quantity) ? quantity : 0,\n metadata: (entry.metadata as Record<string, unknown> | undefined | null) ?? null,\n }\n })\n .filter((entry): entry is ShipmentItem => Boolean(entry))\n const tracking =\n Array.isArray((item as any).tracking_numbers) && (item as any).tracking_numbers.length\n ? ((item as any).tracking_numbers as string[])\n : []\n const customValues = extractCustomFieldValues(item as Record<string, unknown>)\n return {\n id,\n updatedAt: readRowUpdatedAt(item),\n shipmentNumber:\n typeof (item as any).shipment_number === 'string'\n ? (item as any).shipment_number\n : typeof (item as any).shipmentNumber === 'string'\n ? (item as any).shipmentNumber\n : null,\n shippingMethodId:\n typeof (item as any).shipping_method_id === 'string'\n ? (item as any).shipping_method_id\n : typeof (item as any).shippingMethodId === 'string'\n ? (item as any).shippingMethodId\n : null,\n shippingMethodCode:\n typeof (item as any).shipping_method_code === 'string'\n ? (item as any).shipping_method_code\n : typeof (item as any).shippingMethodCode === 'string'\n ? (item as any).shippingMethodCode\n : null,\n shippingMethodName:\n typeof (item as any).shipping_method_name === 'string'\n ? (item as any).shipping_method_name\n : typeof (item as any).shippingMethodName === 'string'\n ? (item as any).shippingMethodName\n : null,\n status:\n typeof item.status === 'string'\n ? item.status\n : typeof (item as any).status === 'string'\n ? (item as any).status\n : null,\n statusLabel:\n typeof (item as any).status_label === 'string'\n ? (item as any).status_label\n : typeof (item as any).statusLabel === 'string'\n ? (item as any).statusLabel\n : null,\n statusEntryId:\n typeof (item as any).status_entry_id === 'string'\n ? (item as any).status_entry_id\n : typeof (item as any).statusEntryId === 'string'\n ? (item as any).statusEntryId\n : null,\n carrierName:\n typeof (item as any).carrier_name === 'string'\n ? (item as any).carrier_name\n : typeof (item as any).carrierName === 'string'\n ? (item as any).carrierName\n : null,\n trackingNumbers: tracking,\n shippedAt:\n typeof (item as any).shipped_at === 'string'\n ? (item as any).shipped_at\n : typeof (item as any).shippedAt === 'string'\n ? (item as any).shippedAt\n : null,\n deliveredAt:\n typeof (item as any).delivered_at === 'string'\n ? (item as any).delivered_at\n : typeof (item as any).deliveredAt === 'string'\n ? (item as any).deliveredAt\n : null,\n notes:\n typeof (item as any).notes === 'string'\n ? (item as any).notes\n : typeof (item as any).notesText === 'string'\n ? (item as any).notesText\n : null,\n metadata: (item as Record<string, unknown> | null | undefined)?.metadata ?? null,\n customValues: Object.keys(customValues).length ? customValues : null,\n items: shipmentItems,\n createdAt:\n typeof (item as any).created_at === 'string'\n ? (item as any).created_at\n : typeof (item as any).createdAt === 'string'\n ? (item as any).createdAt\n : null,\n }\n })\n .filter((entry): entry is ShipmentRow => Boolean(entry))\n setShipments(mapped)\n } catch (err) {\n logger.error('sales.shipments.load', { err })\n setError(t('sales.documents.shipments.errorLoad', 'Failed to load shipments.'))\n } finally {\n setLoading(false)\n }\n }, [orderId, t])\n\n React.useEffect(() => {\n void loadLines()\n void loadShipments()\n }, [loadLines, loadShipments])\n\n React.useEffect(\n () =>\n subscribeSalesDocumentTotalsRefresh((detail) => {\n if (detail.documentId !== orderId) return\n if (detail.kind && detail.kind !== 'order') return\n void loadLines()\n void loadShipments()\n }),\n [loadLines, loadShipments, orderId],\n )\n\n const handleOpenCreate = React.useCallback(() => {\n setDialogState({ mode: 'create', shipment: null })\n }, [])\n\n React.useEffect(() => {\n if (!onActionChange) return\n onActionChange({\n label: addShipmentLabel,\n onClick: handleOpenCreate,\n disabled: false,\n })\n return () => onActionChange(null)\n }, [addShipmentLabel, handleOpenCreate, onActionChange])\n\n const handleEdit = React.useCallback((shipment: ShipmentRow) => {\n setDialogState({ mode: 'edit', shipment })\n }, [])\n\n const handleDelete = React.useCallback(\n async (shipment: ShipmentRow) => {\n const confirmed = await confirm({\n title: t('sales.documents.shipments.confirmDelete', 'Delete this shipment?'),\n variant: 'default',\n })\n if (!confirmed) return\n try {\n const result = await withScopedApiRequestHeaders(\n // The server guards the PARENT order's aggregate version (Gap B), so\n // send the order's `updated_at`, not the shipment row's.\n buildOptimisticLockHeader(documentUpdatedAt ?? undefined),\n () =>\n deleteCrud('sales/shipments', {\n body: {\n id: shipment.id,\n orderId,\n organizationId: resolvedOrganizationId,\n tenantId: resolvedTenantId,\n },\n errorMessage: t('sales.documents.shipments.errorDelete', 'Failed to delete shipment.'),\n })\n )\n if (result.ok) {\n await loadShipments()\n emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: 'order' })\n }\n } catch (err) {\n if (handleSectionMutationError(err, t, () => void loadShipments())) {\n return\n }\n logger.error('sales.shipments.delete', { err })\n flash(t('sales.documents.shipments.errorDelete', 'Failed to delete shipment.'), 'error')\n }\n },\n [confirm, documentUpdatedAt, loadShipments, orderId, resolvedOrganizationId, resolvedTenantId, t]\n )\n\n const renderItemList = (items: ShipmentItem[]) => (\n <ul className=\"space-y-1 text-sm text-muted-foreground\">\n {items.map((item) => {\n const label = item.orderLineNumber ? `#${item.orderLineNumber}` : ''\n return (\n <li key={item.id} className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate\">\n {label ? `${label} \u00B7 ` : null}\n {item.orderLineName ?? item.orderLineId}\n </span>\n <Badge variant=\"secondary\">{item.quantity}</Badge>\n </li>\n )\n })}\n </ul>\n )\n\n if (loading) {\n return (\n <LoadingMessage\n label={t('sales.documents.shipments.loading', 'Loading shipments\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n )\n }\n\n if (error) {\n return <ErrorMessage label={error} />\n }\n\n const empty = !shipments.length\n\n return (\n <div className=\"space-y-4\">\n {empty ? (\n <TabEmptyState\n title={t('sales.documents.shipments.empty.title', 'No shipments yet.')}\n description={t(\n 'sales.documents.shipments.empty.description',\n 'Add shipments for this document to let the user track the order.'\n )}\n action={{\n label: addShipmentLabel,\n onClick: handleOpenCreate,\n icon: <Plus className=\"h-4 w-4\" aria-hidden />,\n disabled: loading,\n }}\n />\n ) : (\n <div className=\"grid gap-3 md:grid-cols-2\">\n {shipments.map((shipment) => {\n const shippedAt = formatDisplayDate(shipment.shippedAt)\n const deliveredAt = formatDisplayDate(shipment.deliveredAt)\n const addressSummary = formatShipmentAddress(shipment.metadata)\n const statusLabel =\n shipment.statusLabel ??\n shipment.status ??\n t('sales.documents.shipments.statusMissing', 'Status pending')\n return (\n <div key={shipment.id} className=\"rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-full bg-primary/10 text-primary\">\n <Truck className=\"h-5 w-5\" />\n </div>\n <div>\n <p className=\"text-sm font-semibold\">\n {shipment.shipmentNumber\n ? t('sales.documents.shipments.numberLabel', 'Shipment {{number}}', {\n number: shipment.shipmentNumber,\n })\n : t('sales.documents.shipments.fallbackNumber', 'Shipment {{id}}', {\n id: shipment.id.slice(0, 6),\n })}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {statusLabel}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleEdit(shipment)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-destructive\"\n onClick={() => void handleDelete(shipment)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n <div className=\"mt-3 space-y-2 text-sm\">\n <div className=\"flex flex-wrap items-center gap-2 text-muted-foreground\">\n {shipment.carrierName ? (\n <Badge variant=\"outline\">{shipment.carrierName}</Badge>\n ) : null}\n {shipment.trackingNumbers.length ? (\n <span className=\"truncate\">\n {t('sales.documents.shipments.tracking', 'Tracking')}: {shipment.trackingNumbers.join(', ')}\n </span>\n ) : null}\n </div>\n {shippedAt ? (\n <p className=\"text-muted-foreground\">\n {t('sales.documents.shipments.shippedOn', 'Shipped on {{date}}', { date: shippedAt })}\n </p>\n ) : null}\n {deliveredAt ? (\n <p className=\"text-muted-foreground\">\n {t('sales.documents.shipments.deliveredOn', 'Delivered on {{date}}', { date: deliveredAt })}\n </p>\n ) : null}\n {addressSummary ? (\n <p className=\"text-xs text-muted-foreground\">{addressSummary}</p>\n ) : null}\n {shipment.notes ? (\n <p className=\"rounded-md bg-muted px-3 py-2 text-muted-foreground\">\n {shipment.notes}\n </p>\n ) : null}\n {renderItemList(shipment.items)}\n </div>\n </div>\n )\n })}\n </div>\n )}\n\n <ShipmentDialog\n open={dialogState !== null}\n mode={dialogState?.mode ?? 'create'}\n shipment={dialogState?.shipment ?? null}\n lines={lines}\n orderId={orderId}\n currencyCode={currencyCode}\n organizationId={resolvedOrganizationId}\n tenantId={resolvedTenantId}\n documentUpdatedAt={documentUpdatedAt ?? null}\n computeAvailable={computeAvailable}\n shippingAddressSnapshot={shippingAddressSnapshot}\n onClose={() => setDialogState(null)}\n onSaved={async () => {\n setDialogState(null)\n await loadShipments()\n emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: 'order' })\n }}\n onAddComment={onAddComment}\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Pencil, Plus, Trash2, Truck } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { ErrorMessage, LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n emitSalesDocumentTotalsRefresh,\n subscribeSalesDocumentTotalsRefresh,\n} from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'\nimport type { SectionAction } from '@open-mercato/core/modules/customers/components/detail/types'\nimport { generateTempId } from '@open-mercato/core/modules/customers/lib/detailHelpers'\nimport { formatAddressString, type AddressValue } from '@open-mercato/core/modules/customers/utils/addressFormat'\nimport { ShipmentDialog } from './ShipmentDialog'\nimport { handleSectionMutationError, readRowUpdatedAt } from './optimisticLock'\nimport { extractCustomFieldValues } from './customFieldHelpers'\nimport type { OrderLine, ShipmentRow, ShipmentItem } from './shipmentTypes'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\nconst ADDRESS_SNAPSHOT_KEY = 'shipmentAddressSnapshot'\nconst ADDRESS_FORMAT: 'line_first' = 'line_first'\n\ntype SalesShipmentsSectionProps = {\n orderId: string\n currencyCode?: string | null\n shippingAddressSnapshot?: Record<string, unknown> | null\n organizationId?: string | null\n tenantId?: string | null\n documentUpdatedAt?: string | null\n onActionChange?: (action: SectionAction | null) => void\n onAddComment?: (body: string) => Promise<void>\n}\n\nexport function formatDisplayDate(value: string | null | undefined, locale?: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)\n}\n\nconst formatShipmentAddress = (metadata?: Record<string, unknown> | null): string | null => {\n if (!metadata || typeof metadata !== 'object') return null\n const snapshot = (metadata as any)[ADDRESS_SNAPSHOT_KEY]\n if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) return null\n const read = (key: string): string | null => {\n const value = (snapshot as any)[key]\n return typeof value === 'string' && value.trim().length ? value.trim() : null\n }\n const addressValue: AddressValue = {\n addressLine1: read('addressLine1') ?? read('address_line1'),\n addressLine2: read('addressLine2') ?? read('address_line2'),\n buildingNumber: read('buildingNumber') ?? read('building_number'),\n flatNumber: read('flatNumber') ?? read('flat_number'),\n city: read('city'),\n region: read('region'),\n postalCode: read('postalCode') ?? read('postal_code'),\n country: read('country'),\n companyName: read('companyName') ?? read('company_name'),\n }\n const summary = formatAddressString(addressValue, ADDRESS_FORMAT)\n return summary && summary.trim().length ? summary : null\n}\n\nconst resolveLineThumbnail = (item: Record<string, unknown>): string | null => {\n const pickThumbnail = (...candidates: Array<unknown>): string | null => {\n const found = candidates.find(\n (candidate): candidate is string =>\n typeof candidate === 'string' && candidate.trim().length > 0,\n )\n return found ?? null\n }\n\n const metadata =\n typeof (item as any).metadata === 'object' && (item as any).metadata !== null\n ? ((item as any).metadata as Record<string, unknown>)\n : null\n const snapshot =\n typeof (item as any).catalog_snapshot === 'object' && (item as any).catalog_snapshot !== null\n ? ((item as any).catalog_snapshot as Record<string, unknown>)\n : null\n const productSnapshot =\n snapshot && typeof (snapshot as any).product === 'object' ? ((snapshot as any).product as Record<string, unknown>) : null\n const variantSnapshot =\n snapshot && typeof (snapshot as any).variant === 'object' ? ((snapshot as any).variant as Record<string, unknown>) : null\n\n const productThumb = pickThumbnail(\n metadata ? (metadata as any).productThumbnail : null,\n productSnapshot ? (productSnapshot as any).thumbnailUrl : null,\n productSnapshot ? (productSnapshot as any).thumbnail_url : null,\n )\n const variantThumb = pickThumbnail(\n metadata ? (metadata as any).variantThumbnail : null,\n variantSnapshot ? (variantSnapshot as any).thumbnailUrl : null,\n variantSnapshot ? (variantSnapshot as any).thumbnail_url : null,\n )\n\n const snapshotThumb = pickThumbnail(\n snapshot ? (snapshot as any).thumbnailUrl : null,\n snapshot ? (snapshot as any).thumbnail_url : null,\n )\n\n return variantThumb ?? productThumb ?? snapshotThumb ?? null\n}\n\nexport function SalesShipmentsSection({\n orderId,\n currencyCode,\n shippingAddressSnapshot,\n organizationId: organizationIdProp,\n tenantId: tenantIdProp,\n documentUpdatedAt,\n onActionChange,\n onAddComment,\n}: SalesShipmentsSectionProps) {\n const t = useT()\n const locale = useLocale()\n const { organizationId, tenantId } = useOrganizationScopeDetail()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const resolvedOrganizationId = organizationIdProp ?? organizationId ?? null\n const resolvedTenantId = tenantIdProp ?? tenantId ?? null\n const addShipmentLabel = React.useMemo(\n () => t('sales.documents.shipments.add', 'Add shipment'),\n [t]\n )\n const [shipments, setShipments] = React.useState<ShipmentRow[]>([])\n const [lines, setLines] = React.useState<OrderLine[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [dialogState, setDialogState] = React.useState<{ mode: 'create' | 'edit'; shipment: ShipmentRow | null } | null>(null)\n\n const lineMap = React.useMemo(() => new Map(lines.map((line) => [line.id, line])), [lines])\n\n const shippedTotals = React.useMemo(() => {\n const totals = new Map<string, number>()\n shipments.forEach((shipment) => {\n shipment.items.forEach((item) => {\n const current = totals.get(item.orderLineId) ?? 0\n totals.set(item.orderLineId, current + (Number.isFinite(item.quantity) ? item.quantity : 0))\n })\n })\n return totals\n }, [shipments])\n\n const computeAvailable = React.useCallback(\n (lineId: string, excludeShipmentId?: string | null) => {\n const line = lineMap.get(lineId)\n if (!line) return 0\n const shipped = shippedTotals.get(lineId) ?? 0\n const editingShipment = excludeShipmentId ? shipments.find((entry) => entry.id === excludeShipmentId) : null\n const editingQty =\n editingShipment?.items\n .filter((item) => item.orderLineId === lineId)\n .reduce((acc, item) => acc + (Number.isFinite(item.quantity) ? item.quantity : 0), 0) ?? 0\n const remaining = line.quantity - (shipped - editingQty)\n return remaining < 0 ? 0 : remaining\n },\n [lineMap, shipments, shippedTotals]\n )\n\n const loadLines = React.useCallback(async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/order-lines?${params.toString()}`,\n undefined,\n { fallback: { items: [] } }\n )\n const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : []\n const mapped: OrderLine[] = items\n .map((item) => {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const name =\n typeof item.name === 'string'\n ? item.name\n : typeof (item as any).catalog_snapshot === 'object' &&\n (item as any).catalog_snapshot &&\n typeof (item as any).catalog_snapshot.name === 'string'\n ? (item as any).catalog_snapshot.name\n : null\n const lineNumber =\n typeof (item as any).line_number === 'number'\n ? (item as any).line_number\n : typeof (item as any).lineNumber === 'number'\n ? (item as any).lineNumber\n : null\n const quantity =\n typeof item.quantity === 'number'\n ? item.quantity\n : typeof (item as any).quantity === 'string'\n ? Number((item as any).quantity)\n : 0\n return {\n id,\n title: name ?? id,\n lineNumber,\n quantity: Number.isFinite(quantity) ? quantity : 0,\n thumbnail: resolveLineThumbnail(item as Record<string, unknown>),\n }\n })\n .filter((entry): entry is OrderLine => Boolean(entry?.id))\n setLines(mapped)\n }, [orderId])\n\n const loadShipments = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/shipments?${params.toString()}`,\n undefined,\n { fallback: { items: [] } }\n )\n const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : []\n const mapped: ShipmentRow[] = items\n .map((item) => {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n const snapshotItemsRaw = Array.isArray((item as any).items_snapshot)\n ? ((item as any).items_snapshot as Array<Record<string, unknown>>)\n : []\n const responseItems = Array.isArray((item as any).items)\n ? ((item as any).items as Array<Record<string, unknown>>)\n : []\n const itemsRaw = responseItems.length ? responseItems : snapshotItemsRaw\n const shipmentItems: ShipmentItem[] = itemsRaw\n .map((entry) => {\n const lineId =\n typeof entry.orderLineId === 'string'\n ? entry.orderLineId\n : typeof (entry as any).order_line_id === 'string'\n ? (entry as any).order_line_id\n : null\n if (!lineId) return null\n const quantity =\n typeof entry.quantity === 'number'\n ? entry.quantity\n : typeof (entry as any).quantity === 'string'\n ? Number((entry as any).quantity)\n : 0\n return {\n id: typeof entry.id === 'string' ? entry.id : generateTempId(),\n orderLineId: lineId,\n orderLineName:\n typeof entry.orderLineName === 'string'\n ? entry.orderLineName\n : typeof (entry as any).order_line_name === 'string'\n ? (entry as any).order_line_name\n : null,\n orderLineNumber:\n typeof entry.orderLineNumber === 'number'\n ? entry.orderLineNumber\n : typeof (entry as any).order_line_number === 'number'\n ? (entry as any).order_line_number\n : null,\n quantity: Number.isFinite(quantity) ? quantity : 0,\n metadata: (entry.metadata as Record<string, unknown> | undefined | null) ?? null,\n }\n })\n .filter((entry): entry is ShipmentItem => Boolean(entry))\n const tracking =\n Array.isArray((item as any).tracking_numbers) && (item as any).tracking_numbers.length\n ? ((item as any).tracking_numbers as string[])\n : []\n const customValues = extractCustomFieldValues(item as Record<string, unknown>)\n return {\n id,\n updatedAt: readRowUpdatedAt(item),\n shipmentNumber:\n typeof (item as any).shipment_number === 'string'\n ? (item as any).shipment_number\n : typeof (item as any).shipmentNumber === 'string'\n ? (item as any).shipmentNumber\n : null,\n shippingMethodId:\n typeof (item as any).shipping_method_id === 'string'\n ? (item as any).shipping_method_id\n : typeof (item as any).shippingMethodId === 'string'\n ? (item as any).shippingMethodId\n : null,\n shippingMethodCode:\n typeof (item as any).shipping_method_code === 'string'\n ? (item as any).shipping_method_code\n : typeof (item as any).shippingMethodCode === 'string'\n ? (item as any).shippingMethodCode\n : null,\n shippingMethodName:\n typeof (item as any).shipping_method_name === 'string'\n ? (item as any).shipping_method_name\n : typeof (item as any).shippingMethodName === 'string'\n ? (item as any).shippingMethodName\n : null,\n status:\n typeof item.status === 'string'\n ? item.status\n : typeof (item as any).status === 'string'\n ? (item as any).status\n : null,\n statusLabel:\n typeof (item as any).status_label === 'string'\n ? (item as any).status_label\n : typeof (item as any).statusLabel === 'string'\n ? (item as any).statusLabel\n : null,\n statusEntryId:\n typeof (item as any).status_entry_id === 'string'\n ? (item as any).status_entry_id\n : typeof (item as any).statusEntryId === 'string'\n ? (item as any).statusEntryId\n : null,\n carrierName:\n typeof (item as any).carrier_name === 'string'\n ? (item as any).carrier_name\n : typeof (item as any).carrierName === 'string'\n ? (item as any).carrierName\n : null,\n trackingNumbers: tracking,\n shippedAt:\n typeof (item as any).shipped_at === 'string'\n ? (item as any).shipped_at\n : typeof (item as any).shippedAt === 'string'\n ? (item as any).shippedAt\n : null,\n deliveredAt:\n typeof (item as any).delivered_at === 'string'\n ? (item as any).delivered_at\n : typeof (item as any).deliveredAt === 'string'\n ? (item as any).deliveredAt\n : null,\n notes:\n typeof (item as any).notes === 'string'\n ? (item as any).notes\n : typeof (item as any).notesText === 'string'\n ? (item as any).notesText\n : null,\n metadata: (item as Record<string, unknown> | null | undefined)?.metadata ?? null,\n customValues: Object.keys(customValues).length ? customValues : null,\n items: shipmentItems,\n createdAt:\n typeof (item as any).created_at === 'string'\n ? (item as any).created_at\n : typeof (item as any).createdAt === 'string'\n ? (item as any).createdAt\n : null,\n }\n })\n .filter((entry): entry is ShipmentRow => Boolean(entry))\n setShipments(mapped)\n } catch (err) {\n logger.error('sales.shipments.load', { err })\n setError(t('sales.documents.shipments.errorLoad', 'Failed to load shipments.'))\n } finally {\n setLoading(false)\n }\n }, [orderId, t])\n\n React.useEffect(() => {\n void loadLines()\n void loadShipments()\n }, [loadLines, loadShipments])\n\n React.useEffect(\n () =>\n subscribeSalesDocumentTotalsRefresh((detail) => {\n if (detail.documentId !== orderId) return\n if (detail.kind && detail.kind !== 'order') return\n void loadLines()\n void loadShipments()\n }),\n [loadLines, loadShipments, orderId],\n )\n\n const handleOpenCreate = React.useCallback(() => {\n setDialogState({ mode: 'create', shipment: null })\n }, [])\n\n React.useEffect(() => {\n if (!onActionChange) return\n onActionChange({\n label: addShipmentLabel,\n onClick: handleOpenCreate,\n disabled: false,\n })\n return () => onActionChange(null)\n }, [addShipmentLabel, handleOpenCreate, onActionChange])\n\n const handleEdit = React.useCallback((shipment: ShipmentRow) => {\n setDialogState({ mode: 'edit', shipment })\n }, [])\n\n const handleDelete = React.useCallback(\n async (shipment: ShipmentRow) => {\n const confirmed = await confirm({\n title: t('sales.documents.shipments.confirmDelete', 'Delete this shipment?'),\n variant: 'default',\n })\n if (!confirmed) return\n try {\n const result = await withScopedApiRequestHeaders(\n // The server guards the PARENT order's aggregate version (Gap B), so\n // send the order's `updated_at`, not the shipment row's.\n buildOptimisticLockHeader(documentUpdatedAt ?? undefined),\n () =>\n deleteCrud('sales/shipments', {\n body: {\n id: shipment.id,\n orderId,\n organizationId: resolvedOrganizationId,\n tenantId: resolvedTenantId,\n },\n errorMessage: t('sales.documents.shipments.errorDelete', 'Failed to delete shipment.'),\n })\n )\n if (result.ok) {\n await loadShipments()\n emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: 'order' })\n }\n } catch (err) {\n if (handleSectionMutationError(err, t, () => void loadShipments())) {\n return\n }\n logger.error('sales.shipments.delete', { err })\n flash(t('sales.documents.shipments.errorDelete', 'Failed to delete shipment.'), 'error')\n }\n },\n [confirm, documentUpdatedAt, loadShipments, orderId, resolvedOrganizationId, resolvedTenantId, t]\n )\n\n const renderItemList = (items: ShipmentItem[]) => (\n <ul className=\"space-y-1 text-sm text-muted-foreground\">\n {items.map((item) => {\n const label = item.orderLineNumber ? `#${item.orderLineNumber}` : ''\n return (\n <li key={item.id} className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate\">\n {label ? `${label} \u00B7 ` : null}\n {item.orderLineName ?? item.orderLineId}\n </span>\n <Badge variant=\"secondary\">{item.quantity}</Badge>\n </li>\n )\n })}\n </ul>\n )\n\n if (loading) {\n return (\n <LoadingMessage\n label={t('sales.documents.shipments.loading', 'Loading shipments\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n )\n }\n\n if (error) {\n return <ErrorMessage label={error} />\n }\n\n const empty = !shipments.length\n\n return (\n <div className=\"space-y-4\">\n {empty ? (\n <TabEmptyState\n title={t('sales.documents.shipments.empty.title', 'No shipments yet.')}\n description={t(\n 'sales.documents.shipments.empty.description',\n 'Add shipments for this document to let the user track the order.'\n )}\n action={{\n label: addShipmentLabel,\n onClick: handleOpenCreate,\n icon: <Plus className=\"h-4 w-4\" aria-hidden />,\n disabled: loading,\n }}\n />\n ) : (\n <div className=\"grid gap-3 md:grid-cols-2\">\n {shipments.map((shipment) => {\n const shippedAt = formatDisplayDate(shipment.shippedAt, locale)\n const deliveredAt = formatDisplayDate(shipment.deliveredAt, locale)\n const addressSummary = formatShipmentAddress(shipment.metadata)\n const statusLabel =\n shipment.statusLabel ??\n shipment.status ??\n t('sales.documents.shipments.statusMissing', 'Status pending')\n return (\n <div key={shipment.id} className=\"rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-full bg-primary/10 text-primary\">\n <Truck className=\"h-5 w-5\" />\n </div>\n <div>\n <p className=\"text-sm font-semibold\">\n {shipment.shipmentNumber\n ? t('sales.documents.shipments.numberLabel', 'Shipment {{number}}', {\n number: shipment.shipmentNumber,\n })\n : t('sales.documents.shipments.fallbackNumber', 'Shipment {{id}}', {\n id: shipment.id.slice(0, 6),\n })}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {statusLabel}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleEdit(shipment)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-destructive\"\n onClick={() => void handleDelete(shipment)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n <div className=\"mt-3 space-y-2 text-sm\">\n <div className=\"flex flex-wrap items-center gap-2 text-muted-foreground\">\n {shipment.carrierName ? (\n <Badge variant=\"outline\">{shipment.carrierName}</Badge>\n ) : null}\n {shipment.trackingNumbers.length ? (\n <span className=\"truncate\">\n {t('sales.documents.shipments.tracking', 'Tracking')}: {shipment.trackingNumbers.join(', ')}\n </span>\n ) : null}\n </div>\n {shippedAt ? (\n <p className=\"text-muted-foreground\">\n {t('sales.documents.shipments.shippedOn', 'Shipped on {{date}}', { date: shippedAt })}\n </p>\n ) : null}\n {deliveredAt ? (\n <p className=\"text-muted-foreground\">\n {t('sales.documents.shipments.deliveredOn', 'Delivered on {{date}}', { date: deliveredAt })}\n </p>\n ) : null}\n {addressSummary ? (\n <p className=\"text-xs text-muted-foreground\">{addressSummary}</p>\n ) : null}\n {shipment.notes ? (\n <p className=\"rounded-md bg-muted px-3 py-2 text-muted-foreground\">\n {shipment.notes}\n </p>\n ) : null}\n {renderItemList(shipment.items)}\n </div>\n </div>\n )\n })}\n </div>\n )}\n\n <ShipmentDialog\n open={dialogState !== null}\n mode={dialogState?.mode ?? 'create'}\n shipment={dialogState?.shipment ?? null}\n lines={lines}\n orderId={orderId}\n currencyCode={currencyCode}\n organizationId={resolvedOrganizationId}\n tenantId={resolvedTenantId}\n documentUpdatedAt={documentUpdatedAt ?? null}\n computeAvailable={computeAvailable}\n shippingAddressSnapshot={shippingAddressSnapshot}\n onClose={() => setDialogState(null)}\n onSaved={async () => {\n setDialogState(null)\n await loadShipments()\n emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: 'order' })\n }}\n onAddComment={onAddComment}\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,9 +7,9 @@ function normalizeNumber(value, fallback = 0) {
|
|
|
7
7
|
}
|
|
8
8
|
return fallback;
|
|
9
9
|
}
|
|
10
|
-
function formatMoney(value, currency) {
|
|
10
|
+
function formatMoney(value, currency, locale) {
|
|
11
11
|
if (!currency) return value.toFixed(2);
|
|
12
|
-
return new Intl.NumberFormat(
|
|
12
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(value);
|
|
13
13
|
}
|
|
14
14
|
const DISCOUNT_MATCH_TOLERANCE = 0.01;
|
|
15
15
|
const DISCOUNT_MATCH_TOLERANCE_PER_UNIT = 1e-4;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/components/documents/lineItemUtils.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nexport function normalizeNumber(value: unknown, fallback = 0): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return fallback\n}\n\nexport function formatMoney(value: number, currency: string | null | undefined): string {\n if (!currency) return value.toFixed(2)\n return new Intl.NumberFormat(
|
|
5
|
-
"mappings": ";AAEO,SAAS,gBAAgB,OAAgB,WAAW,GAAW;AACpE,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nexport function normalizeNumber(value: unknown, fallback = 0): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return fallback\n}\n\n/**\n * Pass `locale` to format in the application locale (`useLocale()` in client components).\n * Omitting it keeps the runtime default, which varies per machine and is therefore not\n * assertable in tests.\n */\nexport function formatMoney(value: number, currency: string | null | undefined, locale?: string): string {\n if (!currency) return value.toFixed(2)\n return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value)\n}\n\nexport type LineDiscountDisplay = {\n amount: number | null\n percent: number | null\n}\n\ntype LineDiscountSource = {\n discountAmount?: unknown\n discountPercent?: unknown\n unitPriceNet?: unknown\n quantity?: unknown\n}\n\nconst DISCOUNT_MATCH_TOLERANCE = 0.01\nconst DISCOUNT_MATCH_TOLERANCE_PER_UNIT = 0.0001\n\nfunction percentAccountsForAmount(\n line: LineDiscountSource,\n percent: number,\n amount: number,\n): boolean {\n if (percent <= 0) return false\n const quantity = normalizeNumber(line.quantity, 0)\n const netBeforeDiscount = normalizeNumber(line.unitPriceNet, 0) * quantity\n if (netBeforeDiscount <= 0) return false\n const tolerance = Math.max(\n DISCOUNT_MATCH_TOLERANCE,\n quantity * DISCOUNT_MATCH_TOLERANCE_PER_UNIT,\n )\n return Math.abs((percent / 100) * netBeforeDiscount - amount) <= tolerance\n}\n\nexport function resolveLineDiscountDisplay(\n line: LineDiscountSource,\n): LineDiscountDisplay | null {\n const amount = normalizeNumber(line.discountAmount, 0)\n const percent = normalizeNumber(line.discountPercent, 0)\n if (amount > 0) {\n return {\n amount,\n percent: percentAccountsForAmount(line, percent, amount) ? percent : null,\n }\n }\n if (percent > 0) return { amount: null, percent }\n return null\n}\n"],
|
|
5
|
+
"mappings": ";AAEO,SAAS,gBAAgB,OAAgB,WAAW,GAAW;AACpE,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAOO,SAAS,YAAY,OAAe,UAAqC,QAAyB;AACvG,MAAI,CAAC,SAAU,QAAO,MAAM,QAAQ,CAAC;AACrC,SAAO,IAAI,KAAK,aAAa,QAAQ,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,KAAK;AACpF;AAcA,MAAM,2BAA2B;AACjC,MAAM,oCAAoC;AAE1C,SAAS,yBACP,MACA,SACA,QACS;AACT,MAAI,WAAW,EAAG,QAAO;AACzB,QAAM,WAAW,gBAAgB,KAAK,UAAU,CAAC;AACjD,QAAM,oBAAoB,gBAAgB,KAAK,cAAc,CAAC,IAAI;AAClE,MAAI,qBAAqB,EAAG,QAAO;AACnC,QAAM,YAAY,KAAK;AAAA,IACrB;AAAA,IACA,WAAW;AAAA,EACb;AACA,SAAO,KAAK,IAAK,UAAU,MAAO,oBAAoB,MAAM,KAAK;AACnE;AAEO,SAAS,2BACd,MAC4B;AAC5B,QAAM,SAAS,gBAAgB,KAAK,gBAAgB,CAAC;AACrD,QAAM,UAAU,gBAAgB,KAAK,iBAAiB,CAAC;AACvD,MAAI,SAAS,GAAG;AACd,WAAO;AAAA,MACL;AAAA,MACA,SAAS,yBAAyB,MAAM,SAAS,MAAM,IAAI,UAAU;AAAA,IACvE;AAAA,EACF;AACA,MAAI,UAAU,EAAG,QAAO,EAAE,QAAQ,MAAM,QAAQ;AAChD,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7090.1.4dbca7b350",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -252,16 +252,16 @@
|
|
|
252
252
|
"zod": "^4.4.3"
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
256
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
257
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7090.1.4dbca7b350",
|
|
256
|
+
"@open-mercato/shared": "0.6.8-develop.7090.1.4dbca7b350",
|
|
257
|
+
"@open-mercato/ui": "0.6.8-develop.7090.1.4dbca7b350",
|
|
258
258
|
"react": "^19.0.0",
|
|
259
259
|
"react-dom": "^19.0.0"
|
|
260
260
|
},
|
|
261
261
|
"devDependencies": {
|
|
262
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
263
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
264
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
262
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7090.1.4dbca7b350",
|
|
263
|
+
"@open-mercato/shared": "0.6.8-develop.7090.1.4dbca7b350",
|
|
264
|
+
"@open-mercato/ui": "0.6.8-develop.7090.1.4dbca7b350",
|
|
265
265
|
"@testing-library/dom": "^10.4.1",
|
|
266
266
|
"@testing-library/jest-dom": "^7.0.0",
|
|
267
267
|
"@testing-library/react": "^16.3.1",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"auth.acl.features.catalog.variants.manage": "Manage catalog variants",
|
|
56
56
|
"auth.acl.features.channel_apns.configure": "Configure APNs credentials",
|
|
57
57
|
"auth.acl.features.channel_apns.view": "View APNs channel configuration",
|
|
58
|
+
"auth.acl.features.channel_discord.ai_auto_reply.run": "Discord-KI-Autoantwort-Agenten ausführen",
|
|
59
|
+
"auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
|
|
60
|
+
"auth.acl.features.channel_discord.view": "View Discord channel configuration",
|
|
58
61
|
"auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
|
|
59
62
|
"auth.acl.features.channel_expo.view": "View Expo push channel configuration",
|
|
60
63
|
"auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"auth.acl.features.catalog.variants.manage": "Manage catalog variants",
|
|
56
56
|
"auth.acl.features.channel_apns.configure": "Configure APNs credentials",
|
|
57
57
|
"auth.acl.features.channel_apns.view": "View APNs channel configuration",
|
|
58
|
+
"auth.acl.features.channel_discord.ai_auto_reply.run": "Run the Discord AI auto-reply agent",
|
|
59
|
+
"auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
|
|
60
|
+
"auth.acl.features.channel_discord.view": "View Discord channel configuration",
|
|
58
61
|
"auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
|
|
59
62
|
"auth.acl.features.channel_expo.view": "View Expo push channel configuration",
|
|
60
63
|
"auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"auth.acl.features.catalog.variants.manage": "Manage catalog variants",
|
|
56
56
|
"auth.acl.features.channel_apns.configure": "Configure APNs credentials",
|
|
57
57
|
"auth.acl.features.channel_apns.view": "View APNs channel configuration",
|
|
58
|
+
"auth.acl.features.channel_discord.ai_auto_reply.run": "Ejecutar el agente de respuesta automática de IA en Discord",
|
|
59
|
+
"auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
|
|
60
|
+
"auth.acl.features.channel_discord.view": "View Discord channel configuration",
|
|
58
61
|
"auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
|
|
59
62
|
"auth.acl.features.channel_expo.view": "View Expo push channel configuration",
|
|
60
63
|
"auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"auth.acl.features.catalog.variants.manage": "Manage catalog variants",
|
|
56
56
|
"auth.acl.features.channel_apns.configure": "Configure APNs credentials",
|
|
57
57
|
"auth.acl.features.channel_apns.view": "View APNs channel configuration",
|
|
58
|
+
"auth.acl.features.channel_discord.ai_auto_reply.run": "Discord AI 자동 응답 에이전트 실행",
|
|
59
|
+
"auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
|
|
60
|
+
"auth.acl.features.channel_discord.view": "View Discord channel configuration",
|
|
58
61
|
"auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
|
|
59
62
|
"auth.acl.features.channel_expo.view": "View Expo push channel configuration",
|
|
60
63
|
"auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"auth.acl.features.catalog.variants.manage": "Zarządzaj wariantami w katalogu",
|
|
56
56
|
"auth.acl.features.channel_apns.configure": "Konfiguruj dane dostępowe APNs",
|
|
57
57
|
"auth.acl.features.channel_apns.view": "Wyświetl konfigurację kanału APNs",
|
|
58
|
+
"auth.acl.features.channel_discord.ai_auto_reply.run": "Uruchamiaj agenta automatycznych odpowiedzi AI na Discordzie",
|
|
59
|
+
"auth.acl.features.channel_discord.configure": "Konfiguruj kanał bota Discord",
|
|
60
|
+
"auth.acl.features.channel_discord.view": "Wyświetl konfigurację kanału Discord",
|
|
58
61
|
"auth.acl.features.channel_expo.configure": "Konfiguruj dane dostępowe powiadomień push Expo",
|
|
59
62
|
"auth.acl.features.channel_expo.view": "Wyświetl konfigurację kanału push Expo",
|
|
60
63
|
"auth.acl.features.channel_fcm.configure": "Konfiguruj dane dostępowe FCM",
|
|
@@ -63,13 +63,12 @@ export type DealsKpiStripProps = {
|
|
|
63
63
|
onNeedsAttentionClick?: () => void
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return compactNumberFormatter.format(value)
|
|
66
|
+
function createCompactFormatter(locale: string): (value: number) => string {
|
|
67
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
68
|
+
notation: 'compact',
|
|
69
|
+
maximumFractionDigits: 1,
|
|
70
|
+
})
|
|
71
|
+
return (value: number) => formatter.format(value)
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
function buildCurrencySuffix(code: string | null, convertedAll: boolean): string {
|
|
@@ -136,6 +135,7 @@ export function DealsKpiStrip({
|
|
|
136
135
|
}: DealsKpiStripProps) {
|
|
137
136
|
const t = useT()
|
|
138
137
|
const locale = useLocale()
|
|
138
|
+
const formatCompact = React.useMemo(() => createCompactFormatter(locale), [locale])
|
|
139
139
|
const pluralCat = React.useCallback((count: number): string => {
|
|
140
140
|
try {
|
|
141
141
|
return new Intl.PluralRules(locale).select(count)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { EyeOff } from 'lucide-react'
|
|
5
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
6
6
|
import { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts/KpiCard'
|
|
7
7
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
8
|
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
@@ -67,6 +67,7 @@ type CompanyKpiBarProps = {
|
|
|
67
67
|
|
|
68
68
|
export function CompanyKpiBar({ data }: CompanyKpiBarProps) {
|
|
69
69
|
const t = useT()
|
|
70
|
+
const locale = useLocale()
|
|
70
71
|
|
|
71
72
|
const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals])
|
|
72
73
|
const activeDealsValue = React.useMemo(
|
|
@@ -126,7 +127,7 @@ export function CompanyKpiBar({ data }: CompanyKpiBarProps) {
|
|
|
126
127
|
title: t('customers.companies.dashboard.kpi.activeDeals', 'ACTIVE DEALS'),
|
|
127
128
|
value: activeDealsValue,
|
|
128
129
|
trend: dealTrend,
|
|
129
|
-
formatValue: (v: number) => formatCurrency(v, dealCurrency),
|
|
130
|
+
formatValue: (v: number) => formatCurrency(v, dealCurrency, locale),
|
|
130
131
|
comparisonLabel: `${data.kpis?.activeDealsCount ?? activeDeals.length} ${(data.kpis?.activeDealsCount ?? activeDeals.length) === 1 ? 'pipeline' : 'pipelines'}`,
|
|
131
132
|
},
|
|
132
133
|
{
|
|
@@ -140,7 +141,7 @@ export function CompanyKpiBar({ data }: CompanyKpiBarProps) {
|
|
|
140
141
|
id: 'ltv',
|
|
141
142
|
title: t('customers.companies.dashboard.kpi.ltv', 'CUSTOMER VALUE (LTV)'),
|
|
142
143
|
value: ltvValue,
|
|
143
|
-
formatValue: ltvValue !== null ? (v: number) => formatCurrency(v, dealCurrency) : undefined,
|
|
144
|
+
formatValue: ltvValue !== null ? (v: number) => formatCurrency(v, dealCurrency, locale) : undefined,
|
|
144
145
|
comparisonLabel: ltvValue !== null
|
|
145
146
|
? t('customers.companies.dashboard.kpi.wonDeals', 'won deals total')
|
|
146
147
|
: t('customers.companies.dashboard.kpi.noWonDeals', 'No won deals'),
|
|
@@ -158,7 +159,7 @@ export function CompanyKpiBar({ data }: CompanyKpiBarProps) {
|
|
|
158
159
|
? `${data.kpis?.completedDealsCount ?? data.deals.filter((d) => isWonDealStatus(d.status)).length} ${t('customers.companies.dashboard.kpi.completedDeals', 'completed deals')}`
|
|
159
160
|
: t('customers.companies.dashboard.kpi.noInteractions', 'No interactions yet'),
|
|
160
161
|
},
|
|
161
|
-
], [t, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis])
|
|
162
|
+
], [t, locale, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis])
|
|
162
163
|
|
|
163
164
|
const visibleTiles = kpiTiles.filter((tile) => !hiddenTiles.has(tile.id))
|
|
164
165
|
|
|
@@ -12,7 +12,7 @@ import { LinkEntityDialog } from '../linking/LinkEntityDialog'
|
|
|
12
12
|
import { createDealLinkAdapter } from '../linking/adapters/dealAdapter'
|
|
13
13
|
import { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'
|
|
14
14
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
15
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
16
16
|
import { hasMoreFromPage } from '@open-mercato/shared/lib/pagination/load-more'
|
|
17
17
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
18
18
|
import { E } from '#generated/entities.ids.generated'
|
|
@@ -275,9 +275,14 @@ function normalizeDeal(deal: Partial<DealSummary> & { id: string; title?: string
|
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
function formatValueLabel(
|
|
278
|
+
function formatValueLabel(
|
|
279
|
+
amount: number | null,
|
|
280
|
+
currency: string | null,
|
|
281
|
+
emptyLabel: string,
|
|
282
|
+
locale?: string,
|
|
283
|
+
): string {
|
|
279
284
|
if (typeof amount === 'number') {
|
|
280
|
-
const formatter = new Intl.NumberFormat(
|
|
285
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
281
286
|
style: currency ? 'currency' : 'decimal',
|
|
282
287
|
currency: currency ?? undefined,
|
|
283
288
|
maximumFractionDigits: 2,
|
|
@@ -317,6 +322,7 @@ export function DealsSection({
|
|
|
317
322
|
runGuardedMutation,
|
|
318
323
|
}: DealsSectionProps) {
|
|
319
324
|
const tHook = useT()
|
|
325
|
+
const locale = useLocale()
|
|
320
326
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
321
327
|
const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])
|
|
322
328
|
const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])
|
|
@@ -963,8 +969,8 @@ export function DealsSection({
|
|
|
963
969
|
) : null}
|
|
964
970
|
<div className="space-y-4">
|
|
965
971
|
{sortedDeals.map((deal) => {
|
|
966
|
-
const valueLabel = formatValueLabel(deal.valueAmount, deal.valueCurrency ?? null, emptyLabel)
|
|
967
|
-
const expectedLabel = deal.expectedCloseAt ? formatDate(deal.expectedCloseAt) ?? emptyLabel : emptyLabel
|
|
972
|
+
const valueLabel = formatValueLabel(deal.valueAmount, deal.valueCurrency ?? null, emptyLabel, locale)
|
|
973
|
+
const expectedLabel = deal.expectedCloseAt ? formatDate(deal.expectedCloseAt, locale) ?? emptyLabel : emptyLabel
|
|
968
974
|
const probabilityLabel =
|
|
969
975
|
typeof deal.probability === 'number' ? `${deal.probability}%` : emptyLabel
|
|
970
976
|
const isUnlinkPending = pendingAction?.kind === 'remove' && pendingAction.id === deal.id
|
|
@@ -5,11 +5,16 @@ import type { CustomerDictionaryKind } from '../../lib/dictionaries'
|
|
|
5
5
|
import { CUSTOMER_INTERACTION_TASK_SOURCE, CUSTOMER_INTERACTION_TASK_TYPE } from '../../lib/interactionCompatibility'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Pass `locale` to format in the application locale (`useLocale()` in client components).
|
|
10
|
+
* Omitting it keeps the runtime default, which varies per machine and is therefore not
|
|
11
|
+
* assertable in tests.
|
|
12
|
+
*/
|
|
13
|
+
export function formatDate(value?: string | null, locale?: string): string | null {
|
|
9
14
|
if (!value) return null
|
|
10
15
|
const date = new Date(value)
|
|
11
16
|
if (Number.isNaN(date.getTime())) return null
|
|
12
|
-
return date.toLocaleDateString()
|
|
17
|
+
return date.toLocaleDateString(locale)
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
export function formatTemplate(template: string, params?: Record<string, string | number>): string {
|
|
@@ -148,15 +153,20 @@ export function createDictionarySelectLabels(
|
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Pass `locale` to format in the application locale (`useLocale()` in client components).
|
|
158
|
+
* Omitting it keeps the runtime default, which varies per machine and is therefore not
|
|
159
|
+
* assertable in tests.
|
|
160
|
+
*/
|
|
161
|
+
export function formatCurrency(amount: number, currency?: string | null, locale?: string): string {
|
|
152
162
|
try {
|
|
153
|
-
return new Intl.NumberFormat(
|
|
163
|
+
return new Intl.NumberFormat(locale, {
|
|
154
164
|
style: 'currency',
|
|
155
165
|
currency: currency || 'PLN',
|
|
156
166
|
maximumFractionDigits: 0,
|
|
157
167
|
}).format(amount)
|
|
158
168
|
} catch {
|
|
159
|
-
return `${amount.toLocaleString()} ${currency || 'PLN'}`
|
|
169
|
+
return `${amount.toLocaleString(locale)} ${currency || 'PLN'}`
|
|
160
170
|
}
|
|
161
171
|
}
|
|
162
172
|
|
|
@@ -38,7 +38,7 @@ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimi
|
|
|
38
38
|
import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
|
|
39
39
|
import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
|
|
40
40
|
import { mapCrudServerErrorToFormErrors } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
41
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
41
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
42
42
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
43
43
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
44
44
|
import { ContactEmailDisplay } from '@open-mercato/core/modules/sales/components/ContactEmailDisplay'
|
|
@@ -79,13 +79,17 @@ import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
|
79
79
|
|
|
80
80
|
const logger = createLogger('sales')
|
|
81
81
|
|
|
82
|
-
function formatMessageAmount(
|
|
82
|
+
function formatMessageAmount(
|
|
83
|
+
amount: number | null | undefined,
|
|
84
|
+
currency: string | null | undefined,
|
|
85
|
+
locale?: string
|
|
86
|
+
): string | null {
|
|
83
87
|
if (typeof amount !== 'number' || !Number.isFinite(amount)) return null
|
|
84
|
-
if (!currency) return amount.toLocaleString()
|
|
88
|
+
if (!currency) return amount.toLocaleString(locale)
|
|
85
89
|
try {
|
|
86
|
-
return new Intl.NumberFormat(
|
|
90
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)
|
|
87
91
|
} catch {
|
|
88
|
-
return `${amount.toLocaleString()} ${currency}`
|
|
92
|
+
return `${amount.toLocaleString(locale)} ${currency}`
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
|
|
@@ -1900,6 +1904,7 @@ export default function SalesDocumentDetailPage({
|
|
|
1900
1904
|
includeAmountInMessageMetadata?: boolean
|
|
1901
1905
|
}) {
|
|
1902
1906
|
const t = useT()
|
|
1907
|
+
const locale = useLocale()
|
|
1903
1908
|
const { enabled: channelsEnabled } = useSalesChannelsEnabled()
|
|
1904
1909
|
const router = useRouter()
|
|
1905
1910
|
const searchParams = useSearchParams()
|
|
@@ -2807,7 +2812,7 @@ export default function SalesDocumentDetailPage({
|
|
|
2807
2812
|
: null
|
|
2808
2813
|
const contactEmail = resolveCustomerEmail(customerSnapshot) ?? metadataEmail ?? record?.contactEmail ?? null
|
|
2809
2814
|
const statusDisplay = record?.status ? statusDictionaryMap[record.status] ?? null : null
|
|
2810
|
-
const previewAmount = formatMessageAmount(record?.grandTotalGrossAmount ?? null, record?.currencyCode ?? null)
|
|
2815
|
+
const previewAmount = formatMessageAmount(record?.grandTotalGrossAmount ?? null, record?.currencyCode ?? null, locale)
|
|
2811
2816
|
const messagePreviewMetadata: Record<string, string> = {}
|
|
2812
2817
|
if (includeAmountInMessageMetadata && previewAmount) {
|
|
2813
2818
|
messagePreviewMetadata[t('sales.documents.detail.totals.grandTotalGross')] = previewAmount
|
|
@@ -3867,7 +3872,7 @@ export default function SalesDocumentDetailPage({
|
|
|
3867
3872
|
renderDisplay: (params) => {
|
|
3868
3873
|
const { value, emptyLabel } = params
|
|
3869
3874
|
if (value && value.length) {
|
|
3870
|
-
return <span className="text-sm text-muted-foreground">{new Date(value).toLocaleDateString()}</span>
|
|
3875
|
+
return <span className="text-sm text-muted-foreground">{new Date(value).toLocaleDateString(locale)}</span>
|
|
3871
3876
|
}
|
|
3872
3877
|
return <span className="text-sm text-muted-foreground">{emptyLabel}</span>
|
|
3873
3878
|
},
|
|
@@ -3944,6 +3949,7 @@ export default function SalesDocumentDetailPage({
|
|
|
3944
3949
|
shippingMethodLoading,
|
|
3945
3950
|
shippingMethodOptions,
|
|
3946
3951
|
t,
|
|
3952
|
+
locale,
|
|
3947
3953
|
kind,
|
|
3948
3954
|
saveShortcutLabel,
|
|
3949
3955
|
])
|
|
@@ -4717,7 +4723,7 @@ export default function SalesDocumentDetailPage({
|
|
|
4717
4723
|
renderDisplay={({ value, emptyLabel }) =>
|
|
4718
4724
|
value && value.length ? (
|
|
4719
4725
|
<span className="text-sm text-muted-foreground">
|
|
4720
|
-
{new Date(value).toLocaleDateString()}
|
|
4726
|
+
{new Date(value).toLocaleDateString(locale)}
|
|
4721
4727
|
</span>
|
|
4722
4728
|
) : (
|
|
4723
4729
|
<span className="text-sm text-muted-foreground">{emptyLabel}</span>
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
+
import { useOptionalLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Pass `locale` to format in the application locale (`useLocale()` in client components).
|
|
9
|
+
* Omitting it keeps the runtime default, which varies per machine and is therefore not
|
|
10
|
+
* assertable in tests.
|
|
11
|
+
*/
|
|
6
12
|
export function formatPriceWithCurrency(
|
|
7
13
|
amount: number | string | null | undefined,
|
|
8
14
|
currency: string | null | undefined,
|
|
9
|
-
fallback = '—'
|
|
15
|
+
fallback = '—',
|
|
16
|
+
locale?: string
|
|
10
17
|
): string {
|
|
11
18
|
if (amount === null || amount === undefined) return fallback
|
|
12
19
|
const parsed = typeof amount === 'string' ? Number(amount) : amount
|
|
13
20
|
if (Number.isNaN(parsed)) return fallback
|
|
14
21
|
if (currency) {
|
|
15
22
|
try {
|
|
16
|
-
return new Intl.NumberFormat(
|
|
23
|
+
return new Intl.NumberFormat(locale, {
|
|
17
24
|
style: 'currency',
|
|
18
25
|
currency,
|
|
19
26
|
minimumFractionDigits: 2,
|
|
@@ -23,7 +30,7 @@ export function formatPriceWithCurrency(
|
|
|
23
30
|
// fall through to plain number formatting
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
|
-
return parsed.toLocaleString(
|
|
33
|
+
return parsed.toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
type PriceWithCurrencyProps = {
|
|
@@ -34,9 +41,14 @@ type PriceWithCurrencyProps = {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export function PriceWithCurrency({ amount, currency, fallback = '—', className }: PriceWithCurrencyProps) {
|
|
44
|
+
// `useOptionalLocale` rather than `useLocale`: this component is deep-importable and had no i18n
|
|
45
|
+
// dependency at all before it started formatting in the app locale, so making it throw outside
|
|
46
|
+
// `I18nProvider` would narrow its mounting contract. Without a provider it falls back to the
|
|
47
|
+
// runtime default, which is exactly the behaviour it had before.
|
|
48
|
+
const locale = useOptionalLocale()
|
|
37
49
|
const label = React.useMemo(
|
|
38
|
-
() => formatPriceWithCurrency(amount, currency, fallback),
|
|
39
|
-
[amount, currency, fallback]
|
|
50
|
+
() => formatPriceWithCurrency(amount, currency, fallback, locale),
|
|
51
|
+
[amount, currency, fallback, locale]
|
|
40
52
|
)
|
|
41
53
|
return <span className={cn('font-mono text-sm text-foreground', className)}>{label}</span>
|
|
42
54
|
}
|