@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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
  3. package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
  4. package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
  5. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
  6. package/dist/modules/customers/components/detail/DealsSection.js +6 -5
  7. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  8. package/dist/modules/customers/components/detail/utils.js +5 -5
  9. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  10. package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
  11. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  12. package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
  13. package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
  14. package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
  15. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  16. package/dist/modules/sales/components/documents/ItemsSection.js +14 -7
  17. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  18. package/dist/modules/sales/components/documents/LineItemDialog.js +8 -7
  19. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  20. package/dist/modules/sales/components/documents/PaymentsSection.js +9 -8
  21. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  22. package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
  23. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  24. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +5 -4
  25. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
  26. package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
  27. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  28. package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
  29. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  30. package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
  31. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  32. package/dist/modules/sales/data/entities.js +8 -2
  33. package/dist/modules/sales/data/entities.js.map +2 -2
  34. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
  35. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
  36. package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
  37. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  38. package/package.json +7 -7
  39. package/src/modules/auth/i18n/de.json +3 -0
  40. package/src/modules/auth/i18n/en.json +3 -0
  41. package/src/modules/auth/i18n/es.json +3 -0
  42. package/src/modules/auth/i18n/ko.json +3 -0
  43. package/src/modules/auth/i18n/pl.json +3 -0
  44. package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
  45. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
  46. package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
  47. package/src/modules/customers/components/detail/utils.ts +15 -5
  48. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
  49. package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
  50. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
  51. package/src/modules/sales/components/documents/ItemsSection.tsx +8 -1
  52. package/src/modules/sales/components/documents/LineItemDialog.tsx +9 -8
  53. package/src/modules/sales/components/documents/PaymentsSection.tsx +9 -8
  54. package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
  55. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +5 -4
  56. package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
  57. package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
  58. package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
  59. package/src/modules/sales/data/entities.ts +6 -0
  60. package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
  61. package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
  62. package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
@@ -1,4 +1,4 @@
1
- [build:core] found 4345 entry points
1
+ [build:core] found 4346 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 218 entry points
4
4
  [build:core:generated] built successfully
@@ -10,12 +10,12 @@ import { Avatar, AvatarStack } from "@open-mercato/ui/primitives/avatar";
10
10
  import { Button } from "@open-mercato/ui/primitives/button";
11
11
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
12
12
  import { PipelineStageBar } from "./kpi/PipelineStageBar.js";
