@open-mercato/core 0.10.1-develop.7847.1.7d83dc5794 → 0.10.1-develop.7862.1.ee520f7662

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 (81) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/api/file/[id]/route.js +6 -1
  3. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  4. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +6 -1
  5. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  6. package/dist/modules/attachments/api/library/[id]/route.js +16 -1
  7. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  8. package/dist/modules/attachments/api/route.js +11 -3
  9. package/dist/modules/attachments/api/route.js.map +2 -2
  10. package/dist/modules/attachments/cli.js +2 -0
  11. package/dist/modules/attachments/cli.js.map +2 -2
  12. package/dist/modules/attachments/data/validators.js +11 -0
  13. package/dist/modules/attachments/data/validators.js.map +2 -2
  14. package/dist/modules/attachments/index.js +10 -0
  15. package/dist/modules/attachments/index.js.map +2 -2
  16. package/dist/modules/attachments/lib/access-query.js +5 -1
  17. package/dist/modules/attachments/lib/access-query.js.map +2 -2
  18. package/dist/modules/attachments/lib/createFromBuffer.js +2 -0
  19. package/dist/modules/attachments/lib/createFromBuffer.js.map +2 -2
  20. package/dist/modules/attachments/lib/drivers/driverFactory.js +54 -33
  21. package/dist/modules/attachments/lib/drivers/driverFactory.js.map +2 -2
  22. package/dist/modules/attachments/lib/drivers/index.js +11 -1
  23. package/dist/modules/attachments/lib/drivers/index.js.map +2 -2
  24. package/dist/modules/attachments/lib/drivers/storageValidation.js +85 -0
  25. package/dist/modules/attachments/lib/drivers/storageValidation.js.map +7 -0
  26. package/dist/modules/attachments/lib/scoped-upload-service.js +3 -1
  27. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  28. package/dist/modules/attachments/lib/storageErrors.js +22 -0
  29. package/dist/modules/attachments/lib/storageErrors.js.map +7 -0
  30. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -4
  31. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +3 -3
  32. package/dist/modules/customers/api/companies/[id]/route.js +4 -9
  33. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  34. package/dist/modules/customers/api/people/[id]/route.js +4 -9
  35. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  36. package/dist/modules/customers/components/detail/TaskDialog.js +2 -0
  37. package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
  38. package/dist/modules/customers/lib/todoCompatibility.js +23 -0
  39. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  40. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +1 -4
  41. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
  42. package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js +8 -1
  43. package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js.map +2 -2
  44. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +18 -3
  45. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  46. package/jest.config.cjs +1 -1
  47. package/package.json +8 -8
  48. package/src/modules/attachments/api/file/[id]/route.ts +7 -1
  49. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -1
  50. package/src/modules/attachments/api/library/[id]/route.ts +14 -1
  51. package/src/modules/attachments/api/route.ts +11 -3
  52. package/src/modules/attachments/cli.ts +2 -0
  53. package/src/modules/attachments/data/validators.ts +11 -0
  54. package/src/modules/attachments/i18n/de.json +1 -0
  55. package/src/modules/attachments/i18n/en.json +1 -0
  56. package/src/modules/attachments/i18n/es.json +1 -0
  57. package/src/modules/attachments/i18n/ko.json +1 -0
  58. package/src/modules/attachments/i18n/pl.json +1 -0
  59. package/src/modules/attachments/index.ts +12 -0
  60. package/src/modules/attachments/lib/access-query.ts +5 -1
  61. package/src/modules/attachments/lib/createFromBuffer.ts +2 -0
  62. package/src/modules/attachments/lib/drivers/driverFactory.ts +79 -55
  63. package/src/modules/attachments/lib/drivers/index.ts +13 -0
  64. package/src/modules/attachments/lib/drivers/storageValidation.ts +114 -0
  65. package/src/modules/attachments/lib/scoped-upload-service.ts +3 -1
  66. package/src/modules/attachments/lib/storageErrors.ts +21 -0
  67. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +3 -1
  68. package/src/modules/audit_logs/i18n/de.json +1 -0
  69. package/src/modules/audit_logs/i18n/en.json +1 -0
  70. package/src/modules/audit_logs/i18n/es.json +1 -0
  71. package/src/modules/audit_logs/i18n/ko.json +1 -0
  72. package/src/modules/audit_logs/i18n/pl.json +1 -0
  73. package/src/modules/customer_accounts/AGENTS.md +1 -1
  74. package/src/modules/customers/api/companies/[id]/route.ts +6 -13
  75. package/src/modules/customers/api/people/[id]/route.ts +6 -13
  76. package/src/modules/customers/components/detail/TaskDialog.tsx +2 -0
  77. package/src/modules/customers/lib/todoCompatibility.ts +28 -0
  78. package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +0 -3
  79. package/src/modules/sales/AGENTS.md +3 -3
  80. package/src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx +8 -1
  81. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +22 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/todoCompatibility.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport {\n CustomerInteraction,\n CustomerTodoLink,\n} from '../data/entities'\nimport type { InteractionRecord } from './interactionCompatibility'\nimport {\n CUSTOMER_INTERACTION_TASK_SOURCE,\n EXAMPLE_TODO_SOURCE,\n resolveExampleIntegrationHref,\n} from './interactionCompatibility'\nimport { hydrateCanonicalInteractions, loadCustomerSummaries } from './interactionReadModel'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport type CustomerTodoRow = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n todoIsDone: boolean | null\n todoPriority?: number | null\n todoSeverity?: string | null\n todoDescription?: string | null\n todoDueAt?: string | null\n todoTimezone?: string | null\n todoCustomValues?: Record<string, unknown> | null\n todoOrganizationId: string | null\n todoUpdatedAt?: string | null\n organizationId: string\n tenantId: string\n createdAt: string\n externalHref?: string | null\n _integrations?: Record<string, unknown>\n customer: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nexport type LegacyTodoDetail = {\n title: string | null\n isDone: boolean | null\n priority: number | null\n severity: string | null\n description: string | null\n dueAt: string | null\n organizationId: string | null\n updatedAt: string | null\n customValues: Record<string, unknown> | null\n}\n\ntype CustomerSummary = {\n id: string | null\n displayName: string | null\n kind: string | null\n}\n\ntype CustomersAuthLike = {\n tenantId: string | null\n orgId?: string | null\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}\n\ntype CustomersContainerLike = {\n resolve: (name: string) => unknown\n}\n\nexport type CanonicalTodoListResult = {\n items: CustomerTodoRow[]\n bridgeIds: Set<string>\n total: number\n}\n\nfunction emptyCanonicalTodoList(): CanonicalTodoListResult {\n return { items: [], bridgeIds: new Set(), total: 0 }\n}\n\nfunction isCollapsedRestrictedOrganizationScope(\n organizationIds: string[] | null | undefined,\n isUnrestricted: boolean | undefined,\n): boolean {\n return isUnrestricted !== true && (!organizationIds || organizationIds.length === 0)\n}\n\nexport type ListTodosPagination = { page: number; pageSize: number }\n\nfunction resolveLegacyTodoSource(source: string | null | undefined): string {\n return typeof source === 'string' && source.trim().length > 0\n ? source\n : EXAMPLE_TODO_SOURCE\n}\n\nfunction extractTodoTitle(record: Record<string, unknown>): string | null {\n // `task_name` / `taskName` carry a workflow user task's name; the query engine\n // returns the raw column on the ORM path and the camelCase property on the\n // indexed one, so both spellings are read.\n const candidates = ['title', 'subject', 'name', 'task_name', 'taskName', 'summary', 'text', 'description']\n for (const key of candidates) {\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 parseNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) return parsed\n }\n return null\n}\n\nfunction parseDateValue(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = new Date(trimmed)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction readCustomField(record: Record<string, unknown>, key: string): unknown {\n const custom = record.custom ?? record.customFields ?? record.cf\n if (custom && typeof custom === 'object') {\n const bucket = custom as Record<string, unknown>\n if (key in bucket) return bucket[key]\n }\n return undefined\n}\n\nexport function normalizeTodoSearch(value: string | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim().toLowerCase()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport function sortTodoRows(rows: CustomerTodoRow[]): CustomerTodoRow[] {\n return [...rows].sort((left, right) => {\n const leftTime = new Date(left.createdAt).getTime()\n const rightTime = new Date(right.createdAt).getTime()\n if (leftTime === rightTime) {\n return right.id.localeCompare(left.id)\n }\n return rightTime - leftTime\n })\n}\n\nexport function filterTodoRows(rows: CustomerTodoRow[], search: string | null): CustomerTodoRow[] {\n if (!search) return rows\n return rows.filter((row) => {\n const haystack = [\n row.customer.displayName,\n row.todoTitle,\n row.todoDescription,\n ]\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(search)\n })\n}\n\nexport function paginateTodoRows(\n rows: CustomerTodoRow[],\n page: number,\n pageSize: number,\n exportAll: boolean,\n): { items: CustomerTodoRow[]; total: number; page: number; pageSize: number; totalPages: number } {\n const total = rows.length\n if (exportAll) {\n return {\n items: rows,\n total,\n page: 1,\n pageSize: total,\n totalPages: 1,\n }\n }\n const start = (page - 1) * pageSize\n return {\n items: rows.slice(start, start + pageSize),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n }\n}\n\nexport async function resolveLegacyTodoDetails(\n queryEngine: QueryEngine,\n links: CustomerTodoLink[],\n tenantId: string | null,\n organizationIds: Array<string | null>,\n): Promise<Map<string, LegacyTodoDetail>> {\n const details = new Map<string, LegacyTodoDetail>()\n if (!links.length || !tenantId) return details\n\n const scopedOrganizationIds = organizationIds.filter(\n (value): value is string => typeof value === 'string' && value.trim().length > 0,\n )\n\n const idsBySource = new Map<string, Set<string>>()\n for (const link of links) {\n const source = resolveLegacyTodoSource(link.todoSource)\n const id =\n typeof link.todoId === 'string' && link.todoId.trim().length > 0\n ? link.todoId\n : String(link.todoId ?? '')\n if (!id) continue\n if (!idsBySource.has(source)) idsBySource.set(source, new Set<string>())\n idsBySource.get(source)!.add(id)\n }\n\n for (const [source, idSet] of idsBySource.entries()) {\n const ids = Array.from(idSet)\n if (!ids.length) continue\n if (!isValidEntityIdShape(source)) {\n // A legacy/malformed `todoSource` (e.g. a bare module name written before\n // the value was required to be a `module:entity` id) does not resolve to\n // any real table. Querying it anyway surfaces a raw DB error (\"relation\n // ... does not exist\") as a dashboard-wide schema-mismatch banner instead\n // of just leaving that row's details unresolved.\n logger.warn('Skipping malformed legacy todo source', { component: 'todoCompatibility', source })\n continue\n }\n try {\n const result = await queryEngine.query<Record<string, unknown>>(source as EntityId, {\n tenantId,\n organizationIds: scopedOrganizationIds.length > 0 ? scopedOrganizationIds : undefined,\n filters: { id: { $in: ids } },\n includeCustomFields: ['priority', 'due_at', 'severity', 'description'],\n page: { page: 1, pageSize: Math.max(ids.length, 1) },\n })\n\n for (const item of result.items ?? []) {\n if (!item || typeof item !== 'object') continue\n const record = item as Record<string, unknown>\n const rawId =\n typeof record.id === 'string' && record.id.trim().length > 0\n ? record.id\n : String(record.id ?? '')\n if (!rawId) continue\n\n const isDone = (() => {\n const direct = parseBooleanFromUnknown(record.is_done)\n if (direct !== null) return direct\n const custom = parseBooleanFromUnknown(readCustomField(record, 'is_done'))\n if (custom !== null) return custom\n const generic = parseBooleanFromUnknown(record.isDone)\n if (generic !== null) return generic\n return parseBooleanFromUnknown(readCustomField(record, 'isDone'))\n })()\n\n const priority = (() => {\n const candidates = [\n record['cf:priority'],\n record['cf_priority'],\n record.priority,\n readCustomField(record, 'priority'),\n ]\n for (const candidate of candidates) {\n const parsed = parseNumber(candidate)\n if (parsed !== null) return parsed\n }\n return null\n })()\n\n const severity = (() => {\n const candidates = [\n record['cf:severity'],\n record['cf_severity'],\n record.severity,\n readCustomField(record, 'severity'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const description = (() => {\n const candidates = [\n record.description,\n record['cf:description'],\n record['cf_description'],\n readCustomField(record, 'description'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const dueAt = (() => {\n const candidates = [\n record.due_at,\n record.dueAt,\n record['cf:due_at'],\n record['cf_due_at'],\n readCustomField(record, 'due_at'),\n readCustomField(record, 'dueAt'),\n ]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n\n const organizationId = (() => {\n const candidates = [\n record.organization_id,\n record.organizationId,\n record['cf:organization_id'],\n record['cf_organization_id'],\n readCustomField(record, 'organization_id'),\n readCustomField(record, 'organizationId'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const customValues: Record<string, unknown> = {}\n const assignCustomValue = (key: string, value: unknown) => {\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n customValues[trimmedKey] = value === undefined ? null : value\n }\n for (const [rawKey, rawValue] of Object.entries(record)) {\n if (rawKey.startsWith('cf:')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n } else if (rawKey.startsWith('cf_')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n }\n }\n const nestedCustom = record.custom ?? record.customFields ?? record.cf\n if (nestedCustom && typeof nestedCustom === 'object') {\n for (const [key, value] of Object.entries(nestedCustom as Record<string, unknown>)) {\n assignCustomValue(key, value)\n }\n }\n\n const updatedAt = (() => {\n const candidates = [record.updated_at, record.updatedAt]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n\n details.set(`${source}:${rawId}`, {\n title: extractTodoTitle(record),\n isDone,\n priority,\n severity,\n description,\n dueAt,\n organizationId,\n updatedAt,\n customValues: Object.keys(customValues).length > 0 ? customValues : null,\n })\n }\n } catch (err) {\n logger.warn('Failed to resolve todo details', { component: 'todoCompatibility', source, err })\n continue\n }\n }\n\n return details\n}\n\nexport async function listLegacyTodoRows(\n em: EntityManager,\n queryEngine: QueryEngine,\n tenantId: string,\n organizationIds: string[] | null,\n entityId: string | undefined,\n options?: { limit?: number | null; isUnrestricted?: boolean },\n): Promise<CustomerTodoRow[]> {\n if (isCollapsedRestrictedOrganizationScope(organizationIds, options?.isUnrestricted)) {\n return []\n }\n const where: Record<string, unknown> = { tenantId }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (entityId) {\n where.entity = entityId\n }\n\n const findOptions: Record<string, unknown> = {\n populate: ['entity'],\n orderBy: { createdAt: 'desc' },\n }\n if (typeof options?.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0) {\n findOptions.limit = options.limit\n }\n\n const links = await em.find(CustomerTodoLink, where, findOptions as any)\n const details = await resolveLegacyTodoDetails(\n queryEngine,\n links,\n tenantId,\n organizationIds ?? [],\n )\n\n return links.map((link) => {\n const source = resolveLegacyTodoSource(link.todoSource)\n return mapLegacyTodoLinkToRow(\n link,\n details.get(`${source}:${link.todoId}`) ?? null,\n )\n })\n}\n\nexport async function listCanonicalTodoRows(\n em: EntityManager,\n container: CustomersContainerLike,\n auth: CustomersAuthLike,\n selectedOrganizationId: string | null,\n organizationIds: string[] | null,\n options?: {\n entityId?: string\n includeDeleted?: boolean\n source?: string | string[] | null\n pagination?: ListTodosPagination | null\n searchText?: string | null\n limit?: number | null\n isUnrestricted?: boolean\n },\n): Promise<CanonicalTodoListResult> {\n if (isCollapsedRestrictedOrganizationScope(organizationIds, options?.isUnrestricted)) {\n return emptyCanonicalTodoList()\n }\n const where: Record<string, unknown> = {\n tenantId: auth.tenantId,\n interactionType: 'task',\n }\n if (!options?.includeDeleted) {\n where.deletedAt = null\n }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (options?.entityId) {\n where.entity = options.entityId\n }\n if (options?.source) {\n where.source = Array.isArray(options.source) ? { $in: options.source } : options.source\n }\n const trimmedSearch =\n typeof options?.searchText === 'string' ? options.searchText.trim() : ''\n if (trimmedSearch.length > 0) {\n const pattern = `%${trimmedSearch}%`\n where.$or = [\n { title: { $ilike: pattern } },\n { body: { $ilike: pattern } },\n ]\n }\n\n const findOptions: Record<string, unknown> = {\n orderBy: { createdAt: 'desc' },\n }\n const pagination = options?.pagination ?? null\n if (pagination) {\n findOptions.offset = Math.max(0, (pagination.page - 1) * pagination.pageSize)\n findOptions.limit = pagination.pageSize\n } else if (\n typeof options?.limit === 'number' &&\n Number.isFinite(options.limit) &&\n options.limit > 0\n ) {\n findOptions.limit = options.limit\n }\n\n let interactions: CustomerInteraction[]\n let total: number\n if (pagination) {\n const [rows, count] = await em.findAndCount(CustomerInteraction, where, findOptions as any)\n interactions = rows\n total = count\n } else {\n interactions = await em.find(CustomerInteraction, where, findOptions as any)\n total = interactions.filter((interaction) => !interaction.deletedAt).length\n }\n const activeInteractions = interactions.filter((interaction) => !interaction.deletedAt)\n const groups = new Map<string, CustomerInteraction[]>()\n\n for (const interaction of activeInteractions) {\n const organizationId =\n typeof interaction.organizationId === 'string' && interaction.organizationId.trim().length > 0\n ? interaction.organizationId\n : selectedOrganizationId ?? ''\n const bucket = groups.get(organizationId)\n if (bucket) {\n bucket.push(interaction)\n } else {\n groups.set(organizationId, [interaction])\n }\n }\n\n const rowByInteractionId = new Map<string, CustomerTodoRow>()\n\n for (const [groupOrganizationId, groupedInteractions] of groups.entries()) {\n const scopedOrganizationId = groupOrganizationId.length > 0 ? groupOrganizationId : null\n const hydrated = await hydrateCanonicalInteractions({\n em,\n container,\n auth: {\n ...auth,\n orgId: auth.orgId ?? null,\n },\n selectedOrganizationId: scopedOrganizationId,\n interactions: groupedInteractions,\n })\n const customerIds = Array.from(\n new Set(\n hydrated\n .map((interaction) => interaction.entityId ?? null)\n .filter((value): value is string => !!value),\n ),\n )\n const customerSummaries = await loadCustomerSummaries(\n em,\n customerIds,\n auth.tenantId,\n scopedOrganizationId,\n )\n\n for (const interaction of hydrated) {\n rowByInteractionId.set(\n interaction.id,\n mapInteractionRecordToTodoRow(\n interaction,\n interaction.entityId ? customerSummaries.get(interaction.entityId) ?? null : null,\n ),\n )\n }\n }\n\n return {\n items: activeInteractions\n .map((interaction) => rowByInteractionId.get(interaction.id) ?? null)\n .filter((row): row is CustomerTodoRow => !!row),\n bridgeIds: new Set(interactions.map((interaction) => interaction.id)),\n total,\n }\n}\n\nexport function mapLegacyTodoLinkToRow(\n link: CustomerTodoLink,\n detail: LegacyTodoDetail | null,\n customerOverride?: CustomerSummary | null,\n): CustomerTodoRow {\n const entity = customerOverride ?? {\n id: typeof link.entity === 'string' ? null : link.entity.id,\n displayName: typeof link.entity === 'string' ? null : link.entity.displayName ?? null,\n kind: typeof link.entity === 'string' ? null : link.entity.kind ?? null,\n }\n\n return {\n id: link.id,\n todoId: link.todoId,\n todoSource: resolveLegacyTodoSource(link.todoSource),\n todoTitle: detail?.title ?? null,\n todoIsDone: detail?.isDone ?? null,\n todoPriority: detail?.priority ?? null,\n todoSeverity: detail?.severity ?? null,\n todoDescription: detail?.description ?? null,\n todoDueAt: detail?.dueAt ?? null,\n todoCustomValues: detail?.customValues ?? null,\n todoOrganizationId: detail?.organizationId ?? link.organizationId ?? null,\n todoUpdatedAt: detail?.updatedAt ?? null,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n createdAt: link.createdAt.toISOString(),\n _integrations: undefined,\n customer: entity,\n }\n}\n\nexport function mapInteractionRecordToTodoRow(\n interaction: InteractionRecord,\n customer: CustomerSummary | null,\n options?: { rowId?: string | null; todoSource?: string | null },\n): CustomerTodoRow {\n const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }\n if (interaction.priority !== undefined && customValues.priority === undefined) {\n customValues.priority = interaction.priority ?? null\n }\n if (interaction.body !== undefined && customValues.description === undefined) {\n customValues.description = interaction.body ?? null\n }\n if (interaction.scheduledAt !== undefined && customValues.due_at === undefined) {\n customValues.due_at = interaction.scheduledAt ?? null\n }\n\n return {\n id:\n typeof options?.rowId === 'string' && options.rowId.trim().length > 0\n ? options.rowId\n : interaction.id,\n todoId: interaction.id,\n todoSource:\n typeof options?.todoSource === 'string' && options.todoSource.trim().length > 0\n ? options.todoSource\n : CUSTOMER_INTERACTION_TASK_SOURCE,\n todoTitle: interaction.title ?? null,\n todoIsDone: interaction.status === 'done',\n todoPriority: interaction.priority ?? null,\n todoSeverity:\n typeof customValues.severity === 'string' && customValues.severity.trim().length > 0\n ? customValues.severity.trim()\n : null,\n todoDescription: interaction.body ?? null,\n todoDueAt: interaction.scheduledAt ?? null,\n todoTimezone: interaction.timezone ?? null,\n todoCustomValues: Object.keys(customValues).length > 0 ? customValues : null,\n todoOrganizationId: interaction.organizationId ?? null,\n todoUpdatedAt: interaction.updatedAt ?? null,\n organizationId: interaction.organizationId ?? '',\n tenantId: interaction.tenantId ?? '',\n createdAt: interaction.createdAt,\n externalHref: resolveExampleIntegrationHref(interaction),\n _integrations: interaction._integrations ?? undefined,\n customer: customer ?? {\n id: interaction.entityId ?? null,\n displayName: null,\n kind: null,\n },\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport {\n CustomerInteraction,\n CustomerTodoLink,\n} from '../data/entities'\nimport type { InteractionRecord } from './interactionCompatibility'\nimport {\n CUSTOMER_INTERACTION_TASK_SOURCE,\n CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,\n EXAMPLE_TODO_SOURCE,\n resolveExampleIntegrationHref,\n} from './interactionCompatibility'\nimport { hydrateCanonicalInteractions, loadCustomerSummaries } from './interactionReadModel'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport async function countCustomerTodos(\n em: EntityManager,\n scope: { entityId: string; tenantId: string; organizationId: string },\n unified: boolean,\n): Promise<number> {\n const entityScope = {\n entity: scope.entityId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n if (unified) {\n return em.count(CustomerInteraction, { ...entityScope, interactionType: 'task', deletedAt: null })\n }\n\n const adapterScope = {\n ...entityScope,\n interactionType: 'task',\n source: CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,\n }\n const adapterIds = em.createQueryBuilder(CustomerInteraction).select('id').where(adapterScope)\n const [legacyCount, adapterCount] = await Promise.all([\n em.count(CustomerTodoLink, { ...entityScope, todoId: { $nin: adapterIds } }),\n em.count(CustomerInteraction, { ...adapterScope, deletedAt: null }),\n ])\n return legacyCount + adapterCount\n}\n\nexport type CustomerTodoRow = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n todoIsDone: boolean | null\n todoPriority?: number | null\n todoSeverity?: string | null\n todoDescription?: string | null\n todoDueAt?: string | null\n todoTimezone?: string | null\n todoCustomValues?: Record<string, unknown> | null\n todoOrganizationId: string | null\n todoUpdatedAt?: string | null\n organizationId: string\n tenantId: string\n createdAt: string\n externalHref?: string | null\n _integrations?: Record<string, unknown>\n customer: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nexport type LegacyTodoDetail = {\n title: string | null\n isDone: boolean | null\n priority: number | null\n severity: string | null\n description: string | null\n dueAt: string | null\n organizationId: string | null\n updatedAt: string | null\n customValues: Record<string, unknown> | null\n}\n\ntype CustomerSummary = {\n id: string | null\n displayName: string | null\n kind: string | null\n}\n\ntype CustomersAuthLike = {\n tenantId: string | null\n orgId?: string | null\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}\n\ntype CustomersContainerLike = {\n resolve: (name: string) => unknown\n}\n\nexport type CanonicalTodoListResult = {\n items: CustomerTodoRow[]\n bridgeIds: Set<string>\n total: number\n}\n\nfunction emptyCanonicalTodoList(): CanonicalTodoListResult {\n return { items: [], bridgeIds: new Set(), total: 0 }\n}\n\nfunction isCollapsedRestrictedOrganizationScope(\n organizationIds: string[] | null | undefined,\n isUnrestricted: boolean | undefined,\n): boolean {\n return isUnrestricted !== true && (!organizationIds || organizationIds.length === 0)\n}\n\nexport type ListTodosPagination = { page: number; pageSize: number }\n\nfunction resolveLegacyTodoSource(source: string | null | undefined): string {\n return typeof source === 'string' && source.trim().length > 0\n ? source\n : EXAMPLE_TODO_SOURCE\n}\n\nfunction extractTodoTitle(record: Record<string, unknown>): string | null {\n // `task_name` / `taskName` carry a workflow user task's name; the query engine\n // returns the raw column on the ORM path and the camelCase property on the\n // indexed one, so both spellings are read.\n const candidates = ['title', 'subject', 'name', 'task_name', 'taskName', 'summary', 'text', 'description']\n for (const key of candidates) {\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 parseNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) return parsed\n }\n return null\n}\n\nfunction parseDateValue(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = new Date(trimmed)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction readCustomField(record: Record<string, unknown>, key: string): unknown {\n const custom = record.custom ?? record.customFields ?? record.cf\n if (custom && typeof custom === 'object') {\n const bucket = custom as Record<string, unknown>\n if (key in bucket) return bucket[key]\n }\n return undefined\n}\n\nexport function normalizeTodoSearch(value: string | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim().toLowerCase()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport function sortTodoRows(rows: CustomerTodoRow[]): CustomerTodoRow[] {\n return [...rows].sort((left, right) => {\n const leftTime = new Date(left.createdAt).getTime()\n const rightTime = new Date(right.createdAt).getTime()\n if (leftTime === rightTime) {\n return right.id.localeCompare(left.id)\n }\n return rightTime - leftTime\n })\n}\n\nexport function filterTodoRows(rows: CustomerTodoRow[], search: string | null): CustomerTodoRow[] {\n if (!search) return rows\n return rows.filter((row) => {\n const haystack = [\n row.customer.displayName,\n row.todoTitle,\n row.todoDescription,\n ]\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(search)\n })\n}\n\nexport function paginateTodoRows(\n rows: CustomerTodoRow[],\n page: number,\n pageSize: number,\n exportAll: boolean,\n): { items: CustomerTodoRow[]; total: number; page: number; pageSize: number; totalPages: number } {\n const total = rows.length\n if (exportAll) {\n return {\n items: rows,\n total,\n page: 1,\n pageSize: total,\n totalPages: 1,\n }\n }\n const start = (page - 1) * pageSize\n return {\n items: rows.slice(start, start + pageSize),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n }\n}\n\nexport async function resolveLegacyTodoDetails(\n queryEngine: QueryEngine,\n links: CustomerTodoLink[],\n tenantId: string | null,\n organizationIds: Array<string | null>,\n): Promise<Map<string, LegacyTodoDetail>> {\n const details = new Map<string, LegacyTodoDetail>()\n if (!links.length || !tenantId) return details\n\n const scopedOrganizationIds = organizationIds.filter(\n (value): value is string => typeof value === 'string' && value.trim().length > 0,\n )\n\n const idsBySource = new Map<string, Set<string>>()\n for (const link of links) {\n const source = resolveLegacyTodoSource(link.todoSource)\n const id =\n typeof link.todoId === 'string' && link.todoId.trim().length > 0\n ? link.todoId\n : String(link.todoId ?? '')\n if (!id) continue\n if (!idsBySource.has(source)) idsBySource.set(source, new Set<string>())\n idsBySource.get(source)!.add(id)\n }\n\n for (const [source, idSet] of idsBySource.entries()) {\n const ids = Array.from(idSet)\n if (!ids.length) continue\n if (!isValidEntityIdShape(source)) {\n // A legacy/malformed `todoSource` (e.g. a bare module name written before\n // the value was required to be a `module:entity` id) does not resolve to\n // any real table. Querying it anyway surfaces a raw DB error (\"relation\n // ... does not exist\") as a dashboard-wide schema-mismatch banner instead\n // of just leaving that row's details unresolved.\n logger.warn('Skipping malformed legacy todo source', { component: 'todoCompatibility', source })\n continue\n }\n try {\n const result = await queryEngine.query<Record<string, unknown>>(source as EntityId, {\n tenantId,\n organizationIds: scopedOrganizationIds.length > 0 ? scopedOrganizationIds : undefined,\n filters: { id: { $in: ids } },\n includeCustomFields: ['priority', 'due_at', 'severity', 'description'],\n page: { page: 1, pageSize: Math.max(ids.length, 1) },\n })\n\n for (const item of result.items ?? []) {\n if (!item || typeof item !== 'object') continue\n const record = item as Record<string, unknown>\n const rawId =\n typeof record.id === 'string' && record.id.trim().length > 0\n ? record.id\n : String(record.id ?? '')\n if (!rawId) continue\n\n const isDone = (() => {\n const direct = parseBooleanFromUnknown(record.is_done)\n if (direct !== null) return direct\n const custom = parseBooleanFromUnknown(readCustomField(record, 'is_done'))\n if (custom !== null) return custom\n const generic = parseBooleanFromUnknown(record.isDone)\n if (generic !== null) return generic\n return parseBooleanFromUnknown(readCustomField(record, 'isDone'))\n })()\n\n const priority = (() => {\n const candidates = [\n record['cf:priority'],\n record['cf_priority'],\n record.priority,\n readCustomField(record, 'priority'),\n ]\n for (const candidate of candidates) {\n const parsed = parseNumber(candidate)\n if (parsed !== null) return parsed\n }\n return null\n })()\n\n const severity = (() => {\n const candidates = [\n record['cf:severity'],\n record['cf_severity'],\n record.severity,\n readCustomField(record, 'severity'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const description = (() => {\n const candidates = [\n record.description,\n record['cf:description'],\n record['cf_description'],\n readCustomField(record, 'description'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const dueAt = (() => {\n const candidates = [\n record.due_at,\n record.dueAt,\n record['cf:due_at'],\n record['cf_due_at'],\n readCustomField(record, 'due_at'),\n readCustomField(record, 'dueAt'),\n ]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n\n const organizationId = (() => {\n const candidates = [\n record.organization_id,\n record.organizationId,\n record['cf:organization_id'],\n record['cf_organization_id'],\n readCustomField(record, 'organization_id'),\n readCustomField(record, 'organizationId'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const customValues: Record<string, unknown> = {}\n const assignCustomValue = (key: string, value: unknown) => {\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n customValues[trimmedKey] = value === undefined ? null : value\n }\n for (const [rawKey, rawValue] of Object.entries(record)) {\n if (rawKey.startsWith('cf:')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n } else if (rawKey.startsWith('cf_')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n }\n }\n const nestedCustom = record.custom ?? record.customFields ?? record.cf\n if (nestedCustom && typeof nestedCustom === 'object') {\n for (const [key, value] of Object.entries(nestedCustom as Record<string, unknown>)) {\n assignCustomValue(key, value)\n }\n }\n\n const updatedAt = (() => {\n const candidates = [record.updated_at, record.updatedAt]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n\n details.set(`${source}:${rawId}`, {\n title: extractTodoTitle(record),\n isDone,\n priority,\n severity,\n description,\n dueAt,\n organizationId,\n updatedAt,\n customValues: Object.keys(customValues).length > 0 ? customValues : null,\n })\n }\n } catch (err) {\n logger.warn('Failed to resolve todo details', { component: 'todoCompatibility', source, err })\n continue\n }\n }\n\n return details\n}\n\nexport async function listLegacyTodoRows(\n em: EntityManager,\n queryEngine: QueryEngine,\n tenantId: string,\n organizationIds: string[] | null,\n entityId: string | undefined,\n options?: { limit?: number | null; isUnrestricted?: boolean },\n): Promise<CustomerTodoRow[]> {\n if (isCollapsedRestrictedOrganizationScope(organizationIds, options?.isUnrestricted)) {\n return []\n }\n const where: Record<string, unknown> = { tenantId }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (entityId) {\n where.entity = entityId\n }\n\n const findOptions: Record<string, unknown> = {\n populate: ['entity'],\n orderBy: { createdAt: 'desc' },\n }\n if (typeof options?.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0) {\n findOptions.limit = options.limit\n }\n\n const links = await em.find(CustomerTodoLink, where, findOptions as any)\n const details = await resolveLegacyTodoDetails(\n queryEngine,\n links,\n tenantId,\n organizationIds ?? [],\n )\n\n return links.map((link) => {\n const source = resolveLegacyTodoSource(link.todoSource)\n return mapLegacyTodoLinkToRow(\n link,\n details.get(`${source}:${link.todoId}`) ?? null,\n )\n })\n}\n\nexport async function listCanonicalTodoRows(\n em: EntityManager,\n container: CustomersContainerLike,\n auth: CustomersAuthLike,\n selectedOrganizationId: string | null,\n organizationIds: string[] | null,\n options?: {\n entityId?: string\n includeDeleted?: boolean\n source?: string | string[] | null\n pagination?: ListTodosPagination | null\n searchText?: string | null\n limit?: number | null\n isUnrestricted?: boolean\n },\n): Promise<CanonicalTodoListResult> {\n if (isCollapsedRestrictedOrganizationScope(organizationIds, options?.isUnrestricted)) {\n return emptyCanonicalTodoList()\n }\n const where: Record<string, unknown> = {\n tenantId: auth.tenantId,\n interactionType: 'task',\n }\n if (!options?.includeDeleted) {\n where.deletedAt = null\n }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (options?.entityId) {\n where.entity = options.entityId\n }\n if (options?.source) {\n where.source = Array.isArray(options.source) ? { $in: options.source } : options.source\n }\n const trimmedSearch =\n typeof options?.searchText === 'string' ? options.searchText.trim() : ''\n if (trimmedSearch.length > 0) {\n const pattern = `%${trimmedSearch}%`\n where.$or = [\n { title: { $ilike: pattern } },\n { body: { $ilike: pattern } },\n ]\n }\n\n const findOptions: Record<string, unknown> = {\n orderBy: { createdAt: 'desc' },\n }\n const pagination = options?.pagination ?? null\n if (pagination) {\n findOptions.offset = Math.max(0, (pagination.page - 1) * pagination.pageSize)\n findOptions.limit = pagination.pageSize\n } else if (\n typeof options?.limit === 'number' &&\n Number.isFinite(options.limit) &&\n options.limit > 0\n ) {\n findOptions.limit = options.limit\n }\n\n let interactions: CustomerInteraction[]\n let total: number\n if (pagination) {\n const [rows, count] = await em.findAndCount(CustomerInteraction, where, findOptions as any)\n interactions = rows\n total = count\n } else {\n interactions = await em.find(CustomerInteraction, where, findOptions as any)\n total = interactions.filter((interaction) => !interaction.deletedAt).length\n }\n const activeInteractions = interactions.filter((interaction) => !interaction.deletedAt)\n const groups = new Map<string, CustomerInteraction[]>()\n\n for (const interaction of activeInteractions) {\n const organizationId =\n typeof interaction.organizationId === 'string' && interaction.organizationId.trim().length > 0\n ? interaction.organizationId\n : selectedOrganizationId ?? ''\n const bucket = groups.get(organizationId)\n if (bucket) {\n bucket.push(interaction)\n } else {\n groups.set(organizationId, [interaction])\n }\n }\n\n const rowByInteractionId = new Map<string, CustomerTodoRow>()\n\n for (const [groupOrganizationId, groupedInteractions] of groups.entries()) {\n const scopedOrganizationId = groupOrganizationId.length > 0 ? groupOrganizationId : null\n const hydrated = await hydrateCanonicalInteractions({\n em,\n container,\n auth: {\n ...auth,\n orgId: auth.orgId ?? null,\n },\n selectedOrganizationId: scopedOrganizationId,\n interactions: groupedInteractions,\n })\n const customerIds = Array.from(\n new Set(\n hydrated\n .map((interaction) => interaction.entityId ?? null)\n .filter((value): value is string => !!value),\n ),\n )\n const customerSummaries = await loadCustomerSummaries(\n em,\n customerIds,\n auth.tenantId,\n scopedOrganizationId,\n )\n\n for (const interaction of hydrated) {\n rowByInteractionId.set(\n interaction.id,\n mapInteractionRecordToTodoRow(\n interaction,\n interaction.entityId ? customerSummaries.get(interaction.entityId) ?? null : null,\n ),\n )\n }\n }\n\n return {\n items: activeInteractions\n .map((interaction) => rowByInteractionId.get(interaction.id) ?? null)\n .filter((row): row is CustomerTodoRow => !!row),\n bridgeIds: new Set(interactions.map((interaction) => interaction.id)),\n total,\n }\n}\n\nexport function mapLegacyTodoLinkToRow(\n link: CustomerTodoLink,\n detail: LegacyTodoDetail | null,\n customerOverride?: CustomerSummary | null,\n): CustomerTodoRow {\n const entity = customerOverride ?? {\n id: typeof link.entity === 'string' ? null : link.entity.id,\n displayName: typeof link.entity === 'string' ? null : link.entity.displayName ?? null,\n kind: typeof link.entity === 'string' ? null : link.entity.kind ?? null,\n }\n\n return {\n id: link.id,\n todoId: link.todoId,\n todoSource: resolveLegacyTodoSource(link.todoSource),\n todoTitle: detail?.title ?? null,\n todoIsDone: detail?.isDone ?? null,\n todoPriority: detail?.priority ?? null,\n todoSeverity: detail?.severity ?? null,\n todoDescription: detail?.description ?? null,\n todoDueAt: detail?.dueAt ?? null,\n todoCustomValues: detail?.customValues ?? null,\n todoOrganizationId: detail?.organizationId ?? link.organizationId ?? null,\n todoUpdatedAt: detail?.updatedAt ?? null,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n createdAt: link.createdAt.toISOString(),\n _integrations: undefined,\n customer: entity,\n }\n}\n\nexport function mapInteractionRecordToTodoRow(\n interaction: InteractionRecord,\n customer: CustomerSummary | null,\n options?: { rowId?: string | null; todoSource?: string | null },\n): CustomerTodoRow {\n const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }\n if (interaction.priority !== undefined && customValues.priority === undefined) {\n customValues.priority = interaction.priority ?? null\n }\n if (interaction.body !== undefined && customValues.description === undefined) {\n customValues.description = interaction.body ?? null\n }\n if (interaction.scheduledAt !== undefined && customValues.due_at === undefined) {\n customValues.due_at = interaction.scheduledAt ?? null\n }\n\n return {\n id:\n typeof options?.rowId === 'string' && options.rowId.trim().length > 0\n ? options.rowId\n : interaction.id,\n todoId: interaction.id,\n todoSource:\n typeof options?.todoSource === 'string' && options.todoSource.trim().length > 0\n ? options.todoSource\n : CUSTOMER_INTERACTION_TASK_SOURCE,\n todoTitle: interaction.title ?? null,\n todoIsDone: interaction.status === 'done',\n todoPriority: interaction.priority ?? null,\n todoSeverity:\n typeof customValues.severity === 'string' && customValues.severity.trim().length > 0\n ? customValues.severity.trim()\n : null,\n todoDescription: interaction.body ?? null,\n todoDueAt: interaction.scheduledAt ?? null,\n todoTimezone: interaction.timezone ?? null,\n todoCustomValues: Object.keys(customValues).length > 0 ? customValues : null,\n todoOrganizationId: interaction.organizationId ?? null,\n todoUpdatedAt: interaction.updatedAt ?? null,\n organizationId: interaction.organizationId ?? '',\n tenantId: interaction.tenantId ?? '',\n createdAt: interaction.createdAt,\n externalHref: resolveExampleIntegrationHref(interaction),\n _integrations: interaction._integrations ?? undefined,\n customer: customer ?? {\n id: interaction.entityId ?? null,\n displayName: null,\n kind: null,\n },\n }\n}\n"],
5
+ "mappings": "AAGA,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B,6BAA6B;AACpE,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AAEvC,eAAsB,mBACpB,IACA,OACA,SACiB;AACjB,QAAM,cAAc;AAAA,IAClB,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,EACxB;AACA,MAAI,SAAS;AACX,WAAO,GAAG,MAAM,qBAAqB,EAAE,GAAG,aAAa,iBAAiB,QAAQ,WAAW,KAAK,CAAC;AAAA,EACnG;AAEA,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,iBAAiB;AAAA,IACjB,QAAQ;AAAA,EACV;AACA,QAAM,aAAa,GAAG,mBAAmB,mBAAmB,EAAE,OAAO,IAAI,EAAE,MAAM,YAAY;AAC7F,QAAM,CAAC,aAAa,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,IACpD,GAAG,MAAM,kBAAkB,EAAE,GAAG,aAAa,QAAQ,EAAE,MAAM,WAAW,EAAE,CAAC;AAAA,IAC3E,GAAG,MAAM,qBAAqB,EAAE,GAAG,cAAc,WAAW,KAAK,CAAC;AAAA,EACpE,CAAC;AACD,SAAO,cAAc;AACvB;AAgEA,SAAS,yBAAkD;AACzD,SAAO,EAAE,OAAO,CAAC,GAAG,WAAW,oBAAI,IAAI,GAAG,OAAO,EAAE;AACrD;AAEA,SAAS,uCACP,iBACA,gBACS;AACT,SAAO,mBAAmB,SAAS,CAAC,mBAAmB,gBAAgB,WAAW;AACpF;AAIA,SAAS,wBAAwB,QAA2C;AAC1E,SAAO,OAAO,WAAW,YAAY,OAAO,KAAK,EAAE,SAAS,IACxD,SACA;AACN;AAEA,SAAS,iBAAiB,QAAgD;AAIxE,QAAM,aAAa,CAAC,SAAS,WAAW,QAAQ,aAAa,YAAY,WAAW,QAAQ,aAAa;AACzG,aAAW,OAAO,YAAY;AAC5B,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,YAAY,OAA+B;AAClD,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,UAAM,SAAS,OAAO,OAAO;AAC7B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAA+B;AACrD,MAAI,iBAAiB,MAAM;AACzB,WAAO,OAAO,MAAM,MAAM,QAAQ,CAAC,IAAI,OAAO,MAAM,YAAY;AAAA,EAClE;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,UAAM,SAAS,IAAI,KAAK,OAAO;AAC/B,WAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,IAAI,OAAO,OAAO,YAAY;AAAA,EACpE;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,QAAiC,KAAsB;AAC9E,QAAM,SAAS,OAAO,UAAU,OAAO,gBAAgB,OAAO;AAC9D,MAAI,UAAU,OAAO,WAAW,UAAU;AACxC,UAAM,SAAS;AACf,QAAI,OAAO,OAAQ,QAAO,OAAO,GAAG;AAAA,EACtC;AACA,SAAO;AACT;AAEO,SAAS,oBAAoB,OAA0C;AAC5E,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAEO,SAAS,aAAa,MAA4C;AACvE,SAAO,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,UAAU;AACrC,UAAM,WAAW,IAAI,KAAK,KAAK,SAAS,EAAE,QAAQ;AAClD,UAAM,YAAY,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AACpD,QAAI,aAAa,WAAW;AAC1B,aAAO,MAAM,GAAG,cAAc,KAAK,EAAE;AAAA,IACvC;AACA,WAAO,YAAY;AAAA,EACrB,CAAC;AACH;AAEO,SAAS,eAAe,MAAyB,QAA0C;AAChG,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,KAAK,OAAO,CAAC,QAAQ;AAC1B,UAAM,WAAW;AAAA,MACf,IAAI,SAAS;AAAA,MACb,IAAI;AAAA,MACJ,IAAI;AAAA,IACN,EACG,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,CAAC,EACvF,KAAK,GAAG,EACR,YAAY;AACf,WAAO,SAAS,SAAS,MAAM;AAAA,EACjC,CAAC;AACH;AAEO,SAAS,iBACd,MACA,MACA,UACA,WACiG;AACjG,QAAM,QAAQ,KAAK;AACnB,MAAI,WAAW;AACb,WAAO;AAAA,MACL,OAAO;AAAA,MACP;AAAA,MACA,MAAM;AAAA,MACN,UAAU;AAAA,MACV,YAAY;AAAA,IACd;AAAA,EACF;AACA,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO;AAAA,IACL,OAAO,KAAK,MAAM,OAAO,QAAQ,QAAQ;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,QAAQ,CAAC;AAAA,EACrD;AACF;AAEA,eAAsB,yBACpB,aACA,OACA,UACA,iBACwC;AACxC,QAAM,UAAU,oBAAI,IAA8B;AAClD,MAAI,CAAC,MAAM,UAAU,CAAC,SAAU,QAAO;AAEvC,QAAM,wBAAwB,gBAAgB;AAAA,IAC5C,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAAA,EACjF;AAEA,QAAM,cAAc,oBAAI,IAAyB;AACjD,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,wBAAwB,KAAK,UAAU;AACtD,UAAM,KACJ,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,IAC3D,KAAK,SACL,OAAO,KAAK,UAAU,EAAE;AAC9B,QAAI,CAAC,GAAI;AACT,QAAI,CAAC,YAAY,IAAI,MAAM,EAAG,aAAY,IAAI,QAAQ,oBAAI,IAAY,CAAC;AACvE,gBAAY,IAAI,MAAM,EAAG,IAAI,EAAE;AAAA,EACjC;AAEA,aAAW,CAAC,QAAQ,KAAK,KAAK,YAAY,QAAQ,GAAG;AACnD,UAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,QAAI,CAAC,IAAI,OAAQ;AACjB,QAAI,CAAC,qBAAqB,MAAM,GAAG;AAMjC,aAAO,KAAK,yCAAyC,EAAE,WAAW,qBAAqB,OAAO,CAAC;AAC/F;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,YAAY,MAA+B,QAAoB;AAAA,QAClF;AAAA,QACA,iBAAiB,sBAAsB,SAAS,IAAI,wBAAwB;AAAA,QAC5E,SAAS,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;AAAA,QAC5B,qBAAqB,CAAC,YAAY,UAAU,YAAY,aAAa;AAAA,QACrE,MAAM,EAAE,MAAM,GAAG,UAAU,KAAK,IAAI,IAAI,QAAQ,CAAC,EAAE;AAAA,MACrD,CAAC;AAED,iBAAW,QAAQ,OAAO,SAAS,CAAC,GAAG;AACrC,YAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,cAAM,SAAS;AACf,cAAM,QACJ,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,KAAK,EAAE,SAAS,IACvD,OAAO,KACP,OAAO,OAAO,MAAM,EAAE;AAC5B,YAAI,CAAC,MAAO;AAEZ,cAAM,UAAU,MAAM;AACpB,gBAAM,SAAS,wBAAwB,OAAO,OAAO;AACrD,cAAI,WAAW,KAAM,QAAO;AAC5B,gBAAM,SAAS,wBAAwB,gBAAgB,QAAQ,SAAS,CAAC;AACzE,cAAI,WAAW,KAAM,QAAO;AAC5B,gBAAM,UAAU,wBAAwB,OAAO,MAAM;AACrD,cAAI,YAAY,KAAM,QAAO;AAC7B,iBAAO,wBAAwB,gBAAgB,QAAQ,QAAQ,CAAC;AAAA,QAClE,GAAG;AAEH,cAAM,YAAY,MAAM;AACtB,gBAAM,aAAa;AAAA,YACjB,OAAO,aAAa;AAAA,YACpB,OAAO,aAAa;AAAA,YACpB,OAAO;AAAA,YACP,gBAAgB,QAAQ,UAAU;AAAA,UACpC;AACA,qBAAW,aAAa,YAAY;AAClC,kBAAM,SAAS,YAAY,SAAS;AACpC,gBAAI,WAAW,KAAM,QAAO;AAAA,UAC9B;AACA,iBAAO;AAAA,QACT,GAAG;AAEH,cAAM,YAAY,MAAM;AACtB,gBAAM,aAAa;AAAA,YACjB,OAAO,aAAa;AAAA,YACpB,OAAO,aAAa;AAAA,YACpB,OAAO;AAAA,YACP,gBAAgB,QAAQ,UAAU;AAAA,UACpC;AACA,qBAAW,aAAa,YAAY;AAClC,gBAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,GAAG;AAChE,qBAAO,UAAU,KAAK;AAAA,YACxB;AAAA,UACF;AACA,iBAAO;AAAA,QACT,GAAG;AAEH,cAAM,eAAe,MAAM;AACzB,gBAAM,aAAa;AAAA,YACjB,OAAO;AAAA,YACP,OAAO,gBAAgB;AAAA,YACvB,OAAO,gBAAgB;AAAA,YACvB,gBAAgB,QAAQ,aAAa;AAAA,UACvC;AACA,qBAAW,aAAa,YAAY;AAClC,gBAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,GAAG;AAChE,qBAAO,UAAU,KAAK;AAAA,YACxB;AAAA,UACF;AACA,iBAAO;AAAA,QACT,GAAG;AAEH,cAAM,SAAS,MAAM;AACnB,gBAAM,aAAa;AAAA,YACjB,OAAO;AAAA,YACP,OAAO;AAAA,YACP,OAAO,WAAW;AAAA,YAClB,OAAO,WAAW;AAAA,YAClB,gBAAgB,QAAQ,QAAQ;AAAA,YAChC,gBAAgB,QAAQ,OAAO;AAAA,UACjC;AACA,qBAAW,aAAa,YAAY;AAClC,kBAAM,SAAS,eAAe,SAAS;AACvC,gBAAI,OAAQ,QAAO;AAAA,UACrB;AACA,iBAAO;AAAA,QACT,GAAG;AAEH,cAAM,kBAAkB,MAAM;AAC5B,gBAAM,aAAa;AAAA,YACjB,OAAO;AAAA,YACP,OAAO;AAAA,YACP,OAAO,oBAAoB;AAAA,YAC3B,OAAO,oBAAoB;AAAA,YAC3B,gBAAgB,QAAQ,iBAAiB;AAAA,YACzC,gBAAgB,QAAQ,gBAAgB;AAAA,UAC1C;AACA,qBAAW,aAAa,YAAY;AAClC,gBAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,GAAG;AAChE,qBAAO,UAAU,KAAK;AAAA,YACxB;AAAA,UACF;AACA,iBAAO;AAAA,QACT,GAAG;AAEH,cAAM,eAAwC,CAAC;AAC/C,cAAM,oBAAoB,CAAC,KAAa,UAAmB;AACzD,gBAAM,aAAa,IAAI,KAAK;AAC5B,cAAI,CAAC,WAAW,OAAQ;AACxB,uBAAa,UAAU,IAAI,UAAU,SAAY,OAAO;AAAA,QAC1D;AACA,mBAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,cAAI,OAAO,WAAW,KAAK,GAAG;AAC5B,8BAAkB,OAAO,MAAM,CAAC,GAAG,QAAQ;AAAA,UAC7C,WAAW,OAAO,WAAW,KAAK,GAAG;AACnC,8BAAkB,OAAO,MAAM,CAAC,GAAG,QAAQ;AAAA,UAC7C;AAAA,QACF;AACA,cAAM,eAAe,OAAO,UAAU,OAAO,gBAAgB,OAAO;AACpE,YAAI,gBAAgB,OAAO,iBAAiB,UAAU;AACpD,qBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAuC,GAAG;AAClF,8BAAkB,KAAK,KAAK;AAAA,UAC9B;AAAA,QACF;AAEA,cAAM,aAAa,MAAM;AACvB,gBAAM,aAAa,CAAC,OAAO,YAAY,OAAO,SAAS;AACvD,qBAAW,aAAa,YAAY;AAClC,kBAAM,SAAS,eAAe,SAAS;AACvC,gBAAI,OAAQ,QAAO;AAAA,UACrB;AACA,iBAAO;AAAA,QACT,GAAG;AAEH,gBAAQ,IAAI,GAAG,MAAM,IAAI,KAAK,IAAI;AAAA,UAChC,OAAO,iBAAiB,MAAM;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,QACtE,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,KAAK,kCAAkC,EAAE,WAAW,qBAAqB,QAAQ,IAAI,CAAC;AAC7F;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,mBACpB,IACA,aACA,UACA,iBACA,UACA,SAC4B;AAC5B,MAAI,uCAAuC,iBAAiB,SAAS,cAAc,GAAG;AACpF,WAAO,CAAC;AAAA,EACV;AACA,QAAM,QAAiC,EAAE,SAAS;AAClD,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AACjD,UAAM,iBAAiB,EAAE,KAAK,gBAAgB;AAAA,EAChD;AACA,MAAI,UAAU;AACZ,UAAM,SAAS;AAAA,EACjB;AAEA,QAAM,cAAuC;AAAA,IAC3C,UAAU,CAAC,QAAQ;AAAA,IACnB,SAAS,EAAE,WAAW,OAAO;AAAA,EAC/B;AACA,MAAI,OAAO,SAAS,UAAU,YAAY,OAAO,SAAS,QAAQ,KAAK,KAAK,QAAQ,QAAQ,GAAG;AAC7F,gBAAY,QAAQ,QAAQ;AAAA,EAC9B;AAEA,QAAM,QAAQ,MAAM,GAAG,KAAK,kBAAkB,OAAO,WAAkB;AACvE,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB,CAAC;AAAA,EACtB;AAEA,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,UAAM,SAAS,wBAAwB,KAAK,UAAU;AACtD,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,IAAI,GAAG,MAAM,IAAI,KAAK,MAAM,EAAE,KAAK;AAAA,IAC7C;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,sBACpB,IACA,WACA,MACA,wBACA,iBACA,SASkC;AAClC,MAAI,uCAAuC,iBAAiB,SAAS,cAAc,GAAG;AACpF,WAAO,uBAAuB;AAAA,EAChC;AACA,QAAM,QAAiC;AAAA,IACrC,UAAU,KAAK;AAAA,IACf,iBAAiB;AAAA,EACnB;AACA,MAAI,CAAC,SAAS,gBAAgB;AAC5B,UAAM,YAAY;AAAA,EACpB;AACA,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AACjD,UAAM,iBAAiB,EAAE,KAAK,gBAAgB;AAAA,EAChD;AACA,MAAI,SAAS,UAAU;AACrB,UAAM,SAAS,QAAQ;AAAA,EACzB;AACA,MAAI,SAAS,QAAQ;AACnB,UAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,KAAK,QAAQ,OAAO,IAAI,QAAQ;AAAA,EACnF;AACA,QAAM,gBACJ,OAAO,SAAS,eAAe,WAAW,QAAQ,WAAW,KAAK,IAAI;AACxE,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,UAAU,IAAI,aAAa;AACjC,UAAM,MAAM;AAAA,MACV,EAAE,OAAO,EAAE,QAAQ,QAAQ,EAAE;AAAA,MAC7B,EAAE,MAAM,EAAE,QAAQ,QAAQ,EAAE;AAAA,IAC9B;AAAA,EACF;AAEA,QAAM,cAAuC;AAAA,IAC3C,SAAS,EAAE,WAAW,OAAO;AAAA,EAC/B;AACA,QAAM,aAAa,SAAS,cAAc;AAC1C,MAAI,YAAY;AACd,gBAAY,SAAS,KAAK,IAAI,IAAI,WAAW,OAAO,KAAK,WAAW,QAAQ;AAC5E,gBAAY,QAAQ,WAAW;AAAA,EACjC,WACE,OAAO,SAAS,UAAU,YAC1B,OAAO,SAAS,QAAQ,KAAK,KAC7B,QAAQ,QAAQ,GAChB;AACA,gBAAY,QAAQ,QAAQ;AAAA,EAC9B;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI,YAAY;AACd,UAAM,CAAC,MAAM,KAAK,IAAI,MAAM,GAAG,aAAa,qBAAqB,OAAO,WAAkB;AAC1F,mBAAe;AACf,YAAQ;AAAA,EACV,OAAO;AACL,mBAAe,MAAM,GAAG,KAAK,qBAAqB,OAAO,WAAkB;AAC3E,YAAQ,aAAa,OAAO,CAAC,gBAAgB,CAAC,YAAY,SAAS,EAAE;AAAA,EACvE;AACA,QAAM,qBAAqB,aAAa,OAAO,CAAC,gBAAgB,CAAC,YAAY,SAAS;AACtF,QAAM,SAAS,oBAAI,IAAmC;AAEtD,aAAW,eAAe,oBAAoB;AAC5C,UAAM,iBACJ,OAAO,YAAY,mBAAmB,YAAY,YAAY,eAAe,KAAK,EAAE,SAAS,IACzF,YAAY,iBACZ,0BAA0B;AAChC,UAAM,SAAS,OAAO,IAAI,cAAc;AACxC,QAAI,QAAQ;AACV,aAAO,KAAK,WAAW;AAAA,IACzB,OAAO;AACL,aAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,qBAAqB,oBAAI,IAA6B;AAE5D,aAAW,CAAC,qBAAqB,mBAAmB,KAAK,OAAO,QAAQ,GAAG;AACzE,UAAM,uBAAuB,oBAAoB,SAAS,IAAI,sBAAsB;AACpF,UAAM,WAAW,MAAM,6BAA6B;AAAA,MAClD;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,GAAG;AAAA,QACH,OAAO,KAAK,SAAS;AAAA,MACvB;AAAA,MACA,wBAAwB;AAAA,MACxB,cAAc;AAAA,IAChB,CAAC;AACD,UAAM,cAAc,MAAM;AAAA,MACxB,IAAI;AAAA,QACF,SACG,IAAI,CAAC,gBAAgB,YAAY,YAAY,IAAI,EACjD,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,oBAAoB,MAAM;AAAA,MAC9B;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL;AAAA,IACF;AAEA,eAAW,eAAe,UAAU;AAClC,yBAAmB;AAAA,QACjB,YAAY;AAAA,QACZ;AAAA,UACE;AAAA,UACA,YAAY,WAAW,kBAAkB,IAAI,YAAY,QAAQ,KAAK,OAAO;AAAA,QAC/E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,mBACJ,IAAI,CAAC,gBAAgB,mBAAmB,IAAI,YAAY,EAAE,KAAK,IAAI,EACnE,OAAO,CAAC,QAAgC,CAAC,CAAC,GAAG;AAAA,IAChD,WAAW,IAAI,IAAI,aAAa,IAAI,CAAC,gBAAgB,YAAY,EAAE,CAAC;AAAA,IACpE;AAAA,EACF;AACF;AAEO,SAAS,uBACd,MACA,QACA,kBACiB;AACjB,QAAM,SAAS,oBAAoB;AAAA,IACjC,IAAI,OAAO,KAAK,WAAW,WAAW,OAAO,KAAK,OAAO;AAAA,IACzD,aAAa,OAAO,KAAK,WAAW,WAAW,OAAO,KAAK,OAAO,eAAe;AAAA,IACjF,MAAM,OAAO,KAAK,WAAW,WAAW,OAAO,KAAK,OAAO,QAAQ;AAAA,EACrE;AAEA,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,QAAQ,KAAK;AAAA,IACb,YAAY,wBAAwB,KAAK,UAAU;AAAA,IACnD,WAAW,QAAQ,SAAS;AAAA,IAC5B,YAAY,QAAQ,UAAU;AAAA,IAC9B,cAAc,QAAQ,YAAY;AAAA,IAClC,cAAc,QAAQ,YAAY;AAAA,IAClC,iBAAiB,QAAQ,eAAe;AAAA,IACxC,WAAW,QAAQ,SAAS;AAAA,IAC5B,kBAAkB,QAAQ,gBAAgB;AAAA,IAC1C,oBAAoB,QAAQ,kBAAkB,KAAK,kBAAkB;AAAA,IACrE,eAAe,QAAQ,aAAa;AAAA,IACpC,gBAAgB,KAAK;AAAA,IACrB,UAAU,KAAK;AAAA,IACf,WAAW,KAAK,UAAU,YAAY;AAAA,IACtC,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;AAEO,SAAS,8BACd,aACA,UACA,SACiB;AACjB,QAAM,eAAwC,EAAE,GAAI,YAAY,gBAAgB,CAAC,EAAG;AACpF,MAAI,YAAY,aAAa,UAAa,aAAa,aAAa,QAAW;AAC7E,iBAAa,WAAW,YAAY,YAAY;AAAA,EAClD;AACA,MAAI,YAAY,SAAS,UAAa,aAAa,gBAAgB,QAAW;AAC5E,iBAAa,cAAc,YAAY,QAAQ;AAAA,EACjD;AACA,MAAI,YAAY,gBAAgB,UAAa,aAAa,WAAW,QAAW;AAC9E,iBAAa,SAAS,YAAY,eAAe;AAAA,EACnD;AAEA,SAAO;AAAA,IACL,IACE,OAAO,SAAS,UAAU,YAAY,QAAQ,MAAM,KAAK,EAAE,SAAS,IAChE,QAAQ,QACR,YAAY;AAAA,IAClB,QAAQ,YAAY;AAAA,IACpB,YACE,OAAO,SAAS,eAAe,YAAY,QAAQ,WAAW,KAAK,EAAE,SAAS,IAC1E,QAAQ,aACR;AAAA,IACN,WAAW,YAAY,SAAS;AAAA,IAChC,YAAY,YAAY,WAAW;AAAA,IACnC,cAAc,YAAY,YAAY;AAAA,IACtC,cACE,OAAO,aAAa,aAAa,YAAY,aAAa,SAAS,KAAK,EAAE,SAAS,IAC/E,aAAa,SAAS,KAAK,IAC3B;AAAA,IACN,iBAAiB,YAAY,QAAQ;AAAA,IACrC,WAAW,YAAY,eAAe;AAAA,IACtC,cAAc,YAAY,YAAY;AAAA,IACtC,kBAAkB,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,IACxE,oBAAoB,YAAY,kBAAkB;AAAA,IAClD,eAAe,YAAY,aAAa;AAAA,IACxC,gBAAgB,YAAY,kBAAkB;AAAA,IAC9C,UAAU,YAAY,YAAY;AAAA,IAClC,WAAW,YAAY;AAAA,IACvB,cAAc,8BAA8B,WAAW;AAAA,IACvD,eAAe,YAAY,iBAAiB;AAAA,IAC5C,UAAU,YAAY;AAAA,MACpB,IAAI,YAAY,YAAY;AAAA,MAC5B,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -125,10 +125,7 @@ const CustomerTodosWidget = ({
125
125
  /* @__PURE__ */ jsx("span", { children: item.entity.displayName ?? t("customers.widgets.common.unknown") }),
126
126
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: createdLabel || t("customers.widgets.common.unknownDate") })
127
127
  ] }),
