@open-mercato/core 0.6.8-develop.7082.1.f2a845b020 → 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/.turbo/turbo-build.log +1 -1
- 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/dist/modules/sales/data/entities.js +8 -2
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
- package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
- package/dist/modules/staff/backend/staff/timesheets/page.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
- package/src/modules/sales/data/entities.ts +6 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
- package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
|
@@ -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
|
}
|
|
@@ -23,7 +23,7 @@ import type { SectionAction } from '@open-mercato/core/modules/customers/compone
|
|
|
23
23
|
import type { SalesAdjustmentKind } from '../../data/entities'
|
|
24
24
|
import { PriceWithCurrency } from '../PriceWithCurrency'
|
|
25
25
|
import { AdjustmentDialog, type AdjustmentRowData, type AdjustmentSubmitPayload } from './AdjustmentDialog'
|
|
26
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
26
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
27
27
|
import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
28
28
|
import { extractCustomFieldValues } from './customFieldHelpers'
|
|
29
29
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
@@ -60,9 +60,9 @@ function normalizeNumber(value: unknown, fallback = 0): number {
|
|
|
60
60
|
return fallback
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function formatPercent(value: number | null | undefined): string {
|
|
63
|
+
export function formatPercent(value: number | null | undefined, locale?: string): string {
|
|
64
64
|
if (value === null || value === undefined) return '—'
|
|
65
|
-
const formatter = new Intl.NumberFormat(
|
|
65
|
+
const formatter = new Intl.NumberFormat(locale, { style: 'percent', maximumFractionDigits: 2 })
|
|
66
66
|
return formatter.format(value / 100)
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -77,6 +77,7 @@ export function SalesDocumentAdjustmentsSection({
|
|
|
77
77
|
onRowsChange,
|
|
78
78
|
}: SalesDocumentAdjustmentsSectionProps) {
|
|
79
79
|
const t = useT()
|
|
80
|
+
const locale = useLocale()
|
|
80
81
|
const { organizationId, tenantId } = useOrganizationScopeDetail()
|
|
81
82
|
const resolvedOrganizationId = orgFromProps ?? organizationId ?? null
|
|
82
83
|
const resolvedTenantId = tenantFromProps ?? tenantId ?? null
|
|
@@ -433,7 +434,7 @@ export function SalesDocumentAdjustmentsSection({
|
|
|
433
434
|
{
|
|
434
435
|
id: 'rate',
|
|
435
436
|
header: t('sales.documents.adjustments.rate', 'Rate'),
|
|
436
|
-
cell: ({ row }) => <span className="font-mono text-sm text-muted-foreground">{formatPercent(row.original.rate)}</span>,
|
|
437
|
+
cell: ({ row }) => <span className="font-mono text-sm text-muted-foreground">{formatPercent(row.original.rate, locale)}</span>,
|
|
437
438
|
},
|
|
438
439
|
{
|
|
439
440
|
id: 'position',
|
|
@@ -441,7 +442,7 @@ export function SalesDocumentAdjustmentsSection({
|
|
|
441
442
|
cell: ({ row }) => <span className="text-sm text-muted-foreground">{row.original.position}</span>,
|
|
442
443
|
},
|
|
443
444
|
],
|
|
444
|
-
[currencyCode, resolveKindLabel, t]
|
|
445
|
+
[currencyCode, locale, resolveKindLabel, t]
|
|
445
446
|
)
|
|
446
447
|
|
|
447
448
|
const showLoadingState = loading && rows.length === 0
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
createDictionaryMap,
|
|
17
17
|
normalizeDictionaryEntries,
|
|
18
18
|
} from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
|
|
19
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
19
|
+
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
20
20
|
import { useOrganizationScopeDetail } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
21
21
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
22
22
|
import { emitSalesDocumentTotalsRefresh } from "@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents";
|
|
@@ -158,6 +158,7 @@ export function SalesDocumentItemsSection({
|
|
|
158
158
|
onItemsChange,
|
|
159
159
|
}: SalesDocumentItemsSectionProps) {
|
|
160
160
|
const t = useT();
|
|
161
|
+
const locale = useLocale();
|
|
161
162
|
const { organizationId, tenantId } = useOrganizationScopeDetail();
|
|
162
163
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
163
164
|
const resolvedOrganizationId = orgFromProps ?? organizationId ?? null;
|
|
@@ -827,6 +828,7 @@ export function SalesDocumentItemsSection({
|
|
|
827
828
|
{formatMoney(
|
|
828
829
|
item.unitPriceGross,
|
|
829
830
|
item.currencyCode ?? currencyCode ?? undefined,
|
|
831
|
+
locale,
|
|
830
832
|
)}{" "}
|
|
831
833
|
<span className="text-xs text-muted-foreground">
|
|
832
834
|
{t("sales.documents.items.table.gross", "gross")}
|
|
@@ -836,6 +838,7 @@ export function SalesDocumentItemsSection({
|
|
|
836
838
|
{formatMoney(
|
|
837
839
|
item.unitPriceNet,
|
|
838
840
|
item.currencyCode ?? currencyCode ?? undefined,
|
|
841
|
+
locale,
|
|
839
842
|
)}{" "}
|
|
840
843
|
{t("sales.documents.items.table.net", "net")}
|
|
841
844
|
</span>
|
|
@@ -850,6 +853,7 @@ export function SalesDocumentItemsSection({
|
|
|
850
853
|
item.currencyCode ??
|
|
851
854
|
currencyCode ??
|
|
852
855
|
undefined,
|
|
856
|
+
locale,
|
|
853
857
|
),
|
|
854
858
|
unit: unitPriceReference.referenceUnitCode,
|
|
855
859
|
},
|
|
@@ -873,6 +877,7 @@ export function SalesDocumentItemsSection({
|
|
|
873
877
|
item.currencyCode ??
|
|
874
878
|
currencyCode ??
|
|
875
879
|
undefined,
|
|
880
|
+
locale,
|
|
876
881
|
),
|
|
877
882
|
},
|
|
878
883
|
)}
|
|
@@ -903,6 +908,7 @@ export function SalesDocumentItemsSection({
|
|
|
903
908
|
{formatMoney(
|
|
904
909
|
item.totalGross,
|
|
905
910
|
item.currencyCode ?? currencyCode ?? undefined,
|
|
911
|
+
locale,
|
|
906
912
|
)}{" "}
|
|
907
913
|
<span className="text-xs font-normal text-muted-foreground">
|
|
908
914
|
{t("sales.documents.items.table.gross", "gross")}
|
|
@@ -912,6 +918,7 @@ export function SalesDocumentItemsSection({
|
|
|
912
918
|
{formatMoney(
|
|
913
919
|
item.totalNet,
|
|
914
920
|
item.currencyCode ?? currencyCode ?? undefined,
|
|
921
|
+
locale,
|
|
915
922
|
)}{" "}
|
|
916
923
|
{t("sales.documents.items.table.net", "net")}
|
|
917
924
|
</span>
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
renderDictionaryColor,
|
|
47
47
|
} from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
|
|
48
48
|
import { E } from "#generated/entities.ids.generated";
|
|
49
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
49
|
+
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
50
50
|
import { useOrganizationScopeDetail } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
51
51
|
import { formatMoney, normalizeNumber } from "./lineItemUtils";
|
|
52
52
|
import type { SalesLineRecord } from "./lineItemTypes";
|
|
@@ -489,6 +489,7 @@ export function LineItemDialog({
|
|
|
489
489
|
onDraftSaved,
|
|
490
490
|
}: SalesLineDialogProps) {
|
|
491
491
|
const t = useT();
|
|
492
|
+
const locale = useLocale();
|
|
492
493
|
const scope = useOrganizationScopeDetail();
|
|
493
494
|
const resolvedOrganizationId = organizationId ?? scope.organizationId ?? null;
|
|
494
495
|
const resolvedTenantId = tenantId ?? scope.tenantId ?? null;
|
|
@@ -1044,10 +1045,10 @@ export function LineItemDialog({
|
|
|
1044
1045
|
displayMode === "including-tax" &&
|
|
1045
1046
|
amountGross !== null &&
|
|
1046
1047
|
currency
|
|
1047
|
-
? formatMoney(amountGross, currency)
|
|
1048
|
+
? formatMoney(amountGross, currency, locale)
|
|
1048
1049
|
: null,
|
|
1049
1050
|
displayMode === "excluding-tax" && amountNet !== null && currency
|
|
1050
|
-
? formatMoney(amountNet, currency)
|
|
1051
|
+
? formatMoney(amountNet, currency, locale)
|
|
1051
1052
|
: null,
|
|
1052
1053
|
displayMode
|
|
1053
1054
|
? displayMode === "including-tax"
|
|
@@ -1063,9 +1064,9 @@ export function LineItemDialog({
|
|
|
1063
1064
|
labelParts.length > 0
|
|
1064
1065
|
? labelParts.join(" • ")
|
|
1065
1066
|
: amountGross !== null && currency
|
|
1066
|
-
? formatMoney(amountGross, currency)
|
|
1067
|
+
? formatMoney(amountGross, currency, locale)
|
|
1067
1068
|
: amountNet !== null && currency
|
|
1068
|
-
? formatMoney(amountNet, currency)
|
|
1069
|
+
? formatMoney(amountNet, currency, locale)
|
|
1069
1070
|
: id;
|
|
1070
1071
|
return {
|
|
1071
1072
|
id,
|
|
@@ -2059,7 +2060,7 @@ export function LineItemDialog({
|
|
|
2059
2060
|
? t("sales.documents.items.priceNet", "Net")
|
|
2060
2061
|
: t("sales.documents.items.priceGross", "Gross");
|
|
2061
2062
|
const lockedAmountLabel = Number.isFinite(lockedAmount)
|
|
2062
|
-
? `${formatMoney(lockedAmount, lockedCurrency)} — ${lockedModeLabel}`
|
|
2063
|
+
? `${formatMoney(lockedAmount, lockedCurrency, locale)} — ${lockedModeLabel}`
|
|
2063
2064
|
: lockedModeLabel;
|
|
2064
2065
|
const lockedPriceDetail =
|
|
2065
2066
|
selectedPrice?.priceKindTitle ??
|
|
@@ -2260,11 +2261,11 @@ export function LineItemDialog({
|
|
|
2260
2261
|
"sales.documents.items.priceBasisTemplate",
|
|
2261
2262
|
"Catalog price basis: {{baseAmount}} / {{baseUnit}}. Converted for {{unit}}: {{baseAmount}} × {{factor}} = {{convertedAmount}}.",
|
|
2262
2263
|
{
|
|
2263
|
-
baseAmount: formatMoney(selectedBaseAmount as number, selectedCurrency),
|
|
2264
|
+
baseAmount: formatMoney(selectedBaseAmount as number, selectedCurrency, locale),
|
|
2264
2265
|
baseUnit: baseUnitCode,
|
|
2265
2266
|
unit: quantityUnitCode,
|
|
2266
2267
|
factor: unitFactor,
|
|
2267
|
-
convertedAmount: formatMoney(convertedAmount, selectedCurrency),
|
|
2268
|
+
convertedAmount: formatMoney(convertedAmount, selectedCurrency, locale),
|
|
2268
2269
|
},
|
|
2269
2270
|
)}
|
|
2270
2271
|
</p>
|
|
@@ -13,7 +13,7 @@ import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
|
13
13
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
14
14
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
15
15
|
import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
16
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
16
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
17
17
|
import { emitSalesDocumentTotalsRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'
|
|
18
18
|
import { extensionPoints } from '@open-mercato/core/modules/sales/extension-points'
|
|
19
19
|
import { PaymentDialog, type PaymentFormData, type PaymentTotals } from './PaymentDialog'
|
|
@@ -60,10 +60,10 @@ function normalizeNumber(value: unknown): number {
|
|
|
60
60
|
return 0
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function formatMoney(value: number, currency: string | null | undefined): string {
|
|
63
|
+
function formatMoney(value: number, currency: string | null | undefined, locale?: string): string {
|
|
64
64
|
if (!currency || currency.trim().length !== 3) return value.toFixed(2)
|
|
65
65
|
try {
|
|
66
|
-
return new Intl.NumberFormat(
|
|
66
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value)
|
|
67
67
|
} catch {
|
|
68
68
|
return `${currency.toUpperCase()} ${value.toFixed(2)}`
|
|
69
69
|
}
|
|
@@ -80,6 +80,7 @@ export function SalesDocumentPaymentsSection({
|
|
|
80
80
|
onPaymentsChange,
|
|
81
81
|
}: SalesDocumentPaymentsSectionProps) {
|
|
82
82
|
const t = useT()
|
|
83
|
+
const locale = useLocale()
|
|
83
84
|
const { organizationId, tenantId } = useOrganizationScopeDetail()
|
|
84
85
|
const resolvedOrganizationId = orgFromProps ?? organizationId ?? null
|
|
85
86
|
const resolvedTenantId = tenantFromProps ?? tenantId ?? null
|
|
@@ -287,24 +288,24 @@ export function SalesDocumentPaymentsSection({
|
|
|
287
288
|
{
|
|
288
289
|
accessorKey: 'amount',
|
|
289
290
|
header: t('sales.documents.payments.amount', 'Amount'),
|
|
290
|
-
cell: ({ row }) => formatMoney(row.original.amount, row.original.currencyCode ?? currencyCode),
|
|
291
|
+
cell: ({ row }) => formatMoney(row.original.amount, row.original.currencyCode ?? currencyCode, locale),
|
|
291
292
|
},
|
|
292
293
|
{
|
|
293
294
|
accessorKey: 'receivedAt',
|
|
294
295
|
header: t('sales.documents.payments.receivedAt', 'Received'),
|
|
295
296
|
cell: ({ row }) =>
|
|
296
297
|
row.original.receivedAt
|
|
297
|
-
? new Date(row.original.receivedAt).toLocaleDateString()
|
|
298
|
+
? new Date(row.original.receivedAt).toLocaleDateString(locale)
|
|
298
299
|
: '—',
|
|
299
300
|
},
|
|
300
301
|
{
|
|
301
302
|
accessorKey: 'createdAt',
|
|
302
303
|
header: t('sales.documents.payments.createdAt', 'Created'),
|
|
303
304
|
cell: ({ row }) =>
|
|
304
|
-
row.original.createdAt ? new Date(row.original.createdAt).toLocaleString() : '—',
|
|
305
|
+
row.original.createdAt ? new Date(row.original.createdAt).toLocaleString(locale) : '—',
|
|
305
306
|
meta: {
|
|
306
307
|
tooltipContent: (row: PaymentRow) =>
|
|
307
|
-
row.createdAt ? new Date(row.createdAt).toLocaleString() : undefined,
|
|
308
|
+
row.createdAt ? new Date(row.createdAt).toLocaleString(locale) : undefined,
|
|
308
309
|
},
|
|
309
310
|
},
|
|
310
311
|
{
|
|
@@ -327,7 +328,7 @@ export function SalesDocumentPaymentsSection({
|
|
|
327
328
|
},
|
|
328
329
|
},
|
|
329
330
|
],
|
|
330
|
-
[currencyCode, deleteActionLabel, editActionLabel, handleDelete, openEditPayment, t]
|
|
331
|
+
[currencyCode, deleteActionLabel, editActionLabel, handleDelete, locale, openEditPayment, t]
|
|
331
332
|
)
|
|
332
333
|
|
|
333
334
|
if (loading) {
|
|
@@ -12,7 +12,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
|
12
12
|
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
13
13
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
14
14
|
import { useOrganizationScopeDetail } 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 {
|
|
17
17
|
emitSalesDocumentTotalsRefresh,
|
|
18
18
|
subscribeSalesDocumentTotalsRefresh,
|
|
@@ -41,15 +41,16 @@ type SalesReturnsSectionProps = {
|
|
|
41
41
|
documentUpdatedAt?: string | null
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function formatDisplayDate(value: string | null | undefined): string | null {
|
|
44
|
+
export function formatDisplayDate(value: string | null | undefined, locale?: string): string | null {
|
|
45
45
|
if (!value) return null
|
|
46
46
|
const date = new Date(value)
|
|
47
47
|
if (Number.isNaN(date.getTime())) return null
|
|
48
|
-
return new Intl.DateTimeFormat(
|
|
48
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function SalesReturnsSection({ orderId, currencyCode, documentUpdatedAt }: SalesReturnsSectionProps) {
|
|
52
52
|
const t = useT()
|
|
53
|
+
const locale = useLocale()
|
|
53
54
|
const { organizationId, tenantId } = useOrganizationScopeDetail()
|
|
54
55
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
55
56
|
const [returns, setReturns] = React.useState<ReturnRow[]>([])
|
|
@@ -328,10 +329,10 @@ export function SalesReturnsSection({ orderId, currencyCode, documentUpdatedAt }
|
|
|
328
329
|
</div>
|
|
329
330
|
</div>
|
|
330
331
|
<div className="whitespace-nowrap text-right text-sm text-muted-foreground">
|
|
331
|
-
{formatDisplayDate(ret.returnedAt) ?? t('sales.returns.notSet', 'Not set')}
|
|
332
|
+
{formatDisplayDate(ret.returnedAt, locale) ?? t('sales.returns.notSet', 'Not set')}
|
|
332
333
|
</div>
|
|
333
334
|
<div className="whitespace-nowrap text-right text-sm font-medium">
|
|
334
|
-
{formatMoney(ret.total, currencyCode ?? null)}
|
|
335
|
+
{formatMoney(ret.total, currencyCode ?? null, locale)}
|
|
335
336
|
</div>
|
|
336
337
|
<div className="flex justify-end">
|
|
337
338
|
<RowActions
|
|
@@ -7,7 +7,7 @@ import { DataTable } from '@open-mercato/ui/backend/DataTable'
|
|
|
7
7
|
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
8
8
|
import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
|
|
9
9
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
10
|
+
import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
|
|
11
11
|
import { LineItemDialog } from './LineItemDialog'
|
|
12
12
|
import type { SalesLineRecord } from './lineItemTypes'
|
|
13
13
|
import { formatMoney, normalizeNumber } from './lineItemUtils'
|
|
@@ -93,6 +93,7 @@ export function SalesOrderDraftLines({
|
|
|
93
93
|
onChange,
|
|
94
94
|
}: SalesOrderDraftLinesProps) {
|
|
95
95
|
const t = useT()
|
|
96
|
+
const locale = useLocale()
|
|
96
97
|
const [dialogOpen, setDialogOpen] = React.useState(false)
|
|
97
98
|
const [editing, setEditing] = React.useState<SalesOrderLineDraft | null>(null)
|
|
98
99
|
|
|
@@ -112,14 +113,14 @@ export function SalesOrderDraftLines({
|
|
|
112
113
|
{
|
|
113
114
|
id: 'unitPrice',
|
|
114
115
|
header: t('sales.documents.items.table.unit', 'Unit price'),
|
|
115
|
-
cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined),
|
|
116
|
+
cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),
|
|
116
117
|
},
|
|
117
118
|
{
|
|
118
119
|
id: 'total',
|
|
119
120
|
header: t('sales.documents.items.table.total', 'Total'),
|
|
120
|
-
cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined),
|
|
121
|
+
cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),
|
|
121
122
|
},
|
|
122
|
-
], [currencyCode, t])
|
|
123
|
+
], [currencyCode, locale, t])
|
|
123
124
|
|
|
124
125
|
const openCreate = React.useCallback(() => {
|
|
125
126
|
setEditing(null)
|
|
@@ -20,7 +20,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
|
20
20
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
21
21
|
import { E } from '#generated/entities.ids.generated'
|
|
22
22
|
import { emitSalesDocumentTotalsRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'
|
|
23
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
23
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
24
24
|
import { formatMoney, normalizeNumber } from './lineItemUtils'
|
|
25
25
|
import type { OrderLine, ShipmentRow } from './shipmentTypes'
|
|
26
26
|
import { formatAddressString, type AddressFormatStrategy, type AddressValue } from '@open-mercato/core/modules/customers/utils/addressFormat'
|
|
@@ -290,6 +290,7 @@ export function ShipmentDialog({
|
|
|
290
290
|
onAddComment,
|
|
291
291
|
}: ShipmentDialogProps) {
|
|
292
292
|
const t = useT()
|
|
293
|
+
const locale = useLocale()
|
|
293
294
|
const [formResetKey, setFormResetKey] = React.useState(0)
|
|
294
295
|
const [shippingMethods, setShippingMethods] = React.useState<ShippingMethodOption[]>([])
|
|
295
296
|
const [shippingMethodLoading, setShippingMethodLoading] = React.useState(false)
|
|
@@ -540,21 +541,21 @@ export function ShipmentDialog({
|
|
|
540
541
|
if (option.avgPrice !== null) {
|
|
541
542
|
parts.push(
|
|
542
543
|
t('sales.documents.shipments.shippingMethodAvg', 'Avg {{price}}', {
|
|
543
|
-
price: formatMoney(option.avgPrice, currency ?? null),
|
|
544
|
+
price: formatMoney(option.avgPrice, currency ?? null, locale),
|
|
544
545
|
}),
|
|
545
546
|
)
|
|
546
547
|
}
|
|
547
548
|
if (option.minPrice !== null) {
|
|
548
549
|
parts.push(
|
|
549
550
|
t('sales.documents.shipments.shippingMethodMin', 'Min {{price}}', {
|
|
550
|
-
price: formatMoney(option.minPrice, currency ?? null),
|
|
551
|
+
price: formatMoney(option.minPrice, currency ?? null, locale),
|
|
551
552
|
}),
|
|
552
553
|
)
|
|
553
554
|
}
|
|
554
555
|
if (parts.length === 0) return undefined
|
|
555
556
|
return parts.join(' · ')
|
|
556
557
|
},
|
|
557
|
-
[currencyCode, t],
|
|
558
|
+
[currencyCode, locale, t],
|
|
558
559
|
)
|
|
559
560
|
|
|
560
561
|
const mapShippingMethod = React.useCallback(
|