13
- const compactNumberFormatter = new Intl.NumberFormat(void 0, {
14
- notation: "compact",
15
- maximumFractionDigits: 1
16
- });
17
- function formatCompact(value) {
18
- return compactNumberFormatter.format(value);
13
+ function createCompactFormatter(locale) {
14
+ const formatter = new Intl.NumberFormat(locale, {
15
+ notation: "compact",
16
+ maximumFractionDigits: 1
17
+ });
18
+ return (value) => formatter.format(value);
19
19
  }
20
20
  function buildCurrencySuffix(code, convertedAll) {
21
21
  if (!code) return convertedAll ? "" : "\u2248";
@@ -62,6 +62,7 @@ function DealsKpiStrip({
62
62
  }) {
63
63
  const t = useT();
64
64
  const locale = useLocale();
65
+ const formatCompact = React.useMemo(() => createCompactFormatter(locale), [locale]);
65
66
  const pluralCat = React.useCallback((count) => {
66
67
  try {
67
68
  return new Intl.PluralRules(locale).select(count);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/components/DealsKpiStrip.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CheckCircle } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { KpiCard, DeltaBadge, Sparkline } from '@open-mercato/ui/backend/charts'\nimport { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport type { DictionaryMap } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { PipelineStageBar } from './kpi/PipelineStageBar'\n\ntype DeltaDirection = 'up' | 'down' | 'unchanged'\n\ntype SummaryDelta = {\n value: number\n direction: DeltaDirection\n}\n\ntype DealsSummaryResponse = {\n baseCurrencyCode: string | null\n convertedAll: boolean\n missingRateCurrencies: string[]\n pipelineValue: {\n value: number\n delta: SummaryDelta\n stages: { stage: string | null; count: number; value: number }[]\n }\n activeDeals: {\n value: number\n delta: SummaryDelta\n ownersCount: number\n needAttention: number\n owners: { id: string; count: number }[]\n ownersOverflow: number\n }\n wonThisQuarter: {\n value: number\n delta: SummaryDelta\n dealsClosed: number\n avgDeal: number\n }\n winRate: {\n value: number\n deltaPp: number\n direction: DeltaDirection\n previousValue: number\n series: { period: string; rate: number }[]\n }\n}\n\nexport type DealsKpiStripProps = {\n ownerNames: Record<string, string>\n stageDictionary: DictionaryMap\n pipelineCount: number\n className?: string\n /** Bumped by the host when the active org scope changes \u2014 forces a KPI refetch so the cards never show another org's data. */\n scopeVersion?: number\n /** Bumped by the host on manual refresh / after mutations \u2014 forces a KPI refetch so totals stay in sync with the table. */\n reloadToken?: number\n onNeedsAttentionClick?: () => void\n}\n\nconst compactNumberFormatter = new Intl.NumberFormat(undefined, {\n notation: 'compact',\n maximumFractionDigits: 1,\n})\n\nfunction formatCompact(value: number): string {\n return compactNumberFormatter.format(value)\n}\n\nfunction buildCurrencySuffix(code: string | null, convertedAll: boolean): string {\n if (!code) return convertedAll ? '' : '\u2248'\n return convertedAll ? code : `\u2248 ${code}`\n}\n\nconst KPI_TITLE_CLASS = 'text-xs font-semibold uppercase tracking-wide text-muted-foreground'\n\nfunction DealKpiCard(props: React.ComponentProps<typeof KpiCard>) {\n return <KpiCard titleClassName={KPI_TITLE_CLASS} {...props} />\n}\n\nfunction KpiDeltaBadge({\n direction,\n value,\n unit,\n title,\n}: {\n direction: DeltaDirection\n value: number\n unit?: string\n title: string\n}) {\n if (direction === 'unchanged' && value === 0) {\n return (\n <span\n className=\"inline-flex items-center rounded-md bg-status-neutral-bg px-2 py-0.5 text-xs font-medium text-status-neutral-text\"\n title={title}\n >\n --\n </span>\n )\n }\n return <DeltaBadge direction={direction} value={value} unit={unit} title={title} />\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n// Guard the summary payload before rendering: a non-conforming response (an unrelated\n// endpoint mock, an error body, or a future contract drift) must surface the error card,\n// never crash the whole deals page by dereferencing missing sections/arrays.\nfunction isDealsSummaryResponse(value: unknown): value is DealsSummaryResponse {\n if (!isObject(value)) return false\n const { pipelineValue, activeDeals, wonThisQuarter, winRate } = value\n return (\n isObject(pipelineValue) && Array.isArray(pipelineValue.stages) &&\n isObject(activeDeals) && Array.isArray(activeDeals.owners) &&\n isObject(wonThisQuarter) &&\n isObject(winRate) && Array.isArray(winRate.series)\n )\n}\n\nexport function DealsKpiStrip({\n ownerNames,\n stageDictionary,\n pipelineCount,\n className,\n scopeVersion,\n reloadToken,\n onNeedsAttentionClick,\n}: DealsKpiStripProps) {\n const t = useT()\n const locale = useLocale()\n const pluralCat = React.useCallback((count: number): string => {\n try {\n return new Intl.PluralRules(locale).select(count)\n } catch {\n return count === 1 ? 'one' : 'other'\n }\n }, [locale])\n const pf = React.useCallback((base: string, count: number): string => {\n const cat = pluralCat(count)\n const key = `${base}.${cat}`\n const out = t(key, { count })\n return out === key ? t(`${base}.other`, { count }) : out\n }, [t, pluralCat])\n const [data, setData] = React.useState<DealsSummaryResponse | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [retryToken, setRetryToken] = React.useState(0)\n const previousScopeVersionRef = React.useRef(scopeVersion)\n\n const retry = React.useCallback(() => {\n setRetryToken((token) => token + 1)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n const scopeChanged = previousScopeVersionRef.current !== scopeVersion\n previousScopeVersionRef.current = scopeVersion\n if (scopeChanged) setData(null)\n setLoading(true)\n setError(null)\n apiCall<DealsSummaryResponse>('/api/customers/deals/summary')\n .then((call) => {\n if (cancelled) return\n if (!call.ok || !isDealsSummaryResponse(call.result)) {\n setError(t('customers.deals.list.kpi.error'))\n return\n }\n setData(call.result)\n })\n .catch(() => {\n if (cancelled) return\n setError(t('customers.deals.list.kpi.error'))\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [t, scopeVersion, reloadToken, retryToken])\n\n const wrapperClassName = cn('space-y-2', className)\n const gridClassName = 'grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4'\n\n if (loading && !data) {\n return (\n <div className={wrapperClassName}>\n <div className={gridClassName}>\n <DealKpiCard loading title={t('customers.deals.list.kpi.pipelineValue')} value={null} />\n <DealKpiCard loading title={t('customers.deals.list.kpi.activeDeals')} value={null} />\n <DealKpiCard loading title={t('customers.deals.list.kpi.wonThisQuarter')} value={null} />\n <DealKpiCard loading title={t('customers.deals.list.kpi.winRate')} value={null} />\n </div>\n </div>\n )\n }\n\n if (!data) {\n const errorMessage = error ?? t('customers.deals.list.kpi.error')\n return (\n <div className={wrapperClassName}>\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-4\">\n <p className=\"text-sm text-destructive\">{errorMessage}</p>\n <Button type=\"button\" variant=\"destructive-outline\" size=\"sm\" onClick={retry}>\n {t('customers.deals.list.kpi.retry')}\n </Button>\n </div>\n </div>\n )\n }\n\n const currencySuffix = buildCurrencySuffix(data.baseCurrencyCode, data.convertedAll)\n const unassignedLabel = t('customers.deals.list.kpi.unassignedStage')\n const deltaTooltip = t('customers.deals.list.kpi.deltaTooltip')\n const deltaUnavailableTooltip = t('customers.deals.list.kpi.deltaUnavailable')\n const scopeLabel = t('customers.deals.list.kpi.scopeAllPipelinesThisQuarter')\n const unknownOwner = t('customers.deals.list.unknownOwner')\n const currencyHint = !data.convertedAll\n ? data.baseCurrencyCode\n ? t('customers.deals.list.kpi.currencyApproxMissing', {\n currencies: data.missingRateCurrencies.length ? data.missingRateCurrencies.join(', ') : currencySuffix,\n })\n : t('customers.deals.list.kpi.currencyApproxNoBase')\n : null\n const attentionLabel = pf('customers.deals.list.kpi.frag.needAttention', data.activeDeals.needAttention)\n\n return (\n <div className={wrapperClassName}>\n {error ? (\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2\">\n <p className=\"text-xs text-destructive\">{error}</p>\n <Button type=\"button\" variant=\"destructive-outline\" size=\"2xs\" onClick={retry}>\n {t('customers.deals.list.kpi.retry')}\n </Button>\n </div>\n ) : null}\n {loading ? (\n <div className=\"flex items-center justify-end gap-2 text-xs text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n <span>{t('customers.deals.list.kpi.updating')}</span>\n </div>\n ) : null}\n <div className={gridClassName}>\n <DealKpiCard\n title={t('customers.deals.list.kpi.pipelineValue')}\n value={data.pipelineValue.value}\n formatValue={formatCompact}\n suffix={currencySuffix}\n headerAction={\n <KpiDeltaBadge\n direction={data.pipelineValue.delta.direction}\n value={data.pipelineValue.delta.value}\n title={data.pipelineValue.delta.direction === 'unchanged' && data.pipelineValue.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.deals.list.kpi.activeAcrossPipelines', {\n deals: pf('customers.deals.list.kpi.frag.activeDeals', data.activeDeals.value),\n pipelines: pf('customers.deals.list.kpi.frag.pipelines', pipelineCount),\n })}\n </p>\n {currencyHint ? <p className=\"text-xs text-muted-foreground\">{currencyHint}</p> : null}\n <PipelineStageBar\n stages={data.pipelineValue.stages}\n stageDictionary={stageDictionary}\n unassignedLabel={unassignedLabel}\n />\n </div>\n }\n />\n\n <DealKpiCard\n title={t('customers.deals.list.kpi.activeDeals')}\n value={data.activeDeals.value}\n formatValue={formatCompact}\n headerAction={\n <KpiDeltaBadge\n direction={data.activeDeals.delta.direction}\n value={data.activeDeals.delta.value}\n title={data.activeDeals.delta.direction === 'unchanged' && data.activeDeals.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <div className=\"flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted-foreground\">\n <span>{pf('customers.deals.list.kpi.frag.owners', data.activeDeals.ownersCount)}</span>\n <span aria-hidden=\"true\">\u00B7</span>\n {onNeedsAttentionClick && data.activeDeals.needAttention > 0 ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"2xs\"\n className=\"h-auto p-0 text-xs\"\n onClick={onNeedsAttentionClick}\n >\n {attentionLabel}\n </Button>\n ) : (\n <span>{attentionLabel}</span>\n )}\n </div>\n {data.activeDeals.owners.length > 0 ? (\n <AvatarStack max={4} size=\"sm\" overflowCount={data.activeDeals.ownersOverflow}>\n {data.activeDeals.owners.map((owner) => {\n const ownerLabel = ownerNames[owner.id]?.trim() || unknownOwner\n return <Avatar key={owner.id} label={ownerLabel} size=\"sm\" />\n })}\n </AvatarStack>\n ) : null}\n </div>\n }\n />\n\n <DealKpiCard\n title={t('customers.deals.list.kpi.wonThisQuarter')}\n value={data.wonThisQuarter.value}\n formatValue={formatCompact}\n suffix={currencySuffix}\n headerAction={\n <KpiDeltaBadge\n direction={data.wonThisQuarter.delta.direction}\n value={data.wonThisQuarter.delta.value}\n title={data.wonThisQuarter.delta.direction === 'unchanged' && data.wonThisQuarter.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-1\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" aria-hidden />\n <span>\n {pf('customers.deals.list.kpi.frag.dealsClosed', data.wonThisQuarter.dealsClosed)}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.deals.list.kpi.avgDeal', {\n value: `${formatCompact(data.wonThisQuarter.avgDeal)}${currencySuffix ? ` ${currencySuffix}` : ''}`,\n })}\n </p>\n {currencyHint ? <p className=\"text-xs text-muted-foreground\">{currencyHint}</p> : null}\n </div>\n }\n />\n\n <DealKpiCard\n title={t('customers.deals.list.kpi.winRate')}\n value={data.winRate.value}\n suffix=\"%\"\n headerAction={\n <KpiDeltaBadge\n direction={data.winRate.direction}\n value={Math.abs(data.winRate.deltaPp)}\n unit=\"pp\"\n title={data.winRate.previousValue === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.deals.list.kpi.fromLastQuarter', { value: data.winRate.previousValue })}\n </p>\n <div className=\"text-primary\">\n <Sparkline\n values={data.winRate.series.map((point) => point.rate)}\n ariaLabel={t('customers.deals.list.kpi.winRate')}\n />\n </div>\n </div>\n }\n />\n </div>\n </div>\n )\n}\n\nexport default DealsKpiStrip\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CheckCircle } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { KpiCard, DeltaBadge, Sparkline } from '@open-mercato/ui/backend/charts'\nimport { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport type { DictionaryMap } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { PipelineStageBar } from './kpi/PipelineStageBar'\n\ntype DeltaDirection = 'up' | 'down' | 'unchanged'\n\ntype SummaryDelta = {\n value: number\n direction: DeltaDirection\n}\n\ntype DealsSummaryResponse = {\n baseCurrencyCode: string | null\n convertedAll: boolean\n missingRateCurrencies: string[]\n pipelineValue: {\n value: number\n delta: SummaryDelta\n stages: { stage: string | null; count: number; value: number }[]\n }\n activeDeals: {\n value: number\n delta: SummaryDelta\n ownersCount: number\n needAttention: number\n owners: { id: string; count: number }[]\n ownersOverflow: number\n }\n wonThisQuarter: {\n value: number\n delta: SummaryDelta\n dealsClosed: number\n avgDeal: number\n }\n winRate: {\n value: number\n deltaPp: number\n direction: DeltaDirection\n previousValue: number\n series: { period: string; rate: number }[]\n }\n}\n\nexport type DealsKpiStripProps = {\n ownerNames: Record<string, string>\n stageDictionary: DictionaryMap\n pipelineCount: number\n className?: string\n /** Bumped by the host when the active org scope changes \u2014 forces a KPI refetch so the cards never show another org's data. */\n scopeVersion?: number\n /** Bumped by the host on manual refresh / after mutations \u2014 forces a KPI refetch so totals stay in sync with the table. */\n reloadToken?: number\n onNeedsAttentionClick?: () => void\n}\n\nfunction createCompactFormatter(locale: string): (value: number) => string {\n const formatter = new Intl.NumberFormat(locale, {\n notation: 'compact',\n maximumFractionDigits: 1,\n })\n return (value: number) => formatter.format(value)\n}\n\nfunction buildCurrencySuffix(code: string | null, convertedAll: boolean): string {\n if (!code) return convertedAll ? '' : '\u2248'\n return convertedAll ? code : `\u2248 ${code}`\n}\n\nconst KPI_TITLE_CLASS = 'text-xs font-semibold uppercase tracking-wide text-muted-foreground'\n\nfunction DealKpiCard(props: React.ComponentProps<typeof KpiCard>) {\n return <KpiCard titleClassName={KPI_TITLE_CLASS} {...props} />\n}\n\nfunction KpiDeltaBadge({\n direction,\n value,\n unit,\n title,\n}: {\n direction: DeltaDirection\n value: number\n unit?: string\n title: string\n}) {\n if (direction === 'unchanged' && value === 0) {\n return (\n <span\n className=\"inline-flex items-center rounded-md bg-status-neutral-bg px-2 py-0.5 text-xs font-medium text-status-neutral-text\"\n title={title}\n >\n --\n </span>\n )\n }\n return <DeltaBadge direction={direction} value={value} unit={unit} title={title} />\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n// Guard the summary payload before rendering: a non-conforming response (an unrelated\n// endpoint mock, an error body, or a future contract drift) must surface the error card,\n// never crash the whole deals page by dereferencing missing sections/arrays.\nfunction isDealsSummaryResponse(value: unknown): value is DealsSummaryResponse {\n if (!isObject(value)) return false\n const { pipelineValue, activeDeals, wonThisQuarter, winRate } = value\n return (\n isObject(pipelineValue) && Array.isArray(pipelineValue.stages) &&\n isObject(activeDeals) && Array.isArray(activeDeals.owners) &&\n isObject(wonThisQuarter) &&\n isObject(winRate) && Array.isArray(winRate.series)\n )\n}\n\nexport function DealsKpiStrip({\n ownerNames,\n stageDictionary,\n pipelineCount,\n className,\n scopeVersion,\n reloadToken,\n onNeedsAttentionClick,\n}: DealsKpiStripProps) {\n const t = useT()\n const locale = useLocale()\n const formatCompact = React.useMemo(() => createCompactFormatter(locale), [locale])\n const pluralCat = React.useCallback((count: number): string => {\n try {\n return new Intl.PluralRules(locale).select(count)\n } catch {\n return count === 1 ? 'one' : 'other'\n }\n }, [locale])\n const pf = React.useCallback((base: string, count: number): string => {\n const cat = pluralCat(count)\n const key = `${base}.${cat}`\n const out = t(key, { count })\n return out === key ? t(`${base}.other`, { count }) : out\n }, [t, pluralCat])\n const [data, setData] = React.useState<DealsSummaryResponse | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [retryToken, setRetryToken] = React.useState(0)\n const previousScopeVersionRef = React.useRef(scopeVersion)\n\n const retry = React.useCallback(() => {\n setRetryToken((token) => token + 1)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n const scopeChanged = previousScopeVersionRef.current !== scopeVersion\n previousScopeVersionRef.current = scopeVersion\n if (scopeChanged) setData(null)\n setLoading(true)\n setError(null)\n apiCall<DealsSummaryResponse>('/api/customers/deals/summary')\n .then((call) => {\n if (cancelled) return\n if (!call.ok || !isDealsSummaryResponse(call.result)) {\n setError(t('customers.deals.list.kpi.error'))\n return\n }\n setData(call.result)\n })\n .catch(() => {\n if (cancelled) return\n setError(t('customers.deals.list.kpi.error'))\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [t, scopeVersion, reloadToken, retryToken])\n\n const wrapperClassName = cn('space-y-2', className)\n const gridClassName = 'grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4'\n\n if (loading && !data) {\n return (\n <div className={wrapperClassName}>\n <div className={gridClassName}>\n <DealKpiCard loading title={t('customers.deals.list.kpi.pipelineValue')} value={null} />\n <DealKpiCard loading title={t('customers.deals.list.kpi.activeDeals')} value={null} />\n <DealKpiCard loading title={t('customers.deals.list.kpi.wonThisQuarter')} value={null} />\n <DealKpiCard loading title={t('customers.deals.list.kpi.winRate')} value={null} />\n </div>\n </div>\n )\n }\n\n if (!data) {\n const errorMessage = error ?? t('customers.deals.list.kpi.error')\n return (\n <div className={wrapperClassName}>\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-4\">\n <p className=\"text-sm text-destructive\">{errorMessage}</p>\n <Button type=\"button\" variant=\"destructive-outline\" size=\"sm\" onClick={retry}>\n {t('customers.deals.list.kpi.retry')}\n </Button>\n </div>\n </div>\n )\n }\n\n const currencySuffix = buildCurrencySuffix(data.baseCurrencyCode, data.convertedAll)\n const unassignedLabel = t('customers.deals.list.kpi.unassignedStage')\n const deltaTooltip = t('customers.deals.list.kpi.deltaTooltip')\n const deltaUnavailableTooltip = t('customers.deals.list.kpi.deltaUnavailable')\n const scopeLabel = t('customers.deals.list.kpi.scopeAllPipelinesThisQuarter')\n const unknownOwner = t('customers.deals.list.unknownOwner')\n const currencyHint = !data.convertedAll\n ? data.baseCurrencyCode\n ? t('customers.deals.list.kpi.currencyApproxMissing', {\n currencies: data.missingRateCurrencies.length ? data.missingRateCurrencies.join(', ') : currencySuffix,\n })\n : t('customers.deals.list.kpi.currencyApproxNoBase')\n : null\n const attentionLabel = pf('customers.deals.list.kpi.frag.needAttention', data.activeDeals.needAttention)\n\n return (\n <div className={wrapperClassName}>\n {error ? (\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2\">\n <p className=\"text-xs text-destructive\">{error}</p>\n <Button type=\"button\" variant=\"destructive-outline\" size=\"2xs\" onClick={retry}>\n {t('customers.deals.list.kpi.retry')}\n </Button>\n </div>\n ) : null}\n {loading ? (\n <div className=\"flex items-center justify-end gap-2 text-xs text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n <span>{t('customers.deals.list.kpi.updating')}</span>\n </div>\n ) : null}\n <div className={gridClassName}>\n <DealKpiCard\n title={t('customers.deals.list.kpi.pipelineValue')}\n value={data.pipelineValue.value}\n formatValue={formatCompact}\n suffix={currencySuffix}\n headerAction={\n <KpiDeltaBadge\n direction={data.pipelineValue.delta.direction}\n value={data.pipelineValue.delta.value}\n title={data.pipelineValue.delta.direction === 'unchanged' && data.pipelineValue.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.deals.list.kpi.activeAcrossPipelines', {\n deals: pf('customers.deals.list.kpi.frag.activeDeals', data.activeDeals.value),\n pipelines: pf('customers.deals.list.kpi.frag.pipelines', pipelineCount),\n })}\n </p>\n {currencyHint ? <p className=\"text-xs text-muted-foreground\">{currencyHint}</p> : null}\n <PipelineStageBar\n stages={data.pipelineValue.stages}\n stageDictionary={stageDictionary}\n unassignedLabel={unassignedLabel}\n />\n </div>\n }\n />\n\n <DealKpiCard\n title={t('customers.deals.list.kpi.activeDeals')}\n value={data.activeDeals.value}\n formatValue={formatCompact}\n headerAction={\n <KpiDeltaBadge\n direction={data.activeDeals.delta.direction}\n value={data.activeDeals.delta.value}\n title={data.activeDeals.delta.direction === 'unchanged' && data.activeDeals.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <div className=\"flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted-foreground\">\n <span>{pf('customers.deals.list.kpi.frag.owners', data.activeDeals.ownersCount)}</span>\n <span aria-hidden=\"true\">\u00B7</span>\n {onNeedsAttentionClick && data.activeDeals.needAttention > 0 ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"2xs\"\n className=\"h-auto p-0 text-xs\"\n onClick={onNeedsAttentionClick}\n >\n {attentionLabel}\n </Button>\n ) : (\n <span>{attentionLabel}</span>\n )}\n </div>\n {data.activeDeals.owners.length > 0 ? (\n <AvatarStack max={4} size=\"sm\" overflowCount={data.activeDeals.ownersOverflow}>\n {data.activeDeals.owners.map((owner) => {\n const ownerLabel = ownerNames[owner.id]?.trim() || unknownOwner\n return <Avatar key={owner.id} label={ownerLabel} size=\"sm\" />\n })}\n </AvatarStack>\n ) : null}\n </div>\n }\n />\n\n <DealKpiCard\n title={t('customers.deals.list.kpi.wonThisQuarter')}\n value={data.wonThisQuarter.value}\n formatValue={formatCompact}\n suffix={currencySuffix}\n headerAction={\n <KpiDeltaBadge\n direction={data.wonThisQuarter.delta.direction}\n value={data.wonThisQuarter.delta.value}\n title={data.wonThisQuarter.delta.direction === 'unchanged' && data.wonThisQuarter.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-1\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" aria-hidden />\n <span>\n {pf('customers.deals.list.kpi.frag.dealsClosed', data.wonThisQuarter.dealsClosed)}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.deals.list.kpi.avgDeal', {\n value: `${formatCompact(data.wonThisQuarter.avgDeal)}${currencySuffix ? ` ${currencySuffix}` : ''}`,\n })}\n </p>\n {currencyHint ? <p className=\"text-xs text-muted-foreground\">{currencyHint}</p> : null}\n </div>\n }\n />\n\n <DealKpiCard\n title={t('customers.deals.list.kpi.winRate')}\n value={data.winRate.value}\n suffix=\"%\"\n headerAction={\n <KpiDeltaBadge\n direction={data.winRate.direction}\n value={Math.abs(data.winRate.deltaPp)}\n unit=\"pp\"\n title={data.winRate.previousValue === 0 ? deltaUnavailableTooltip : deltaTooltip}\n />\n }\n footer={\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">{scopeLabel}</p>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.deals.list.kpi.fromLastQuarter', { value: data.winRate.previousValue })}\n </p>\n <div className=\"text-primary\">\n <Sparkline\n values={data.winRate.series.map((point) => point.rate)}\n ariaLabel={t('customers.deals.list.kpi.winRate')}\n />\n </div>\n </div>\n }\n />\n </div>\n </div>\n )\n}\n\nexport default DealsKpiStrip\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
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 } 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";
@@ -54,6 +54,7 @@ function computeDealTrend(deals) {
54
54
  }
55
55
  function CompanyKpiBar({ data }) {
56
56
  const t = useT();
57
+ const locale = useLocale();
57
58
  const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals]);
58
59
  const activeDealsValue = React.useMemo(
59
60
  () => data.kpis?.activeDealsValue ?? sumActiveDeals(data.deals),
@@ -103,7 +104,7 @@ function CompanyKpiBar({ data }) {
103
104
  title: t("customers.companies.dashboard.kpi.activeDeals", "ACTIVE DEALS"),
104
105
  value: activeDealsValue,
105
106
  trend: dealTrend,
106
- formatValue: (v) => formatCurrency(v, dealCurrency),
107
+ formatValue: (v) => formatCurrency(v, dealCurrency, locale),
107
108
  comparisonLabel: `${data.kpis?.activeDealsCount ?? activeDeals.length} ${(data.kpis?.activeDealsCount ?? activeDeals.length) === 1 ? "pipeline" : "pipelines"}`
108
109
  },
109
110
  {
@@ -117,7 +118,7 @@ function CompanyKpiBar({ data }) {
117
118
  id: "ltv",
118
119
  title: t("customers.companies.dashboard.kpi.ltv", "CUSTOMER VALUE (LTV)"),
119
120
  value: ltvValue,
120
- formatValue: ltvValue !== null ? (v) => formatCurrency(v, dealCurrency) : void 0,
121
+ formatValue: ltvValue !== null ? (v) => formatCurrency(v, dealCurrency, locale) : void 0,
121
122
  comparisonLabel: ltvValue !== null ? t("customers.companies.dashboard.kpi.wonDeals", "won deals total") : t("customers.companies.dashboard.kpi.noWonDeals", "No won deals")
122
123
  },
123
124
  {
@@ -127,7 +128,7 @@ function CompanyKpiBar({ data }) {
127
128
  formatValue: clientTenureYears !== null ? (v) => v < 1 ? `< 1 ${t("customers.companies.dashboard.kpi.year", "year")}` : `${v} ${v === 1 ? t("customers.companies.dashboard.kpi.year", "year") : t("customers.companies.dashboard.kpi.years", "years")}` : void 0,
128
129
  comparisonLabel: clientTenureYears !== null ? `${data.kpis?.completedDealsCount ?? data.deals.filter((d) => isWonDealStatus(d.status)).length} ${t("customers.companies.dashboard.kpi.completedDeals", "completed deals")}` : t("customers.companies.dashboard.kpi.noInteractions", "No interactions yet")
129
130
  }
130
- ], [t, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis]);
131
+ ], [t, locale, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis]);
131
132
  const visibleTiles = kpiTiles.filter((tile) => !hiddenTiles.has(tile.id));
132
133
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
133
134
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4", children: visibleTiles.map((tile) => /* @__PURE__ */ jsxs("div", { className: "group relative", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/CompanyKpiBar.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { EyeOff } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts/KpiCard'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n readVersionedIdSet,\n writeVersionedIdSet,\n clearVersionedPreference,\n} from '@open-mercato/shared/lib/browser/versionedPreference'\nimport { isOpenDealStatus, isWonDealStatus } from '../../lib/dealStatus'\nimport type { CompanyOverview, DealSummary, InteractionSummary } from '../formConfig'\nimport { formatCurrency } from './utils'\n\nconst STORAGE_KEY = 'om:company-detail-kpi-hidden'\nconst STORAGE_VERSION = 1\n\nfunction sumActiveDeals(deals: DealSummary[]): number {\n return deals\n .filter((d) => isOpenDealStatus(d.status))\n .reduce((sum, d) => {\n const amount = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))\n return sum + (Number.isFinite(amount) ? amount : 0)\n }, 0)\n}\n\nfunction getActiveDeals(deals: DealSummary[]): DealSummary[] {\n return deals.filter((d) => isOpenDealStatus(d.status))\n}\n\nfunction computeActivityTrend(interactions: InteractionSummary[]): KpiTrend | undefined {\n const now = Date.now()\n const weekMs = 7 * 86_400_000\n const thisWeek = interactions.filter((i) => {\n const d = i.occurredAt ?? i.scheduledAt\n return d && now - new Date(d).getTime() < weekMs\n }).length\n const lastWeek = interactions.filter((i) => {\n const d = i.occurredAt ?? i.scheduledAt\n if (!d) return false\n const diff = now - new Date(d).getTime()\n return diff >= weekMs && diff < weekMs * 2\n }).length\n if (lastWeek === 0 && thisWeek === 0) return undefined\n if (lastWeek === 0) return { value: 100, direction: 'up' }\n const pct = ((thisWeek - lastWeek) / lastWeek) * 100\n if (Math.abs(pct) < 0.5) return { value: 0, direction: 'unchanged' }\n return { value: Math.abs(pct), direction: pct > 0 ? 'up' : 'down' }\n}\n\nfunction computeDealTrend(deals: DealSummary[]): KpiTrend | undefined {\n const active = deals.filter((d) => isOpenDealStatus(d.status))\n if (active.length === 0) return undefined\n const now = Date.now()\n const monthMs = 30 * 86_400_000\n const recentDeals = active.filter((d) => d.createdAt && now - new Date(d.createdAt).getTime() < monthMs).length\n if (recentDeals > 0) return { value: recentDeals * 10, direction: 'up' }\n return { value: 0, direction: 'unchanged' }\n}\n\ntype CompanyKpiBarProps = {\n data: CompanyOverview\n}\n\nexport function CompanyKpiBar({ data }: CompanyKpiBarProps) {\n const t = useT()\n\n const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals])\n const activeDealsValue = React.useMemo(\n () => data.kpis?.activeDealsValue ?? sumActiveDeals(data.deals),\n [data.deals, data.kpis?.activeDealsValue],\n )\n const dealCurrency = data.kpis?.dealCurrency ?? activeDeals[0]?.valueCurrency ?? data.deals[0]?.valueCurrency ?? 'PLN'\n const activityTrend = React.useMemo(\n () => data.kpis?.activityTrend ?? computeActivityTrend(data.interactions),\n [data.interactions, data.kpis?.activityTrend],\n )\n const dealTrend = React.useMemo(() => computeDealTrend(data.deals), [data.deals])\n\n const ltvValue = React.useMemo(() => {\n if (data.kpis?.ltvValue !== undefined) return data.kpis.ltvValue\n const wonDeals = data.deals.filter((d) => isWonDealStatus(d.status))\n if (wonDeals.length === 0) return null\n return wonDeals.reduce((sum, d) => {\n const amt = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))\n return sum + (Number.isFinite(amt) ? amt : 0)\n }, 0)\n }, [data.deals, data.kpis?.ltvValue])\n\n const clientTenureYears = React.useMemo(() => {\n if (data.kpis?.clientTenureYears !== undefined) return data.kpis.clientTenureYears\n const allDates = data.interactions\n .map((i) => i.occurredAt ?? i.scheduledAt ?? i.createdAt)\n .filter(Boolean)\n .map((d) => new Date(d).getTime())\n if (allDates.length === 0) return null\n const earliest = Math.min(...allDates)\n return Math.floor((Date.now() - earliest) / (365.25 * 86_400_000))\n }, [data.interactions, data.kpis?.clientTenureYears])\n\n const [hiddenTiles, setHiddenTiles] = React.useState<Set<string>>(\n () => readVersionedIdSet(STORAGE_KEY, STORAGE_VERSION),\n )\n\n const toggleTile = React.useCallback((tileId: string) => {\n setHiddenTiles((prev) => {\n const next = new Set(prev)\n if (next.has(tileId)) next.delete(tileId)\n else next.add(tileId)\n writeVersionedIdSet(STORAGE_KEY, STORAGE_VERSION, next)\n return next\n })\n }, [])\n\n const showAllTiles = React.useCallback(() => {\n setHiddenTiles(new Set())\n clearVersionedPreference(STORAGE_KEY)\n }, [])\n\n const kpiTiles = React.useMemo(() => [\n {\n id: 'activeDeals',\n title: t('customers.companies.dashboard.kpi.activeDeals', 'ACTIVE DEALS'),\n value: activeDealsValue,\n trend: dealTrend,\n formatValue: (v: number) => formatCurrency(v, dealCurrency),\n comparisonLabel: `${data.kpis?.activeDealsCount ?? activeDeals.length} ${(data.kpis?.activeDealsCount ?? activeDeals.length) === 1 ? 'pipeline' : 'pipelines'}`,\n },\n {\n id: 'activities',\n title: t('customers.companies.dashboard.kpi.activities', 'ACTIVITIES'),\n value: data.kpis?.activityCount ?? data.interactions.length,\n trend: activityTrend,\n comparisonLabel: t('customers.companies.dashboard.kpi.last12months', 'last 12 months'),\n },\n {\n id: 'ltv',\n title: t('customers.companies.dashboard.kpi.ltv', 'CUSTOMER VALUE (LTV)'),\n value: ltvValue,\n formatValue: ltvValue !== null ? (v: number) => formatCurrency(v, dealCurrency) : undefined,\n comparisonLabel: ltvValue !== null\n ? t('customers.companies.dashboard.kpi.wonDeals', 'won deals total')\n : t('customers.companies.dashboard.kpi.noWonDeals', 'No won deals'),\n },\n {\n id: 'clientSince',\n title: t('customers.companies.dashboard.kpi.clientSince', 'CLIENT SINCE'),\n value: clientTenureYears,\n formatValue: clientTenureYears !== null\n ? (v: number) => v < 1\n ? `< 1 ${t('customers.companies.dashboard.kpi.year', 'year')}`\n : `${v} ${v === 1 ? t('customers.companies.dashboard.kpi.year', 'year') : t('customers.companies.dashboard.kpi.years', 'years')}`\n : undefined,\n comparisonLabel: clientTenureYears !== null\n ? `${data.kpis?.completedDealsCount ?? data.deals.filter((d) => isWonDealStatus(d.status)).length} ${t('customers.companies.dashboard.kpi.completedDeals', 'completed deals')}`\n : t('customers.companies.dashboard.kpi.noInteractions', 'No interactions yet'),\n },\n ], [t, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis])\n\n const visibleTiles = kpiTiles.filter((tile) => !hiddenTiles.has(tile.id))\n\n return (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {visibleTiles.map((tile) => (\n <div key={tile.id} className=\"group relative\">\n <KpiCard\n title={tile.title}\n value={tile.value}\n trend={tile.trend}\n formatValue={tile.formatValue}\n comparisonLabel={tile.comparisonLabel}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => toggleTile(tile.id)}\n className=\"absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-60\"\n aria-label={t('customers.companies.dashboard.hideTile', 'Hide tile')}\n >\n <EyeOff className=\"size-3.5\" />\n </IconButton>\n </div>\n ))}\n </div>\n {hiddenTiles.size > 0 && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {t('customers.companies.dashboard.hiddenTiles', '{{count}} tiles hidden', { count: hiddenTiles.size })}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-1.5 py-0.5 text-xs hover:bg-transparent\"\n onClick={showAllTiles}\n >\n {t('customers.companies.dashboard.showAll', 'Show all')}\n </Button>\n </div>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { EyeOff } from 'lucide-react'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts/KpiCard'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n readVersionedIdSet,\n writeVersionedIdSet,\n clearVersionedPreference,\n} from '@open-mercato/shared/lib/browser/versionedPreference'\nimport { isOpenDealStatus, isWonDealStatus } from '../../lib/dealStatus'\nimport type { CompanyOverview, DealSummary, InteractionSummary } from '../formConfig'\nimport { formatCurrency } from './utils'\n\nconst STORAGE_KEY = 'om:company-detail-kpi-hidden'\nconst STORAGE_VERSION = 1\n\nfunction sumActiveDeals(deals: DealSummary[]): number {\n return deals\n .filter((d) => isOpenDealStatus(d.status))\n .reduce((sum, d) => {\n const amount = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))\n return sum + (Number.isFinite(amount) ? amount : 0)\n }, 0)\n}\n\nfunction getActiveDeals(deals: DealSummary[]): DealSummary[] {\n return deals.filter((d) => isOpenDealStatus(d.status))\n}\n\nfunction computeActivityTrend(interactions: InteractionSummary[]): KpiTrend | undefined {\n const now = Date.now()\n const weekMs = 7 * 86_400_000\n const thisWeek = interactions.filter((i) => {\n const d = i.occurredAt ?? i.scheduledAt\n return d && now - new Date(d).getTime() < weekMs\n }).length\n const lastWeek = interactions.filter((i) => {\n const d = i.occurredAt ?? i.scheduledAt\n if (!d) return false\n const diff = now - new Date(d).getTime()\n return diff >= weekMs && diff < weekMs * 2\n }).length\n if (lastWeek === 0 && thisWeek === 0) return undefined\n if (lastWeek === 0) return { value: 100, direction: 'up' }\n const pct = ((thisWeek - lastWeek) / lastWeek) * 100\n if (Math.abs(pct) < 0.5) return { value: 0, direction: 'unchanged' }\n return { value: Math.abs(pct), direction: pct > 0 ? 'up' : 'down' }\n}\n\nfunction computeDealTrend(deals: DealSummary[]): KpiTrend | undefined {\n const active = deals.filter((d) => isOpenDealStatus(d.status))\n if (active.length === 0) return undefined\n const now = Date.now()\n const monthMs = 30 * 86_400_000\n const recentDeals = active.filter((d) => d.createdAt && now - new Date(d.createdAt).getTime() < monthMs).length\n if (recentDeals > 0) return { value: recentDeals * 10, direction: 'up' }\n return { value: 0, direction: 'unchanged' }\n}\n\ntype CompanyKpiBarProps = {\n data: CompanyOverview\n}\n\nexport function CompanyKpiBar({ data }: CompanyKpiBarProps) {\n const t = useT()\n const locale = useLocale()\n\n const activeDeals = React.useMemo(() => getActiveDeals(data.deals), [data.deals])\n const activeDealsValue = React.useMemo(\n () => data.kpis?.activeDealsValue ?? sumActiveDeals(data.deals),\n [data.deals, data.kpis?.activeDealsValue],\n )\n const dealCurrency = data.kpis?.dealCurrency ?? activeDeals[0]?.valueCurrency ?? data.deals[0]?.valueCurrency ?? 'PLN'\n const activityTrend = React.useMemo(\n () => data.kpis?.activityTrend ?? computeActivityTrend(data.interactions),\n [data.interactions, data.kpis?.activityTrend],\n )\n const dealTrend = React.useMemo(() => computeDealTrend(data.deals), [data.deals])\n\n const ltvValue = React.useMemo(() => {\n if (data.kpis?.ltvValue !== undefined) return data.kpis.ltvValue\n const wonDeals = data.deals.filter((d) => isWonDealStatus(d.status))\n if (wonDeals.length === 0) return null\n return wonDeals.reduce((sum, d) => {\n const amt = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))\n return sum + (Number.isFinite(amt) ? amt : 0)\n }, 0)\n }, [data.deals, data.kpis?.ltvValue])\n\n const clientTenureYears = React.useMemo(() => {\n if (data.kpis?.clientTenureYears !== undefined) return data.kpis.clientTenureYears\n const allDates = data.interactions\n .map((i) => i.occurredAt ?? i.scheduledAt ?? i.createdAt)\n .filter(Boolean)\n .map((d) => new Date(d).getTime())\n if (allDates.length === 0) return null\n const earliest = Math.min(...allDates)\n return Math.floor((Date.now() - earliest) / (365.25 * 86_400_000))\n }, [data.interactions, data.kpis?.clientTenureYears])\n\n const [hiddenTiles, setHiddenTiles] = React.useState<Set<string>>(\n () => readVersionedIdSet(STORAGE_KEY, STORAGE_VERSION),\n )\n\n const toggleTile = React.useCallback((tileId: string) => {\n setHiddenTiles((prev) => {\n const next = new Set(prev)\n if (next.has(tileId)) next.delete(tileId)\n else next.add(tileId)\n writeVersionedIdSet(STORAGE_KEY, STORAGE_VERSION, next)\n return next\n })\n }, [])\n\n const showAllTiles = React.useCallback(() => {\n setHiddenTiles(new Set())\n clearVersionedPreference(STORAGE_KEY)\n }, [])\n\n const kpiTiles = React.useMemo(() => [\n {\n id: 'activeDeals',\n title: t('customers.companies.dashboard.kpi.activeDeals', 'ACTIVE DEALS'),\n value: activeDealsValue,\n trend: dealTrend,\n formatValue: (v: number) => formatCurrency(v, dealCurrency, locale),\n comparisonLabel: `${data.kpis?.activeDealsCount ?? activeDeals.length} ${(data.kpis?.activeDealsCount ?? activeDeals.length) === 1 ? 'pipeline' : 'pipelines'}`,\n },\n {\n id: 'activities',\n title: t('customers.companies.dashboard.kpi.activities', 'ACTIVITIES'),\n value: data.kpis?.activityCount ?? data.interactions.length,\n trend: activityTrend,\n comparisonLabel: t('customers.companies.dashboard.kpi.last12months', 'last 12 months'),\n },\n {\n id: 'ltv',\n title: t('customers.companies.dashboard.kpi.ltv', 'CUSTOMER VALUE (LTV)'),\n value: ltvValue,\n formatValue: ltvValue !== null ? (v: number) => formatCurrency(v, dealCurrency, locale) : undefined,\n comparisonLabel: ltvValue !== null\n ? t('customers.companies.dashboard.kpi.wonDeals', 'won deals total')\n : t('customers.companies.dashboard.kpi.noWonDeals', 'No won deals'),\n },\n {\n id: 'clientSince',\n title: t('customers.companies.dashboard.kpi.clientSince', 'CLIENT SINCE'),\n value: clientTenureYears,\n formatValue: clientTenureYears !== null\n ? (v: number) => v < 1\n ? `< 1 ${t('customers.companies.dashboard.kpi.year', 'year')}`\n : `${v} ${v === 1 ? t('customers.companies.dashboard.kpi.year', 'year') : t('customers.companies.dashboard.kpi.years', 'years')}`\n : undefined,\n comparisonLabel: clientTenureYears !== null\n ? `${data.kpis?.completedDealsCount ?? data.deals.filter((d) => isWonDealStatus(d.status)).length} ${t('customers.companies.dashboard.kpi.completedDeals', 'completed deals')}`\n : t('customers.companies.dashboard.kpi.noInteractions', 'No interactions yet'),\n },\n ], [t, locale, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis])\n\n const visibleTiles = kpiTiles.filter((tile) => !hiddenTiles.has(tile.id))\n\n return (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {visibleTiles.map((tile) => (\n <div key={tile.id} className=\"group relative\">\n <KpiCard\n title={tile.title}\n value={tile.value}\n trend={tile.trend}\n formatValue={tile.formatValue}\n comparisonLabel={tile.comparisonLabel}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => toggleTile(tile.id)}\n className=\"absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-60\"\n aria-label={t('customers.companies.dashboard.hideTile', 'Hide tile')}\n >\n <EyeOff className=\"size-3.5\" />\n </IconButton>\n </div>\n ))}\n </div>\n {hiddenTiles.size > 0 && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {t('customers.companies.dashboard.hiddenTiles', '{{count}} tiles hidden', { count: hiddenTiles.size })}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-1.5 py-0.5 text-xs hover:bg-transparent\"\n onClick={showAllTiles}\n >\n {t('customers.companies.dashboard.showAll', 'Show all')}\n </Button>\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -12,7 +12,7 @@ import { LinkEntityDialog } from "../linking/LinkEntityDialog.js";
12
12
  import { createDealLinkAdapter } from "../linking/adapters/dealAdapter.js";
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.js";
@@ -190,9 +190,9 @@ function normalizeDeal(deal) {
190
190
  customFields
191
191
  };
192
192
  }
193
- function formatValueLabel(amount, currency, emptyLabel) {
193
+ function formatValueLabel(amount, currency, emptyLabel, locale) {
194
194
  if (typeof amount === "number") {
195
- const formatter = new Intl.NumberFormat(void 0, {
195
+ const formatter = new Intl.NumberFormat(locale, {
196
196
  style: currency ? "currency" : "decimal",
197
197
  currency: currency ?? void 0,
198
198
  maximumFractionDigits: 2
@@ -218,6 +218,7 @@ function DealsSection({
218
218
  runGuardedMutation
219
219
  }) {
220
220
  const tHook = useT();
221
+ const locale = useLocale();
221
222
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
222
223
  const fallbackTranslator = React.useMemo(() => createTranslatorWithFallback(tHook), [tHook]);
223
224
  const t = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator]);
@@ -721,8 +722,8 @@ function DealsSection({
721
722
  ) : null,
722
723
  /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
723
724
  sortedDeals.map((deal) => {
724
- const valueLabel = formatValueLabel(deal.valueAmount, deal.valueCurrency ?? null, emptyLabel);
725
- const expectedLabel = deal.expectedCloseAt ? formatDate(deal.expectedCloseAt) ?? emptyLabel : emptyLabel;
725
+ const valueLabel = formatValueLabel(deal.valueAmount, deal.valueCurrency ?? null, emptyLabel, locale);
726
+ const expectedLabel = deal.expectedCloseAt ? formatDate(deal.expectedCloseAt, locale) ?? emptyLabel : emptyLabel;
726
727
  const probabilityLabel = typeof deal.probability === "number" ? `${deal.probability}%` : emptyLabel;
727
728
  const isUnlinkPending = pendingAction?.kind === "remove" && pendingAction.id === deal.id;
728
729
  const statusLabel = deal.status && statusDictionaryMap ? statusDictionaryMap[deal.status]?.label ?? deal.status : deal.status ?? emptyLabel;