128
- /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5", children: [
129
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: item.todoTitle ?? t("customers.widgets.todos.untitled") }),
130
- item.todoSource ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: item.todoSource }) : null
131
- ] }),
128
+ /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-0.5", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: item.todoTitle ?? t("customers.widgets.todos.untitled") }) }),
132
129
  /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-3 text-xs", children: [
133
130
  href ? /* @__PURE__ */ jsx(Link, { className: "text-primary hover:underline", href, children: t("customers.widgets.common.viewRecord") }) : null,
134
131
  taskHref ? /* @__PURE__ */ jsx(Link, { className: "text-primary hover:underline", href: taskHref, children: t("customers.workPlan.customerTodos.table.actions.openTask") }) : null
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DEFAULT_SETTINGS, hydrateCustomerTodoSettings, type CustomerTodoWidgetSettings } from './config'\nimport { resolveExampleIntegrationHref } from '../../../lib/interactionCompatibility'\nimport { resolveTodoHref } from '../../../components/detail/utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype TodoLinkSummary = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n createdAt: string\n _integrations?: {\n example?: {\n href?: string | null\n }\n [key: string]: unknown\n }\n entity: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nasync function loadTodos(settings: CustomerTodoWidgetSettings): Promise<TodoLinkSummary[]> {\n const params = new URLSearchParams({\n limit: String(settings.pageSize),\n })\n const call = await apiCall<{ items?: unknown[]; error?: string }>(\n `/api/customers/dashboard/widgets/customer-todos?${params.toString()}`,\n )\n if (!call.ok) {\n const message =\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\n ? ((call.result as Record<string, unknown>).error as string)\n : `Request failed with status ${call.status}`\n throw new Error(message)\n }\n const payload = call.result ?? {}\n const rawItems = Array.isArray((payload as { items?: unknown }).items)\n ? ((payload as { items: unknown[] }).items)\n : []\n return rawItems\n .map((item): TodoLinkSummary | null => {\n if (!item || typeof item !== 'object') return null\n const data = item as any\n const entity = data.entity ?? {}\n return {\n id: typeof data.id === 'string' ? data.id : null,\n todoId: typeof data.todoId === 'string' ? data.todoId : '',\n todoSource: typeof data.todoSource === 'string' ? data.todoSource : '',\n todoTitle: typeof data.todoTitle === 'string' ? data.todoTitle : null,\n createdAt: typeof data.createdAt === 'string' ? data.createdAt : '',\n _integrations: data._integrations && typeof data._integrations === 'object'\n ? (data._integrations as TodoLinkSummary['_integrations'])\n : undefined,\n entity: {\n id: typeof entity.id === 'string' ? entity.id : null,\n displayName: typeof entity.displayName === 'string' ? entity.displayName : null,\n kind: typeof entity.kind === 'string' ? entity.kind : null,\n },\n }\n })\n .filter((item): item is TodoLinkSummary => !!item && !!item.id)\n}\n\nfunction formatDate(value: string | null, locale?: string): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString(locale ?? undefined)\n}\n\nfunction resolveDetailHref(entity: { id: string | null; kind: string | null } | null | undefined): string | null {\n if (!entity?.id) return null\n if (entity.kind === 'company') return `/backend/customers/companies-v2/${encodeURIComponent(entity.id)}`\n if (entity.kind === 'person') return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n}\n\nconst CustomerTodosWidget: React.FC<DashboardWidgetComponentProps<CustomerTodoWidgetSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateCustomerTodoSettings(settings), [settings])\n const [items, setItems] = React.useState<TodoLinkSummary[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\n\n React.useEffect(() => {\n if (typeof navigator !== 'undefined') {\n setLocale(navigator.language)\n }\n }, [])\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await loadTodos(hydrated)\n setItems(data)\n } catch (err) {\n logger.error('Failed to load customer todos widget data', { err })\n setError(t('customers.widgets.todos.error'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <div className=\"space-y-1.5\">\n <label htmlFor=\"customer-todos-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\n {t('customers.widgets.todos.settings.pageSize')}\n </label>\n <Input\n id=\"customer-todos-page-size\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.pageSize}\n onChange={(event) => {\n const next = Number(event.target.value)\n onSettingsChange({ ...hydrated, pageSize: Number.isFinite(next) ? next : hydrated.pageSize })\n }}\n />\n </div>\n <p className=\"text-xs text-muted-foreground\">{t('customers.widgets.todos.settings.help')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {error ? (\n <p className=\"text-sm text-destructive\">{error}</p>\n ) : loading ? (\n <div className=\"flex h-32 items-center justify-center\">\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\n </div>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">{t('customers.widgets.todos.empty')}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => {\n const createdLabel = formatDate(item.createdAt, locale)\n const href = resolveDetailHref(item.entity)\n const exampleHref = resolveExampleIntegrationHref(item)\n const taskHref = exampleHref ?? resolveTodoHref(item.todoSource, item.todoId)\n return (\n <li key={item.id} className=\"rounded-md border p-3\">\n <div className=\"flex items-start justify-between gap-3 text-sm font-medium\">\n <span>{item.entity.displayName ?? t('customers.widgets.common.unknown')}</span>\n <span className=\"text-xs text-muted-foreground\">{createdLabel || t('customers.widgets.common.unknownDate')}</span>\n </div>\n <div className=\"mt-1 space-y-0.5\">\n <p className=\"text-sm font-medium text-foreground\">\n {item.todoTitle ?? t('customers.widgets.todos.untitled')}\n </p>\n {item.todoSource ? (\n <p className=\"text-xs text-muted-foreground\">{item.todoSource}</p>\n ) : null}\n </div>\n <div className=\"mt-2 flex flex-wrap gap-3 text-xs\">\n {href ? (\n <Link className=\"text-primary hover:underline\" href={href}>\n {t('customers.widgets.common.viewRecord')}\n </Link>\n ) : null}\n {taskHref ? (\n <Link className=\"text-primary hover:underline\" href={taskHref}>\n {t('customers.workPlan.customerTodos.table.actions.openTask')}\n </Link>\n ) : null}\n </div>\n </li>\n )\n })}\n </ul>\n )}\n </div>\n )\n}\n\nexport default CustomerTodosWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DEFAULT_SETTINGS, hydrateCustomerTodoSettings, type CustomerTodoWidgetSettings } from './config'\nimport { resolveExampleIntegrationHref } from '../../../lib/interactionCompatibility'\nimport { resolveTodoHref } from '../../../components/detail/utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype TodoLinkSummary = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n createdAt: string\n _integrations?: {\n example?: {\n href?: string | null\n }\n [key: string]: unknown\n }\n entity: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nasync function loadTodos(settings: CustomerTodoWidgetSettings): Promise<TodoLinkSummary[]> {\n const params = new URLSearchParams({\n limit: String(settings.pageSize),\n })\n const call = await apiCall<{ items?: unknown[]; error?: string }>(\n `/api/customers/dashboard/widgets/customer-todos?${params.toString()}`,\n )\n if (!call.ok) {\n const message =\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\n ? ((call.result as Record<string, unknown>).error as string)\n : `Request failed with status ${call.status}`\n throw new Error(message)\n }\n const payload = call.result ?? {}\n const rawItems = Array.isArray((payload as { items?: unknown }).items)\n ? ((payload as { items: unknown[] }).items)\n : []\n return rawItems\n .map((item): TodoLinkSummary | null => {\n if (!item || typeof item !== 'object') return null\n const data = item as any\n const entity = data.entity ?? {}\n return {\n id: typeof data.id === 'string' ? data.id : null,\n todoId: typeof data.todoId === 'string' ? data.todoId : '',\n todoSource: typeof data.todoSource === 'string' ? data.todoSource : '',\n todoTitle: typeof data.todoTitle === 'string' ? data.todoTitle : null,\n createdAt: typeof data.createdAt === 'string' ? data.createdAt : '',\n _integrations: data._integrations && typeof data._integrations === 'object'\n ? (data._integrations as TodoLinkSummary['_integrations'])\n : undefined,\n entity: {\n id: typeof entity.id === 'string' ? entity.id : null,\n displayName: typeof entity.displayName === 'string' ? entity.displayName : null,\n kind: typeof entity.kind === 'string' ? entity.kind : null,\n },\n }\n })\n .filter((item): item is TodoLinkSummary => !!item && !!item.id)\n}\n\nfunction formatDate(value: string | null, locale?: string): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString(locale ?? undefined)\n}\n\nfunction resolveDetailHref(entity: { id: string | null; kind: string | null } | null | undefined): string | null {\n if (!entity?.id) return null\n if (entity.kind === 'company') return `/backend/customers/companies-v2/${encodeURIComponent(entity.id)}`\n if (entity.kind === 'person') return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n}\n\nconst CustomerTodosWidget: React.FC<DashboardWidgetComponentProps<CustomerTodoWidgetSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateCustomerTodoSettings(settings), [settings])\n const [items, setItems] = React.useState<TodoLinkSummary[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\n\n React.useEffect(() => {\n if (typeof navigator !== 'undefined') {\n setLocale(navigator.language)\n }\n }, [])\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await loadTodos(hydrated)\n setItems(data)\n } catch (err) {\n logger.error('Failed to load customer todos widget data', { err })\n setError(t('customers.widgets.todos.error'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <div className=\"space-y-1.5\">\n <label htmlFor=\"customer-todos-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\n {t('customers.widgets.todos.settings.pageSize')}\n </label>\n <Input\n id=\"customer-todos-page-size\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.pageSize}\n onChange={(event) => {\n const next = Number(event.target.value)\n onSettingsChange({ ...hydrated, pageSize: Number.isFinite(next) ? next : hydrated.pageSize })\n }}\n />\n </div>\n <p className=\"text-xs text-muted-foreground\">{t('customers.widgets.todos.settings.help')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {error ? (\n <p className=\"text-sm text-destructive\">{error}</p>\n ) : loading ? (\n <div className=\"flex h-32 items-center justify-center\">\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\n </div>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">{t('customers.widgets.todos.empty')}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => {\n const createdLabel = formatDate(item.createdAt, locale)\n const href = resolveDetailHref(item.entity)\n const exampleHref = resolveExampleIntegrationHref(item)\n const taskHref = exampleHref ?? resolveTodoHref(item.todoSource, item.todoId)\n return (\n <li key={item.id} className=\"rounded-md border p-3\">\n <div className=\"flex items-start justify-between gap-3 text-sm font-medium\">\n <span>{item.entity.displayName ?? t('customers.widgets.common.unknown')}</span>\n <span className=\"text-xs text-muted-foreground\">{createdLabel || t('customers.widgets.common.unknownDate')}</span>\n </div>\n <div className=\"mt-1 space-y-0.5\">\n <p className=\"text-sm font-medium text-foreground\">\n {item.todoTitle ?? t('customers.widgets.todos.untitled')}\n </p>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-3 text-xs\">\n {href ? (\n <Link className=\"text-primary hover:underline\" href={href}>\n {t('customers.widgets.common.viewRecord')}\n </Link>\n ) : null}\n {taskHref ? (\n <Link className=\"text-primary hover:underline\" href={taskHref}>\n {t('customers.workPlan.customerTodos.table.actions.openTask')}\n </Link>\n ) : null}\n </div>\n </li>\n )\n })}\n </ul>\n )}\n </div>\n )\n}\n\nexport default CustomerTodosWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@open-mercato/shared/lib/utils";
3
4
  import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
4
5
  function WarrantyWorkspace({
5
6
  title,
@@ -32,7 +33,13 @@ function WarrantyWorkspace({
32
33
  },
33
34
  tab.id
34
35
  )) }) }) : null,
35
- /* @__PURE__ */ jsx("div", { className: `min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16 ${contentClassName ?? ""}`, children })
36
+ /* @__PURE__ */ jsx("div", { className: cn(
37
+ "min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16",
38
+ "md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card",
39
+ "md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40",
40
+ "md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30",
41
+ contentClassName
42
+ ), children })
36
43
  ] });
