@open-mercato/core 0.6.1-develop.3287.1.450f4ffb56 → 0.6.1-develop.3306.1.9ad9ff2526

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 (122) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +14 -0
  3. package/dist/modules/auth/backend/roles/[id]/edit/page.js +2 -1
  4. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -5
  6. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  7. package/dist/modules/catalog/ai-agents.js +47 -5
  8. package/dist/modules/catalog/ai-agents.js.map +2 -2
  9. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -5
  10. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  11. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +4 -3
  12. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +2 -9
  14. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +2 -9
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  17. package/dist/modules/customers/ai-agents.js +174 -1
  18. package/dist/modules/customers/ai-agents.js.map +2 -2
  19. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +196 -0
  20. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +7 -0
  21. package/dist/modules/customers/ai-tools.js +3 -1
  22. package/dist/modules/customers/ai-tools.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/companies/page.js +11 -1
  24. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  25. package/dist/modules/customers/backend/customers/deals/page.js +11 -1
  26. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  27. package/dist/modules/customers/backend/customers/people/page.js +11 -1
  28. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  29. package/dist/modules/customers/commands/companies.js +37 -15
  30. package/dist/modules/customers/commands/companies.js.map +2 -2
  31. package/dist/modules/customers/components/detail/AiActionChips.js +2 -2
  32. package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
  33. package/dist/modules/customers/components/detail/ChangelogTab.js +3 -9
  34. package/dist/modules/customers/components/detail/ChangelogTab.js.map +2 -2
  35. package/dist/modules/customers/components/detail/changelogValues.js +35 -0
  36. package/dist/modules/customers/components/detail/changelogValues.js.map +7 -0
  37. package/dist/modules/customers/components/detail/customFieldUtils.js +1 -1
  38. package/dist/modules/customers/components/detail/customFieldUtils.js.map +1 -1
  39. package/dist/modules/customers/components/formConfig.js +3 -2
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +4 -9
  42. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  43. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js +302 -0
  44. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map +7 -0
  45. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js +17 -0
  46. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js.map +7 -0
  47. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +2 -2
  48. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +2 -2
  49. package/dist/modules/customers/widgets/injection-table.js +2 -2
  50. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  51. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -5
  52. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  53. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +2 -5
  54. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  55. package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -5
  56. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  57. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +2 -25
  58. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  59. package/dist/modules/sales/components/channels/ChannelOfferForm.js +2 -43
  60. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  61. package/dist/modules/sales/components/documents/LineItemDialog.js +162 -42
  62. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  63. package/dist/modules/sales/components/documents/customFieldHelpers.js +4 -31
  64. package/dist/modules/sales/components/documents/customFieldHelpers.js.map +2 -2
  65. package/package.json +15 -6
  66. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +2 -1
  67. package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -5
  68. package/src/modules/business_rules/migrations/.snapshot-open-mercato.json +746 -394
  69. package/src/modules/catalog/ai-agents.ts +62 -4
  70. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +2 -5
  71. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
  72. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2 -12
  73. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -10
  74. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +2519 -1307
  75. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +828 -834
  76. package/src/modules/customers/AGENTS.md +6 -1
  77. package/src/modules/customers/ai-agents.ts +213 -6
  78. package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +286 -0
  79. package/src/modules/customers/ai-tools.ts +2 -0
  80. package/src/modules/customers/backend/customers/companies/page.tsx +10 -0
  81. package/src/modules/customers/backend/customers/deals/page.tsx +10 -0
  82. package/src/modules/customers/backend/customers/people/page.tsx +10 -0
  83. package/src/modules/customers/commands/companies.ts +38 -14
  84. package/src/modules/customers/components/detail/AiActionChips.tsx +2 -2
  85. package/src/modules/customers/components/detail/ChangelogTab.tsx +3 -10
  86. package/src/modules/customers/components/detail/changelogValues.ts +42 -0
  87. package/src/modules/customers/components/detail/customFieldUtils.ts +1 -1
  88. package/src/modules/customers/components/formConfig.tsx +3 -2
  89. package/src/modules/customers/i18n/de.json +7 -0
  90. package/src/modules/customers/i18n/en.json +7 -0
  91. package/src/modules/customers/i18n/es.json +7 -0
  92. package/src/modules/customers/i18n/pl.json +7 -0
  93. package/src/modules/customers/migrations/.snapshot-open-mercato.json +2313 -2336
  94. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -6
  95. package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx +358 -0
  96. package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.ts +34 -0
  97. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +2 -2
  98. package/src/modules/customers/widgets/injection-table.ts +12 -9
  99. package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +554 -210
  100. package/src/modules/dictionaries/migrations/.snapshot-open-mercato.json +146 -146
  101. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +2 -5
  102. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +2 -5
  103. package/src/modules/directory/migrations/.snapshot-open-mercato.json +273 -140
  104. package/src/modules/feature_toggles/migrations/.snapshot-open-mercato.json +242 -137
  105. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +498 -195
  106. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +158 -157
  107. package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +274 -129
  108. package/src/modules/planner/migrations/.snapshot-open-mercato.json +327 -157
  109. package/src/modules/progress/AGENTS.md +72 -0
  110. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -5
  111. package/src/modules/resources/migrations/.snapshot-open-mercato.json +920 -470
  112. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +2 -27
  113. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +2 -49
  114. package/src/modules/sales/components/documents/LineItemDialog.tsx +214 -78
  115. package/src/modules/sales/components/documents/customFieldHelpers.ts +4 -43
  116. package/src/modules/sales/i18n/de.json +3 -0
  117. package/src/modules/sales/i18n/en.json +3 -0
  118. package/src/modules/sales/i18n/es.json +3 -0
  119. package/src/modules/sales/i18n/pl.json +3 -0
  120. package/src/modules/sales/migrations/.snapshot-open-mercato.json +4740 -4778
  121. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +340 -81
  122. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1439 -729
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ChangelogTab.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ArrowLeft, ArrowRight, ArrowUpDown, ChevronDown, ChevronUp, Clock3 } from 'lucide-react'\nimport { extractChangeRows } from '@open-mercato/core/modules/audit_logs/lib/changeRows'\nimport type { ActionLogProjectionType, ActionLogSourceKey } from '@open-mercato/core/modules/audit_logs/lib/projections'\nimport { deriveActionLogActionType, deriveActionLogSource } from '@open-mercato/core/modules/audit_logs/lib/projections'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { ErrorMessage, LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { ChangelogFilters } from './ChangelogFilters'\nimport { ChangelogKpiCards } from './ChangelogKpiCards'\nimport { ChangelogEntryRow } from './ChangelogEntryRow'\n\ntype AuditAction = {\n id: string\n commandId: string\n actionLabel: string | null\n executionState: 'done' | 'undone' | 'failed' | 'redone'\n actorUserId: string | null\n actorUserName: string | null\n resourceKind: string | null\n resourceId: string | null\n snapshotBefore: unknown | null\n snapshotAfter: unknown | null\n changes: Record<string, unknown> | null\n context: Record<string, unknown> | null\n createdAt: string\n updatedAt: string\n}\n\ntype AuditActionsResponse = {\n items: AuditAction[]\n total?: number\n}\n\ntype ChangelogActionType = ActionLogProjectionType\ntype ChangelogSource = ActionLogSourceKey\n\ntype ChangelogEntry = {\n id: string\n createdAt: string\n actorUserId: string | null\n actorUserName: string | null\n actionType: ChangelogActionType\n source: ChangelogSource\n description: string | null\n changes: Array<{\n fieldName: string\n oldValue: string\n newValue: string\n }>\n}\n\ntype ChangelogTabProps = {\n entityId: string\n entityType: 'company' | 'person' | 'deal'\n}\n\ntype FilterState = {\n fieldNames: string[]\n actorUserIds: string[]\n actionTypes: string[]\n dateRange: '7d' | '30d' | '90d'\n}\n\ntype FilterOption = {\n value: string\n label: string\n}\n\ntype SortField = 'createdAt' | 'user' | 'action' | 'field' | 'source'\n\ntype SortState = {\n field: SortField\n dir: 'asc' | 'desc'\n}\n\nconst PAGE_SIZE = 50\nconst ACTION_OPTIONS: Array<{ value: ChangelogActionType; key: string; fallback: string }> = [\n { value: 'create', key: 'customers.changelog.actions.create', fallback: 'Create' },\n { value: 'edit', key: 'customers.changelog.actions.edit', fallback: 'Edit' },\n { value: 'delete', key: 'customers.changelog.actions.delete', fallback: 'Delete' },\n { value: 'assign', key: 'customers.changelog.actions.assign', fallback: 'Assign' },\n]\nconst CRITICAL_FIELDS = ['status', 'lifecycleStage', 'ownerUserId', 'temperature', 'source', 'renewalQuarter'] as const\nconst HEADER_COLUMNS: Array<{ field: SortField; key: string; fallback: string; align?: 'left' | 'right' }> = [\n { field: 'createdAt', key: 'customers.changelog.col.when', fallback: 'When' },\n { field: 'user', key: 'customers.changelog.col.user', fallback: 'User' },\n { field: 'action', key: 'customers.changelog.col.action', fallback: 'Action' },\n { field: 'field', key: 'customers.changelog.col.change', fallback: 'What changed' },\n { field: 'source', key: 'customers.changelog.col.source', fallback: 'Source', align: 'right' },\n]\n\nfunction formatValue(value: unknown): string {\n if (value == null) return ''\n if (value instanceof Date) return value.toISOString()\n if (Array.isArray(value)) return value.map((entry) => formatValue(entry)).filter(Boolean).join(', ')\n if (typeof value === 'object') return JSON.stringify(value)\n return String(value)\n}\n\nfunction formatFieldLabel(fieldName: string): string {\n return fieldName\n .replace(/\\./g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^\\w/, (char) => char.toUpperCase())\n}\n\nfunction mergeFilterOptions(...groups: FilterOption[][]): FilterOption[] {\n const options = new Map<string, FilterOption>()\n groups.flat().forEach((option) => {\n options.set(option.value, option)\n })\n return Array.from(options.values()).sort((left, right) =>\n left.label.localeCompare(right.label, undefined, { sensitivity: 'base' }),\n )\n}\n\nfunction mapAuditActionToEntry(action: AuditAction): ChangelogEntry {\n const changes = extractChangeRows(action.changes, action.snapshotBefore).map((change) => ({\n fieldName: change.field,\n oldValue: formatValue(change.from),\n newValue: formatValue(change.to),\n }))\n\n return {\n id: action.id,\n createdAt: action.createdAt,\n actorUserId: action.actorUserId,\n actorUserName: action.actorUserName,\n actionType: deriveActionLogActionType(action),\n source: deriveActionLogSource(action.context, action.actorUserId),\n description: action.actionLabel,\n changes,\n }\n}\n\nfunction startOfDay(daysAgo = 0): Date {\n const date = new Date()\n date.setHours(0, 0, 0, 0)\n if (daysAgo > 0) date.setDate(date.getDate() - daysAgo)\n return date\n}\n\nfunction computeRangeStart(range: FilterState['dateRange']): Date {\n const days = Number.parseInt(range.replace('d', ''), 10)\n const date = new Date()\n date.setHours(0, 0, 0, 0)\n date.setDate(date.getDate() - (Number.isFinite(days) ? days : 90))\n return date\n}\n\nfunction buildQueryUrl({\n entityId,\n entityType,\n dateRange,\n filters,\n limit,\n offset,\n sorting,\n includeTotal,\n before,\n after,\n}: {\n entityId: string\n entityType: 'company' | 'person' | 'deal'\n dateRange: FilterState['dateRange']\n filters: Pick<FilterState, 'fieldNames' | 'actorUserIds' | 'actionTypes'>\n limit: number\n offset?: number\n sorting?: SortState\n includeTotal?: boolean\n before?: string\n after?: string\n}) {\n const params = new URLSearchParams({\n resourceKind: `customers.${entityType}`,\n resourceId: entityId,\n includeRelated: 'true',\n limit: String(limit),\n offset: String(offset ?? 0),\n sortField: sorting?.field ?? 'createdAt',\n sortDir: sorting?.dir ?? 'desc',\n after: after ?? computeRangeStart(dateRange).toISOString(),\n })\n if (includeTotal) params.set('includeTotal', 'true')\n if (before) params.set('before', before)\n if (filters.fieldNames.length > 0) params.set('fieldName', filters.fieldNames.join(','))\n if (filters.actorUserIds.length > 0) params.set('actorUserId', filters.actorUserIds.join(','))\n if (filters.actionTypes.length > 0) params.set('actionType', filters.actionTypes.join(','))\n return `/api/audit_logs/audit-logs/actions?${params.toString()}`\n}\n\nfunction buildExportUrl({\n entityId,\n entityType,\n filters,\n dateRange,\n sorting,\n}: {\n entityId: string\n entityType: 'company' | 'person' | 'deal'\n filters: Pick<FilterState, 'fieldNames' | 'actorUserIds' | 'actionTypes'>\n dateRange: FilterState['dateRange']\n sorting: SortState\n}) {\n const params = new URLSearchParams({\n resourceKind: `customers.${entityType}`,\n resourceId: entityId,\n includeRelated: 'true',\n limit: '1000',\n sortField: sorting.field,\n sortDir: sorting.dir,\n after: computeRangeStart(dateRange).toISOString(),\n })\n if (filters.fieldNames.length > 0) params.set('fieldName', filters.fieldNames.join(','))\n if (filters.actorUserIds.length > 0) params.set('actorUserId', filters.actorUserIds.join(','))\n if (filters.actionTypes.length > 0) params.set('actionType', filters.actionTypes.join(','))\n return `/api/audit_logs/audit-logs/actions/export?${params.toString()}`\n}\n\nfunction groupEntriesByDay(entries: ChangelogEntry[], t: ReturnType<typeof useT>) {\n const groups = new Map<string, ChangelogEntry[]>()\n entries.forEach((entry) => {\n const date = new Date(entry.createdAt)\n const key = Number.isNaN(date.getTime()) ? entry.createdAt : date.toDateString()\n const bucket = groups.get(key) ?? []\n bucket.push(entry)\n groups.set(key, bucket)\n })\n\n const today = new Date().toDateString()\n const yesterdayDate = new Date()\n yesterdayDate.setDate(yesterdayDate.getDate() - 1)\n const yesterday = yesterdayDate.toDateString()\n\n return Array.from(groups.entries()).map(([key, items]) => ({\n key,\n label:\n key === today\n ? t('customers.changelog.groupLabel.today', 'TODAY')\n : key === yesterday\n ? t('customers.changelog.groupLabel.yesterday', 'YESTERDAY')\n : new Date(key).toLocaleDateString(undefined, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n }).toUpperCase(),\n items,\n }))\n}\n\nfunction SortIcon({ active, dir }: { active: boolean; dir: 'asc' | 'desc' }) {\n if (!active) return <ArrowUpDown className=\"size-3 text-muted-foreground\" />\n return dir === 'asc'\n ? <ChevronUp className=\"size-3 text-foreground\" />\n : <ChevronDown className=\"size-3 text-foreground\" />\n}\n\nexport function ChangelogTab({ entityId, entityType }: ChangelogTabProps) {\n const t = useT()\n const [filters, setFilters] = React.useState<FilterState>({\n fieldNames: [],\n actorUserIds: [],\n actionTypes: [],\n dateRange: '90d',\n })\n const [entries, setEntries] = React.useState<ChangelogEntry[]>([])\n const [totalCount, setTotalCount] = React.useState(0)\n const [todayCount, setTodayCount] = React.useState(0)\n const [yesterdayCount, setYesterdayCount] = React.useState(0)\n const [fieldOptions, setFieldOptions] = React.useState<FilterOption[]>([])\n const [userOptions, setUserOptions] = React.useState<FilterOption[]>([])\n const [actionOptions, setActionOptions] = React.useState<FilterOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [exporting, setExporting] = React.useState(false)\n const [pageIndex, setPageIndex] = React.useState(0)\n const [sorting, setSorting] = React.useState<SortState>({\n field: 'createdAt',\n dir: 'desc',\n })\n\n const days = React.useMemo(() => Number.parseInt(filters.dateRange.replace('d', ''), 10) || 90, [filters.dateRange])\n\n const loadTab = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const todayStart = startOfDay(0).toISOString()\n const yesterdayStart = startOfDay(1).toISOString()\n\n const [mainResponse, todayResponse, yesterdayResponse] = await Promise.all([\n readApiResultOrThrow<AuditActionsResponse>(\n buildQueryUrl({\n entityId,\n entityType,\n dateRange: filters.dateRange,\n filters,\n limit: PAGE_SIZE,\n offset: pageIndex * PAGE_SIZE,\n sorting,\n includeTotal: true,\n }),\n ),\n readApiResultOrThrow<AuditActionsResponse>(\n buildQueryUrl({\n entityId,\n entityType,\n dateRange: filters.dateRange,\n filters,\n limit: 1,\n sorting,\n includeTotal: true,\n after: todayStart,\n }),\n ).catch(() => ({ items: [], total: 0 })),\n readApiResultOrThrow<AuditActionsResponse>(\n buildQueryUrl({\n entityId,\n entityType,\n dateRange: filters.dateRange,\n filters,\n limit: 1,\n sorting,\n includeTotal: true,\n after: yesterdayStart,\n before: todayStart,\n }),\n ).catch(() => ({ items: [], total: 0 })),\n ])\n\n const mappedEntries = Array.isArray(mainResponse.items) ? mainResponse.items.map(mapAuditActionToEntry) : []\n\n setEntries(mappedEntries)\n setTotalCount(typeof mainResponse.total === 'number' ? mainResponse.total : mappedEntries.length)\n setTodayCount(typeof todayResponse.total === 'number' ? todayResponse.total : 0)\n setYesterdayCount(typeof yesterdayResponse.total === 'number' ? yesterdayResponse.total : 0)\n\n const nextFieldOptions = mergeFilterOptions(\n filters.fieldNames.map((fieldName) => ({ value: fieldName, label: formatFieldLabel(fieldName) })),\n Array.from(\n new Set(\n mappedEntries.flatMap((entry) => entry.changes.map((change) => change.fieldName)),\n ),\n )\n .sort((left, right) => left.localeCompare(right))\n .map((fieldName) => ({ value: fieldName, label: formatFieldLabel(fieldName) })),\n )\n const nextActionOptions = ACTION_OPTIONS\n .filter((option) => filters.actionTypes.includes(option.value) || mappedEntries.some((entry) => entry.actionType === option.value))\n .map((option) => ({\n value: option.value,\n label: t(option.key, option.fallback),\n }))\n\n setFieldOptions(nextFieldOptions)\n setUserOptions((current) =>\n mergeFilterOptions(\n current.filter((option) => filters.actorUserIds.includes(option.value)),\n Array.from(\n new Map(\n mappedEntries\n .filter((entry) => entry.actorUserId && entry.actorUserName)\n .map((entry) => [entry.actorUserId as string, entry.actorUserName as string]),\n ).entries(),\n ).map(([value, label]) => ({ value, label })),\n ),\n )\n setActionOptions(nextActionOptions)\n } catch (loadError) {\n setEntries([])\n setTotalCount(0)\n setTodayCount(0)\n setYesterdayCount(0)\n setFieldOptions([])\n setUserOptions([])\n setActionOptions([])\n setError(t('customers.changelog.error', 'Failed to load change log'))\n } finally {\n setLoading(false)\n }\n }, [entityId, entityType, filters, pageIndex, sorting, t])\n\n React.useEffect(() => {\n void loadTab()\n }, [loadTab])\n\n const showGroupedEntries = sorting.field === 'createdAt'\n const groupedEntries = React.useMemo(() => groupEntriesByDay(entries, t), [entries, t])\n const uniqueUsers = React.useMemo(\n () => new Set(entries.map((entry) => entry.actorUserId).filter((value): value is string => Boolean(value))).size,\n [entries],\n )\n const criticalFieldCount = React.useMemo(\n () => entries.filter((entry) => entry.changes.some((change) => CRITICAL_FIELDS.includes(change.fieldName as (typeof CRITICAL_FIELDS)[number]))).length,\n [entries],\n )\n const criticalFieldLabel = React.useMemo(\n () => CRITICAL_FIELDS.map((fieldName) => formatFieldLabel(fieldName)).slice(0, 3).join(', '),\n [],\n )\n const previousPageLabel = React.useMemo(() => {\n if (sorting.field !== 'createdAt') return t('customers.changelog.previous', 'Previous')\n return sorting.dir === 'desc'\n ? t('customers.changelog.newer', 'Newer')\n : t('customers.changelog.older', 'Older')\n }, [sorting, t])\n const nextPageLabel = React.useMemo(() => {\n if (sorting.field !== 'createdAt') return t('customers.changelog.next', 'Next')\n return sorting.dir === 'desc'\n ? t('customers.changelog.older', 'Older')\n : t('customers.changelog.newer', 'Newer')\n }, [sorting, t])\n const shownCount = entries.length\n const hasOlder = (pageIndex + 1) * PAGE_SIZE < totalCount\n const hasNewer = pageIndex > 0\n\n const updateFilters = React.useCallback((partial: Partial<FilterState>) => {\n setFilters((current) => ({ ...current, ...partial }))\n setPageIndex(0)\n }, [])\n\n const handleOlder = React.useCallback(() => {\n if (!hasOlder) return\n setPageIndex((current) => current + 1)\n }, [hasOlder])\n\n const handleNewer = React.useCallback(() => {\n if (!hasNewer) return\n setPageIndex((current) => Math.max(current - 1, 0))\n }, [hasNewer])\n\n const handleSort = React.useCallback((field: SortField) => {\n setSorting((current) => {\n if (current.field === field) {\n return {\n field,\n dir: current.dir === 'desc' ? 'asc' : 'desc',\n }\n }\n\n return {\n field,\n dir: field === 'createdAt' ? 'desc' : 'asc',\n }\n })\n setPageIndex(0)\n }, [])\n\n const handleExport = React.useCallback(async () => {\n setExporting(true)\n try {\n const response = await apiCallOrThrow<string>(\n buildExportUrl({\n entityId,\n entityType,\n filters,\n dateRange: filters.dateRange,\n sorting,\n }),\n undefined,\n {\n parse: async (res) => res.text(),\n },\n )\n\n const body = response.result ?? ''\n const blob = new Blob([body], { type: 'text/csv;charset=utf-8' })\n const blobUrl = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = blobUrl\n anchor.download = 'changelog-export.csv'\n document.body.appendChild(anchor)\n anchor.click()\n anchor.remove()\n URL.revokeObjectURL(blobUrl)\n } catch (exportError) {\n console.error('customers.changelog.export failed', exportError)\n flash(t('customers.changelog.exportError', 'Failed to export change log'), 'error')\n } finally {\n setExporting(false)\n }\n }, [entityId, entityType, filters, sorting, t])\n\n return (\n <div className=\"space-y-5\">\n <ChangelogFilters\n dateRange={filters.dateRange}\n fieldNames={filters.fieldNames}\n actorUserIds={filters.actorUserIds}\n actionTypes={filters.actionTypes}\n fieldOptions={fieldOptions}\n userOptions={userOptions}\n actionOptions={actionOptions}\n exportDisabled={exporting}\n onDateRangeChange={(value) => updateFilters({ dateRange: value })}\n onFieldNamesChange={(value) => updateFilters({ fieldNames: value })}\n onActorUserIdsChange={(value) => updateFilters({ actorUserIds: value })}\n onActionTypesChange={(value) => updateFilters({ actionTypes: value })}\n onExport={handleExport}\n />\n\n <ChangelogKpiCards\n loading={loading}\n totalCount={totalCount}\n todayCount={todayCount}\n yesterdayCount={yesterdayCount}\n uniqueUsers={uniqueUsers}\n criticalFieldCount={criticalFieldCount}\n criticalFieldLabel={criticalFieldLabel}\n dateRangeDays={days}\n />\n\n <div className=\"overflow-hidden rounded-xl border bg-card\">\n <div className=\"flex items-center gap-2 border-b px-5 py-4\">\n <Clock3 className=\"size-4 text-muted-foreground\" />\n <h3 className=\"text-base font-semibold text-foreground\">\n {t('customers.changelog.title', 'Change log')}\n </h3>\n <Badge variant=\"secondary\" className=\"text-xs\">\n {totalCount}\n </Badge>\n </div>\n\n <div className=\"grid grid-cols-[92px_190px_120px_1fr_80px] gap-3 border-b bg-muted/20 px-5 py-2 text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {HEADER_COLUMNS.map((column) => {\n const isActive = sorting.field === column.field\n return (\n <Button\n key={column.field}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => handleSort(column.field)}\n className={`h-auto gap-1 rounded-none px-0 py-0 text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground hover:bg-transparent ${\n column.align === 'right' ? 'justify-end' : 'justify-start'\n }`}\n >\n <span>{t(column.key, column.fallback)}</span>\n <SortIcon active={isActive} dir={sorting.dir} />\n </Button>\n )\n })}\n </div>\n\n {loading && entries.length === 0 ? (\n <LoadingMessage label={t('customers.changelog.loading', 'Loading changes...')} className=\"min-h-[220px] justify-center\" />\n ) : error ? (\n <ErrorMessage label={error} className=\"m-5\" />\n ) : entries.length === 0 ? (\n <div className=\"p-5\">\n <TabEmptyState\n title={t('customers.changelog.emptyTitle', 'No changes in this period')}\n description={t('customers.changelog.empty', 'No changes recorded in this period.')}\n />\n </div>\n ) : (\n <div>\n {showGroupedEntries ? (\n groupedEntries.map((group) => (\n <div key={group.key}>\n <div className=\"border-b bg-muted/10 px-5 py-2 text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {group.label}\n </div>\n {group.items.map((entry) => (\n <div key={entry.id} className=\"border-b last:border-b-0\">\n <ChangelogEntryRow entry={entry} />\n </div>\n ))}\n </div>\n ))\n ) : (\n entries.map((entry) => (\n <div key={entry.id} className=\"border-b last:border-b-0\">\n <ChangelogEntryRow entry={entry} />\n </div>\n ))\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-3 border-t px-5 py-4 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between\">\n <span>\n {t('customers.changelog.showingWithPeriod', 'Showing {{shown}} of {{total}} entries \u00B7 last {{days}} days', {\n shown: shownCount,\n total: totalCount,\n days,\n })}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleNewer}\n disabled={!hasNewer || loading}\n className=\"h-8 rounded-lg px-3 text-xs\"\n >\n <ArrowLeft className=\"mr-1.5 size-3.5\" />\n {previousPageLabel}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleOlder}\n disabled={!hasOlder || loading}\n className=\"h-8 rounded-lg px-3 text-xs\"\n >\n {nextPageLabel}\n <ArrowRight className=\"ml-1.5 size-3.5\" />\n </Button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nexport default ChangelogTab\n"],
5
- "mappings": ";AAqQsB,cAsQd,YAtQc;AAnQtB,YAAY,WAAW;AACvB,SAAS,WAAW,YAAY,aAAa,aAAa,WAAW,cAAc;AACnF,SAAS,yBAAyB;AAElC,SAAS,2BAA2B,6BAA6B;AACjE,SAAS,YAAY;AACrB,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,cAAc,gBAAgB,qBAAqB;AAC5D,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAkElC,MAAM,YAAY;AAClB,MAAM,iBAAuF;AAAA,EAC3F,EAAE,OAAO,UAAU,KAAK,sCAAsC,UAAU,SAAS;AAAA,EACjF,EAAE,OAAO,QAAQ,KAAK,oCAAoC,UAAU,OAAO;AAAA,EAC3E,EAAE,OAAO,UAAU,KAAK,sCAAsC,UAAU,SAAS;AAAA,EACjF,EAAE,OAAO,UAAU,KAAK,sCAAsC,UAAU,SAAS;AACnF;AACA,MAAM,kBAAkB,CAAC,UAAU,kBAAkB,eAAe,eAAe,UAAU,gBAAgB;AAC7G,MAAM,iBAAuG;AAAA,EAC3G,EAAE,OAAO,aAAa,KAAK,gCAAgC,UAAU,OAAO;AAAA,EAC5E,EAAE,OAAO,QAAQ,KAAK,gCAAgC,UAAU,OAAO;AAAA,EACvE,EAAE,OAAO,UAAU,KAAK,kCAAkC,UAAU,SAAS;AAAA,EAC7E,EAAE,OAAO,SAAS,KAAK,kCAAkC,UAAU,eAAe;AAAA,EAClF,EAAE,OAAO,UAAU,KAAK,kCAAkC,UAAU,UAAU,OAAO,QAAQ;AAC/F;AAEA,SAAS,YAAY,OAAwB;AAC3C,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,UAAU,YAAY,KAAK,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AACnG,MAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC1D,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,iBAAiB,WAA2B;AACnD,SAAO,UACJ,QAAQ,OAAO,GAAG,EAClB,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,UAAU,GAAG,EACrB,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,QAAQ,OAAO,CAAC,SAAS,KAAK,YAAY,CAAC;AAChD;AAEA,SAAS,sBAAsB,QAA0C;AACvE,QAAM,UAAU,oBAAI,IAA0B;AAC9C,SAAO,KAAK,EAAE,QAAQ,CAAC,WAAW;AAChC,YAAQ,IAAI,OAAO,OAAO,MAAM;AAAA,EAClC,CAAC;AACD,SAAO,MAAM,KAAK,QAAQ,OAAO,CAAC,EAAE;AAAA,IAAK,CAAC,MAAM,UAC9C,KAAK,MAAM,cAAc,MAAM,OAAO,QAAW,EAAE,aAAa,OAAO,CAAC;AAAA,EAC1E;AACF;AAEA,SAAS,sBAAsB,QAAqC;AAClE,QAAM,UAAU,kBAAkB,OAAO,SAAS,OAAO,cAAc,EAAE,IAAI,CAAC,YAAY;AAAA,IACxF,WAAW,OAAO;AAAA,IAClB,UAAU,YAAY,OAAO,IAAI;AAAA,IACjC,UAAU,YAAY,OAAO,EAAE;AAAA,EACjC,EAAE;AAEF,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,WAAW,OAAO;AAAA,IAClB,aAAa,OAAO;AAAA,IACpB,eAAe,OAAO;AAAA,IACtB,YAAY,0BAA0B,MAAM;AAAA,IAC5C,QAAQ,sBAAsB,OAAO,SAAS,OAAO,WAAW;AAAA,IAChE,aAAa,OAAO;AAAA,IACpB;AAAA,EACF;AACF;AAEA,SAAS,WAAW,UAAU,GAAS;AACrC,QAAM,OAAO,oBAAI,KAAK;AACtB,OAAK,SAAS,GAAG,GAAG,GAAG,CAAC;AACxB,MAAI,UAAU,EAAG,MAAK,QAAQ,KAAK,QAAQ,IAAI,OAAO;AACtD,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAuC;AAChE,QAAM,OAAO,OAAO,SAAS,MAAM,QAAQ,KAAK,EAAE,GAAG,EAAE;AACvD,QAAM,OAAO,oBAAI,KAAK;AACtB,OAAK,SAAS,GAAG,GAAG,GAAG,CAAC;AACxB,OAAK,QAAQ,KAAK,QAAQ,KAAK,OAAO,SAAS,IAAI,IAAI,OAAO,GAAG;AACjE,SAAO;AACT;AAEA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAWG;AACD,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,cAAc,aAAa,UAAU;AAAA,IACrC,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,OAAO,OAAO,KAAK;AAAA,IACnB,QAAQ,OAAO,UAAU,CAAC;AAAA,IAC1B,WAAW,SAAS,SAAS;AAAA,IAC7B,SAAS,SAAS,OAAO;AAAA,IACzB,OAAO,SAAS,kBAAkB,SAAS,EAAE,YAAY;AAAA,EAC3D,CAAC;AACD,MAAI,aAAc,QAAO,IAAI,gBAAgB,MAAM;AACnD,MAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AACvC,MAAI,QAAQ,WAAW,SAAS,EAAG,QAAO,IAAI,aAAa,QAAQ,WAAW,KAAK,GAAG,CAAC;AACvF,MAAI,QAAQ,aAAa,SAAS,EAAG,QAAO,IAAI,eAAe,QAAQ,aAAa,KAAK,GAAG,CAAC;AAC7F,MAAI,QAAQ,YAAY,SAAS,EAAG,QAAO,IAAI,cAAc,QAAQ,YAAY,KAAK,GAAG,CAAC;AAC1F,SAAO,sCAAsC,OAAO,SAAS,CAAC;AAChE;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,cAAc,aAAa,UAAU;AAAA,IACrC,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,WAAW,QAAQ;AAAA,IACnB,SAAS,QAAQ;AAAA,IACjB,OAAO,kBAAkB,SAAS,EAAE,YAAY;AAAA,EAClD,CAAC;AACD,MAAI,QAAQ,WAAW,SAAS,EAAG,QAAO,IAAI,aAAa,QAAQ,WAAW,KAAK,GAAG,CAAC;AACvF,MAAI,QAAQ,aAAa,SAAS,EAAG,QAAO,IAAI,eAAe,QAAQ,aAAa,KAAK,GAAG,CAAC;AAC7F,MAAI,QAAQ,YAAY,SAAS,EAAG,QAAO,IAAI,cAAc,QAAQ,YAAY,KAAK,GAAG,CAAC;AAC1F,SAAO,6CAA6C,OAAO,SAAS,CAAC;AACvE;AAEA,SAAS,kBAAkB,SAA2B,GAA4B;AAChF,QAAM,SAAS,oBAAI,IAA8B;AACjD,UAAQ,QAAQ,CAAC,UAAU;AACzB,UAAM,OAAO,IAAI,KAAK,MAAM,SAAS;AACrC,UAAM,MAAM,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,MAAM,YAAY,KAAK,aAAa;AAC/E,UAAM,SAAS,OAAO,IAAI,GAAG,KAAK,CAAC;AACnC,WAAO,KAAK,KAAK;AACjB,WAAO,IAAI,KAAK,MAAM;AAAA,EACxB,CAAC;AAED,QAAM,SAAQ,oBAAI,KAAK,GAAE,aAAa;AACtC,QAAM,gBAAgB,oBAAI,KAAK;AAC/B,gBAAc,QAAQ,cAAc,QAAQ,IAAI,CAAC;AACjD,QAAM,YAAY,cAAc,aAAa;AAE7C,SAAO,MAAM,KAAK,OAAO,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,IACzD;AAAA,IACA,OACE,QAAQ,QACJ,EAAE,wCAAwC,OAAO,IACjD,QAAQ,YACN,EAAE,4CAA4C,WAAW,IACzD,IAAI,KAAK,GAAG,EAAE,mBAAmB,QAAW;AAAA,MAC5C,SAAS;AAAA,MACT,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,IACR,CAAC,EAAE,YAAY;AAAA,IACrB;AAAA,EACF,EAAE;AACJ;AAEA,SAAS,SAAS,EAAE,QAAQ,IAAI,GAA6C;AAC3E,MAAI,CAAC,OAAQ,QAAO,oBAAC,eAAY,WAAU,gCAA+B;AAC1E,SAAO,QAAQ,QACX,oBAAC,aAAU,WAAU,0BAAyB,IAC9C,oBAAC,eAAY,WAAU,0BAAyB;AACtD;AAEO,SAAS,aAAa,EAAE,UAAU,WAAW,GAAsB;AACxE,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAsB;AAAA,IACxD,YAAY,CAAC;AAAA,IACb,cAAc,CAAC;AAAA,IACf,aAAa,CAAC;AAAA,IACd,WAAW;AAAA,EACb,CAAC;AACD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAA2B,CAAC,CAAC;AACjE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,CAAC;AAC5D,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAyB,CAAC,CAAC;AACzE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAyB,CAAC,CAAC;AACvE,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC3E,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,CAAC;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAoB;AAAA,IACtD,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC;AAED,QAAM,OAAO,MAAM,QAAQ,MAAM,OAAO,SAAS,QAAQ,UAAU,QAAQ,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC,QAAQ,SAAS,CAAC;AAEnH,QAAM,UAAU,MAAM,YAAY,YAAY;AAC5C,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,aAAa,WAAW,CAAC,EAAE,YAAY;AAC7C,YAAM,iBAAiB,WAAW,CAAC,EAAE,YAAY;AAEjD,YAAM,CAAC,cAAc,eAAe,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,QACzE;AAAA,UACE,cAAc;AAAA,YACZ;AAAA,YACA;AAAA,YACA,WAAW,QAAQ;AAAA,YACnB;AAAA,YACA,OAAO;AAAA,YACP,QAAQ,YAAY;AAAA,YACpB;AAAA,YACA,cAAc;AAAA,UAChB,CAAC;AAAA,QACH;AAAA,QACA;AAAA,UACE,cAAc;AAAA,YACZ;AAAA,YACA;AAAA,YACA,WAAW,QAAQ;AAAA,YACnB;AAAA,YACA,OAAO;AAAA,YACP;AAAA,YACA,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH,EAAE,MAAM,OAAO,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,EAAE;AAAA,QACvC;AAAA,UACE,cAAc;AAAA,YACZ;AAAA,YACA;AAAA,YACA,WAAW,QAAQ;AAAA,YACnB;AAAA,YACA,OAAO;AAAA,YACP;AAAA,YACA,cAAc;AAAA,YACd,OAAO;AAAA,YACP,QAAQ;AAAA,UACV,CAAC;AAAA,QACH,EAAE,MAAM,OAAO,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,EAAE;AAAA,MACzC,CAAC;AAED,YAAM,gBAAgB,MAAM,QAAQ,aAAa,KAAK,IAAI,aAAa,MAAM,IAAI,qBAAqB,IAAI,CAAC;AAE3G,iBAAW,aAAa;AACxB,oBAAc,OAAO,aAAa,UAAU,WAAW,aAAa,QAAQ,cAAc,MAAM;AAChG,oBAAc,OAAO,cAAc,UAAU,WAAW,cAAc,QAAQ,CAAC;AAC/E,wBAAkB,OAAO,kBAAkB,UAAU,WAAW,kBAAkB,QAAQ,CAAC;AAE3F,YAAM,mBAAmB;AAAA,QACvB,QAAQ,WAAW,IAAI,CAAC,eAAe,EAAE,OAAO,WAAW,OAAO,iBAAiB,SAAS,EAAE,EAAE;AAAA,QAChG,MAAM;AAAA,UACJ,IAAI;AAAA,YACJ,cAAc,QAAQ,CAAC,UAAU,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,SAAS,CAAC;AAAA,UAChF;AAAA,QACF,EACG,KAAK,CAAC,MAAM,UAAU,KAAK,cAAc,KAAK,CAAC,EAC/C,IAAI,CAAC,eAAe,EAAE,OAAO,WAAW,OAAO,iBAAiB,SAAS,EAAE,EAAE;AAAA,MAClF;AACA,YAAM,oBAAoB,eACvB,OAAO,CAAC,WAAW,QAAQ,YAAY,SAAS,OAAO,KAAK,KAAK,cAAc,KAAK,CAAC,UAAU,MAAM,eAAe,OAAO,KAAK,CAAC,EACjI,IAAI,CAAC,YAAY;AAAA,QAChB,OAAO,OAAO;AAAA,QACd,OAAO,EAAE,OAAO,KAAK,OAAO,QAAQ;AAAA,MACtC,EAAE;AAEJ,sBAAgB,gBAAgB;AAChC;AAAA,QAAe,CAAC,YACd;AAAA,UACE,QAAQ,OAAO,CAAC,WAAW,QAAQ,aAAa,SAAS,OAAO,KAAK,CAAC;AAAA,UACtE,MAAM;AAAA,YACJ,IAAI;AAAA,cACF,cACG,OAAO,CAAC,UAAU,MAAM,eAAe,MAAM,aAAa,EAC1D,IAAI,CAAC,UAAU,CAAC,MAAM,aAAuB,MAAM,aAAuB,CAAC;AAAA,YAChF,EAAE,QAAQ;AAAA,UACZ,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,EAAE,OAAO,MAAM,EAAE;AAAA,QAC9C;AAAA,MACF;AACA,uBAAiB,iBAAiB;AAAA,IACpC,SAAS,WAAW;AAClB,iBAAW,CAAC,CAAC;AACb,oBAAc,CAAC;AACf,oBAAc,CAAC;AACf,wBAAkB,CAAC;AACnB,sBAAgB,CAAC,CAAC;AAClB,qBAAe,CAAC,CAAC;AACjB,uBAAiB,CAAC,CAAC;AACnB,eAAS,EAAE,6BAA6B,2BAA2B,CAAC;AAAA,IACtE,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,UAAU,YAAY,SAAS,WAAW,SAAS,CAAC,CAAC;AAEzD,QAAM,UAAU,MAAM;AACpB,SAAK,QAAQ;AAAA,EACf,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,qBAAqB,QAAQ,UAAU;AAC7C,QAAM,iBAAiB,MAAM,QAAQ,MAAM,kBAAkB,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACtF,QAAM,cAAc,MAAM;AAAA,IACxB,MAAM,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,WAAW,EAAE,OAAO,CAAC,UAA2B,QAAQ,KAAK,CAAC,CAAC,EAAE;AAAA,IAC5G,CAAC,OAAO;AAAA,EACV;AACA,QAAM,qBAAqB,MAAM;AAAA,IAC/B,MAAM,QAAQ,OAAO,CAAC,UAAU,MAAM,QAAQ,KAAK,CAAC,WAAW,gBAAgB,SAAS,OAAO,SAA6C,CAAC,CAAC,EAAE;AAAA,IAChJ,CAAC,OAAO;AAAA,EACV;AACA,QAAM,qBAAqB,MAAM;AAAA,IAC/B,MAAM,gBAAgB,IAAI,CAAC,cAAc,iBAAiB,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAAA,IAC3F,CAAC;AAAA,EACH;AACA,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,QAAI,QAAQ,UAAU,YAAa,QAAO,EAAE,gCAAgC,UAAU;AACtF,WAAO,QAAQ,QAAQ,SACnB,EAAE,6BAA6B,OAAO,IACtC,EAAE,6BAA6B,OAAO;AAAA,EAC5C,GAAG,CAAC,SAAS,CAAC,CAAC;AACf,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,QAAQ,UAAU,YAAa,QAAO,EAAE,4BAA4B,MAAM;AAC9E,WAAO,QAAQ,QAAQ,SACnB,EAAE,6BAA6B,OAAO,IACtC,EAAE,6BAA6B,OAAO;AAAA,EAC5C,GAAG,CAAC,SAAS,CAAC,CAAC;AACf,QAAM,aAAa,QAAQ;AAC3B,QAAM,YAAY,YAAY,KAAK,YAAY;AAC/C,QAAM,WAAW,YAAY;AAE7B,QAAM,gBAAgB,MAAM,YAAY,CAAC,YAAkC;AACzE,eAAW,CAAC,aAAa,EAAE,GAAG,SAAS,GAAG,QAAQ,EAAE;AACpD,iBAAa,CAAC;AAAA,EAChB,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,MAAM,YAAY,MAAM;AAC1C,QAAI,CAAC,SAAU;AACf,iBAAa,CAAC,YAAY,UAAU,CAAC;AAAA,EACvC,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,cAAc,MAAM,YAAY,MAAM;AAC1C,QAAI,CAAC,SAAU;AACf,iBAAa,CAAC,YAAY,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC;AAAA,EACpD,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,aAAa,MAAM,YAAY,CAAC,UAAqB;AACzD,eAAW,CAAC,YAAY;AACtB,UAAI,QAAQ,UAAU,OAAO;AAC3B,eAAO;AAAA,UACL;AAAA,UACA,KAAK,QAAQ,QAAQ,SAAS,QAAQ;AAAA,QACxC;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA,KAAK,UAAU,cAAc,SAAS;AAAA,MACxC;AAAA,IACF,CAAC;AACD,iBAAa,CAAC;AAAA,EAChB,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM,YAAY,YAAY;AACjD,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,WAAW,MAAM;AAAA,QACrB,eAAe;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA,WAAW,QAAQ;AAAA,UACnB;AAAA,QACF,CAAC;AAAA,QACD;AAAA,QACA;AAAA,UACE,OAAO,OAAO,QAAQ,IAAI,KAAK;AAAA,QACjC;AAAA,MACF;AAEA,YAAM,OAAO,SAAS,UAAU;AAChC,YAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAM,UAAU,IAAI,gBAAgB,IAAI;AACxC,YAAM,SAAS,SAAS,cAAc,GAAG;AACzC,aAAO,OAAO;AACd,aAAO,WAAW;AAClB,eAAS,KAAK,YAAY,MAAM;AAChC,aAAO,MAAM;AACb,aAAO,OAAO;AACd,UAAI,gBAAgB,OAAO;AAAA,IAC7B,SAAS,aAAa;AACpB,cAAQ,MAAM,qCAAqC,WAAW;AAC9D,YAAM,EAAE,mCAAmC,6BAA6B,GAAG,OAAO;AAAA,IACpF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,UAAU,YAAY,SAAS,SAAS,CAAC,CAAC;AAE9C,SACE,qBAAC,SAAI,WAAU,aACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,QAAQ;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB,cAAc,QAAQ;AAAA,QACtB,aAAa,QAAQ;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAChB,mBAAmB,CAAC,UAAU,cAAc,EAAE,WAAW,MAAM,CAAC;AAAA,QAChE,oBAAoB,CAAC,UAAU,cAAc,EAAE,YAAY,MAAM,CAAC;AAAA,QAClE,sBAAsB,CAAC,UAAU,cAAc,EAAE,cAAc,MAAM,CAAC;AAAA,QACtE,qBAAqB,CAAC,UAAU,cAAc,EAAE,aAAa,MAAM,CAAC;AAAA,QACpE,UAAU;AAAA;AAAA,IACZ;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,eAAe;AAAA;AAAA,IACjB;AAAA,IAEA,qBAAC,SAAI,WAAU,6CACb;AAAA,2BAAC,SAAI,WAAU,8CACb;AAAA,4BAAC,UAAO,WAAU,gCAA+B;AAAA,QACjD,oBAAC,QAAG,WAAU,2CACX,YAAE,6BAA6B,YAAY,GAC9C;AAAA,QACA,oBAAC,SAAM,SAAQ,aAAY,WAAU,WAClC,sBACH;AAAA,SACF;AAAA,MAEA,oBAAC,SAAI,WAAU,iKACZ,yBAAe,IAAI,CAAC,WAAW;AAC9B,cAAM,WAAW,QAAQ,UAAU,OAAO;AAC1C,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,MAAM,WAAW,OAAO,KAAK;AAAA,YACtC,WAAW,0IACT,OAAO,UAAU,UAAU,gBAAgB,eAC7C;AAAA,YAEA;AAAA,kCAAC,UAAM,YAAE,OAAO,KAAK,OAAO,QAAQ,GAAE;AAAA,cACtC,oBAAC,YAAS,QAAQ,UAAU,KAAK,QAAQ,KAAK;AAAA;AAAA;AAAA,UAVzC,OAAO;AAAA,QAWd;AAAA,MAEJ,CAAC,GACH;AAAA,MAEC,WAAW,QAAQ,WAAW,IAC7B,oBAAC,kBAAe,OAAO,EAAE,+BAA+B,oBAAoB,GAAG,WAAU,gCAA+B,IACtH,QACF,oBAAC,gBAAa,OAAO,OAAO,WAAU,OAAM,IAC1C,QAAQ,WAAW,IACrB,oBAAC,SAAI,WAAU,OACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,kCAAkC,2BAA2B;AAAA,UACtE,aAAa,EAAE,6BAA6B,qCAAqC;AAAA;AAAA,MACnF,GACF,IAEA,oBAAC,SACE,+BACC,eAAe,IAAI,CAAC,UAClB,qBAAC,SACC;AAAA,4BAAC,SAAI,WAAU,gHACZ,gBAAM,OACT;AAAA,QACC,MAAM,MAAM,IAAI,CAAC,UAChB,oBAAC,SAAmB,WAAU,4BAC5B,8BAAC,qBAAkB,OAAc,KADzB,MAAM,EAEhB,CACD;AAAA,WARO,MAAM,GAShB,CACD,IAED,QAAQ,IAAI,CAAC,UACX,oBAAC,SAAmB,WAAU,4BAC5B,8BAAC,qBAAkB,OAAc,KADzB,MAAM,EAEhB,CACD,GAEL;AAAA,MAGF,qBAAC,SAAI,WAAU,uHACb;AAAA,4BAAC,UACE,YAAE,yCAAyC,kEAA+D;AAAA,UACzG,OAAO;AAAA,UACP,OAAO;AAAA,UACP;AAAA,QACF,CAAC,GACH;AAAA,QACA,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,UAAU,CAAC,YAAY;AAAA,cACvB,WAAU;AAAA,cAEV;AAAA,oCAAC,aAAU,WAAU,mBAAkB;AAAA,gBACtC;AAAA;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,UAAU,CAAC,YAAY;AAAA,cACvB,WAAU;AAAA,cAET;AAAA;AAAA,gBACD,oBAAC,cAAW,WAAU,mBAAkB;AAAA;AAAA;AAAA,UAC1C;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ArrowLeft, ArrowRight, ArrowUpDown, ChevronDown, ChevronUp, Clock3 } from 'lucide-react'\nimport { extractChangeRows } from '@open-mercato/core/modules/audit_logs/lib/changeRows'\nimport type { ActionLogProjectionType, ActionLogSourceKey } from '@open-mercato/core/modules/audit_logs/lib/projections'\nimport { deriveActionLogActionType, deriveActionLogSource } from '@open-mercato/core/modules/audit_logs/lib/projections'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { ErrorMessage, LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { ChangelogFilters } from './ChangelogFilters'\nimport { ChangelogKpiCards } from './ChangelogKpiCards'\nimport { ChangelogEntryRow } from './ChangelogEntryRow'\nimport { formatChangelogValue } from './changelogValues'\n\ntype AuditAction = {\n id: string\n commandId: string\n actionLabel: string | null\n executionState: 'done' | 'undone' | 'failed' | 'redone'\n actorUserId: string | null\n actorUserName: string | null\n resourceKind: string | null\n resourceId: string | null\n snapshotBefore: unknown | null\n snapshotAfter: unknown | null\n changes: Record<string, unknown> | null\n context: Record<string, unknown> | null\n createdAt: string\n updatedAt: string\n}\n\ntype AuditActionsResponse = {\n items: AuditAction[]\n total?: number\n}\n\ntype ChangelogActionType = ActionLogProjectionType\ntype ChangelogSource = ActionLogSourceKey\n\ntype ChangelogEntry = {\n id: string\n createdAt: string\n actorUserId: string | null\n actorUserName: string | null\n actionType: ChangelogActionType\n source: ChangelogSource\n description: string | null\n changes: Array<{\n fieldName: string\n oldValue: string\n newValue: string\n }>\n}\n\ntype ChangelogTabProps = {\n entityId: string\n entityType: 'company' | 'person' | 'deal'\n}\n\ntype FilterState = {\n fieldNames: string[]\n actorUserIds: string[]\n actionTypes: string[]\n dateRange: '7d' | '30d' | '90d'\n}\n\ntype FilterOption = {\n value: string\n label: string\n}\n\ntype SortField = 'createdAt' | 'user' | 'action' | 'field' | 'source'\n\ntype SortState = {\n field: SortField\n dir: 'asc' | 'desc'\n}\n\nconst PAGE_SIZE = 50\nconst ACTION_OPTIONS: Array<{ value: ChangelogActionType; key: string; fallback: string }> = [\n { value: 'create', key: 'customers.changelog.actions.create', fallback: 'Create' },\n { value: 'edit', key: 'customers.changelog.actions.edit', fallback: 'Edit' },\n { value: 'delete', key: 'customers.changelog.actions.delete', fallback: 'Delete' },\n { value: 'assign', key: 'customers.changelog.actions.assign', fallback: 'Assign' },\n]\nconst CRITICAL_FIELDS = ['status', 'lifecycleStage', 'ownerUserId', 'temperature', 'source', 'renewalQuarter'] as const\nconst HEADER_COLUMNS: Array<{ field: SortField; key: string; fallback: string; align?: 'left' | 'right' }> = [\n { field: 'createdAt', key: 'customers.changelog.col.when', fallback: 'When' },\n { field: 'user', key: 'customers.changelog.col.user', fallback: 'User' },\n { field: 'action', key: 'customers.changelog.col.action', fallback: 'Action' },\n { field: 'field', key: 'customers.changelog.col.change', fallback: 'What changed' },\n { field: 'source', key: 'customers.changelog.col.source', fallback: 'Source', align: 'right' },\n]\n\nfunction formatFieldLabel(fieldName: string): string {\n return fieldName\n .replace(/\\./g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^\\w/, (char) => char.toUpperCase())\n}\n\nfunction mergeFilterOptions(...groups: FilterOption[][]): FilterOption[] {\n const options = new Map<string, FilterOption>()\n groups.flat().forEach((option) => {\n options.set(option.value, option)\n })\n return Array.from(options.values()).sort((left, right) =>\n left.label.localeCompare(right.label, undefined, { sensitivity: 'base' }),\n )\n}\n\nfunction mapAuditActionToEntry(action: AuditAction): ChangelogEntry {\n const changes = extractChangeRows(action.changes, action.snapshotBefore).map((change) => ({\n fieldName: change.field,\n oldValue: formatChangelogValue(change.from),\n newValue: formatChangelogValue(change.to),\n }))\n\n return {\n id: action.id,\n createdAt: action.createdAt,\n actorUserId: action.actorUserId,\n actorUserName: action.actorUserName,\n actionType: deriveActionLogActionType(action),\n source: deriveActionLogSource(action.context, action.actorUserId),\n description: action.actionLabel,\n changes,\n }\n}\n\nfunction startOfDay(daysAgo = 0): Date {\n const date = new Date()\n date.setHours(0, 0, 0, 0)\n if (daysAgo > 0) date.setDate(date.getDate() - daysAgo)\n return date\n}\n\nfunction computeRangeStart(range: FilterState['dateRange']): Date {\n const days = Number.parseInt(range.replace('d', ''), 10)\n const date = new Date()\n date.setHours(0, 0, 0, 0)\n date.setDate(date.getDate() - (Number.isFinite(days) ? days : 90))\n return date\n}\n\nfunction buildQueryUrl({\n entityId,\n entityType,\n dateRange,\n filters,\n limit,\n offset,\n sorting,\n includeTotal,\n before,\n after,\n}: {\n entityId: string\n entityType: 'company' | 'person' | 'deal'\n dateRange: FilterState['dateRange']\n filters: Pick<FilterState, 'fieldNames' | 'actorUserIds' | 'actionTypes'>\n limit: number\n offset?: number\n sorting?: SortState\n includeTotal?: boolean\n before?: string\n after?: string\n}) {\n const params = new URLSearchParams({\n resourceKind: `customers.${entityType}`,\n resourceId: entityId,\n includeRelated: 'true',\n limit: String(limit),\n offset: String(offset ?? 0),\n sortField: sorting?.field ?? 'createdAt',\n sortDir: sorting?.dir ?? 'desc',\n after: after ?? computeRangeStart(dateRange).toISOString(),\n })\n if (includeTotal) params.set('includeTotal', 'true')\n if (before) params.set('before', before)\n if (filters.fieldNames.length > 0) params.set('fieldName', filters.fieldNames.join(','))\n if (filters.actorUserIds.length > 0) params.set('actorUserId', filters.actorUserIds.join(','))\n if (filters.actionTypes.length > 0) params.set('actionType', filters.actionTypes.join(','))\n return `/api/audit_logs/audit-logs/actions?${params.toString()}`\n}\n\nfunction buildExportUrl({\n entityId,\n entityType,\n filters,\n dateRange,\n sorting,\n}: {\n entityId: string\n entityType: 'company' | 'person' | 'deal'\n filters: Pick<FilterState, 'fieldNames' | 'actorUserIds' | 'actionTypes'>\n dateRange: FilterState['dateRange']\n sorting: SortState\n}) {\n const params = new URLSearchParams({\n resourceKind: `customers.${entityType}`,\n resourceId: entityId,\n includeRelated: 'true',\n limit: '1000',\n sortField: sorting.field,\n sortDir: sorting.dir,\n after: computeRangeStart(dateRange).toISOString(),\n })\n if (filters.fieldNames.length > 0) params.set('fieldName', filters.fieldNames.join(','))\n if (filters.actorUserIds.length > 0) params.set('actorUserId', filters.actorUserIds.join(','))\n if (filters.actionTypes.length > 0) params.set('actionType', filters.actionTypes.join(','))\n return `/api/audit_logs/audit-logs/actions/export?${params.toString()}`\n}\n\nfunction groupEntriesByDay(entries: ChangelogEntry[], t: ReturnType<typeof useT>) {\n const groups = new Map<string, ChangelogEntry[]>()\n entries.forEach((entry) => {\n const date = new Date(entry.createdAt)\n const key = Number.isNaN(date.getTime()) ? entry.createdAt : date.toDateString()\n const bucket = groups.get(key) ?? []\n bucket.push(entry)\n groups.set(key, bucket)\n })\n\n const today = new Date().toDateString()\n const yesterdayDate = new Date()\n yesterdayDate.setDate(yesterdayDate.getDate() - 1)\n const yesterday = yesterdayDate.toDateString()\n\n return Array.from(groups.entries()).map(([key, items]) => ({\n key,\n label:\n key === today\n ? t('customers.changelog.groupLabel.today', 'TODAY')\n : key === yesterday\n ? t('customers.changelog.groupLabel.yesterday', 'YESTERDAY')\n : new Date(key).toLocaleDateString(undefined, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n }).toUpperCase(),\n items,\n }))\n}\n\nfunction SortIcon({ active, dir }: { active: boolean; dir: 'asc' | 'desc' }) {\n if (!active) return <ArrowUpDown className=\"size-3 text-muted-foreground\" />\n return dir === 'asc'\n ? <ChevronUp className=\"size-3 text-foreground\" />\n : <ChevronDown className=\"size-3 text-foreground\" />\n}\n\nexport function ChangelogTab({ entityId, entityType }: ChangelogTabProps) {\n const t = useT()\n const [filters, setFilters] = React.useState<FilterState>({\n fieldNames: [],\n actorUserIds: [],\n actionTypes: [],\n dateRange: '90d',\n })\n const [entries, setEntries] = React.useState<ChangelogEntry[]>([])\n const [totalCount, setTotalCount] = React.useState(0)\n const [todayCount, setTodayCount] = React.useState(0)\n const [yesterdayCount, setYesterdayCount] = React.useState(0)\n const [fieldOptions, setFieldOptions] = React.useState<FilterOption[]>([])\n const [userOptions, setUserOptions] = React.useState<FilterOption[]>([])\n const [actionOptions, setActionOptions] = React.useState<FilterOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [exporting, setExporting] = React.useState(false)\n const [pageIndex, setPageIndex] = React.useState(0)\n const [sorting, setSorting] = React.useState<SortState>({\n field: 'createdAt',\n dir: 'desc',\n })\n\n const days = React.useMemo(() => Number.parseInt(filters.dateRange.replace('d', ''), 10) || 90, [filters.dateRange])\n\n const loadTab = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const todayStart = startOfDay(0).toISOString()\n const yesterdayStart = startOfDay(1).toISOString()\n\n const [mainResponse, todayResponse, yesterdayResponse] = await Promise.all([\n readApiResultOrThrow<AuditActionsResponse>(\n buildQueryUrl({\n entityId,\n entityType,\n dateRange: filters.dateRange,\n filters,\n limit: PAGE_SIZE,\n offset: pageIndex * PAGE_SIZE,\n sorting,\n includeTotal: true,\n }),\n ),\n readApiResultOrThrow<AuditActionsResponse>(\n buildQueryUrl({\n entityId,\n entityType,\n dateRange: filters.dateRange,\n filters,\n limit: 1,\n sorting,\n includeTotal: true,\n after: todayStart,\n }),\n ).catch(() => ({ items: [], total: 0 })),\n readApiResultOrThrow<AuditActionsResponse>(\n buildQueryUrl({\n entityId,\n entityType,\n dateRange: filters.dateRange,\n filters,\n limit: 1,\n sorting,\n includeTotal: true,\n after: yesterdayStart,\n before: todayStart,\n }),\n ).catch(() => ({ items: [], total: 0 })),\n ])\n\n const mappedEntries = Array.isArray(mainResponse.items) ? mainResponse.items.map(mapAuditActionToEntry) : []\n\n setEntries(mappedEntries)\n setTotalCount(typeof mainResponse.total === 'number' ? mainResponse.total : mappedEntries.length)\n setTodayCount(typeof todayResponse.total === 'number' ? todayResponse.total : 0)\n setYesterdayCount(typeof yesterdayResponse.total === 'number' ? yesterdayResponse.total : 0)\n\n const nextFieldOptions = mergeFilterOptions(\n filters.fieldNames.map((fieldName) => ({ value: fieldName, label: formatFieldLabel(fieldName) })),\n Array.from(\n new Set(\n mappedEntries.flatMap((entry) => entry.changes.map((change) => change.fieldName)),\n ),\n )\n .sort((left, right) => left.localeCompare(right))\n .map((fieldName) => ({ value: fieldName, label: formatFieldLabel(fieldName) })),\n )\n const nextActionOptions = ACTION_OPTIONS\n .filter((option) => filters.actionTypes.includes(option.value) || mappedEntries.some((entry) => entry.actionType === option.value))\n .map((option) => ({\n value: option.value,\n label: t(option.key, option.fallback),\n }))\n\n setFieldOptions(nextFieldOptions)\n setUserOptions((current) =>\n mergeFilterOptions(\n current.filter((option) => filters.actorUserIds.includes(option.value)),\n Array.from(\n new Map(\n mappedEntries\n .filter((entry) => entry.actorUserId && entry.actorUserName)\n .map((entry) => [entry.actorUserId as string, entry.actorUserName as string]),\n ).entries(),\n ).map(([value, label]) => ({ value, label })),\n ),\n )\n setActionOptions(nextActionOptions)\n } catch (loadError) {\n setEntries([])\n setTotalCount(0)\n setTodayCount(0)\n setYesterdayCount(0)\n setFieldOptions([])\n setUserOptions([])\n setActionOptions([])\n setError(t('customers.changelog.error', 'Failed to load change log'))\n } finally {\n setLoading(false)\n }\n }, [entityId, entityType, filters, pageIndex, sorting, t])\n\n React.useEffect(() => {\n void loadTab()\n }, [loadTab])\n\n const showGroupedEntries = sorting.field === 'createdAt'\n const groupedEntries = React.useMemo(() => groupEntriesByDay(entries, t), [entries, t])\n const uniqueUsers = React.useMemo(\n () => new Set(entries.map((entry) => entry.actorUserId).filter((value): value is string => Boolean(value))).size,\n [entries],\n )\n const criticalFieldCount = React.useMemo(\n () => entries.filter((entry) => entry.changes.some((change) => CRITICAL_FIELDS.includes(change.fieldName as (typeof CRITICAL_FIELDS)[number]))).length,\n [entries],\n )\n const criticalFieldLabel = React.useMemo(\n () => CRITICAL_FIELDS.map((fieldName) => formatFieldLabel(fieldName)).slice(0, 3).join(', '),\n [],\n )\n const previousPageLabel = React.useMemo(() => {\n if (sorting.field !== 'createdAt') return t('customers.changelog.previous', 'Previous')\n return sorting.dir === 'desc'\n ? t('customers.changelog.newer', 'Newer')\n : t('customers.changelog.older', 'Older')\n }, [sorting, t])\n const nextPageLabel = React.useMemo(() => {\n if (sorting.field !== 'createdAt') return t('customers.changelog.next', 'Next')\n return sorting.dir === 'desc'\n ? t('customers.changelog.older', 'Older')\n : t('customers.changelog.newer', 'Newer')\n }, [sorting, t])\n const shownCount = entries.length\n const hasOlder = (pageIndex + 1) * PAGE_SIZE < totalCount\n const hasNewer = pageIndex > 0\n\n const updateFilters = React.useCallback((partial: Partial<FilterState>) => {\n setFilters((current) => ({ ...current, ...partial }))\n setPageIndex(0)\n }, [])\n\n const handleOlder = React.useCallback(() => {\n if (!hasOlder) return\n setPageIndex((current) => current + 1)\n }, [hasOlder])\n\n const handleNewer = React.useCallback(() => {\n if (!hasNewer) return\n setPageIndex((current) => Math.max(current - 1, 0))\n }, [hasNewer])\n\n const handleSort = React.useCallback((field: SortField) => {\n setSorting((current) => {\n if (current.field === field) {\n return {\n field,\n dir: current.dir === 'desc' ? 'asc' : 'desc',\n }\n }\n\n return {\n field,\n dir: field === 'createdAt' ? 'desc' : 'asc',\n }\n })\n setPageIndex(0)\n }, [])\n\n const handleExport = React.useCallback(async () => {\n setExporting(true)\n try {\n const response = await apiCallOrThrow<string>(\n buildExportUrl({\n entityId,\n entityType,\n filters,\n dateRange: filters.dateRange,\n sorting,\n }),\n undefined,\n {\n parse: async (res) => res.text(),\n },\n )\n\n const body = response.result ?? ''\n const blob = new Blob([body], { type: 'text/csv;charset=utf-8' })\n const blobUrl = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = blobUrl\n anchor.download = 'changelog-export.csv'\n document.body.appendChild(anchor)\n anchor.click()\n anchor.remove()\n URL.revokeObjectURL(blobUrl)\n } catch (exportError) {\n console.error('customers.changelog.export failed', exportError)\n flash(t('customers.changelog.exportError', 'Failed to export change log'), 'error')\n } finally {\n setExporting(false)\n }\n }, [entityId, entityType, filters, sorting, t])\n\n return (\n <div className=\"space-y-5\">\n <ChangelogFilters\n dateRange={filters.dateRange}\n fieldNames={filters.fieldNames}\n actorUserIds={filters.actorUserIds}\n actionTypes={filters.actionTypes}\n fieldOptions={fieldOptions}\n userOptions={userOptions}\n actionOptions={actionOptions}\n exportDisabled={exporting}\n onDateRangeChange={(value) => updateFilters({ dateRange: value })}\n onFieldNamesChange={(value) => updateFilters({ fieldNames: value })}\n onActorUserIdsChange={(value) => updateFilters({ actorUserIds: value })}\n onActionTypesChange={(value) => updateFilters({ actionTypes: value })}\n onExport={handleExport}\n />\n\n <ChangelogKpiCards\n loading={loading}\n totalCount={totalCount}\n todayCount={todayCount}\n yesterdayCount={yesterdayCount}\n uniqueUsers={uniqueUsers}\n criticalFieldCount={criticalFieldCount}\n criticalFieldLabel={criticalFieldLabel}\n dateRangeDays={days}\n />\n\n <div className=\"overflow-hidden rounded-xl border bg-card\">\n <div className=\"flex items-center gap-2 border-b px-5 py-4\">\n <Clock3 className=\"size-4 text-muted-foreground\" />\n <h3 className=\"text-base font-semibold text-foreground\">\n {t('customers.changelog.title', 'Change log')}\n </h3>\n <Badge variant=\"secondary\" className=\"text-xs\">\n {totalCount}\n </Badge>\n </div>\n\n <div className=\"grid grid-cols-[92px_190px_120px_1fr_80px] gap-3 border-b bg-muted/20 px-5 py-2 text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {HEADER_COLUMNS.map((column) => {\n const isActive = sorting.field === column.field\n return (\n <Button\n key={column.field}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => handleSort(column.field)}\n className={`h-auto gap-1 rounded-none px-0 py-0 text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground hover:bg-transparent ${\n column.align === 'right' ? 'justify-end' : 'justify-start'\n }`}\n >\n <span>{t(column.key, column.fallback)}</span>\n <SortIcon active={isActive} dir={sorting.dir} />\n </Button>\n )\n })}\n </div>\n\n {loading && entries.length === 0 ? (\n <LoadingMessage label={t('customers.changelog.loading', 'Loading changes...')} className=\"min-h-[220px] justify-center\" />\n ) : error ? (\n <ErrorMessage label={error} className=\"m-5\" />\n ) : entries.length === 0 ? (\n <div className=\"p-5\">\n <TabEmptyState\n title={t('customers.changelog.emptyTitle', 'No changes in this period')}\n description={t('customers.changelog.empty', 'No changes recorded in this period.')}\n />\n </div>\n ) : (\n <div>\n {showGroupedEntries ? (\n groupedEntries.map((group) => (\n <div key={group.key}>\n <div className=\"border-b bg-muted/10 px-5 py-2 text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {group.label}\n </div>\n {group.items.map((entry) => (\n <div key={entry.id} className=\"border-b last:border-b-0\">\n <ChangelogEntryRow entry={entry} />\n </div>\n ))}\n </div>\n ))\n ) : (\n entries.map((entry) => (\n <div key={entry.id} className=\"border-b last:border-b-0\">\n <ChangelogEntryRow entry={entry} />\n </div>\n ))\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-3 border-t px-5 py-4 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between\">\n <span>\n {t('customers.changelog.showingWithPeriod', 'Showing {{shown}} of {{total}} entries \u00B7 last {{days}} days', {\n shown: shownCount,\n total: totalCount,\n days,\n })}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleNewer}\n disabled={!hasNewer || loading}\n className=\"h-8 rounded-lg px-3 text-xs\"\n >\n <ArrowLeft className=\"mr-1.5 size-3.5\" />\n {previousPageLabel}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleOlder}\n disabled={!hasOlder || loading}\n className=\"h-8 rounded-lg px-3 text-xs\"\n >\n {nextPageLabel}\n <ArrowRight className=\"ml-1.5 size-3.5\" />\n </Button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nexport default ChangelogTab\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,35 @@
1
+ function isRecord(value) {
2
+ return !!value && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function readString(record, ...keys) {
5
+ for (const key of keys) {
6
+ const value = record[key];
7
+ if (typeof value === "string" && value.trim().length > 0) {
8
+ return value.trim();
9
+ }
10
+ }
11
+ return null;
12
+ }
13
+ function formatObjectSummary(record) {
14
+ const personName = [readString(record, "firstName"), readString(record, "lastName")].filter(Boolean).join(" ").trim();
15
+ const label = readString(record, "displayName", "label", "name", "title", "fullName") ?? (personName || null) ?? readString(record, "email");
16
+ if (!label) return null;
17
+ return record.isPrimary === true ? `${label} (primary)` : label;
18
+ }
19
+ function formatChangelogValue(value) {
20
+ if (value == null) return "";
21
+ if (value instanceof Date) return value.toISOString();
22
+ if (Array.isArray(value)) {
23
+ return value.map((entry) => formatChangelogValue(entry)).filter(Boolean).join(", ");
24
+ }
25
+ if (isRecord(value)) {
26
+ const summary = formatObjectSummary(value);
27
+ if (summary) return summary;
28
+ return JSON.stringify(value);
29
+ }
30
+ return String(value);
31
+ }
32
+ export {
33
+ formatChangelogValue
34
+ };
35
+ //# sourceMappingURL=changelogValues.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customers/components/detail/changelogValues.ts"],
4
+ "sourcesContent": ["function isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction readString(record: Record<string, unknown>, ...keys: string[]): string | null {\n for (const key of keys) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction formatObjectSummary(record: Record<string, unknown>): string | null {\n const personName = [readString(record, 'firstName'), readString(record, 'lastName')]\n .filter(Boolean)\n .join(' ')\n .trim()\n const label = readString(record, 'displayName', 'label', 'name', 'title', 'fullName')\n ?? (personName || null)\n ?? readString(record, 'email')\n if (!label) return null\n return record.isPrimary === true ? `${label} (primary)` : label\n}\n\nexport function formatChangelogValue(value: unknown): string {\n if (value == null) return ''\n if (value instanceof Date) return value.toISOString()\n if (Array.isArray(value)) {\n return value\n .map((entry) => formatChangelogValue(entry))\n .filter(Boolean)\n .join(', ')\n }\n if (isRecord(value)) {\n const summary = formatObjectSummary(value)\n if (summary) return summary\n return JSON.stringify(value)\n }\n return String(value)\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS,OAAkD;AAClE,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AACrE;AAEA,SAAS,WAAW,WAAoC,MAA+B;AACrF,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,QAAgD;AAC3E,QAAM,aAAa,CAAC,WAAW,QAAQ,WAAW,GAAG,WAAW,QAAQ,UAAU,CAAC,EAChF,OAAO,OAAO,EACd,KAAK,GAAG,EACR,KAAK;AACR,QAAM,QAAQ,WAAW,QAAQ,eAAe,SAAS,QAAQ,SAAS,UAAU,MAC9E,cAAc,SACf,WAAW,QAAQ,OAAO;AAC/B,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,OAAO,cAAc,OAAO,GAAG,KAAK,eAAe;AAC5D;AAEO,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MACJ,IAAI,CAAC,UAAU,qBAAqB,KAAK,CAAC,EAC1C,OAAO,OAAO,EACd,KAAK,IAAI;AAAA,EACd;AACA,MAAI,SAAS,KAAK,GAAG;AACnB,UAAM,UAAU,oBAAoB,KAAK;AACzC,QAAI,QAAS,QAAO;AACpB,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,SAAO,OAAO,KAAK;AACrB;",
6
+ "names": []
7
+ }
@@ -91,7 +91,7 @@ function normalizeCustomFieldSubmitValue(value) {
91
91
  if (Array.isArray(value)) {
92
92
  return value.filter((entry) => entry !== void 0);
93
93
  }
94
- if (value === void 0) return null;
94
+ if (value === void 0) return void 0;
95
95
  return value;
96
96
  }
97
97
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/customFieldUtils.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\n\nfunction stripCustomFieldPrefix(value: string): string {\n return value.replace(/^(?:cf|Cf)(?:[_:\\s-]+)/, '')\n}\n\nfunction normalizeCustomFieldToken(value: string): string {\n return stripCustomFieldPrefix(value.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[\\s-]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase()\n}\n\nexport function normalizeCustomFieldKey(key: string | null | undefined): string {\n if (typeof key !== 'string') return ''\n return normalizeCustomFieldToken(key)\n}\n\nexport function formatCustomFieldLabel(key: string): string {\n if (!key) return ''\n const spaced = stripCustomFieldPrefix(key.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n if (!spaced.length) return stripCustomFieldPrefix(key.trim())\n return spaced\n .split(' ')\n .map((part) => (part.length ? part[0].toUpperCase() + part.slice(1) : part))\n .join(' ')\n}\n\nexport function resolveCustomFieldLabel(label: string | null | undefined, key: string): string {\n const fallbackLabel = formatCustomFieldLabel(key)\n if (typeof label !== 'string') return fallbackLabel\n const trimmed = label.trim()\n if (!trimmed.length) return fallbackLabel\n\n const normalizedKey = normalizeCustomFieldKey(key)\n const normalizedLabel = normalizeCustomFieldToken(trimmed)\n if (normalizedKey && normalizedLabel === normalizedKey) {\n return fallbackLabel\n }\n\n return /^(?:cf|Cf)(?:[_:\\s-]+)/.test(trimmed)\n ? formatCustomFieldLabel(trimmed)\n : trimmed\n}\n\nexport function isEmptyCustomValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string') return value.trim().length === 0\n if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyCustomValue(entry))\n if (typeof value === 'object') {\n try {\n return Object.values(value as Record<string, unknown>).every(isEmptyCustomValue)\n } catch {\n return false\n }\n }\n return false\n}\n\nexport function stringifyCustomValue(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (Array.isArray(value)) {\n const parts = value\n .map((entry) => stringifyCustomValue(entry))\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n return parts.join(', ')\n }\n if (value instanceof Date) {\n const iso = value.toISOString()\n return formatDateTime(iso) ?? iso\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return ''\n return formatDateTime(trimmed) ?? trimmed\n }\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return String(value)\n }\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>\n const candidate =\n record.label ?? record.name ?? record.title ?? record.value ?? record.id ?? record.key ?? null\n if (typeof candidate === 'string' && candidate.trim().length) {\n return candidate.trim()\n }\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n }\n return ''\n}\n\nexport function extractDictionaryValue(entry: unknown): string | null {\n if (typeof entry === 'string') {\n const trimmed = entry.trim()\n return trimmed.length ? trimmed : null\n }\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const candidate = record.value ?? record.name ?? record.id ?? record.key ?? record.label\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n return trimmed.length ? trimmed : null\n }\n return null\n}\n\nexport function normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return null\n return value\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\n\nfunction stripCustomFieldPrefix(value: string): string {\n return value.replace(/^(?:cf|Cf)(?:[_:\\s-]+)/, '')\n}\n\nfunction normalizeCustomFieldToken(value: string): string {\n return stripCustomFieldPrefix(value.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[\\s-]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase()\n}\n\nexport function normalizeCustomFieldKey(key: string | null | undefined): string {\n if (typeof key !== 'string') return ''\n return normalizeCustomFieldToken(key)\n}\n\nexport function formatCustomFieldLabel(key: string): string {\n if (!key) return ''\n const spaced = stripCustomFieldPrefix(key.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n if (!spaced.length) return stripCustomFieldPrefix(key.trim())\n return spaced\n .split(' ')\n .map((part) => (part.length ? part[0].toUpperCase() + part.slice(1) : part))\n .join(' ')\n}\n\nexport function resolveCustomFieldLabel(label: string | null | undefined, key: string): string {\n const fallbackLabel = formatCustomFieldLabel(key)\n if (typeof label !== 'string') return fallbackLabel\n const trimmed = label.trim()\n if (!trimmed.length) return fallbackLabel\n\n const normalizedKey = normalizeCustomFieldKey(key)\n const normalizedLabel = normalizeCustomFieldToken(trimmed)\n if (normalizedKey && normalizedLabel === normalizedKey) {\n return fallbackLabel\n }\n\n return /^(?:cf|Cf)(?:[_:\\s-]+)/.test(trimmed)\n ? formatCustomFieldLabel(trimmed)\n : trimmed\n}\n\nexport function isEmptyCustomValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string') return value.trim().length === 0\n if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyCustomValue(entry))\n if (typeof value === 'object') {\n try {\n return Object.values(value as Record<string, unknown>).every(isEmptyCustomValue)\n } catch {\n return false\n }\n }\n return false\n}\n\nexport function stringifyCustomValue(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (Array.isArray(value)) {\n const parts = value\n .map((entry) => stringifyCustomValue(entry))\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n return parts.join(', ')\n }\n if (value instanceof Date) {\n const iso = value.toISOString()\n return formatDateTime(iso) ?? iso\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return ''\n return formatDateTime(trimmed) ?? trimmed\n }\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return String(value)\n }\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>\n const candidate =\n record.label ?? record.name ?? record.title ?? record.value ?? record.id ?? record.key ?? null\n if (typeof candidate === 'string' && candidate.trim().length) {\n return candidate.trim()\n }\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n }\n return ''\n}\n\nexport function extractDictionaryValue(entry: unknown): string | null {\n if (typeof entry === 'string') {\n const trimmed = entry.trim()\n return trimmed.length ? trimmed : null\n }\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const candidate = record.value ?? record.name ?? record.id ?? record.key ?? record.label\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n return trimmed.length ? trimmed : null\n }\n return null\n}\n\nexport function normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return undefined\n return value\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,6 +6,7 @@ import Link from "next/link";
6
6
  import { Check, Pencil, Plus } from "lucide-react";
