@open-mercato/core 0.6.6-develop.6515.1.01911d00e2 → 0.6.6-develop.6519.1.bdc91fb755

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 (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/configs/lib/upgrade-actions.js +31 -1
  3. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  4. package/dist/modules/customers/ai-tools/activities-tasks-pack.js +11 -2
  5. package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +2 -2
  6. package/dist/modules/customers/api/companies/[id]/route.js +3 -2
  7. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  8. package/dist/modules/customers/api/dictionaries/context.js +4 -2
  9. package/dist/modules/customers/api/dictionaries/context.js.map +2 -2
  10. package/dist/modules/customers/api/interactions/conflicts/route.js +2 -1
  11. package/dist/modules/customers/api/interactions/conflicts/route.js.map +2 -2
  12. package/dist/modules/customers/api/interactions/counts/route.js +7 -2
  13. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  14. package/dist/modules/customers/api/people/[id]/route.js +3 -2
  15. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  16. package/dist/modules/customers/cli.js +19 -0
  17. package/dist/modules/customers/cli.js.map +2 -2
  18. package/dist/modules/customers/commands/interactions.js +8 -3
  19. package/dist/modules/customers/commands/interactions.js.map +2 -2
  20. package/dist/modules/customers/commands/shared.js +2 -1
  21. package/dist/modules/customers/commands/shared.js.map +2 -2
  22. package/dist/modules/customers/components/DictionarySettings.js +5 -0
  23. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  24. package/dist/modules/customers/components/DictionarySortSettings.js +5 -0
  25. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  26. package/dist/modules/customers/components/detail/ActivitiesCard.js +3 -2
  27. package/dist/modules/customers/components/detail/ActivitiesCard.js.map +2 -2
  28. package/dist/modules/customers/components/detail/ActivityCard.js +2 -1
  29. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  30. package/dist/modules/customers/components/detail/ActivityTimeline.js +3 -2
  31. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  32. package/dist/modules/customers/components/detail/TaskDialog.js +3 -2
  33. package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
  34. package/dist/modules/customers/components/detail/TaskForm.js +170 -69
  35. package/dist/modules/customers/components/detail/TaskForm.js.map +2 -2
  36. package/dist/modules/customers/components/detail/TasksSection.js +27 -4
  37. package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
  38. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  39. package/dist/modules/customers/data/enrichers.js +2 -2
  40. package/dist/modules/customers/data/enrichers.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +6 -2
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/dictionaries.js +2 -1
  44. package/dist/modules/customers/lib/dictionaries.js.map +2 -2
  45. package/dist/modules/customers/lib/interactionProjection.js +2 -1
  46. package/dist/modules/customers/lib/interactionProjection.js.map +2 -2
  47. package/dist/modules/customers/lib/interactionStatus.js +24 -0
  48. package/dist/modules/customers/lib/interactionStatus.js.map +7 -0
  49. package/dist/modules/customers/lib/link-channel-message-handler.js +4 -5
  50. package/dist/modules/customers/lib/link-channel-message-handler.js.map +2 -2
  51. package/dist/modules/dictionaries/components/AppearanceSelector.js +7 -3
  52. package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
  53. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +1 -0
  54. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  55. package/package.json +7 -7
  56. package/src/modules/configs/lib/upgrade-actions.ts +31 -1
  57. package/src/modules/customers/ai-tools/activities-tasks-pack.ts +12 -3
  58. package/src/modules/customers/api/companies/[id]/route.ts +3 -2
  59. package/src/modules/customers/api/dictionaries/context.ts +2 -0
  60. package/src/modules/customers/api/interactions/conflicts/route.ts +2 -1
  61. package/src/modules/customers/api/interactions/counts/route.ts +7 -2
  62. package/src/modules/customers/api/people/[id]/route.ts +3 -2
  63. package/src/modules/customers/cli.ts +19 -0
  64. package/src/modules/customers/commands/interactions.ts +8 -3
  65. package/src/modules/customers/commands/shared.ts +1 -0
  66. package/src/modules/customers/components/DictionarySettings.tsx +5 -0
  67. package/src/modules/customers/components/DictionarySortSettings.tsx +5 -0
  68. package/src/modules/customers/components/detail/ActivitiesCard.tsx +3 -2
  69. package/src/modules/customers/components/detail/ActivityCard.tsx +2 -1
  70. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -2
  71. package/src/modules/customers/components/detail/TaskDialog.tsx +4 -1
  72. package/src/modules/customers/components/detail/TaskForm.tsx +218 -71
  73. package/src/modules/customers/components/detail/TasksSection.tsx +28 -2
  74. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +1 -0
  75. package/src/modules/customers/data/enrichers.ts +2 -7
  76. package/src/modules/customers/data/validators.ts +16 -2
  77. package/src/modules/customers/i18n/de.json +10 -0
  78. package/src/modules/customers/i18n/en.json +10 -0
  79. package/src/modules/customers/i18n/es.json +10 -0
  80. package/src/modules/customers/i18n/pl.json +10 -0
  81. package/src/modules/customers/lib/dictionaries.ts +1 -0
  82. package/src/modules/customers/lib/interactionProjection.ts +6 -2
  83. package/src/modules/customers/lib/interactionStatus.ts +30 -0
  84. package/src/modules/customers/lib/link-channel-message-handler.ts +4 -5
  85. package/src/modules/dictionaries/components/AppearanceSelector.tsx +10 -1
  86. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/components/DictionarySettings.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n getCustomerDictionarySettingsSectionId,\n type CustomerDictionaryKind,\n} from '../lib/dictionaries'\nimport { ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport {\n DictionaryForm,\n type DictionaryFormValues,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryForm'\nimport {\n DictionaryTable,\n type DictionaryTableEntry,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype SectionDefinition = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\ntype DictionaryDeleteError = Error & {\n code?: string\n usageCount?: number\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: DictionaryTableEntry }\n\nconst DEFAULT_FORM_VALUES: DictionaryFormValues = {\n value: '',\n label: '',\n color: null,\n icon: null,\n}\n\nconst DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS = [100, 350, 1000] as const\n\nfunction getCurrentDictionaryHashTarget(): HTMLElement | null {\n if (typeof window === 'undefined') return null\n const hash = window.location.hash\n if (!hash.startsWith('#customer-dictionary-')) return null\n try {\n return document.getElementById(decodeURIComponent(hash.slice(1)))\n } catch {\n return document.getElementById(hash.slice(1))\n }\n}\n\nexport default function DictionarySettings() {\n const t = useT()\n\n const sections = React.useMemo<SectionDefinition[]>(() => [\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n React.useEffect(() => {\n const timeoutIds: number[] = []\n let frameId: number | null = null\n\n const clearScheduledScrolls = () => {\n if (frameId !== null) {\n window.cancelAnimationFrame(frameId)\n frameId = null\n }\n while (timeoutIds.length > 0) {\n const timeoutId = timeoutIds.pop()\n if (timeoutId !== undefined) window.clearTimeout(timeoutId)\n }\n }\n\n const scrollToHashTarget = () => {\n getCurrentDictionaryHashTarget()?.scrollIntoView({ block: 'start' })\n }\n\n const scheduleHashScroll = () => {\n clearScheduledScrolls()\n if (!window.location.hash.startsWith('#customer-dictionary-')) return\n scrollToHashTarget()\n if (typeof window.requestAnimationFrame === 'function') {\n frameId = window.requestAnimationFrame(scrollToHashTarget)\n }\n DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS.forEach((delay) => {\n timeoutIds.push(window.setTimeout(scrollToHashTarget, delay))\n })\n }\n\n scheduleHashScroll()\n window.addEventListener('hashchange', scheduleHashScroll)\n return () => {\n window.removeEventListener('hashchange', scheduleHashScroll)\n clearScheduledScrolls()\n }\n }, [])\n\n return (\n <div className=\"space-y-8\">\n <header className=\"space-y-2\">\n <h1 className=\"text-2xl font-semibold\">\n {t('customers.config.dictionaries.title', 'Customers dictionaries')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionaries.description', 'Manage the dictionaries used by the customers module.')}\n </p>\n </header>\n\n <div className=\"space-y-6\">\n {sections.map((section) => (\n <CustomerDictionarySection key={section.kind} {...section} />\n ))}\n </div>\n </div>\n )\n}\n\ntype CustomerDictionarySectionProps = SectionDefinition\n\nfunction CustomerDictionarySection({ kind, title, description }: CustomerDictionarySectionProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [entries, setEntries] = React.useState<DictionaryTableEntry[]>([])\n const [loading, setLoading] = React.useState<boolean>(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [submitting, setSubmitting] = React.useState(false)\n\n const saveContextId = `customers-dictionary-${kind}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: saveContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: saveContextId,\n resourceKind: 'customers.dictionary',\n retryLastMutation,\n }),\n [retryLastMutation, saveContextId],\n )\n\n const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')\n const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')\n const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')\n const errorLoad = t('customers.config.dictionaries.error.load', 'Failed to load dictionary entries.')\n const errorSave = t('customers.config.dictionaries.error.save', 'Failed to save dictionary entry.')\n const errorDelete = t('customers.config.dictionaries.error.delete', 'Failed to delete dictionary entry.')\n const successSave = t('customers.config.dictionaries.success.save', 'Dictionary entry saved.')\n const successDelete = t('customers.config.dictionaries.success.delete', 'Dictionary entry deleted.')\n const deleteConfirmTemplate = t('customers.config.dictionaries.deleteConfirm', 'Delete \"{{value}}\"?')\n const roleTypeDeleteBlockedTitle = t('customers.config.dictionaries.roleTypes.deleteBlocked.title', 'Role type is in use')\n const roleTypeDeleteBlockedText = t(\n 'customers.config.dictionaries.roleTypes.deleteBlocked.text',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n )\n const usageCountLabel = t('customers.config.dictionaries.roleTypes.usageCount', 'In use on {{count}} records')\n const searchPlaceholder = t('customers.config.dictionaries.searchPlaceholder', 'Search entries\u2026')\n\n const formatCountMessage = React.useCallback((template: string, count: number) => {\n return template.replace('{{count}}', String(count))\n }, [])\n\n const loadEntries = React.useCallback(async () => {\n setLoading(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/dictionaries/${kind}`,\n undefined,\n { errorMessage: errorLoad },\n )\n if (!Array.isArray(data?.items)) throw new Error(errorLoad)\n const mapped: DictionaryTableEntry[] = data.items.map((item: any) => ({\n id: String(item.id),\n value: String(item.value ?? ''),\n label: typeof item.label === 'string' ? item.label : '',\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n usageCount: typeof item.usageCount === 'number' ? item.usageCount : undefined,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : null,\n tenantId: typeof item.tenantId === 'string' ? item.tenantId : null,\n isInherited: item.isInherited === true,\n createdAt: typeof item.createdAt === 'string' ? item.createdAt : null,\n updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,\n }))\n setEntries(mapped)\n } catch (err) {\n logger.error('customers.dictionaries.list failed', { err })\n flash(errorLoad, 'error')\n } finally {\n setLoading(false)\n }\n }, [errorLoad, kind, scopeVersion])\n\n React.useEffect(() => {\n loadEntries().catch(() => {})\n }, [loadEntries])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n }, [])\n\n const handleCreate = React.useCallback(() => {\n setDialog({ mode: 'create' })\n }, [])\n\n const handleEdit = React.useCallback((entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n setDialog({ mode: 'edit', entry })\n }, [inheritedActionBlocked])\n\n const handleDelete = React.useCallback(async (entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n if (kind === 'person-company-roles' && (entry.usageCount ?? 0) > 0) {\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, entry.usageCount ?? 0),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const message = deleteConfirmTemplate.replace('{{value}}', entry.label || entry.value)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,\n { method: 'DELETE' },\n { errorMessage: errorDelete },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'delete', id: entry.id, kind },\n })\n flash(successDelete, 'success')\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.delete failed', { err })\n if (kind === 'person-company-roles' && (err as DictionaryDeleteError)?.code === 'role_type_in_use') {\n const usageCount = Number((err as DictionaryDeleteError).usageCount ?? 0)\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, usageCount),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const messageValue = err instanceof Error ? err.message : errorDelete\n flash(messageValue, 'error')\n }\n }, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])\n\n const submitForm = React.useCallback(async (values: DictionaryFormValues) => {\n const payload = {\n value: values.value,\n label: values.label,\n color: values.color,\n icon: values.icon,\n }\n setSubmitting(true)\n try {\n if (!dialog || dialog.mode === 'create') {\n await runMutation({\n operation: () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: errorSave },\n ),\n context: mutationContext,\n mutationPayload: { action: 'create', kind, ...payload },\n })\n flash(successSave, 'success')\n } else if (dialog.mode === 'edit') {\n const target = dialog.entry\n if (target.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n const body: Record<string, unknown> = {}\n if (values.value !== target.value) body.value = values.value\n if (values.label !== target.label) body.label = values.label\n const nextColor = values.color ?? null\n if (nextColor !== (target.color ?? null)) body.color = nextColor\n const nextIcon = values.icon ?? null\n if (nextIcon !== (target.icon ?? null)) body.icon = nextIcon\n if (Object.keys(body).length === 0) {\n closeDialog()\n return\n }\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n { errorMessage: errorSave },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'update', id: target.id, kind, ...body },\n })\n flash(successSave, 'success')\n }\n closeDialog()\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.submit failed', { err })\n throw err instanceof Error ? err : new Error(errorSave)\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])\n\n const currentValues = React.useMemo<DictionaryFormValues>(() => {\n if (dialog && dialog.mode === 'edit') {\n return {\n value: dialog.entry.value,\n label: dialog.entry.label,\n color: dialog.entry.color,\n icon: dialog.entry.icon,\n }\n }\n return DEFAULT_FORM_VALUES\n }, [dialog])\n\n const tableTranslations = React.useMemo(() => ({\n title,\n valueColumn: t('customers.config.dictionaries.columns.value', 'Value'),\n labelColumn: t('customers.config.dictionaries.columns.label', 'Label'),\n appearanceColumn: t('customers.config.dictionaries.columns.appearance', 'Appearance'),\n addLabel: t('customers.config.dictionaries.actions.add', 'Add entry'),\n editLabel: t('customers.config.dictionaries.actions.edit', 'Edit'),\n deleteLabel: t('customers.config.dictionaries.actions.delete', 'Delete'),\n refreshLabel: t('customers.config.dictionaries.actions.refresh', 'Refresh'),\n inheritedLabel,\n inheritedTooltip,\n emptyLabel: t('customers.config.dictionaries.empty', 'No entries yet.'),\n usageCountLabel: kind === 'person-company-roles' ? usageCountLabel : undefined,\n searchPlaceholder,\n }), [inheritedLabel, inheritedTooltip, kind, searchPlaceholder, t, title, usageCountLabel])\n\n const formTranslations = React.useMemo(() => ({\n title: dialog?.mode === 'edit'\n ? t('customers.config.dictionaries.dialog.editTitle', 'Edit entry')\n : t('customers.config.dictionaries.dialog.addTitle', 'Add entry'),\n valueLabel: t('customers.config.dictionaries.dialog.valueLabel', 'Value'),\n labelLabel: t('customers.config.dictionaries.dialog.labelLabel', 'Label'),\n saveLabel: t('customers.config.dictionaries.dialog.save', 'Save'),\n cancelLabel: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n appearance: {\n colorLabel: t('customers.config.dictionaries.dialog.colorLabel', 'Color'),\n colorHelp: t('customers.config.dictionaries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.dictionaries.dialog.colorClear', 'Remove color'),\n iconLabel: t('customers.config.dictionaries.dialog.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.dictionaries.dialog.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.dictionaries.dialog.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.dictionaries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.dictionaries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.dictionaries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.dictionaries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.dictionaries.appearance.empty', 'None'),\n },\n }), [dialog, t])\n\n return (\n <section\n id={getCustomerDictionarySettingsSectionId(kind)}\n className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-6 py-4 space-y-1\">\n <h2 className=\"text-lg font-medium\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n <div className=\"px-2 py-4 sm:px-4\">\n <DictionaryTable\n entries={entries}\n loading={loading}\n canManage\n onCreate={handleCreate}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onRefresh={loadEntries}\n translations={tableTranslations}\n />\n </div>\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{formTranslations.title}</DialogTitle>\n </DialogHeader>\n <DictionaryForm\n mode={dialog?.mode === 'edit' ? 'edit' : 'create'}\n initialValues={currentValues}\n onSubmit={submitForm}\n onCancel={closeDialog}\n submitting={submitting}\n translations={formTranslations}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n getCustomerDictionarySettingsSectionId,\n type CustomerDictionaryKind,\n} from '../lib/dictionaries'\nimport { ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport {\n DictionaryForm,\n type DictionaryFormValues,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryForm'\nimport {\n DictionaryTable,\n type DictionaryTableEntry,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype SectionDefinition = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\ntype DictionaryDeleteError = Error & {\n code?: string\n usageCount?: number\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: DictionaryTableEntry }\n\nconst DEFAULT_FORM_VALUES: DictionaryFormValues = {\n value: '',\n label: '',\n color: null,\n icon: null,\n}\n\nconst DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS = [100, 350, 1000] as const\n\nfunction getCurrentDictionaryHashTarget(): HTMLElement | null {\n if (typeof window === 'undefined') return null\n const hash = window.location.hash\n if (!hash.startsWith('#customer-dictionary-')) return null\n try {\n return document.getElementById(decodeURIComponent(hash.slice(1)))\n } catch {\n return document.getElementById(hash.slice(1))\n }\n}\n\nexport default function DictionarySettings() {\n const t = useT()\n\n const sections = React.useMemo<SectionDefinition[]>(() => [\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'interaction-statuses',\n title: t('customers.config.dictionaries.sections.interactionStatuses.title', 'Interaction statuses'),\n description: t('customers.config.dictionaries.sections.interactionStatuses.description', 'Manage the statuses available for tasks and logged interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n React.useEffect(() => {\n const timeoutIds: number[] = []\n let frameId: number | null = null\n\n const clearScheduledScrolls = () => {\n if (frameId !== null) {\n window.cancelAnimationFrame(frameId)\n frameId = null\n }\n while (timeoutIds.length > 0) {\n const timeoutId = timeoutIds.pop()\n if (timeoutId !== undefined) window.clearTimeout(timeoutId)\n }\n }\n\n const scrollToHashTarget = () => {\n getCurrentDictionaryHashTarget()?.scrollIntoView({ block: 'start' })\n }\n\n const scheduleHashScroll = () => {\n clearScheduledScrolls()\n if (!window.location.hash.startsWith('#customer-dictionary-')) return\n scrollToHashTarget()\n if (typeof window.requestAnimationFrame === 'function') {\n frameId = window.requestAnimationFrame(scrollToHashTarget)\n }\n DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS.forEach((delay) => {\n timeoutIds.push(window.setTimeout(scrollToHashTarget, delay))\n })\n }\n\n scheduleHashScroll()\n window.addEventListener('hashchange', scheduleHashScroll)\n return () => {\n window.removeEventListener('hashchange', scheduleHashScroll)\n clearScheduledScrolls()\n }\n }, [])\n\n return (\n <div className=\"space-y-8\">\n <header className=\"space-y-2\">\n <h1 className=\"text-2xl font-semibold\">\n {t('customers.config.dictionaries.title', 'Customers dictionaries')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionaries.description', 'Manage the dictionaries used by the customers module.')}\n </p>\n </header>\n\n <div className=\"space-y-6\">\n {sections.map((section) => (\n <CustomerDictionarySection key={section.kind} {...section} />\n ))}\n </div>\n </div>\n )\n}\n\ntype CustomerDictionarySectionProps = SectionDefinition\n\nfunction CustomerDictionarySection({ kind, title, description }: CustomerDictionarySectionProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [entries, setEntries] = React.useState<DictionaryTableEntry[]>([])\n const [loading, setLoading] = React.useState<boolean>(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [submitting, setSubmitting] = React.useState(false)\n\n const saveContextId = `customers-dictionary-${kind}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: saveContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: saveContextId,\n resourceKind: 'customers.dictionary',\n retryLastMutation,\n }),\n [retryLastMutation, saveContextId],\n )\n\n const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')\n const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')\n const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')\n const errorLoad = t('customers.config.dictionaries.error.load', 'Failed to load dictionary entries.')\n const errorSave = t('customers.config.dictionaries.error.save', 'Failed to save dictionary entry.')\n const errorDelete = t('customers.config.dictionaries.error.delete', 'Failed to delete dictionary entry.')\n const successSave = t('customers.config.dictionaries.success.save', 'Dictionary entry saved.')\n const successDelete = t('customers.config.dictionaries.success.delete', 'Dictionary entry deleted.')\n const deleteConfirmTemplate = t('customers.config.dictionaries.deleteConfirm', 'Delete \"{{value}}\"?')\n const roleTypeDeleteBlockedTitle = t('customers.config.dictionaries.roleTypes.deleteBlocked.title', 'Role type is in use')\n const roleTypeDeleteBlockedText = t(\n 'customers.config.dictionaries.roleTypes.deleteBlocked.text',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n )\n const usageCountLabel = t('customers.config.dictionaries.roleTypes.usageCount', 'In use on {{count}} records')\n const searchPlaceholder = t('customers.config.dictionaries.searchPlaceholder', 'Search entries\u2026')\n\n const formatCountMessage = React.useCallback((template: string, count: number) => {\n return template.replace('{{count}}', String(count))\n }, [])\n\n const loadEntries = React.useCallback(async () => {\n setLoading(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/dictionaries/${kind}`,\n undefined,\n { errorMessage: errorLoad },\n )\n if (!Array.isArray(data?.items)) throw new Error(errorLoad)\n const mapped: DictionaryTableEntry[] = data.items.map((item: any) => ({\n id: String(item.id),\n value: String(item.value ?? ''),\n label: typeof item.label === 'string' ? item.label : '',\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n usageCount: typeof item.usageCount === 'number' ? item.usageCount : undefined,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : null,\n tenantId: typeof item.tenantId === 'string' ? item.tenantId : null,\n isInherited: item.isInherited === true,\n createdAt: typeof item.createdAt === 'string' ? item.createdAt : null,\n updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,\n }))\n setEntries(mapped)\n } catch (err) {\n logger.error('customers.dictionaries.list failed', { err })\n flash(errorLoad, 'error')\n } finally {\n setLoading(false)\n }\n }, [errorLoad, kind, scopeVersion])\n\n React.useEffect(() => {\n loadEntries().catch(() => {})\n }, [loadEntries])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n }, [])\n\n const handleCreate = React.useCallback(() => {\n setDialog({ mode: 'create' })\n }, [])\n\n const handleEdit = React.useCallback((entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n setDialog({ mode: 'edit', entry })\n }, [inheritedActionBlocked])\n\n const handleDelete = React.useCallback(async (entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n if (kind === 'person-company-roles' && (entry.usageCount ?? 0) > 0) {\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, entry.usageCount ?? 0),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const message = deleteConfirmTemplate.replace('{{value}}', entry.label || entry.value)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,\n { method: 'DELETE' },\n { errorMessage: errorDelete },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'delete', id: entry.id, kind },\n })\n flash(successDelete, 'success')\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.delete failed', { err })\n if (kind === 'person-company-roles' && (err as DictionaryDeleteError)?.code === 'role_type_in_use') {\n const usageCount = Number((err as DictionaryDeleteError).usageCount ?? 0)\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, usageCount),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const messageValue = err instanceof Error ? err.message : errorDelete\n flash(messageValue, 'error')\n }\n }, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])\n\n const submitForm = React.useCallback(async (values: DictionaryFormValues) => {\n const payload = {\n value: values.value,\n label: values.label,\n color: values.color,\n icon: values.icon,\n }\n setSubmitting(true)\n try {\n if (!dialog || dialog.mode === 'create') {\n await runMutation({\n operation: () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: errorSave },\n ),\n context: mutationContext,\n mutationPayload: { action: 'create', kind, ...payload },\n })\n flash(successSave, 'success')\n } else if (dialog.mode === 'edit') {\n const target = dialog.entry\n if (target.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n const body: Record<string, unknown> = {}\n if (values.value !== target.value) body.value = values.value\n if (values.label !== target.label) body.label = values.label\n const nextColor = values.color ?? null\n if (nextColor !== (target.color ?? null)) body.color = nextColor\n const nextIcon = values.icon ?? null\n if (nextIcon !== (target.icon ?? null)) body.icon = nextIcon\n if (Object.keys(body).length === 0) {\n closeDialog()\n return\n }\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n { errorMessage: errorSave },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'update', id: target.id, kind, ...body },\n })\n flash(successSave, 'success')\n }\n closeDialog()\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.submit failed', { err })\n throw err instanceof Error ? err : new Error(errorSave)\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])\n\n const currentValues = React.useMemo<DictionaryFormValues>(() => {\n if (dialog && dialog.mode === 'edit') {\n return {\n value: dialog.entry.value,\n label: dialog.entry.label,\n color: dialog.entry.color,\n icon: dialog.entry.icon,\n }\n }\n return DEFAULT_FORM_VALUES\n }, [dialog])\n\n const tableTranslations = React.useMemo(() => ({\n title,\n valueColumn: t('customers.config.dictionaries.columns.value', 'Value'),\n labelColumn: t('customers.config.dictionaries.columns.label', 'Label'),\n appearanceColumn: t('customers.config.dictionaries.columns.appearance', 'Appearance'),\n addLabel: t('customers.config.dictionaries.actions.add', 'Add entry'),\n editLabel: t('customers.config.dictionaries.actions.edit', 'Edit'),\n deleteLabel: t('customers.config.dictionaries.actions.delete', 'Delete'),\n refreshLabel: t('customers.config.dictionaries.actions.refresh', 'Refresh'),\n inheritedLabel,\n inheritedTooltip,\n emptyLabel: t('customers.config.dictionaries.empty', 'No entries yet.'),\n usageCountLabel: kind === 'person-company-roles' ? usageCountLabel : undefined,\n searchPlaceholder,\n }), [inheritedLabel, inheritedTooltip, kind, searchPlaceholder, t, title, usageCountLabel])\n\n const formTranslations = React.useMemo(() => ({\n title: dialog?.mode === 'edit'\n ? t('customers.config.dictionaries.dialog.editTitle', 'Edit entry')\n : t('customers.config.dictionaries.dialog.addTitle', 'Add entry'),\n valueLabel: t('customers.config.dictionaries.dialog.valueLabel', 'Value'),\n labelLabel: t('customers.config.dictionaries.dialog.labelLabel', 'Label'),\n saveLabel: t('customers.config.dictionaries.dialog.save', 'Save'),\n cancelLabel: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n appearance: {\n colorLabel: t('customers.config.dictionaries.dialog.colorLabel', 'Color'),\n colorHelp: t('customers.config.dictionaries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.dictionaries.dialog.colorClear', 'Remove color'),\n iconLabel: t('customers.config.dictionaries.dialog.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.dictionaries.dialog.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.dictionaries.dialog.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.dictionaries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.dictionaries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.dictionaries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.dictionaries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.dictionaries.appearance.empty', 'None'),\n },\n }), [dialog, t])\n\n return (\n <section\n id={getCustomerDictionarySettingsSectionId(kind)}\n className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-6 py-4 space-y-1\">\n <h2 className=\"text-lg font-medium\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n <div className=\"px-2 py-4 sm:px-4\">\n <DictionaryTable\n entries={entries}\n loading={loading}\n canManage\n onCreate={handleCreate}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onRefresh={loadEntries}\n translations={tableTranslations}\n />\n </div>\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{formTranslations.title}</DialogTitle>\n </DialogHeader>\n <DictionaryForm\n mode={dialog?.mode === 'edit' ? 'edit' : 'create'}\n initialValues={currentValues}\n onSubmit={submitForm}\n onCancel={closeDialog}\n submitting={submitting}\n translations={formTranslations}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n )\n}\n"],
5
+ "mappings": ";AAqKM,SACE,KADF;AAnKN,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,gBAAgB,sBAAsB,mCAAmC;AAClF,SAAS,iCAAiC;AAC1C,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,OAEK;AACP,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AAiBvC,MAAM,sBAA4C;AAAA,EAChD,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AACR;AAEA,MAAM,yCAAyC,CAAC,KAAK,KAAK,GAAI;AAE9D,SAAS,iCAAqD;AAC5D,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,OAAO,OAAO,SAAS;AAC7B,MAAI,CAAC,KAAK,WAAW,uBAAuB,EAAG,QAAO;AACtD,MAAI;AACF,WAAO,SAAS,eAAe,mBAAmB,KAAK,MAAM,CAAC,CAAC,CAAC;AAAA,EAClE,QAAQ;AACN,WAAO,SAAS,eAAe,KAAK,MAAM,CAAC,CAAC;AAAA,EAC9C;AACF;AAEe,SAAR,qBAAsC;AAC3C,QAAM,IAAI,KAAK;AAEf,QAAM,WAAW,MAAM,QAA6B,MAAM;AAAA,IACxD;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,mEAAmE,YAAY;AAAA,MACxF,aAAa,EAAE,yEAAyE,iEAAiE;AAAA,IAC3J;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,yDAAyD,UAAU;AAAA,MAC5E,aAAa,EAAE,+DAA+D,qDAAqD;AAAA,IACrI;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,6DAA6D,eAAe;AAAA,MACrF,aAAa,EAAE,mEAAmE,0CAA0C;AAAA,IAC9H;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,0DAA0D,YAAY;AAAA,MAC/E,aAAa,EAAE,gEAAgE,6CAA6C;AAAA,IAC9H;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,wDAAwD,SAAS;AAAA,MAC1E,aAAa,EAAE,8DAA8D,sCAAsC;AAAA,IACrH;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,2DAA2D,YAAY;AAAA,MAChF,aAAa,EAAE,iEAAiE,0CAA0C;AAAA,IAC5H;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,0DAA0D,kBAAkB;AAAA,MACrF,aAAa,EAAE,gEAAgE,wDAAwD;AAAA,IACzI;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,8DAA8D,gBAAgB;AAAA,MACvF,aAAa,EAAE,oEAAoE,2DAA2D;AAAA,IAChJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,oEAAoE,sBAAsB;AAAA,MACnG,aAAa,EAAE,0EAA0E,kEAAkE;AAAA,IAC7J;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,6DAA6D,eAAe;AAAA,MACrF,aAAa,EAAE,mEAAmE,qCAAqC;AAAA,IACzH;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,UAAM,aAAuB,CAAC;AAC9B,QAAI,UAAyB;AAE7B,UAAM,wBAAwB,MAAM;AAClC,UAAI,YAAY,MAAM;AACpB,eAAO,qBAAqB,OAAO;AACnC,kBAAU;AAAA,MACZ;AACA,aAAO,WAAW,SAAS,GAAG;AAC5B,cAAM,YAAY,WAAW,IAAI;AACjC,YAAI,cAAc,OAAW,QAAO,aAAa,SAAS;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM,qBAAqB,MAAM;AAC/B,qCAA+B,GAAG,eAAe,EAAE,OAAO,QAAQ,CAAC;AAAA,IACrE;AAEA,UAAM,qBAAqB,MAAM;AAC/B,4BAAsB;AACtB,UAAI,CAAC,OAAO,SAAS,KAAK,WAAW,uBAAuB,EAAG;AAC/D,yBAAmB;AACnB,UAAI,OAAO,OAAO,0BAA0B,YAAY;AACtD,kBAAU,OAAO,sBAAsB,kBAAkB;AAAA,MAC3D;AACA,6CAAuC,QAAQ,CAAC,UAAU;AACxD,mBAAW,KAAK,OAAO,WAAW,oBAAoB,KAAK,CAAC;AAAA,MAC9D,CAAC;AAAA,IACH;AAEA,uBAAmB;AACnB,WAAO,iBAAiB,cAAc,kBAAkB;AACxD,WAAO,MAAM;AACX,aAAO,oBAAoB,cAAc,kBAAkB;AAC3D,4BAAsB;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,YAAO,WAAU,aAChB;AAAA,0BAAC,QAAG,WAAU,0BACX,YAAE,uCAAuC,wBAAwB,GACpE;AAAA,MACA,oBAAC,OAAE,WAAU,iCACV,YAAE,6CAA6C,uDAAuD,GACzG;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,aACZ,mBAAS,IAAI,CAAC,YACb,oBAAC,6BAA8C,GAAG,WAAlB,QAAQ,IAAmB,CAC5D,GACH;AAAA,KACF;AAEJ;AAIA,SAAS,0BAA0B,EAAE,MAAM,OAAO,YAAY,GAAmC;AAC/F,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,eAAe,4BAA4B;AACjD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAiC,CAAC,CAAC;AACvE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAkB,IAAI;AAC1D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA6B,IAAI;AACnE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAExD,QAAM,gBAAgB,wBAAwB,IAAI;AAClD,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAIxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AACD,QAAM,kBAAkB,MAAM;AAAA,IAC5B,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,IACF;AAAA,IACA,CAAC,mBAAmB,aAAa;AAAA,EACnC;AAEA,QAAM,yBAAyB,EAAE,mDAAmD,oEAAoE;AACxJ,QAAM,mBAAmB,EAAE,mDAAmD,gCAAgC;AAC9G,QAAM,iBAAiB,EAAE,iDAAiD,WAAW;AACrF,QAAM,YAAY,EAAE,4CAA4C,oCAAoC;AACpG,QAAM,YAAY,EAAE,4CAA4C,kCAAkC;AAClG,QAAM,cAAc,EAAE,8CAA8C,oCAAoC;AACxG,QAAM,cAAc,EAAE,8CAA8C,yBAAyB;AAC7F,QAAM,gBAAgB,EAAE,gDAAgD,2BAA2B;AACnG,QAAM,wBAAwB,EAAE,+CAA+C,qBAAqB;AACpG,QAAM,6BAA6B,EAAE,+DAA+D,qBAAqB;AACzH,QAAM,4BAA4B;AAAA,IAChC;AAAA,IACA;AAAA,EACF;AACA,QAAM,kBAAkB,EAAE,sDAAsD,6BAA6B;AAC7G,QAAM,oBAAoB,EAAE,mDAAmD,sBAAiB;AAEhG,QAAM,qBAAqB,MAAM,YAAY,CAAC,UAAkB,UAAkB;AAChF,WAAO,SAAS,QAAQ,aAAa,OAAO,KAAK,CAAC;AAAA,EACpD,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,MAAM,YAAY,YAAY;AAChD,eAAW,IAAI;AACf,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,+BAA+B,IAAI;AAAA,QACnC;AAAA,QACA,EAAE,cAAc,UAAU;AAAA,MAC5B;AACA,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,EAAG,OAAM,IAAI,MAAM,SAAS;AAC1D,YAAM,SAAiC,KAAK,MAAM,IAAI,CAAC,UAAe;AAAA,QACpE,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,QAC9B,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,QACrD,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,QACrD,MAAM,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AAAA,QAClD,YAAY,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAAA,QACpE,gBAAgB,OAAO,KAAK,mBAAmB,WAAW,KAAK,iBAAiB;AAAA,QAChF,UAAU,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AAAA,QAC9D,aAAa,KAAK,gBAAgB;AAAA,QAClC,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,QACjE,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,MACnE,EAAE;AACF,iBAAW,MAAM;AAAA,IACnB,SAAS,KAAK;AACZ,aAAO,MAAM,sCAAsC,EAAE,IAAI,CAAC;AAC1D,YAAM,WAAW,OAAO;AAAA,IAC1B,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,WAAW,MAAM,YAAY,CAAC;AAElC,QAAM,UAAU,MAAM;AACpB,gBAAY,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC9B,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,cAAc,MAAM,YAAY,MAAM;AAC1C,cAAU,IAAI;AAAA,EAChB,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,cAAU,EAAE,MAAM,SAAS,CAAC;AAAA,EAC9B,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,MAAM,YAAY,CAAC,UAAgC;AACpE,QAAI,MAAM,aAAa;AACrB,YAAM,wBAAwB,MAAM;AACpC;AAAA,IACF;AACA,cAAU,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,EACnC,GAAG,CAAC,sBAAsB,CAAC;AAE3B,QAAM,eAAe,MAAM,YAAY,OAAO,UAAgC;AAC5E,QAAI,MAAM,aAAa;AACrB,YAAM,wBAAwB,MAAM;AACpC;AAAA,IACF;AACA,QAAI,SAAS,2BAA2B,MAAM,cAAc,KAAK,GAAG;AAClE,YAAM,QAAQ;AAAA,QACZ,OAAO;AAAA,QACP,aAAa,mBAAmB,2BAA2B,MAAM,cAAc,CAAC;AAAA,QAChF,aAAa;AAAA,QACb,YAAY,EAAE,+CAA+C,QAAQ;AAAA,MACvE,CAAC;AACD;AAAA,IACF;AACA,UAAM,UAAU,sBAAsB,QAAQ,aAAa,MAAM,SAAS,MAAM,KAAK;AACrF,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAChB,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,MACT;AAAA,UAA4B,0BAA0B,MAAM,SAAS;AAAA,UAAG,MACtE;AAAA,YACE,+BAA+B,IAAI,IAAI,mBAAmB,MAAM,EAAE,CAAC;AAAA,YACnE,EAAE,QAAQ,SAAS;AAAA,YACnB,EAAE,cAAc,YAAY;AAAA,UAC9B;AAAA,QACF;AAAA,QACF,SAAS;AAAA,QACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,MAAM,IAAI,KAAK;AAAA,MAC1D,CAAC;AACD,YAAM,eAAe,SAAS;AAC9B,YAAM,YAAY;AAAA,IACpB,SAAS,KAAK;AACZ,aAAO,MAAM,wCAAwC,EAAE,IAAI,CAAC;AAC5D,UAAI,SAAS,0BAA2B,KAA+B,SAAS,oBAAoB;AAClG,cAAM,aAAa,OAAQ,IAA8B,cAAc,CAAC;AACxE,cAAM,QAAQ;AAAA,UACZ,OAAO;AAAA,UACP,aAAa,mBAAmB,2BAA2B,UAAU;AAAA,UACrE,aAAa;AAAA,UACb,YAAY,EAAE,+CAA+C,QAAQ;AAAA,QACvE,CAAC;AACD;AAAA,MACF;AACA,YAAM,eAAe,eAAe,QAAQ,IAAI,UAAU;AAC1D,YAAM,cAAc,OAAO;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,SAAS,uBAAuB,aAAa,oBAAoB,wBAAwB,MAAM,aAAa,iBAAiB,2BAA2B,4BAA4B,aAAa,eAAe,CAAC,CAAC;AAEtN,QAAM,aAAa,MAAM,YAAY,OAAO,WAAiC;AAC3E,UAAM,UAAU;AAAA,MACd,OAAO,OAAO;AAAA,MACd,OAAO,OAAO;AAAA,MACd,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,IACf;AACA,kBAAc,IAAI;AAClB,QAAI;AACF,UAAI,CAAC,UAAU,OAAO,SAAS,UAAU;AACvC,cAAM,YAAY;AAAA,UAChB,WAAW,MACT;AAAA,YACE,+BAA+B,IAAI;AAAA,YACnC;AAAA,cACE,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,YAC9B;AAAA,YACA,EAAE,cAAc,UAAU;AAAA,UAC5B;AAAA,UACF,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,MAAM,GAAG,QAAQ;AAAA,QACxD,CAAC;AACD,cAAM,aAAa,SAAS;AAAA,MAC9B,WAAW,OAAO,SAAS,QAAQ;AACjC,cAAM,SAAS,OAAO;AACtB,YAAI,OAAO,aAAa;AACtB,gBAAM,wBAAwB,MAAM;AACpC;AAAA,QACF;AACA,cAAM,OAAgC,CAAC;AACvC,YAAI,OAAO,UAAU,OAAO,MAAO,MAAK,QAAQ,OAAO;AACvD,YAAI,OAAO,UAAU,OAAO,MAAO,MAAK,QAAQ,OAAO;AACvD,cAAM,YAAY,OAAO,SAAS;AAClC,YAAI,eAAe,OAAO,SAAS,MAAO,MAAK,QAAQ;AACvD,cAAM,WAAW,OAAO,QAAQ;AAChC,YAAI,cAAc,OAAO,QAAQ,MAAO,MAAK,OAAO;AACpD,YAAI,OAAO,KAAK,IAAI,EAAE,WAAW,GAAG;AAClC,sBAAY;AACZ;AAAA,QACF;AACA,cAAM,YAAY;AAAA,UAChB,WAAW,MACT;AAAA,YAA4B,0BAA0B,OAAO,SAAS;AAAA,YAAG,MACvE;AAAA,cACE,+BAA+B,IAAI,IAAI,mBAAmB,OAAO,EAAE,CAAC;AAAA,cACpE;AAAA,gBACE,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,cAC3B;AAAA,cACA,EAAE,cAAc,UAAU;AAAA,YAC5B;AAAA,UACF;AAAA,UACF,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,OAAO,IAAI,MAAM,GAAG,KAAK;AAAA,QACpE,CAAC;AACD,cAAM,aAAa,SAAS;AAAA,MAC9B;AACA,kBAAY;AACZ,YAAM,YAAY;AAAA,IACpB,SAAS,KAAK;AACZ,aAAO,MAAM,wCAAwC,EAAE,IAAI,CAAC;AAC5D,YAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,SAAS;AAAA,IACxD,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,aAAa,QAAQ,WAAW,wBAAwB,MAAM,aAAa,iBAAiB,aAAa,WAAW,CAAC;AAEzH,QAAM,gBAAgB,MAAM,QAA8B,MAAM;AAC9D,QAAI,UAAU,OAAO,SAAS,QAAQ;AACpC,aAAO;AAAA,QACL,OAAO,OAAO,MAAM;AAAA,QACpB,OAAO,OAAO,MAAM;AAAA,QACpB,OAAO,OAAO,MAAM;AAAA,QACpB,MAAM,OAAO,MAAM;AAAA,MACrB;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,oBAAoB,MAAM,QAAQ,OAAO;AAAA,IAC7C;AAAA,IACA,aAAa,EAAE,+CAA+C,OAAO;AAAA,IACrE,aAAa,EAAE,+CAA+C,OAAO;AAAA,IACrE,kBAAkB,EAAE,oDAAoD,YAAY;AAAA,IACpF,UAAU,EAAE,6CAA6C,WAAW;AAAA,IACpE,WAAW,EAAE,8CAA8C,MAAM;AAAA,IACjE,aAAa,EAAE,gDAAgD,QAAQ;AAAA,IACvE,cAAc,EAAE,iDAAiD,SAAS;AAAA,IAC1E;AAAA,IACA;AAAA,IACA,YAAY,EAAE,uCAAuC,iBAAiB;AAAA,IACtE,iBAAiB,SAAS,yBAAyB,kBAAkB;AAAA,IACrE;AAAA,EACF,IAAI,CAAC,gBAAgB,kBAAkB,MAAM,mBAAmB,GAAG,OAAO,eAAe,CAAC;AAE1F,QAAM,mBAAmB,MAAM,QAAQ,OAAO;AAAA,IAC5C,OAAO,QAAQ,SAAS,SACpB,EAAE,kDAAkD,YAAY,IAChE,EAAE,iDAAiD,WAAW;AAAA,IAClE,YAAY,EAAE,mDAAmD,OAAO;AAAA,IACxE,YAAY,EAAE,mDAAmD,OAAO;AAAA,IACxE,WAAW,EAAE,6CAA6C,MAAM;AAAA,IAChE,aAAa,EAAE,+CAA+C,QAAQ;AAAA,IACtE,YAAY;AAAA,MACV,YAAY,EAAE,mDAAmD,OAAO;AAAA,MACxE,WAAW,EAAE,kDAAkD,wCAAwC;AAAA,MACvG,iBAAiB,EAAE,mDAAmD,cAAc;AAAA,MACpF,WAAW,EAAE,kDAAkD,MAAM;AAAA,MACrE,iBAAiB,EAAE,wDAAwD,+CAA+C;AAAA,MAC1H,wBAAwB,EAAE,mDAAmD,yBAAyB;AAAA,MACtG,uBAAuB,EAAE,8DAA8D,8BAAyB;AAAA,MAChH,sBAAsB,EAAE,wDAAwD,6BAA6B;AAAA,MAC7G,sBAAsB,EAAE,wDAAwD,aAAa;AAAA,MAC7F,gBAAgB,EAAE,kDAAkD,aAAa;AAAA,MACjF,mBAAmB,EAAE,kDAAkD,MAAM;AAAA,IAC/E;AAAA,EACF,IAAI,CAAC,QAAQ,CAAC,CAAC;AAEf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,uCAAuC,IAAI;AAAA,MAC/C,WAAU;AAAA,MAEV;AAAA,6BAAC,SAAI,WAAU,gCACb;AAAA,8BAAC,QAAG,WAAU,uBAAuB,iBAAM;AAAA,UAC3C,oBAAC,OAAE,WAAU,iCAAiC,uBAAY;AAAA,WAC5D;AAAA,QACA,oBAAC,SAAI,WAAU,qBACb;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA,WAAS;AAAA,YACT,UAAU;AAAA,YACV,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,WAAW;AAAA,YACX,cAAc;AAAA;AAAA,QAChB,GACF;AAAA,QACA,oBAAC,UAAO,MAAM,WAAW,MAAM,cAAc,CAAC,SAAS;AAAE,cAAI,CAAC,KAAM,aAAY;AAAA,QAAE,GAChF,+BAAC,iBAAc,WAAU,YACvB;AAAA,8BAAC,gBACC,8BAAC,eAAa,2BAAiB,OAAM,GACvC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,QAAQ,SAAS,SAAS,SAAS;AAAA,cACzC,eAAe;AAAA,cACf,UAAU;AAAA,cACV,UAAU;AAAA,cACV;AAAA,cACA,cAAc;AAAA,cACd,iBAAiB;AAAA;AAAA,UACnB;AAAA,WACF,GACF;AAAA,QACC;AAAA;AAAA;AAAA,EACH;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -77,6 +77,11 @@ function DictionarySortSettings() {
77
77
  title: t("customers.config.dictionaries.sections.activityTypes.title", "Activity types"),
78
78
  description: t("customers.config.dictionaries.sections.activityTypes.description", "Define the activity types used for customer interactions.")
79
79
  },
80
+ {
81
+ kind: "interaction-statuses",
82
+ title: t("customers.config.dictionaries.sections.interactionStatuses.title", "Interaction statuses"),
83
+ description: t("customers.config.dictionaries.sections.interactionStatuses.description", "Manage the statuses available for tasks and logged interactions.")
84
+ },
80
85
  {
81
86
  kind: "address-types",
82
87
  title: t("customers.config.dictionaries.sections.addressTypes.title", "Address types"),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/components/DictionarySortSettings.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n dictionaryEntrySortModes,\n type DictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport type { CustomerDictionaryKind } from '../lib/dictionaries'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst SAVE_CONTEXT_ID = 'customers-dictionary-sort-modes'\n\ntype SortModeMap = Partial<Record<CustomerDictionaryKind, DictionaryEntrySortMode>>\n\ntype DictionarySortSection = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\nfunction isDictionaryEntrySortMode(value: string): value is DictionaryEntrySortMode {\n return dictionaryEntrySortModes.includes(value as DictionaryEntrySortMode)\n}\n\nexport function DictionarySortSettings() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [sortModes, setSortModes] = React.useState<SortModeMap>({})\n const [loading, setLoading] = React.useState(true)\n const [savingKind, setSavingKind] = React.useState<CustomerDictionaryKind | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const sections = React.useMemo<DictionarySortSection[]>(() => [\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'pipeline-stages',\n title: t('customers.config.dictionaries.sections.pipelineStages.title', 'Pipeline stages'),\n description: t('customers.config.dictionaries.sections.pipelineStages.description', 'Define the stages used in your deal pipeline.'),\n },\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n const sortOptions = React.useMemo(\n () => dictionaryEntrySortModes.map((mode) => ({\n value: mode,\n label:\n mode === 'label_asc'\n ? t('dictionaries.config.sortModes.labelAsc', 'A to Z')\n : mode === 'label_desc'\n ? t('dictionaries.config.sortModes.labelDesc', 'Z to A')\n : mode === 'value_asc'\n ? t('dictionaries.config.sortModes.valueAsc', 'Value A to Z')\n : mode === 'value_desc'\n ? t('dictionaries.config.sortModes.valueDesc', 'Value Z to A')\n : mode === 'created_at_asc'\n ? t('dictionaries.config.sortModes.createdAtAsc', 'Oldest first')\n : t('dictionaries.config.sortModes.createdAtDesc', 'Newest first'),\n })),\n [t],\n )\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: SAVE_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const data = await readApiResultOrThrow<{ dictionarySortModes?: Record<string, string> }>(\n '/api/customers/settings/dictionary-sort-modes',\n undefined,\n { errorMessage: t('customers.config.dictionarySorting.errorLoad', 'Failed to load dictionary sorting settings.') },\n )\n const next: SortModeMap = {}\n for (const section of sections) {\n const value = data?.dictionarySortModes?.[section.kind]\n next[section.kind] = value && isDictionaryEntrySortMode(value)\n ? value\n : DEFAULT_DICTIONARY_ENTRY_SORT_MODE\n }\n if (!cancelled) setSortModes(next)\n } catch (err) {\n logger.error('customers.dictionarySorting.load failed', { err })\n if (!cancelled) {\n const message = err instanceof Error && err.message\n ? err.message\n : t('customers.config.dictionarySorting.errorLoad', 'Failed to load dictionary sorting settings.')\n setError(message)\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [scopeVersion, sections, t])\n\n const handleChange = React.useCallback(async (kind: CustomerDictionaryKind, mode: DictionaryEntrySortMode) => {\n const previous = sortModes\n const next = { ...sortModes, [kind]: mode }\n setSortModes(next)\n setSavingKind(kind)\n setError(null)\n try {\n await runMutation({\n // optimistic-lock-exempt: tenant-scoped settings blob (dictionary sort\n // modes), not a versioned per-record entity \u2014 there is no `updatedAt`\n // round-trip to lock against, and concurrent writes converge on the\n // last-selected preference. Mirrors other singleton settings PATCHes.\n operation: async () => {\n await apiCallOrThrow(\n '/api/customers/settings/dictionary-sort-modes',\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ dictionarySortModes: next }),\n },\n { errorMessage: t('customers.config.dictionarySorting.errorSave', 'Failed to save dictionary sorting settings.') },\n )\n },\n context: {\n formId: SAVE_CONTEXT_ID,\n resourceKind: 'customers.settings',\n retryLastMutation,\n },\n })\n flash(t('customers.config.dictionarySorting.success', 'Dictionary sorting settings saved.'), 'success')\n } catch (err) {\n setSortModes(previous)\n const message = err instanceof Error && err.message\n ? err.message\n : t('customers.config.dictionarySorting.errorSave', 'Failed to save dictionary sorting settings.')\n setError(message)\n flash(message, 'error')\n } finally {\n setSavingKind(null)\n }\n }, [retryLastMutation, runMutation, sortModes, t])\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-background p-4\">\n <header className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">\n {t('customers.config.dictionarySorting.title', 'Dictionary sorting')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionarySorting.description', 'Choose how customer dictionary values are ordered in dropdowns and settings.')}\n </p>\n </header>\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('customers.config.dictionarySorting.loading', 'Loading dictionary sorting settings\u2026')}\n </div>\n ) : (\n <div className=\"grid gap-3 lg:grid-cols-2\">\n {sections.map((section) => {\n const value = sortModes[section.kind] ?? DEFAULT_DICTIONARY_ENTRY_SORT_MODE\n const saving = savingKind === section.kind\n return (\n <div key={section.kind} className=\"rounded border p-3\">\n <div className=\"mb-3 space-y-1\">\n <div className=\"text-sm font-medium\">{section.title}</div>\n <div className=\"text-xs text-muted-foreground\">{section.description}</div>\n </div>\n <Select\n value={value}\n onValueChange={(next) => {\n if (!isDictionaryEntrySortMode(next) || next === value) return\n handleChange(section.kind, next).catch(() => {})\n }}\n disabled={savingKind !== null}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {saving ? (\n <div className=\"mt-2 inline-flex items-center gap-2 text-xs text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n {t('customers.config.dictionarySorting.updating', 'Saving\u2026')}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </section>\n )\n}\n\nexport default DictionarySortSettings\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n dictionaryEntrySortModes,\n type DictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport type { CustomerDictionaryKind } from '../lib/dictionaries'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst SAVE_CONTEXT_ID = 'customers-dictionary-sort-modes'\n\ntype SortModeMap = Partial<Record<CustomerDictionaryKind, DictionaryEntrySortMode>>\n\ntype DictionarySortSection = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\nfunction isDictionaryEntrySortMode(value: string): value is DictionaryEntrySortMode {\n return dictionaryEntrySortModes.includes(value as DictionaryEntrySortMode)\n}\n\nexport function DictionarySortSettings() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [sortModes, setSortModes] = React.useState<SortModeMap>({})\n const [loading, setLoading] = React.useState(true)\n const [savingKind, setSavingKind] = React.useState<CustomerDictionaryKind | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const sections = React.useMemo<DictionarySortSection[]>(() => [\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'pipeline-stages',\n title: t('customers.config.dictionaries.sections.pipelineStages.title', 'Pipeline stages'),\n description: t('customers.config.dictionaries.sections.pipelineStages.description', 'Define the stages used in your deal pipeline.'),\n },\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'interaction-statuses',\n title: t('customers.config.dictionaries.sections.interactionStatuses.title', 'Interaction statuses'),\n description: t('customers.config.dictionaries.sections.interactionStatuses.description', 'Manage the statuses available for tasks and logged interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n const sortOptions = React.useMemo(\n () => dictionaryEntrySortModes.map((mode) => ({\n value: mode,\n label:\n mode === 'label_asc'\n ? t('dictionaries.config.sortModes.labelAsc', 'A to Z')\n : mode === 'label_desc'\n ? t('dictionaries.config.sortModes.labelDesc', 'Z to A')\n : mode === 'value_asc'\n ? t('dictionaries.config.sortModes.valueAsc', 'Value A to Z')\n : mode === 'value_desc'\n ? t('dictionaries.config.sortModes.valueDesc', 'Value Z to A')\n : mode === 'created_at_asc'\n ? t('dictionaries.config.sortModes.createdAtAsc', 'Oldest first')\n : t('dictionaries.config.sortModes.createdAtDesc', 'Newest first'),\n })),\n [t],\n )\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: SAVE_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const data = await readApiResultOrThrow<{ dictionarySortModes?: Record<string, string> }>(\n '/api/customers/settings/dictionary-sort-modes',\n undefined,\n { errorMessage: t('customers.config.dictionarySorting.errorLoad', 'Failed to load dictionary sorting settings.') },\n )\n const next: SortModeMap = {}\n for (const section of sections) {\n const value = data?.dictionarySortModes?.[section.kind]\n next[section.kind] = value && isDictionaryEntrySortMode(value)\n ? value\n : DEFAULT_DICTIONARY_ENTRY_SORT_MODE\n }\n if (!cancelled) setSortModes(next)\n } catch (err) {\n logger.error('customers.dictionarySorting.load failed', { err })\n if (!cancelled) {\n const message = err instanceof Error && err.message\n ? err.message\n : t('customers.config.dictionarySorting.errorLoad', 'Failed to load dictionary sorting settings.')\n setError(message)\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [scopeVersion, sections, t])\n\n const handleChange = React.useCallback(async (kind: CustomerDictionaryKind, mode: DictionaryEntrySortMode) => {\n const previous = sortModes\n const next = { ...sortModes, [kind]: mode }\n setSortModes(next)\n setSavingKind(kind)\n setError(null)\n try {\n await runMutation({\n // optimistic-lock-exempt: tenant-scoped settings blob (dictionary sort\n // modes), not a versioned per-record entity \u2014 there is no `updatedAt`\n // round-trip to lock against, and concurrent writes converge on the\n // last-selected preference. Mirrors other singleton settings PATCHes.\n operation: async () => {\n await apiCallOrThrow(\n '/api/customers/settings/dictionary-sort-modes',\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ dictionarySortModes: next }),\n },\n { errorMessage: t('customers.config.dictionarySorting.errorSave', 'Failed to save dictionary sorting settings.') },\n )\n },\n context: {\n formId: SAVE_CONTEXT_ID,\n resourceKind: 'customers.settings',\n retryLastMutation,\n },\n })\n flash(t('customers.config.dictionarySorting.success', 'Dictionary sorting settings saved.'), 'success')\n } catch (err) {\n setSortModes(previous)\n const message = err instanceof Error && err.message\n ? err.message\n : t('customers.config.dictionarySorting.errorSave', 'Failed to save dictionary sorting settings.')\n setError(message)\n flash(message, 'error')\n } finally {\n setSavingKind(null)\n }\n }, [retryLastMutation, runMutation, sortModes, t])\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-background p-4\">\n <header className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">\n {t('customers.config.dictionarySorting.title', 'Dictionary sorting')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionarySorting.description', 'Choose how customer dictionary values are ordered in dropdowns and settings.')}\n </p>\n </header>\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('customers.config.dictionarySorting.loading', 'Loading dictionary sorting settings\u2026')}\n </div>\n ) : (\n <div className=\"grid gap-3 lg:grid-cols-2\">\n {sections.map((section) => {\n const value = sortModes[section.kind] ?? DEFAULT_DICTIONARY_ENTRY_SORT_MODE\n const saving = savingKind === section.kind\n return (\n <div key={section.kind} className=\"rounded border p-3\">\n <div className=\"mb-3 space-y-1\">\n <div className=\"text-sm font-medium\">{section.title}</div>\n <div className=\"text-xs text-muted-foreground\">{section.description}</div>\n </div>\n <Select\n value={value}\n onValueChange={(next) => {\n if (!isDictionaryEntrySortMode(next) || next === value) return\n handleChange(section.kind, next).catch(() => {})\n }}\n disabled={savingKind !== null}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {saving ? (\n <div className=\"mt-2 inline-flex items-center gap-2 text-xs text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n {t('customers.config.dictionarySorting.updating', 'Saving\u2026')}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </section>\n )\n}\n\nexport default DictionarySortSettings\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
8
8
  import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
9
9
  import { ActivitiesDayStrip } from "./ActivitiesDayStrip.js";
10
10
  import { ActivitiesAddNewMenu } from "./ActivitiesAddNewMenu.js";
11
+ import { isOpenInteractionStatus } from "../../lib/interactionStatus.js";
11
12
  import { createLogger } from "@open-mercato/shared/lib/logger";
12
13
  const logger = createLogger("customers");
13
14
  const TYPE_ICONS = {
@@ -39,7 +40,7 @@ function isOverdue(activity, now) {
39
40
  if (!scheduled) return false;
40
41
  const date = new Date(scheduled);
41
42
  if (Number.isNaN(date.getTime())) return false;
42
- return date.getTime() < now.getTime() && activity.status !== "done";
43
+ return date.getTime() < now.getTime() && isOpenInteractionStatus(activity.status);
43
44
  }
44
45
  const FETCH_WINDOW_DAYS = 31;
45
46
  function formatTime(date) {
@@ -178,7 +179,7 @@ function PlannedEventRow({ activity, onClick, entityCompanyName, t }) {
178
179
  const validDate = !Number.isNaN(date.getTime());
179
180
  const Icon = TYPE_ICONS[activity.interactionType] ?? Users;
180
181
  const duration = typeof activity.duration === "number" && activity.duration > 0 ? activity.duration : null;
181
- const overdue = validDate && date.getTime() < Date.now() && activity.status !== "done";
182
+ const overdue = validDate && date.getTime() < Date.now() && isOpenInteractionStatus(activity.status);
182
183
  const typeLabel = labelForType(activity.interactionType, t);
183
184
  const subtitleSuffix = activity.dealTitle ?? entityCompanyName ?? null;
184
185
  const subtitle = subtitleSuffix ? `${typeLabel} \xB7 ${subtitleSuffix}` : typeLabel;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ActivitiesCard.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Calendar, CalendarClock, Clock, Mail, Phone, StickyNote, Users } from 'lucide-react'\nimport { toZonedTime } from 'date-fns-tz'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { ActivitiesDayStrip } from './ActivitiesDayStrip'\nimport { ActivitiesAddNewMenu, type ActivityKind } from './ActivitiesAddNewMenu'\nimport type { InteractionSummary } from './types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ninterface ActivitiesCardProps {\n entityId: string\n /**\n * Initial planned activities (from the parent route's `plannedActivitiesPreview`).\n * Used as the seed value before the broader `/api/customers/interactions` fetch\n * resolves, and as the fallback when the fetch fails. The card always prefers\n * its own fetched window (issue #1809 \u2014 fixes E1 status alignment and E2 type\n * coverage by sourcing from the same endpoint as the day strip rather than the\n * 5-item server preview that excluded most types in practice).\n */\n plannedActivities: InteractionSummary[]\n refreshKey?: number\n onAddNew: (kind: ActivityKind) => void\n onEditActivity?: (activity: InteractionSummary) => void\n /**\n * Optional company name for the parent entity. When the planned activity has no `dealTitle`,\n * the row subtitle falls back to \"{type} \u00B7 {company}\" to mirror Figma 784:809.\n */\n entityCompanyName?: string | null\n}\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n}\n\nconst USER_TIMEZONE = (() => {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n } catch {\n return 'UTC'\n }\n})()\n\n// Project a UTC instant to the user's local timezone before extracting day/month/year\n// for \"same day\" comparisons (issue #1809 \u2014 E3 timezone drift).\nfunction toLocalZonedDate(value: string | Date): Date {\n return toZonedTime(value, USER_TIMEZONE)\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction isOverdue(activity: InteractionSummary, now: Date): boolean {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n return date.getTime() < now.getTime() && activity.status !== 'done'\n}\n\n// Visible window for the day-strip + activity list. Mirrors `VISIBLE_DAYS = 5`\n// in ActivitiesDayStrip with extra padding so navigation forward/back doesn't\n// race the fetch.\nconst FETCH_WINDOW_DAYS = 31\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n}\n\nfunction formatRelativeDay(date: Date, t: TranslateFn): string {\n const now = new Date()\n const today = startOfDay(now)\n const target = startOfDay(date)\n const diff = Math.round((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))\n if (diff === 0) return t('customers.timeline.date.today', 'today')\n if (diff === 1) return t('customers.timeline.date.tomorrow', 'tomorrow')\n if (diff === -1) return t('customers.timeline.date.yesterday', 'yesterday')\n return target.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n}\n\nfunction formatDuration(minutes: number, t: TranslateFn): string {\n if (minutes >= 60) {\n const hours = Math.round((minutes / 60) * 10) / 10\n return t('customers.activities.calendar.hoursShort', '{hours}h', { hours })\n }\n return t('customers.activities.calendar.minutesShort', '{minutes}m', { minutes })\n}\n\nexport function ActivitiesCard({\n entityId,\n plannedActivities,\n refreshKey = 0,\n onAddNew,\n onEditActivity,\n entityCompanyName,\n}: ActivitiesCardProps) {\n const t = useT()\n const [selectedDate, setSelectedDate] = React.useState<Date>(() => startOfDay(new Date()))\n // Fetch the same broader window as the day strip via the canonical interactions\n // endpoint. This single source of truth aligns the day-strip count with the\n // visible event list (issue #1809 \u2014 E1) and surfaces all interaction types\n // (issue #1809 \u2014 E2: the previous reliance on the server-side 5-item preview\n // produced \"Person view shows only Calls\" because the limit happened to drop\n // every non-call entry from the prefix-window).\n const [fetchedEvents, setFetchedEvents] = React.useState<InteractionSummary[] | null>(null)\n\n React.useEffect(() => {\n if (!entityId) {\n setFetchedEvents(null)\n return\n }\n const controller = new AbortController()\n const today = startOfDay(new Date())\n const fromDate = new Date(today)\n fromDate.setDate(today.getDate() - FETCH_WINDOW_DAYS)\n const toDate = new Date(today)\n toDate.setDate(today.getDate() + FETCH_WINDOW_DAYS)\n toDate.setHours(23, 59, 59, 999)\n const params = new URLSearchParams({\n entityId,\n from: fromDate.toISOString(),\n to: toDate.toISOString(),\n // Server caps at 100 (interactions querySchema). 100 is well above what\n // an active CRM record accumulates in a 31-day window of meetings/calls,\n // and the day strip + list naturally degrade to truncation if exceeded.\n limit: '100',\n sortField: 'scheduledAt',\n sortDir: 'asc',\n excludeInteractionType: 'task',\n })\n void (async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: InteractionSummary[] }>(\n `/api/customers/interactions?${params.toString()}`,\n { signal: controller.signal },\n )\n setFetchedEvents(Array.isArray(payload?.items) ? payload.items : [])\n } catch (err) {\n if ((err as { name?: string } | null)?.name !== 'AbortError') {\n logger.warn('failed to load interactions', { component: 'ActivitiesCard', err })\n setFetchedEvents(null)\n }\n }\n })()\n return () => controller.abort()\n }, [entityId, refreshKey])\n\n // Prefer the broader fetch when it has resolved; fall back to the seed prop\n // (route-supplied preview) only while the fetch is in flight or after a\n // hard failure. This guarantees that the rare prop-only render path keeps\n // backwards-compat with existing unit tests while live UI uses the broader fetch.\n const effectiveEvents: InteractionSummary[] = fetchedEvents ?? plannedActivities\n\n const eventsForSelectedDay = React.useMemo(() => {\n const items = effectiveEvents.filter((activity) => {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n // Compare in the user's local timezone so a 23:30 local activity stays\n // on its local-day chip instead of bleeding into the next UTC day\n // (issue #1809 \u2014 E3).\n return isSameDay(toLocalZonedDate(scheduled), selectedDate)\n })\n return items.sort((left, right) => {\n const leftTime = new Date(left.scheduledAt ?? left.occurredAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.scheduledAt ?? right.occurredAt ?? right.createdAt).getTime()\n return leftTime - rightTime\n })\n }, [effectiveEvents, selectedDate])\n\n const overdueCount = React.useMemo(() => {\n const now = new Date()\n return effectiveEvents.filter((activity) => isOverdue(activity, now)).length\n }, [effectiveEvents])\n\n return (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-card pt-4 pb-4 px-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"size-4 text-foreground\" />\n <h3 className=\"text-sm font-semibold leading-none text-foreground\">\n {t('customers.activities.card.title', 'Activities')}\n </h3>\n {overdueCount > 0 ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-error-bg px-1.5 py-0.5 text-xs font-medium text-status-error-text\">\n <CalendarClock className=\"size-3\" />\n {t('customers.activities.card.overdue', '{count} overdue', { count: overdueCount })}\n </span>\n ) : null}\n </div>\n <ActivitiesAddNewMenu onSelect={onAddNew} />\n </div>\n\n <ActivitiesDayStrip\n entityId={entityId}\n selectedDate={selectedDate}\n onSelectDate={setSelectedDate}\n refreshKey={refreshKey}\n events={fetchedEvents ?? undefined}\n />\n\n {eventsForSelectedDay.length > 0 ? (\n <>\n <div className=\"h-px w-full bg-border\" />\n <ul className=\"flex flex-col\">\n {eventsForSelectedDay.map((activity) => (\n <PlannedEventRow\n key={activity.id}\n activity={activity}\n onClick={onEditActivity}\n entityCompanyName={entityCompanyName ?? null}\n t={t}\n />\n ))}\n </ul>\n </>\n ) : (\n <>\n <div className=\"h-px w-full bg-border\" />\n <p className=\"px-1 py-2 text-xs text-muted-foreground\">\n {t('customers.activities.card.empty', 'Nothing scheduled for this day.')}\n </p>\n </>\n )}\n </div>\n )\n}\n\ninterface PlannedEventRowProps {\n activity: InteractionSummary\n onClick?: (activity: InteractionSummary) => void\n entityCompanyName: string | null\n t: TranslateFn\n}\n\nfunction PlannedEventRow({ activity, onClick, entityCompanyName, t }: PlannedEventRowProps) {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const date = new Date(dateStr)\n const validDate = !Number.isNaN(date.getTime())\n const Icon = TYPE_ICONS[activity.interactionType] ?? Users\n const duration = typeof activity.duration === 'number' && activity.duration > 0 ? activity.duration : null\n const overdue = validDate && date.getTime() < Date.now() && activity.status !== 'done'\n const typeLabel = labelForType(activity.interactionType, t)\n const subtitleSuffix = activity.dealTitle ?? entityCompanyName ?? null\n const subtitle = subtitleSuffix ? `${typeLabel} \u00B7 ${subtitleSuffix}` : typeLabel\n const interactive = !!onClick\n\n return (\n <li>\n <button\n type=\"button\"\n onClick={interactive ? () => onClick?.(activity) : undefined}\n disabled={!interactive}\n className={cn(\n 'flex w-full items-start gap-[9px] pt-[8px] text-left transition-colors',\n interactive ? 'cursor-pointer rounded-md hover:bg-accent/30 px-1' : 'px-1',\n )}\n >\n <div className=\"flex h-[44px] w-[43px] shrink-0 flex-col gap-[2px] pt-[2px]\">\n <span className=\"text-xs font-semibold leading-none text-foreground\">\n {validDate ? formatTime(date) : ''}\n </span>\n <span className=\"text-[10px] leading-none font-normal text-muted-foreground\">\n {validDate ? formatRelativeDay(date, t) : ''}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center justify-center rounded-full bg-muted border-4 border-background size-7\">\n <Icon className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"min-w-0 flex flex-1 flex-col gap-[4px]\">\n <span className=\"text-sm leading-5 tracking-[-0.084px] text-foreground\">\n {activity.title ?? activity.body ?? labelForType(activity.interactionType, t)}\n </span>\n {duration ? (\n <span className={cn(\n 'inline-flex w-fit items-center gap-[2px] rounded-full pl-[4px] pr-[8px] py-[2px] text-xs font-medium leading-[16px]',\n overdue\n ? 'bg-status-error-bg text-status-error-text'\n : 'bg-status-warning-bg text-status-warning-text',\n )}>\n <Clock className=\"size-4\" />\n {formatDuration(duration, t)}\n </span>\n ) : null}\n <span className=\"text-[11px] font-normal text-muted-foreground\">{subtitle}</span>\n </div>\n </button>\n </li>\n )\n}\n\nfunction labelForType(type: string, t: TranslateFn): string {\n const map: Record<string, [string, string]> = {\n meeting: ['customers.timeline.filter.meeting', 'Meeting'],\n call: ['customers.timeline.filter.call', 'Call'],\n email: ['customers.timeline.filter.email', 'Email'],\n note: ['customers.timeline.filter.note', 'Note'],\n task: ['customers.timeline.filter.task', 'Task'],\n }\n const entry = map[type]\n return entry ? t(entry[0], entry[1]) : type\n}\n\nexport default ActivitiesCard\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Calendar, CalendarClock, Clock, Mail, Phone, StickyNote, Users } from 'lucide-react'\nimport { toZonedTime } from 'date-fns-tz'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { ActivitiesDayStrip } from './ActivitiesDayStrip'\nimport { ActivitiesAddNewMenu, type ActivityKind } from './ActivitiesAddNewMenu'\nimport type { InteractionSummary } from './types'\nimport { isOpenInteractionStatus } from '../../lib/interactionStatus'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ninterface ActivitiesCardProps {\n entityId: string\n /**\n * Initial planned activities (from the parent route's `plannedActivitiesPreview`).\n * Used as the seed value before the broader `/api/customers/interactions` fetch\n * resolves, and as the fallback when the fetch fails. The card always prefers\n * its own fetched window (issue #1809 \u2014 fixes E1 status alignment and E2 type\n * coverage by sourcing from the same endpoint as the day strip rather than the\n * 5-item server preview that excluded most types in practice).\n */\n plannedActivities: InteractionSummary[]\n refreshKey?: number\n onAddNew: (kind: ActivityKind) => void\n onEditActivity?: (activity: InteractionSummary) => void\n /**\n * Optional company name for the parent entity. When the planned activity has no `dealTitle`,\n * the row subtitle falls back to \"{type} \u00B7 {company}\" to mirror Figma 784:809.\n */\n entityCompanyName?: string | null\n}\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n}\n\nconst USER_TIMEZONE = (() => {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n } catch {\n return 'UTC'\n }\n})()\n\n// Project a UTC instant to the user's local timezone before extracting day/month/year\n// for \"same day\" comparisons (issue #1809 \u2014 E3 timezone drift).\nfunction toLocalZonedDate(value: string | Date): Date {\n return toZonedTime(value, USER_TIMEZONE)\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction isOverdue(activity: InteractionSummary, now: Date): boolean {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n return date.getTime() < now.getTime() && isOpenInteractionStatus(activity.status)\n}\n\n// Visible window for the day-strip + activity list. Mirrors `VISIBLE_DAYS = 5`\n// in ActivitiesDayStrip with extra padding so navigation forward/back doesn't\n// race the fetch.\nconst FETCH_WINDOW_DAYS = 31\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n}\n\nfunction formatRelativeDay(date: Date, t: TranslateFn): string {\n const now = new Date()\n const today = startOfDay(now)\n const target = startOfDay(date)\n const diff = Math.round((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))\n if (diff === 0) return t('customers.timeline.date.today', 'today')\n if (diff === 1) return t('customers.timeline.date.tomorrow', 'tomorrow')\n if (diff === -1) return t('customers.timeline.date.yesterday', 'yesterday')\n return target.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n}\n\nfunction formatDuration(minutes: number, t: TranslateFn): string {\n if (minutes >= 60) {\n const hours = Math.round((minutes / 60) * 10) / 10\n return t('customers.activities.calendar.hoursShort', '{hours}h', { hours })\n }\n return t('customers.activities.calendar.minutesShort', '{minutes}m', { minutes })\n}\n\nexport function ActivitiesCard({\n entityId,\n plannedActivities,\n refreshKey = 0,\n onAddNew,\n onEditActivity,\n entityCompanyName,\n}: ActivitiesCardProps) {\n const t = useT()\n const [selectedDate, setSelectedDate] = React.useState<Date>(() => startOfDay(new Date()))\n // Fetch the same broader window as the day strip via the canonical interactions\n // endpoint. This single source of truth aligns the day-strip count with the\n // visible event list (issue #1809 \u2014 E1) and surfaces all interaction types\n // (issue #1809 \u2014 E2: the previous reliance on the server-side 5-item preview\n // produced \"Person view shows only Calls\" because the limit happened to drop\n // every non-call entry from the prefix-window).\n const [fetchedEvents, setFetchedEvents] = React.useState<InteractionSummary[] | null>(null)\n\n React.useEffect(() => {\n if (!entityId) {\n setFetchedEvents(null)\n return\n }\n const controller = new AbortController()\n const today = startOfDay(new Date())\n const fromDate = new Date(today)\n fromDate.setDate(today.getDate() - FETCH_WINDOW_DAYS)\n const toDate = new Date(today)\n toDate.setDate(today.getDate() + FETCH_WINDOW_DAYS)\n toDate.setHours(23, 59, 59, 999)\n const params = new URLSearchParams({\n entityId,\n from: fromDate.toISOString(),\n to: toDate.toISOString(),\n // Server caps at 100 (interactions querySchema). 100 is well above what\n // an active CRM record accumulates in a 31-day window of meetings/calls,\n // and the day strip + list naturally degrade to truncation if exceeded.\n limit: '100',\n sortField: 'scheduledAt',\n sortDir: 'asc',\n excludeInteractionType: 'task',\n })\n void (async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: InteractionSummary[] }>(\n `/api/customers/interactions?${params.toString()}`,\n { signal: controller.signal },\n )\n setFetchedEvents(Array.isArray(payload?.items) ? payload.items : [])\n } catch (err) {\n if ((err as { name?: string } | null)?.name !== 'AbortError') {\n logger.warn('failed to load interactions', { component: 'ActivitiesCard', err })\n setFetchedEvents(null)\n }\n }\n })()\n return () => controller.abort()\n }, [entityId, refreshKey])\n\n // Prefer the broader fetch when it has resolved; fall back to the seed prop\n // (route-supplied preview) only while the fetch is in flight or after a\n // hard failure. This guarantees that the rare prop-only render path keeps\n // backwards-compat with existing unit tests while live UI uses the broader fetch.\n const effectiveEvents: InteractionSummary[] = fetchedEvents ?? plannedActivities\n\n const eventsForSelectedDay = React.useMemo(() => {\n const items = effectiveEvents.filter((activity) => {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n // Compare in the user's local timezone so a 23:30 local activity stays\n // on its local-day chip instead of bleeding into the next UTC day\n // (issue #1809 \u2014 E3).\n return isSameDay(toLocalZonedDate(scheduled), selectedDate)\n })\n return items.sort((left, right) => {\n const leftTime = new Date(left.scheduledAt ?? left.occurredAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.scheduledAt ?? right.occurredAt ?? right.createdAt).getTime()\n return leftTime - rightTime\n })\n }, [effectiveEvents, selectedDate])\n\n const overdueCount = React.useMemo(() => {\n const now = new Date()\n return effectiveEvents.filter((activity) => isOverdue(activity, now)).length\n }, [effectiveEvents])\n\n return (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-card pt-4 pb-4 px-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"size-4 text-foreground\" />\n <h3 className=\"text-sm font-semibold leading-none text-foreground\">\n {t('customers.activities.card.title', 'Activities')}\n </h3>\n {overdueCount > 0 ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-error-bg px-1.5 py-0.5 text-xs font-medium text-status-error-text\">\n <CalendarClock className=\"size-3\" />\n {t('customers.activities.card.overdue', '{count} overdue', { count: overdueCount })}\n </span>\n ) : null}\n </div>\n <ActivitiesAddNewMenu onSelect={onAddNew} />\n </div>\n\n <ActivitiesDayStrip\n entityId={entityId}\n selectedDate={selectedDate}\n onSelectDate={setSelectedDate}\n refreshKey={refreshKey}\n events={fetchedEvents ?? undefined}\n />\n\n {eventsForSelectedDay.length > 0 ? (\n <>\n <div className=\"h-px w-full bg-border\" />\n <ul className=\"flex flex-col\">\n {eventsForSelectedDay.map((activity) => (\n <PlannedEventRow\n key={activity.id}\n activity={activity}\n onClick={onEditActivity}\n entityCompanyName={entityCompanyName ?? null}\n t={t}\n />\n ))}\n </ul>\n </>\n ) : (\n <>\n <div className=\"h-px w-full bg-border\" />\n <p className=\"px-1 py-2 text-xs text-muted-foreground\">\n {t('customers.activities.card.empty', 'Nothing scheduled for this day.')}\n </p>\n </>\n )}\n </div>\n )\n}\n\ninterface PlannedEventRowProps {\n activity: InteractionSummary\n onClick?: (activity: InteractionSummary) => void\n entityCompanyName: string | null\n t: TranslateFn\n}\n\nfunction PlannedEventRow({ activity, onClick, entityCompanyName, t }: PlannedEventRowProps) {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const date = new Date(dateStr)\n const validDate = !Number.isNaN(date.getTime())\n const Icon = TYPE_ICONS[activity.interactionType] ?? Users\n const duration = typeof activity.duration === 'number' && activity.duration > 0 ? activity.duration : null\n const overdue = validDate && date.getTime() < Date.now() && isOpenInteractionStatus(activity.status)\n const typeLabel = labelForType(activity.interactionType, t)\n const subtitleSuffix = activity.dealTitle ?? entityCompanyName ?? null\n const subtitle = subtitleSuffix ? `${typeLabel} \u00B7 ${subtitleSuffix}` : typeLabel\n const interactive = !!onClick\n\n return (\n <li>\n <button\n type=\"button\"\n onClick={interactive ? () => onClick?.(activity) : undefined}\n disabled={!interactive}\n className={cn(\n 'flex w-full items-start gap-[9px] pt-[8px] text-left transition-colors',\n interactive ? 'cursor-pointer rounded-md hover:bg-accent/30 px-1' : 'px-1',\n )}\n >\n <div className=\"flex h-[44px] w-[43px] shrink-0 flex-col gap-[2px] pt-[2px]\">\n <span className=\"text-xs font-semibold leading-none text-foreground\">\n {validDate ? formatTime(date) : ''}\n </span>\n <span className=\"text-[10px] leading-none font-normal text-muted-foreground\">\n {validDate ? formatRelativeDay(date, t) : ''}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center justify-center rounded-full bg-muted border-4 border-background size-7\">\n <Icon className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"min-w-0 flex flex-1 flex-col gap-[4px]\">\n <span className=\"text-sm leading-5 tracking-[-0.084px] text-foreground\">\n {activity.title ?? activity.body ?? labelForType(activity.interactionType, t)}\n </span>\n {duration ? (\n <span className={cn(\n 'inline-flex w-fit items-center gap-[2px] rounded-full pl-[4px] pr-[8px] py-[2px] text-xs font-medium leading-[16px]',\n overdue\n ? 'bg-status-error-bg text-status-error-text'\n : 'bg-status-warning-bg text-status-warning-text',\n )}>\n <Clock className=\"size-4\" />\n {formatDuration(duration, t)}\n </span>\n ) : null}\n <span className=\"text-[11px] font-normal text-muted-foreground\">{subtitle}</span>\n </div>\n </button>\n </li>\n )\n}\n\nfunction labelForType(type: string, t: TranslateFn): string {\n const map: Record<string, [string, string]> = {\n meeting: ['customers.timeline.filter.meeting', 'Meeting'],\n call: ['customers.timeline.filter.call', 'Call'],\n email: ['customers.timeline.filter.email', 'Email'],\n note: ['customers.timeline.filter.note', 'Note'],\n task: ['customers.timeline.filter.task', 'Task'],\n }\n const entry = map[type]\n return entry ? t(entry[0], entry[1]) : type\n}\n\nexport default ActivitiesCard\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,6 +9,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
9
9
  import { apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
10
10
  import { useT } from "@open-mercato/shared/lib/i18n/context";
11
11
  import { cn } from "@open-mercato/shared/lib/utils";
12
+ import { isOpenInteractionStatus } from "../../lib/interactionStatus.js";
12
13
  import { ActivityAiActions } from "./ActivityAiActions.js";
13
14
  import { EmailCardActions } from "./EmailCardActions.js";
14
15
  import { createLogger } from "@open-mercato/shared/lib/logger";
@@ -145,7 +146,7 @@ function ActivityCard({ activity, onOpen, onChanged, runMutation }) {
145
146
  ] }) : null
146
147
  ] }),
147
148
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
148
- activity.status === "planned" ? /* @__PURE__ */ jsxs(
149
+ isOpenInteractionStatus(activity.status) ? /* @__PURE__ */ jsxs(
149
150
  Button,
150
151
  {
151
152
  type: "button",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ActivityCard.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Calendar, Check, ExternalLink, ListTodo, Mail, MoreHorizontal, Phone, StickyNote, Users } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { InteractionSummary } from './types'\nimport { ActivityAiActions } from './ActivityAiActions'\nimport { EmailCardActions, type EmailCardWidgetData } from './EmailCardActions'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype GuardedMutationRunner = <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype ActivityCardProps = {\n activity: InteractionSummary\n onOpen?: (activity: InteractionSummary) => void\n /** Called after a successful mark-done so the parent can refresh the timeline. */\n onChanged?: () => void\n /**\n * Optional guarded-mutation runner. When provided, mutations route through the parent's\n * `useGuardedMutation` so retry-last-mutation and the global injection contract apply.\n * When omitted, mutations run directly via `apiCallOrThrow` (e.g. read-only contexts\n * or jest unit tests that don't supply a guarded runner).\n */\n runMutation?: GuardedMutationRunner\n}\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n task: ListTodo,\n}\n\nfunction formatDayLabel(value: string, t: ReturnType<typeof useT>): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const now = new Date()\n const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()\n const day = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n const diffDays = Math.round((today - day) / 86400000)\n if (diffDays === 0) return t('customers.timeline.date.today', 'today')\n if (diffDays === 1) return t('customers.timeline.date.yesterday', 'yesterday')\n return date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n}\n\nfunction formatTimeLabel(value: string): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n}\n\nfunction trimSnippet(value: string | null | undefined): string | null {\n const normalized = value?.trim()\n if (!normalized) return null\n if (normalized.length <= 200) return normalized\n return `${normalized.slice(0, 197)}...`\n}\n\nfunction resolveTarget(activity: InteractionSummary): string | null {\n const participant = activity.participants?.find((item) => item.name || item.email)\n if (participant?.name) return participant.name\n if (participant?.email) return participant.email\n if (activity.customer?.displayName) return activity.customer.displayName\n return null\n}\n\n/**\n * Build the `EmailCardWidgetData` for the email-card-actions injection spot.\n * Email metadata (rfcMessageId, addresses, threading headers, current visibility,\n * and the author flag) is populated server-side by `interactionEmailCardEnricher`\n * and surfaced via the `_integrations.email` passthrough; missing values fall\n * back to null.\n */\nfunction buildEmailCardWidgetData(activity: InteractionSummary): EmailCardWidgetData {\n const integrations = activity._integrations as Record<string, unknown> | undefined\n const emailMeta = (integrations?.email ?? {}) as Record<string, unknown>\n return {\n interactionId: activity.id,\n // The MessageChannelLink UUID stored on the interaction row.\n externalMessageId:\n typeof emailMeta.externalMessageId === 'string' ? emailMeta.externalMessageId : null,\n // RFC2822 Message-ID (e.g. \"<abc@mail.gmail.com>\") for In-Reply-To / References headers.\n rfcMessageId:\n typeof emailMeta.rfcMessageId === 'string' ? emailMeta.rfcMessageId : null,\n // entityId is the CustomerEntity id \u2014 on a person detail page this IS the personId.\n personId: activity.entityId ?? null,\n fromAddress:\n typeof emailMeta.fromAddress === 'string' ? emailMeta.fromAddress : null,\n toAddresses: Array.isArray(emailMeta.toAddresses) ? (emailMeta.toAddresses as string[]) : null,\n ccAddresses: Array.isArray(emailMeta.ccAddresses) ? (emailMeta.ccAddresses as string[]) : null,\n subject: activity.title ?? null,\n inReplyTo:\n typeof emailMeta.inReplyTo === 'string' ? emailMeta.inReplyTo : null,\n references: Array.isArray(emailMeta.references) ? (emailMeta.references as string[]) : null,\n currentVisibility:\n emailMeta.currentVisibility === 'private' || emailMeta.currentVisibility === 'shared'\n ? emailMeta.currentVisibility\n : null,\n isAuthor: typeof emailMeta.isAuthor === 'boolean' ? emailMeta.isAuthor : null,\n }\n}\n\nexport function ActivityCard({ activity, onOpen, onChanged, runMutation }: ActivityCardProps) {\n const t = useT()\n const timestamp = activity.occurredAt ?? activity.scheduledAt ?? activity.createdAt\n const TypeIcon = TYPE_ICONS[activity.interactionType] ?? StickyNote\n const titleBase = activity.title ?? activity.body ?? activity.interactionType\n const title = activity.duration ? `${titleBase} (${activity.duration} min)` : titleBase\n const snippet = trimSnippet(activity.body && activity.title ? activity.body : activity.body ?? null)\n const actorLabel = activity.authorName ?? activity.authorEmail ?? t('customers.changelog.user.system', 'System')\n const target = resolveTarget(activity)\n const direction = activity.interactionType === 'email'\n ? t('customers.activityLog.direction.to', 'to')\n : activity.interactionType === 'call' || activity.interactionType === 'meeting'\n ? t('customers.activityLog.direction.with', 'with')\n : ''\n const showExternalLink = Boolean(activity._integrations && Object.keys(activity._integrations).length > 0)\n const [markingDone, setMarkingDone] = React.useState(false)\n\n const handleMarkDone = React.useCallback(async () => {\n if (markingDone) return\n setMarkingDone(true)\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activity.id, occurredAt: new Date().toISOString() }),\n })\n if (runMutation) {\n await runMutation(operation, {\n id: activity.id,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n onChanged?.()\n } catch (err) {\n logger.warn('Mark done failed', { component: 'ActivityCard', activityId: activity.id, err })\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n } finally {\n setMarkingDone(false)\n }\n }, [activity.id, markingDone, onChanged, runMutation, t])\n\n return (\n <div\n className={cn(\n 'grid gap-3',\n onOpen ? 'cursor-pointer' : '',\n )}\n style={{ gridTemplateColumns: '64px 36px minmax(0,1fr)' }}\n onClick={() => onOpen?.(activity)}\n role={onOpen ? 'button' : undefined}\n tabIndex={onOpen ? 0 : undefined}\n onKeyDown={onOpen ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onOpen(activity)\n }\n } : undefined}\n >\n <div className=\"pt-1 text-xs text-muted-foreground\">\n <div className=\"font-semibold text-foreground\">{formatDayLabel(timestamp, t)}</div>\n <div>{formatTimeLabel(timestamp)}</div>\n </div>\n\n <div className=\"flex size-9 items-center justify-center rounded-lg bg-muted/80\">\n <TypeIcon className=\"size-4 text-muted-foreground\" />\n </div>\n\n <div className=\"rounded-xl border bg-card px-4 py-3 shadow-sm\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <h4 className=\"truncate text-sm font-semibold text-foreground\">{title}</h4>\n {showExternalLink ? <ExternalLink className=\"size-3.5 text-muted-foreground\" /> : null}\n </div>\n {activity.location ? (\n <div className=\"mt-1 flex items-center gap-1 text-xs text-muted-foreground\">\n <Calendar className=\"size-3.5\" />\n <span className=\"truncate\">{activity.location}</span>\n </div>\n ) : null}\n </div>\n\n <div className=\"flex items-center gap-1.5\">\n {activity.status === 'planned' ? (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n disabled={markingDone}\n onClick={(event) => {\n event.stopPropagation()\n void handleMarkDone()\n }}\n >\n <Check className=\"size-3.5\" />\n {t('customers.activities.actions.markDone', 'Mark done')}\n </Button>\n ) : null}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('customers.timeline.more', 'More')}\n onClick={(event) => {\n event.stopPropagation()\n onOpen?.(activity)\n }}\n >\n <MoreHorizontal className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n\n <div className=\"mt-2\">\n <ActivityAiActions activityType={activity.interactionType} />\n </div>\n\n {activity.interactionType === 'email' ? (\n <div\n className=\"mt-1\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n role=\"presentation\"\n >\n <EmailCardActions data={buildEmailCardWidgetData(activity)} />\n </div>\n ) : null}\n\n {snippet ? (\n <p className=\"mt-2 text-sm text-muted-foreground\">{snippet}</p>\n ) : null}\n\n <div className=\"mt-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground\">\n <Avatar label={actorLabel} size=\"xs\" variant=\"monochrome\" />\n <span className=\"font-medium text-foreground\">{actorLabel}</span>\n {target && direction ? (\n <>\n <span>\u00B7</span>\n <span>{direction}</span>\n <span className=\"text-foreground\">{target}</span>\n </>\n ) : null}\n </div>\n </div>\n </div>\n )\n}\n\nexport default ActivityCard\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Calendar, Check, ExternalLink, ListTodo, Mail, MoreHorizontal, Phone, StickyNote, Users } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { InteractionSummary } from './types'\nimport { isOpenInteractionStatus } from '../../lib/interactionStatus'\nimport { ActivityAiActions } from './ActivityAiActions'\nimport { EmailCardActions, type EmailCardWidgetData } from './EmailCardActions'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype GuardedMutationRunner = <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype ActivityCardProps = {\n activity: InteractionSummary\n onOpen?: (activity: InteractionSummary) => void\n /** Called after a successful mark-done so the parent can refresh the timeline. */\n onChanged?: () => void\n /**\n * Optional guarded-mutation runner. When provided, mutations route through the parent's\n * `useGuardedMutation` so retry-last-mutation and the global injection contract apply.\n * When omitted, mutations run directly via `apiCallOrThrow` (e.g. read-only contexts\n * or jest unit tests that don't supply a guarded runner).\n */\n runMutation?: GuardedMutationRunner\n}\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n task: ListTodo,\n}\n\nfunction formatDayLabel(value: string, t: ReturnType<typeof useT>): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const now = new Date()\n const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()\n const day = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n const diffDays = Math.round((today - day) / 86400000)\n if (diffDays === 0) return t('customers.timeline.date.today', 'today')\n if (diffDays === 1) return t('customers.timeline.date.yesterday', 'yesterday')\n return date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n}\n\nfunction formatTimeLabel(value: string): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n}\n\nfunction trimSnippet(value: string | null | undefined): string | null {\n const normalized = value?.trim()\n if (!normalized) return null\n if (normalized.length <= 200) return normalized\n return `${normalized.slice(0, 197)}...`\n}\n\nfunction resolveTarget(activity: InteractionSummary): string | null {\n const participant = activity.participants?.find((item) => item.name || item.email)\n if (participant?.name) return participant.name\n if (participant?.email) return participant.email\n if (activity.customer?.displayName) return activity.customer.displayName\n return null\n}\n\n/**\n * Build the `EmailCardWidgetData` for the email-card-actions injection spot.\n * Email metadata (rfcMessageId, addresses, threading headers, current visibility,\n * and the author flag) is populated server-side by `interactionEmailCardEnricher`\n * and surfaced via the `_integrations.email` passthrough; missing values fall\n * back to null.\n */\nfunction buildEmailCardWidgetData(activity: InteractionSummary): EmailCardWidgetData {\n const integrations = activity._integrations as Record<string, unknown> | undefined\n const emailMeta = (integrations?.email ?? {}) as Record<string, unknown>\n return {\n interactionId: activity.id,\n // The MessageChannelLink UUID stored on the interaction row.\n externalMessageId:\n typeof emailMeta.externalMessageId === 'string' ? emailMeta.externalMessageId : null,\n // RFC2822 Message-ID (e.g. \"<abc@mail.gmail.com>\") for In-Reply-To / References headers.\n rfcMessageId:\n typeof emailMeta.rfcMessageId === 'string' ? emailMeta.rfcMessageId : null,\n // entityId is the CustomerEntity id \u2014 on a person detail page this IS the personId.\n personId: activity.entityId ?? null,\n fromAddress:\n typeof emailMeta.fromAddress === 'string' ? emailMeta.fromAddress : null,\n toAddresses: Array.isArray(emailMeta.toAddresses) ? (emailMeta.toAddresses as string[]) : null,\n ccAddresses: Array.isArray(emailMeta.ccAddresses) ? (emailMeta.ccAddresses as string[]) : null,\n subject: activity.title ?? null,\n inReplyTo:\n typeof emailMeta.inReplyTo === 'string' ? emailMeta.inReplyTo : null,\n references: Array.isArray(emailMeta.references) ? (emailMeta.references as string[]) : null,\n currentVisibility:\n emailMeta.currentVisibility === 'private' || emailMeta.currentVisibility === 'shared'\n ? emailMeta.currentVisibility\n : null,\n isAuthor: typeof emailMeta.isAuthor === 'boolean' ? emailMeta.isAuthor : null,\n }\n}\n\nexport function ActivityCard({ activity, onOpen, onChanged, runMutation }: ActivityCardProps) {\n const t = useT()\n const timestamp = activity.occurredAt ?? activity.scheduledAt ?? activity.createdAt\n const TypeIcon = TYPE_ICONS[activity.interactionType] ?? StickyNote\n const titleBase = activity.title ?? activity.body ?? activity.interactionType\n const title = activity.duration ? `${titleBase} (${activity.duration} min)` : titleBase\n const snippet = trimSnippet(activity.body && activity.title ? activity.body : activity.body ?? null)\n const actorLabel = activity.authorName ?? activity.authorEmail ?? t('customers.changelog.user.system', 'System')\n const target = resolveTarget(activity)\n const direction = activity.interactionType === 'email'\n ? t('customers.activityLog.direction.to', 'to')\n : activity.interactionType === 'call' || activity.interactionType === 'meeting'\n ? t('customers.activityLog.direction.with', 'with')\n : ''\n const showExternalLink = Boolean(activity._integrations && Object.keys(activity._integrations).length > 0)\n const [markingDone, setMarkingDone] = React.useState(false)\n\n const handleMarkDone = React.useCallback(async () => {\n if (markingDone) return\n setMarkingDone(true)\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activity.id, occurredAt: new Date().toISOString() }),\n })\n if (runMutation) {\n await runMutation(operation, {\n id: activity.id,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n onChanged?.()\n } catch (err) {\n logger.warn('Mark done failed', { component: 'ActivityCard', activityId: activity.id, err })\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n } finally {\n setMarkingDone(false)\n }\n }, [activity.id, markingDone, onChanged, runMutation, t])\n\n return (\n <div\n className={cn(\n 'grid gap-3',\n onOpen ? 'cursor-pointer' : '',\n )}\n style={{ gridTemplateColumns: '64px 36px minmax(0,1fr)' }}\n onClick={() => onOpen?.(activity)}\n role={onOpen ? 'button' : undefined}\n tabIndex={onOpen ? 0 : undefined}\n onKeyDown={onOpen ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onOpen(activity)\n }\n } : undefined}\n >\n <div className=\"pt-1 text-xs text-muted-foreground\">\n <div className=\"font-semibold text-foreground\">{formatDayLabel(timestamp, t)}</div>\n <div>{formatTimeLabel(timestamp)}</div>\n </div>\n\n <div className=\"flex size-9 items-center justify-center rounded-lg bg-muted/80\">\n <TypeIcon className=\"size-4 text-muted-foreground\" />\n </div>\n\n <div className=\"rounded-xl border bg-card px-4 py-3 shadow-sm\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <h4 className=\"truncate text-sm font-semibold text-foreground\">{title}</h4>\n {showExternalLink ? <ExternalLink className=\"size-3.5 text-muted-foreground\" /> : null}\n </div>\n {activity.location ? (\n <div className=\"mt-1 flex items-center gap-1 text-xs text-muted-foreground\">\n <Calendar className=\"size-3.5\" />\n <span className=\"truncate\">{activity.location}</span>\n </div>\n ) : null}\n </div>\n\n <div className=\"flex items-center gap-1.5\">\n {isOpenInteractionStatus(activity.status) ? (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n disabled={markingDone}\n onClick={(event) => {\n event.stopPropagation()\n void handleMarkDone()\n }}\n >\n <Check className=\"size-3.5\" />\n {t('customers.activities.actions.markDone', 'Mark done')}\n </Button>\n ) : null}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('customers.timeline.more', 'More')}\n onClick={(event) => {\n event.stopPropagation()\n onOpen?.(activity)\n }}\n >\n <MoreHorizontal className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n\n <div className=\"mt-2\">\n <ActivityAiActions activityType={activity.interactionType} />\n </div>\n\n {activity.interactionType === 'email' ? (\n <div\n className=\"mt-1\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n role=\"presentation\"\n >\n <EmailCardActions data={buildEmailCardWidgetData(activity)} />\n </div>\n ) : null}\n\n {snippet ? (\n <p className=\"mt-2 text-sm text-muted-foreground\">{snippet}</p>\n ) : null}\n\n <div className=\"mt-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground\">\n <Avatar label={actorLabel} size=\"xs\" variant=\"monochrome\" />\n <span className=\"font-medium text-foreground\">{actorLabel}</span>\n {target && direction ? (\n <>\n <span>\u00B7</span>\n <span>{direction}</span>\n <span className=\"text-foreground\">{target}</span>\n </>\n ) : null}\n </div>\n </div>\n </div>\n )\n}\n\nexport default ActivityCard\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,7 @@ import { Check, ListTodo, Phone, Mail, Users, StickyNote, User } from "lucide-re
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { Button } from "@open-mercato/ui/primitives/button";
7
7
  import { AiActionChips } from "./AiActionChips.js";
8
+ import { isOpenInteractionStatus } from "../../lib/interactionStatus.js";
8
9
  const TYPE_ICONS = {
9
10
  call: Phone,
10
11
  email: Mail,
@@ -53,7 +54,7 @@ function TimelineEntry({
53
54
  const TypeIcon = TYPE_ICONS[activity.interactionType];
54
55
  const title = activity.title ?? activity.body ?? activity.interactionType;
55
56
  const duration = activity.duration ? ` (${activity.duration} min)` : "";
56
- const isPlanned = activity.status === "planned";
57
+ const isOpen = isOpenInteractionStatus(activity.status);
57
58
  const [markingDone, setMarkingDone] = React.useState(false);
58
59
  const handleMarkDone = React.useCallback(async (event) => {
59
60
  event.stopPropagation();
@@ -87,7 +88,7 @@ function TimelineEntry({
87
88
  title,
88
89
  duration
89
90
  ] }),
90
- isPlanned && onMarkDone ? /* @__PURE__ */ jsxs(
91
+ isOpen && onMarkDone ? /* @__PURE__ */ jsxs(
91
92
  Button,
92
93
  {
93
94
  type: "button",