37
44
  }
38
45
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type WarrantyWorkspaceTab = {\n id: string\n label: string\n count?: number\n disabled?: boolean\n}\n\ntype WarrantyWorkspaceProps = {\n title: string\n description?: string\n action?: React.ReactNode\n tabs?: WarrantyWorkspaceTab[]\n activeTab?: string\n onTabChange?: (value: string) => void\n summary?: React.ReactNode\n contentClassName?: string\n children: React.ReactNode\n}\n\nexport function WarrantyWorkspace({\n title,\n description,\n action,\n tabs,\n activeTab,\n onTabChange,\n summary,\n contentClassName,\n children,\n}: WarrantyWorkspaceProps) {\n return (\n <section className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex flex-col gap-4 px-8 pb-6 pt-7 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n {summary ?? null}\n {tabs?.length && activeTab && onTabChange ? (\n <Tabs value={activeTab} onValueChange={onTabChange} variant=\"underline\">\n <TabsList className=\"flex h-auto w-full gap-1 overflow-x-auto px-7 pb-2\" aria-label={title}>\n {tabs.map((tab) => (\n <TabsTrigger\n key={tab.id}\n value={tab.id}\n count={tab.count}\n disabled={tab.disabled}\n className=\"shrink-0 gap-2 px-4 pb-2 pt-3 [&_[data-slot=tabs-trigger-count]]:h-auto [&_[data-slot=tabs-trigger-count]]:min-w-0 [&_[data-slot=tabs-trigger-count]]:rounded-md [&_[data-slot=tabs-trigger-count]]:bg-muted [&_[data-slot=tabs-trigger-count]]:px-1.5 [&_[data-slot=tabs-trigger-count]]:py-0.5 [&_[data-slot=tabs-trigger-count]]:text-overline [&_[data-slot=tabs-trigger-count]]:text-muted-foreground\"\n >\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n ) : null}\n <div className={`min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16 ${contentClassName ?? ''}`}>{children}</div>\n </section>\n )\n}\n"],
5
- "mappings": ";AAsCQ,SACE,KADF;AAnCR,SAAS,MAAM,UAAU,mBAAmB;AAqBrC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,qBAAC,aAAQ,WAAU,2DACjB;AAAA,yBAAC,SAAI,WAAU,qFACb;AAAA,2BAAC,SAAI,WAAU,WACb;AAAA,4BAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,QACxE,cAAc,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAAO;AAAA,SACvF;AAAA,MACC,SAAS,oBAAC,SAAI,WAAU,YAAY,kBAAO,IAAS;AAAA,OACvD;AAAA,IACC,WAAW;AAAA,IACX,MAAM,UAAU,aAAa,cAC5B,oBAAC,QAAK,OAAO,WAAW,eAAe,aAAa,SAAQ,aAC1D,8BAAC,YAAS,WAAU,sDAAqD,cAAY,OAClF,eAAK,IAAI,CAAC,QACT;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,WAAU;AAAA,QAET,cAAI;AAAA;AAAA,MANA,IAAI;AAAA,IAOX,CACD,GACH,GACF,IACE;AAAA,IACJ,oBAAC,SAAI,WAAW,4QAA4Q,oBAAoB,EAAE,IAAK,UAAS;AAAA,KAClU;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type WarrantyWorkspaceTab = {\n id: string\n label: string\n count?: number\n disabled?: boolean\n}\n\ntype WarrantyWorkspaceProps = {\n title: string\n description?: string\n action?: React.ReactNode\n tabs?: WarrantyWorkspaceTab[]\n activeTab?: string\n onTabChange?: (value: string) => void\n summary?: React.ReactNode\n contentClassName?: string\n children: React.ReactNode\n}\n\nexport function WarrantyWorkspace({\n title,\n description,\n action,\n tabs,\n activeTab,\n onTabChange,\n summary,\n contentClassName,\n children,\n}: WarrantyWorkspaceProps) {\n return (\n <section className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex flex-col gap-4 px-8 pb-6 pt-7 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n {summary ?? null}\n {tabs?.length && activeTab && onTabChange ? (\n <Tabs value={activeTab} onValueChange={onTabChange} variant=\"underline\">\n <TabsList className=\"flex h-auto w-full gap-1 overflow-x-auto px-7 pb-2\" aria-label={title}>\n {tabs.map((tab) => (\n <TabsTrigger\n key={tab.id}\n value={tab.id}\n count={tab.count}\n disabled={tab.disabled}\n className=\"shrink-0 gap-2 px-4 pb-2 pt-3 [&_[data-slot=tabs-trigger-count]]:h-auto [&_[data-slot=tabs-trigger-count]]:min-w-0 [&_[data-slot=tabs-trigger-count]]:rounded-md [&_[data-slot=tabs-trigger-count]]:bg-muted [&_[data-slot=tabs-trigger-count]]:px-1.5 [&_[data-slot=tabs-trigger-count]]:py-0.5 [&_[data-slot=tabs-trigger-count]]:text-overline [&_[data-slot=tabs-trigger-count]]:text-muted-foreground\"\n >\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n ) : null}\n <div className={cn(\n 'min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40',\n 'md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30',\n contentClassName,\n )}>{children}</div>\n </section>\n )\n}\n"],
5
+ "mappings": ";AAuCQ,SACE,KADF;AApCR,SAAS,UAAU;AACnB,SAAS,MAAM,UAAU,mBAAmB;AAqBrC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,qBAAC,aAAQ,WAAU,2DACjB;AAAA,yBAAC,SAAI,WAAU,qFACb;AAAA,2BAAC,SAAI,WAAU,WACb;AAAA,4BAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,QACxE,cAAc,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAAO;AAAA,SACvF;AAAA,MACC,SAAS,oBAAC,SAAI,WAAU,YAAY,kBAAO,IAAS;AAAA,OACvD;AAAA,IACC,WAAW;AAAA,IACX,MAAM,UAAU,aAAa,cAC5B,oBAAC,QAAK,OAAO,WAAW,eAAe,aAAa,SAAQ,aAC1D,8BAAC,YAAS,WAAU,sDAAqD,cAAY,OAClF,eAAK,IAAI,CAAC,QACT;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,WAAU;AAAA,QAET,cAAI;AAAA;AAAA,MANA,IAAI;AAAA,IAOX,CACD,GACH,GACF,IACE;AAAA,IACJ,oBAAC,SAAI,WAAW;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,GAAI,UAAS;AAAA,KACf;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -144,10 +144,12 @@ function buildTrackerSteps(claim, t, locale) {
144
144
  };
145
145
  });
