@open-mercato/core 0.6.5-develop.5382.1.f542de69af → 0.6.5

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 (237) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/bootstrap.js +46 -6
  3. package/dist/bootstrap.js.map +2 -2
  4. package/dist/generated/entities/organization/index.js +2 -0
  5. package/dist/generated/entities/organization/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +1 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/crmFixtures.js +4 -0
  9. package/dist/helpers/integration/crmFixtures.js.map +2 -2
  10. package/dist/modules/attachments/api/route.js +2 -0
  11. package/dist/modules/attachments/api/route.js.map +2 -2
  12. package/dist/modules/attachments/lib/access.js +18 -0
  13. package/dist/modules/attachments/lib/access.js.map +2 -2
  14. package/dist/modules/audit_logs/data/entities.js +2 -1
  15. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  16. package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
  17. package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
  18. package/dist/modules/audit_logs/services/accessLogService.js +10 -0
  19. package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
  20. package/dist/modules/auth/api/admin/nav.js +9 -0
  21. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  22. package/dist/modules/auth/api/login.js +4 -13
  23. package/dist/modules/auth/api/login.js.map +2 -2
  24. package/dist/modules/auth/data/entities.js +3 -1
  25. package/dist/modules/auth/data/entities.js.map +2 -2
  26. package/dist/modules/auth/lib/backendChrome.js +35 -2
  27. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  28. package/dist/modules/auth/lib/consentIntegrity.js +3 -3
  29. package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
  30. package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
  31. package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
  32. package/dist/modules/auth/services/authService.js +5 -3
  33. package/dist/modules/auth/services/authService.js.map +2 -2
  34. package/dist/modules/auth/services/rbacService.js +3 -2
  35. package/dist/modules/auth/services/rbacService.js.map +2 -2
  36. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
  37. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
  38. package/dist/modules/customers/api/deals/route.js +43 -2
  39. package/dist/modules/customers/api/deals/route.js.map +2 -2
  40. package/dist/modules/customers/api/deals/summary/route.js +402 -0
  41. package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
  42. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
  43. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
  44. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
  45. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
  46. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
  47. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  48. package/dist/modules/customers/backend/customers/deals/page.js +221 -56
  49. package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
  50. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  51. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  52. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
  53. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  54. package/dist/modules/customers/cli.js +15 -9
  55. package/dist/modules/customers/cli.js.map +2 -2
  56. package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
  57. package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
  58. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
  59. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  60. package/dist/modules/customers/components/detail/DealForm.js +100 -17
  61. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  62. package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
  63. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  64. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
  65. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  66. package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
  67. package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
  68. package/dist/modules/customers/lib/dealsMetrics.js +82 -0
  69. package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
  70. package/dist/modules/directory/api/organization-branding/route.js +214 -0
  71. package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
  72. package/dist/modules/directory/api/organizations/route.js +7 -0
  73. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  74. package/dist/modules/directory/backend/directory/branding/page.js +214 -0
  75. package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
  76. package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
  77. package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
  78. package/dist/modules/directory/commands/organizations.js +8 -1
  79. package/dist/modules/directory/commands/organizations.js.map +2 -2
  80. package/dist/modules/directory/data/entities.js +3 -0
  81. package/dist/modules/directory/data/entities.js.map +2 -2
  82. package/dist/modules/directory/data/validators.js +9 -0
  83. package/dist/modules/directory/data/validators.js.map +2 -2
  84. package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
  85. package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
  86. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
  87. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
  88. package/dist/modules/directory/utils/organizationScope.js +59 -27
  89. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  90. package/dist/modules/entities/api/definitions.batch.js +2 -1
  91. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  92. package/dist/modules/entities/api/entities.js +7 -0
  93. package/dist/modules/entities/api/entities.js.map +2 -2
  94. package/dist/modules/entities/api/records.js +26 -15
  95. package/dist/modules/entities/api/records.js.map +2 -2
  96. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
  97. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  98. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
  99. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
  100. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
  101. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  102. package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
  103. package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
  104. package/dist/modules/query_index/data/entities.js +2 -1
  105. package/dist/modules/query_index/data/entities.js.map +2 -2
  106. package/dist/modules/query_index/lib/engine.js +4 -2
  107. package/dist/modules/query_index/lib/engine.js.map +2 -2
  108. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
  109. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
  110. package/dist/modules/sales/commands/documents.js +7 -5
  111. package/dist/modules/sales/commands/documents.js.map +2 -2
  112. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
  113. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  114. package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
  115. package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
  116. package/dist/modules/staff/api/team-members.js +9 -2
  117. package/dist/modules/staff/api/team-members.js.map +2 -2
  118. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
  119. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  120. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
  121. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  122. package/dist/modules/staff/commands/team-members.js +1 -1
  123. package/dist/modules/staff/commands/team-members.js.map +2 -2
  124. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  125. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  126. package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
  127. package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
  128. package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
  129. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  130. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
  131. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  132. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
  133. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  134. package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
  135. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  136. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
  137. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
  138. package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
  139. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  140. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
  141. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
  142. package/generated/entities/organization/index.ts +1 -0
  143. package/generated/entity-fields-registry.ts +1 -0
  144. package/package.json +11 -12
  145. package/src/bootstrap.ts +65 -7
  146. package/src/helpers/integration/crmFixtures.ts +21 -1
  147. package/src/modules/attachments/AGENTS.md +79 -0
  148. package/src/modules/attachments/api/route.ts +2 -0
  149. package/src/modules/attachments/lib/access.ts +36 -0
  150. package/src/modules/audit_logs/data/entities.ts +1 -0
  151. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
  152. package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
  153. package/src/modules/audit_logs/services/accessLogService.ts +15 -0
  154. package/src/modules/auth/api/admin/nav.ts +9 -0
  155. package/src/modules/auth/api/login.ts +13 -13
  156. package/src/modules/auth/data/entities.ts +2 -0
  157. package/src/modules/auth/i18n/de.json +0 -1
  158. package/src/modules/auth/i18n/en.json +0 -1
  159. package/src/modules/auth/i18n/es.json +0 -1
  160. package/src/modules/auth/i18n/pl.json +0 -1
  161. package/src/modules/auth/lib/backendChrome.tsx +37 -1
  162. package/src/modules/auth/lib/consentIntegrity.ts +6 -3
  163. package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -0
  164. package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
  165. package/src/modules/auth/services/authService.ts +24 -4
  166. package/src/modules/auth/services/rbacService.ts +11 -2
  167. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
  168. package/src/modules/customers/api/deals/route.ts +51 -2
  169. package/src/modules/customers/api/deals/summary/route.ts +496 -0
  170. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
  171. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
  172. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
  173. package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
  174. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
  175. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
  176. package/src/modules/customers/cli.ts +15 -15
  177. package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
  178. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
  179. package/src/modules/customers/components/detail/DealForm.tsx +121 -19
  180. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
  181. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
  182. package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
  183. package/src/modules/customers/i18n/de.json +43 -0
  184. package/src/modules/customers/i18n/en.json +43 -0
  185. package/src/modules/customers/i18n/es.json +43 -0
  186. package/src/modules/customers/i18n/pl.json +43 -0
  187. package/src/modules/customers/lib/dealsMetrics.ts +159 -0
  188. package/src/modules/directory/api/organization-branding/route.ts +238 -0
  189. package/src/modules/directory/api/organizations/route.ts +7 -0
  190. package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
  191. package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
  192. package/src/modules/directory/commands/organizations.ts +9 -1
  193. package/src/modules/directory/data/entities.ts +3 -0
  194. package/src/modules/directory/data/validators.ts +12 -0
  195. package/src/modules/directory/i18n/de.json +21 -0
  196. package/src/modules/directory/i18n/en.json +21 -0
  197. package/src/modules/directory/i18n/es.json +21 -0
  198. package/src/modules/directory/i18n/pl.json +21 -0
  199. package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
  200. package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
  201. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
  202. package/src/modules/directory/utils/organizationScope.ts +85 -30
  203. package/src/modules/entities/api/definitions.batch.ts +11 -7
  204. package/src/modules/entities/api/entities.ts +11 -0
  205. package/src/modules/entities/api/records.ts +46 -25
  206. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
  207. package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
  208. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
  209. package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
  210. package/src/modules/entities/i18n/de.json +1 -0
  211. package/src/modules/entities/i18n/en.json +1 -0
  212. package/src/modules/entities/i18n/es.json +1 -0
  213. package/src/modules/entities/i18n/pl.json +1 -0
  214. package/src/modules/query_index/data/entities.ts +1 -0
  215. package/src/modules/query_index/lib/engine.ts +11 -5
  216. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
  217. package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
  218. package/src/modules/sales/commands/documents.ts +7 -5
  219. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
  220. package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
  221. package/src/modules/staff/api/team-members.ts +9 -2
  222. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
  223. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
  224. package/src/modules/staff/commands/team-members.ts +5 -2
  225. package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
  226. package/src/modules/staff/i18n/de.json +1 -0
  227. package/src/modules/staff/i18n/en.json +1 -0
  228. package/src/modules/staff/i18n/es.json +1 -0
  229. package/src/modules/staff/i18n/pl.json +1 -0
  230. package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
  231. package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
  232. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
  233. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
  234. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
  235. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
  236. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
  237. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