7
7
  import { useT } from "@open-mercato/shared/lib/i18n/context";
8
8
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
9
+ import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields-client";
9
10
  import { Button } from "@open-mercato/ui/primitives/button";
10
11
  import { Input } from "@open-mercato/ui/primitives/input";
11
12
  import { EmailInput } from "@open-mercato/ui/primitives/email-input";
@@ -1358,7 +1359,7 @@ function mapCompanyOverviewToFormValues(overview) {
1358
1359
  industry: overview.profile?.industry ?? "",
1359
1360
  sizeBucket: overview.profile?.sizeBucket ?? "",
1360
1361
  annualRevenue: overview.profile?.annualRevenue ?? "",
1361
- ...overview.customFields
1362
+ ...extractCustomFieldEntries({ customFields: overview.customFields })
1362
1363
  };
1363
1364
  }
1364
1365
  function mapPersonOverviewToFormValues(overview) {
@@ -1380,7 +1381,7 @@ function mapPersonOverviewToFormValues(overview) {
1380
1381
  department: overview.profile?.department ?? "",
1381
1382
  linkedInUrl: overview.profile?.linkedInUrl ?? "",
1382
1383
  twitterUrl: overview.profile?.twitterUrl ?? "",
1383
- ...overview.customFields
1384
+ ...extractCustomFieldEntries({ customFields: overview.customFields })
1384
1385
  };
1385
1386
  }
1386
1387
  export {