146
146
  }
147
- function buildClaimTitle(claim, t) {
147
+ function buildClaimTitle(claim, reasonOptions, t) {
148
148
  const typeLabel = t(`warranty_claims.claimType.${claim.claimType}`);
149
149
  if (claim.reasonCode) {
150
- const reasonLabel = localizeDictionaryLabel(t, "reason", claim.reasonCode, claim.reasonCode);
150
+ const reasonCode = claim.reasonCode;
151
+ const dictionaryLabel = reasonOptions.find((option) => option.value === reasonCode)?.label ?? reasonCode;
152
+ const reasonLabel = localizeDictionaryLabel(t, "reason", reasonCode, dictionaryLabel);
151
153
  return t("warranty_claims.portal.tracker.titleWithReason", { reason: reasonLabel, type: typeLabel });
152
154
  }
153
155
  return t("warranty_claims.portal.tracker.titleFallback", { type: typeLabel });
@@ -221,6 +223,7 @@ function WarrantyClaimPortalDetailPage({ params }) {
221
223
  const [claim, setClaim] = React.useState(null);
222
224
  const [events, setEvents] = React.useState([]);
223
225
  const [attachments, setAttachments] = React.useState([]);
226
+ const [reasonOptions, setReasonOptions] = React.useState([]);
224
227
  const [isLoading, setIsLoading] = React.useState(true);
225
228
  const [error, setError] = React.useState(null);
226
229
  const [notFound, setNotFound] = React.useState(false);
@@ -275,6 +278,18 @@ function WarrantyClaimPortalDetailPage({ params }) {
275
278
  React.useEffect(() => {
276
279
  if (user) void loadClaim();
277
280
  }, [loadClaim, user]);
281
+ React.useEffect(() => {
282
+ if (!user) return;
283
+ let cancelled = false;
284
+ apiCall("/api/warranty_claims/portal/options").then((result) => {
285
+ if (cancelled) return;
286
+ setReasonOptions(result.result?.ok ? result.result.result?.reasons ?? [] : []);
287
+ }).catch(() => {
288
+ });
289
+ return () => {
290
+ cancelled = true;
291
+ };
292
+ }, [user]);
278
293
  const submitComment = React.useCallback(async () => {
279
294
  if (!claim || commentSubmitting) return;
280
295
  const body = comment.trim();
@@ -580,7 +595,7 @@ function WarrantyClaimPortalDetailPage({ params }) {
580
595
  }
581
596
  ) : null
582
597
  ] }),
583
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-bold text-foreground", children: buildClaimTitle(claim, t) }),
598
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-bold text-foreground", children: buildClaimTitle(claim, reasonOptions, t) }),
584
599
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: buildClaimSubtitle(claim, t, locale) })
585
600
  ] }),
586
601
  /* @__PURE__ */ jsx("div", { className: "w-full px-6 pb-6", children: /* @__PURE__ */ jsxs("div", { className: `flex w-full items-start gap-3 rounded-lg px-4 py-3 ${toneClasses.wrapper}`, children: [