@@ -53,7 +53,7 @@ type DictionaryDefault = {
53
53
  type CustomFieldValuesPayload = Parameters<DataEngine['setCustomFields']>[0]['values']
54
54
  type ProgressBarHandle = ReturnType<typeof createProgressBar>
55
55
 
56
- const DEAL_STATUS_DEFAULTS: DictionaryDefault[] = [
56
+ export const DEAL_STATUS_DEFAULTS: DictionaryDefault[] = [
57
57
  { value: 'open', label: 'Open', color: '#2563eb', icon: 'lucide:circle' },
58
58
  { value: 'closed', label: 'Closed', color: '#6b7280', icon: 'lucide:check-circle' },
59
59
  { value: 'win', label: 'Win', color: '#22c55e', icon: 'lucide:trophy' },
@@ -61,7 +61,7 @@ const DEAL_STATUS_DEFAULTS: DictionaryDefault[] = [
61
61
  { value: 'in_progress', label: 'In progress', color: '#f59e0b', icon: 'lucide:activity' },
62
62
  ]
63
63
 
64
- const PIPELINE_STAGE_DEFAULTS: DictionaryDefault[] = [
64
+ export const PIPELINE_STAGE_DEFAULTS: DictionaryDefault[] = [
65
65
  { value: 'opportunity', label: 'Opportunity', color: '#38bdf8', icon: 'lucide:target' },
66
66
  { value: 'marketing_qualified_lead', label: 'Marketing Qualified Lead', color: '#a855f7', icon: 'lucide:sparkles' },
67
67
  { value: 'sales_qualified_lead', label: 'Sales Qualified Lead', color: '#f97316', icon: 'lucide:users' },
@@ -72,14 +72,14 @@ const PIPELINE_STAGE_DEFAULTS: DictionaryDefault[] = [
72
72
  { value: 'stalled', label: 'Stalled', color: '#6b7280', icon: 'lucide:alert-circle' },
73
73
  ]
74
74
 
75
- const ENTITY_STATUS_DEFAULTS: DictionaryDefault[] = [
75
+ export const ENTITY_STATUS_DEFAULTS: DictionaryDefault[] = [
76
76
  { value: 'active', label: 'Active', color: '#22c55e', icon: 'lucide:user-check' },
77
77
  { value: 'inactive', label: 'Inactive', color: '#94a3b8', icon: 'lucide:pause-circle' },
78
78
  { value: 'pending', label: 'Pending', color: '#f59e0b', icon: 'lucide:clock' },
79
79
  { value: 'archived', label: 'Archived', color: '#64748b', icon: 'lucide:archive' },
80
80
  ]
81
81
 
82
- const ENTITY_LIFECYCLE_STAGE_DEFAULTS: DictionaryDefault[] = [
82
+ export const ENTITY_LIFECYCLE_STAGE_DEFAULTS: DictionaryDefault[] = [
83
83
  { value: 'lead', label: 'Lead', color: '#3b82f6', icon: 'lucide:sparkles' },
84
84
  { value: 'prospect', label: 'Prospect', color: '#8b5cf6', icon: 'lucide:eye' },
85
85
  { value: 'customer', label: 'Customer', color: '#22c55e', icon: 'lucide:handshake' },
@@ -88,7 +88,7 @@ const ENTITY_LIFECYCLE_STAGE_DEFAULTS: DictionaryDefault[] = [
88
88
  { value: 'other', label: 'Other', color: '#94a3b8', icon: 'lucide:circle' },
89
89
  ]
90
90
 
91
- const ENTITY_SOURCE_DEFAULTS: DictionaryDefault[] = [
91
+ export const ENTITY_SOURCE_DEFAULTS: DictionaryDefault[] = [
92
92
  { value: 'linkedin', label: 'LinkedIn', color: '#0a66c2', icon: 'lucide:linkedin' },
93
93
  { value: 'email', label: 'Email', color: '#3b82f6', icon: 'lucide:mail' },
94
94
  { value: 'web_form', label: 'Web form', color: '#22c55e', icon: 'lucide:globe' },
@@ -291,7 +291,7 @@ function isoDaysFromNow(days: number, options?: { hour?: number; minute?: number
291
291
  return base.toISOString()
292
292
  }
293
293
 
294
- const CUSTOMER_EXAMPLES: ExampleCompany[] = [
294
+ export const CUSTOMER_EXAMPLES: ExampleCompany[] = [
295
295
  {
296
296
  slug: 'brightside-solar',
297
297
  displayName: 'Brightside Solar',
@@ -307,7 +307,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
307
307
  primaryPhone: '+1 415-555-0148',
308
308
  source: 'partner_referral',
309
309
  lifecycleStage: 'customer',
310
- status: 'customer',
310
+ status: 'active',
311
311
  custom: {
312
312
  relationship_health: 'healthy',
313
313
  renewal_quarter: 'Q3',
@@ -364,7 +364,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
364
364
  phone: '+1 628-555-0199',
365
365
  timezone: 'America/Los_Angeles',
366
366
  linkedInUrl: 'https://www.linkedin.com/in/danielcho-energy/',
367
- source: 'outbound_campaign',
367
+ source: 'cold_outreach',
368
368
  custom: {
369
369
  buying_role: 'economic_buyer',
370
370
  preferred_pronouns: 'he/him',
@@ -438,7 +438,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
438
438
  valueCurrency: 'USD',
439
439
  expectedCloseAt: isoDaysFromNow(65),
440
440
  probability: 40,
441
- source: 'inbound_web',
441
+ source: 'web_form',
442
442
  custom: {
443
443
  competitive_risk: 'high',
444
444
  implementation_complexity: 'complex',
@@ -513,7 +513,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
513
513
  'Boston-based analytics platform helping consumer brands optimize merchandising decisions.',
514
514
  primaryEmail: 'info@harborviewanalytics.com',
515
515
  primaryPhone: '+1 617-555-0024',
516
- source: 'industry_event',
516
+ source: 'event',
517
517
  lifecycleStage: 'prospect',
518
518
  status: 'active',
519
519
  custom: {
@@ -545,7 +545,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
545
545
  phone: '+1 617-555-0168',
546
546
  timezone: 'America/New_York',
547
547
  linkedInUrl: 'https://www.linkedin.com/in/arjunpatel-sales/',
548
- source: 'industry_event',
548
+ source: 'event',
549
549
  custom: {
550
550
  buying_role: 'economic_buyer',
551
551
  preferred_pronouns: 'he/him',
@@ -563,7 +563,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
563
563
  phone: '+1 617-555-0179',
564
564
  timezone: 'America/New_York',
565
565
  linkedInUrl: 'https://www.linkedin.com/in/lenaortiz-retail/',
566
- source: 'industry_event',
566
+ source: 'event',
567
567
  custom: {
568
568
  buying_role: 'champion',
569
569
  preferred_pronouns: 'she/her',
@@ -582,7 +582,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
582
582
  valueCurrency: 'USD',
583
583
  expectedCloseAt: isoDaysFromNow(-25),
584
584
  probability: 100,
585
- source: 'industry_event',
585
+ source: 'event',
586
586
  custom: {
587
587
  competitive_risk: 'low',
588
588
  implementation_complexity: 'standard',
@@ -637,7 +637,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
637
637
  valueCurrency: 'USD',
638
638
  expectedCloseAt: isoDaysFromNow(120),
639
639
  probability: 35,
640
- source: 'outbound_campaign',
640
+ source: 'cold_outreach',
641
641
  custom: {
642
642
  competitive_risk: 'medium',
643
643
  implementation_complexity: 'complex',
@@ -715,7 +715,7 @@ const CUSTOMER_EXAMPLES: ExampleCompany[] = [
715
715
  primaryPhone: '+1 512-555-0456',
716
716
  source: 'customer_referral',
717
717
  lifecycleStage: 'customer',
718
- status: 'customer',
718
+ status: 'active',
719
719
  custom: {
720
720
  relationship_health: 'healthy',
721
721
  renewal_quarter: 'Q1',
@@ -0,0 +1,389 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { CheckCircle } from 'lucide-react'
5
+ import { cn } from '@open-mercato/shared/lib/utils'
6
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
7
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
8
+ import { KpiCard, DeltaBadge, Sparkline } from '@open-mercato/ui/backend/charts'
9
+ import { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'
10
+ import { Button } from '@open-mercato/ui/primitives/button'
11
+ import { Spinner } from '@open-mercato/ui/primitives/spinner'
12
+ import type { DictionaryMap } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
13
+ import { PipelineStageBar } from './kpi/PipelineStageBar'
14
+
15
+ type DeltaDirection = 'up' | 'down' | 'unchanged'
16
+
17
+ type SummaryDelta = {
18
+ value: number
19
+ direction: DeltaDirection
20
+ }
21
+
22
+ type DealsSummaryResponse = {
23
+ baseCurrencyCode: string | null
24
+ convertedAll: boolean
25
+ missingRateCurrencies: string[]
26
+ pipelineValue: {
27
+ value: number
28
+ delta: SummaryDelta
29
+ stages: { stage: string | null; count: number; value: number }[]
30
+ }
31
+ activeDeals: {
32
+ value: number
33
+ delta: SummaryDelta
34
+ ownersCount: number
35
+ needAttention: number
36
+ owners: { id: string; count: number }[]
37
+ ownersOverflow: number
38
+ }
39
+ wonThisQuarter: {
40
+ value: number
41
+ delta: SummaryDelta
42
+ dealsClosed: number
43
+ avgDeal: number
44
+ }
45
+ winRate: {
46
+ value: number
47
+ deltaPp: number
48
+ direction: DeltaDirection
49
+ previousValue: number
50
+ series: { period: string; rate: number }[]
51
+ }
52
+ }
53
+
54
+ export type DealsKpiStripProps = {
55
+ ownerNames: Record<string, string>
56
+ stageDictionary: DictionaryMap
57
+ pipelineCount: number
58
+ className?: string
59
+ /** Bumped by the host when the active org scope changes — forces a KPI refetch so the cards never show another org's data. */
60
+ scopeVersion?: number
61
+ /** Bumped by the host on manual refresh / after mutations — forces a KPI refetch so totals stay in sync with the table. */
62
+ reloadToken?: number
63
+ onNeedsAttentionClick?: () => void
64
+ }
65
+
66
+ const compactNumberFormatter = new Intl.NumberFormat(undefined, {
67
+ notation: 'compact',
68
+ maximumFractionDigits: 1,
69
+ })
70
+
71
+ function formatCompact(value: number): string {
72
+ return compactNumberFormatter.format(value)
73
+ }
74
+
75
+ function buildCurrencySuffix(code: string | null, convertedAll: boolean): string {
76
+ if (!code) return convertedAll ? '' : '≈'
77
+ return convertedAll ? code : `≈ ${code}`
78
+ }
79
+
80
+ const KPI_TITLE_CLASS = 'text-xs font-semibold uppercase tracking-wide text-muted-foreground'
81
+
82
+ function DealKpiCard(props: React.ComponentProps<typeof KpiCard>) {
83
+ return <KpiCard titleClassName={KPI_TITLE_CLASS} {...props} />
84
+ }
85
+
86
+ function KpiDeltaBadge({
87
+ direction,
88
+ value,
89
+ unit,
90
+ title,
91
+ }: {
92
+ direction: DeltaDirection
93
+ value: number
94
+ unit?: string
95
+ title: string
96
+ }) {
97
+ if (direction === 'unchanged' && value === 0) {
98
+ return (
99
+ <span
100
+ className="inline-flex items-center rounded-md bg-status-neutral-bg px-2 py-0.5 text-xs font-medium text-status-neutral-text"
101
+ title={title}
102
+ >
103
+ --
104
+ </span>
105
+ )
106
+ }
107
+ return <DeltaBadge direction={direction} value={value} unit={unit} title={title} />
108
+ }
109
+
110
+ function isObject(value: unknown): value is Record<string, unknown> {
111
+ return typeof value === 'object' && value !== null
112
+ }
113
+
114
+ // Guard the summary payload before rendering: a non-conforming response (an unrelated
115
+ // endpoint mock, an error body, or a future contract drift) must surface the error card,
116
+ // never crash the whole deals page by dereferencing missing sections/arrays.
117
+ function isDealsSummaryResponse(value: unknown): value is DealsSummaryResponse {
118
+ if (!isObject(value)) return false
119
+ const { pipelineValue, activeDeals, wonThisQuarter, winRate } = value
120
+ return (
121
+ isObject(pipelineValue) && Array.isArray(pipelineValue.stages) &&
122
+ isObject(activeDeals) && Array.isArray(activeDeals.owners) &&
123
+ isObject(wonThisQuarter) &&
124
+ isObject(winRate) && Array.isArray(winRate.series)
125
+ )
126
+ }
127
+
128
+ export function DealsKpiStrip({
129
+ ownerNames,
130
+ stageDictionary,
131
+ pipelineCount,
132
+ className,
133
+ scopeVersion,
134
+ reloadToken,
135
+ onNeedsAttentionClick,
136
+ }: DealsKpiStripProps) {
137
+ const t = useT()
138
+ const locale = useLocale()
139
+ const pluralCat = React.useCallback((count: number): string => {
140
+ try {
141
+ return new Intl.PluralRules(locale).select(count)
142
+ } catch {
143
+ return count === 1 ? 'one' : 'other'
144
+ }
145
+ }, [locale])
146
+ const pf = React.useCallback((base: string, count: number): string => {
147
+ const cat = pluralCat(count)
148
+ const key = `${base}.${cat}`
149
+ const out = t(key, { count })
150
+ return out === key ? t(`${base}.other`, { count }) : out
151
+ }, [t, pluralCat])
152
+ const [data, setData] = React.useState<DealsSummaryResponse | null>(null)
153
+ const [loading, setLoading] = React.useState(true)
154
+ const [error, setError] = React.useState<string | null>(null)
155
+ const [retryToken, setRetryToken] = React.useState(0)
156
+ const previousScopeVersionRef = React.useRef(scopeVersion)
157
+
158
+ const retry = React.useCallback(() => {
159
+ setRetryToken((token) => token + 1)
160
+ }, [])
161
+
162
+ React.useEffect(() => {
163
+ let cancelled = false
164
+ const scopeChanged = previousScopeVersionRef.current !== scopeVersion
165
+ previousScopeVersionRef.current = scopeVersion
166
+ if (scopeChanged) setData(null)
167
+ setLoading(true)
168
+ setError(null)
169
+ apiCall<DealsSummaryResponse>('/api/customers/deals/summary')
170
+ .then((call) => {
171
+ if (cancelled) return
172
+ if (!call.ok || !isDealsSummaryResponse(call.result)) {
173
+ setError(t('customers.deals.list.kpi.error'))
174
+ return
175
+ }
176
+ setData(call.result)
177
+ })
178
+ .catch(() => {
179
+ if (cancelled) return
180
+ setError(t('customers.deals.list.kpi.error'))
181
+ })
182
+ .finally(() => {
183
+ if (!cancelled) setLoading(false)
184
+ })
185
+ return () => {
186
+ cancelled = true
187
+ }
188
+ }, [t, scopeVersion, reloadToken, retryToken])
189
+
190
+ const wrapperClassName = cn('space-y-2', className)
191
+ const gridClassName = 'grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4'
192
+
193
+ if (loading && !data) {
194
+ return (
195
+ <div className={wrapperClassName}>
196
+ <div className={gridClassName}>
197
+ <DealKpiCard loading title={t('customers.deals.list.kpi.pipelineValue')} value={null} />
198
+ <DealKpiCard loading title={t('customers.deals.list.kpi.activeDeals')} value={null} />
199
+ <DealKpiCard loading title={t('customers.deals.list.kpi.wonThisQuarter')} value={null} />
200
+ <DealKpiCard loading title={t('customers.deals.list.kpi.winRate')} value={null} />
201
+ </div>
202
+ </div>
203
+ )
204
+ }
205
+
206
+ if (!data) {
207
+ const errorMessage = error ?? t('customers.deals.list.kpi.error')
208
+ return (
209
+ <div className={wrapperClassName}>
210
+ <div className="flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-4">
211
+ <p className="text-sm text-destructive">{errorMessage}</p>
212
+ <Button type="button" variant="destructive-outline" size="sm" onClick={retry}>
213
+ {t('customers.deals.list.kpi.retry')}
214
+ </Button>
215
+ </div>
216
+ </div>
217
+ )
218
+ }
219
+
220
+ const currencySuffix = buildCurrencySuffix(data.baseCurrencyCode, data.convertedAll)
221
+ const unassignedLabel = t('customers.deals.list.kpi.unassignedStage')
222
+ const deltaTooltip = t('customers.deals.list.kpi.deltaTooltip')
223
+ const deltaUnavailableTooltip = t('customers.deals.list.kpi.deltaUnavailable')
224
+ const scopeLabel = t('customers.deals.list.kpi.scopeAllPipelinesThisQuarter')
225
+ const unknownOwner = t('customers.deals.list.unknownOwner')
226
+ const currencyHint = !data.convertedAll
227
+ ? data.baseCurrencyCode
228
+ ? t('customers.deals.list.kpi.currencyApproxMissing', {
229
+ currencies: data.missingRateCurrencies.length ? data.missingRateCurrencies.join(', ') : currencySuffix,
230
+ })
231
+ : t('customers.deals.list.kpi.currencyApproxNoBase')
232
+ : null
233
+ const attentionLabel = pf('customers.deals.list.kpi.frag.needAttention', data.activeDeals.needAttention)
234
+
235
+ return (
236
+ <div className={wrapperClassName}>
237
+ {error ? (
238
+ <div className="flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
239
+ <p className="text-xs text-destructive">{error}</p>
240
+ <Button type="button" variant="destructive-outline" size="2xs" onClick={retry}>
241
+ {t('customers.deals.list.kpi.retry')}
242
+ </Button>
243
+ </div>
244
+ ) : null}
245
+ {loading ? (
246
+ <div className="flex items-center justify-end gap-2 text-xs text-muted-foreground">
247
+ <Spinner className="h-3 w-3" />
248
+ <span>{t('customers.deals.list.kpi.updating')}</span>
249
+ </div>
250
+ ) : null}
251
+ <div className={gridClassName}>
252
+ <DealKpiCard
253
+ title={t('customers.deals.list.kpi.pipelineValue')}
254
+ value={data.pipelineValue.value}
255
+ formatValue={formatCompact}
256
+ suffix={currencySuffix}
257
+ headerAction={
258
+ <KpiDeltaBadge
259
+ direction={data.pipelineValue.delta.direction}
260
+ value={data.pipelineValue.delta.value}
261
+ title={data.pipelineValue.delta.direction === 'unchanged' && data.pipelineValue.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}
262
+ />
263
+ }
264
+ footer={
265
+ <div className="space-y-2">
266
+ <p className="text-xs text-muted-foreground">{scopeLabel}</p>
267
+ <p className="text-xs text-muted-foreground">
268
+ {t('customers.deals.list.kpi.activeAcrossPipelines', {
269
+ deals: pf('customers.deals.list.kpi.frag.activeDeals', data.activeDeals.value),
270
+ pipelines: pf('customers.deals.list.kpi.frag.pipelines', pipelineCount),
271
+ })}
272
+ </p>
273
+ {currencyHint ? <p className="text-xs text-muted-foreground">{currencyHint}</p> : null}
274
+ <PipelineStageBar
275
+ stages={data.pipelineValue.stages}
276
+ stageDictionary={stageDictionary}
277
+ unassignedLabel={unassignedLabel}
278
+ />
279
+ </div>
280
+ }
281
+ />
282
+
283
+ <DealKpiCard
284
+ title={t('customers.deals.list.kpi.activeDeals')}
285
+ value={data.activeDeals.value}
286
+ formatValue={formatCompact}
287
+ headerAction={
288
+ <KpiDeltaBadge
289
+ direction={data.activeDeals.delta.direction}
290
+ value={data.activeDeals.delta.value}
291
+ title={data.activeDeals.delta.direction === 'unchanged' && data.activeDeals.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}
292
+ />
293
+ }
294
+ footer={
295
+ <div className="space-y-2">
296
+ <p className="text-xs text-muted-foreground">{scopeLabel}</p>
297
+ <div className="flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted-foreground">
298
+ <span>{pf('customers.deals.list.kpi.frag.owners', data.activeDeals.ownersCount)}</span>
299
+ <span aria-hidden="true">·</span>
300
+ {onNeedsAttentionClick && data.activeDeals.needAttention > 0 ? (
301
+ <Button
302
+ type="button"
303
+ variant="link"
304
+ size="2xs"
305
+ className="h-auto p-0 text-xs"
306
+ onClick={onNeedsAttentionClick}
307
+ >
308
+ {attentionLabel}
309
+ </Button>
310
+ ) : (
311
+ <span>{attentionLabel}</span>
312
+ )}
313
+ </div>
314
+ {data.activeDeals.owners.length > 0 ? (
315
+ <AvatarStack max={4} size="sm" overflowCount={data.activeDeals.ownersOverflow}>
316
+ {data.activeDeals.owners.map((owner) => {
317
+ const ownerLabel = ownerNames[owner.id]?.trim() || unknownOwner
318
+ return <Avatar key={owner.id} label={ownerLabel} size="sm" />
319
+ })}
320
+ </AvatarStack>
321
+ ) : null}
322
+ </div>
323
+ }
324
+ />
325
+
326
+ <DealKpiCard
327
+ title={t('customers.deals.list.kpi.wonThisQuarter')}
328
+ value={data.wonThisQuarter.value}
329
+ formatValue={formatCompact}
330
+ suffix={currencySuffix}
331
+ headerAction={
332
+ <KpiDeltaBadge
333
+ direction={data.wonThisQuarter.delta.direction}
334
+ value={data.wonThisQuarter.delta.value}
335
+ title={data.wonThisQuarter.delta.direction === 'unchanged' && data.wonThisQuarter.delta.value === 0 ? deltaUnavailableTooltip : deltaTooltip}
336
+ />
337
+ }
338
+ footer={
339
+ <div className="space-y-1">
340
+ <p className="text-xs text-muted-foreground">{scopeLabel}</p>
341
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
342
+ <CheckCircle className="h-4 w-4 text-status-success-text" aria-hidden />
343
+ <span>
344
+ {pf('customers.deals.list.kpi.frag.dealsClosed', data.wonThisQuarter.dealsClosed)}
345
+ </span>
346
+ </div>
347
+ <p className="text-xs text-muted-foreground">
348
+ {t('customers.deals.list.kpi.avgDeal', {
349
+ value: `${formatCompact(data.wonThisQuarter.avgDeal)}${currencySuffix ? ` ${currencySuffix}` : ''}`,
350
+ })}
351
+ </p>
352
+ {currencyHint ? <p className="text-xs text-muted-foreground">{currencyHint}</p> : null}
353
+ </div>
354
+ }
355
+ />
356
+
357
+ <DealKpiCard
358
+ title={t('customers.deals.list.kpi.winRate')}
359
+ value={data.winRate.value}
360
+ suffix="%"
361
+ headerAction={
362
+ <KpiDeltaBadge
363
+ direction={data.winRate.direction}
364
+ value={Math.abs(data.winRate.deltaPp)}
365
+ unit="pp"
366
+ title={data.winRate.previousValue === 0 ? deltaUnavailableTooltip : deltaTooltip}
367
+ />
368
+ }
369
+ footer={
370
+ <div className="space-y-2">
371
+ <p className="text-xs text-muted-foreground">{scopeLabel}</p>
372
+ <p className="text-xs text-muted-foreground">
373
+ {t('customers.deals.list.kpi.fromLastQuarter', { value: data.winRate.previousValue })}
374
+ </p>
375
+ <div className="text-primary">
376
+ <Sparkline
377
+ values={data.winRate.series.map((point) => point.rate)}
378
+ ariaLabel={t('customers.deals.list.kpi.winRate')}
379
+ />
380
+ </div>
381
+ </div>
382
+ }
383
+ />
384
+ </div>
385
+ </div>
386
+ )
387
+ }
388
+
389
+ export default DealsKpiStrip
@@ -116,7 +116,6 @@ export function ConfirmDealLostDialog({
116
116
 
117
117
  <div className="space-y-6 px-7 py-6">
118
118
  <Alert variant="warning" className="rounded-md">
119
- <AlertTriangle className="size-4" />
120
119
  <AlertTitle>
121
120
  {t('customers.deals.detail.lost.warningTitle', 'This action closes the deal')}
122
121
  </AlertTitle>