@open-mercato/core 0.5.1-develop.2663.2c29774b5b → 0.5.1-develop.2681.c559bb2bc3

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 (687) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/action_log/index.js +8 -0
  3. package/dist/generated/entities/action_log/index.js.map +2 -2
  4. package/dist/generated/entities/customer_company_billing/index.js +23 -0
  5. package/dist/generated/entities/customer_company_billing/index.js.map +7 -0
  6. package/dist/generated/entities/customer_deal/index.js +8 -0
  7. package/dist/generated/entities/customer_deal/index.js.map +2 -2
  8. package/dist/generated/entities/customer_deal_stage_transition/index.js +31 -0
  9. package/dist/generated/entities/customer_deal_stage_transition/index.js.map +7 -0
  10. package/dist/generated/entities/customer_dictionary_kind_setting/index.js +21 -0
  11. package/dist/generated/entities/customer_dictionary_kind_setting/index.js.map +7 -0
  12. package/dist/generated/entities/customer_entity/index.js +8 -0
  13. package/dist/generated/entities/customer_entity/index.js.map +2 -2
  14. package/dist/generated/entities/customer_entity_role/index.js +23 -0
  15. package/dist/generated/entities/customer_entity_role/index.js.map +7 -0
  16. package/dist/generated/entities/customer_interaction/index.js +23 -1
  17. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  18. package/dist/generated/entities/customer_label/index.js +19 -0
  19. package/dist/generated/entities/customer_label/index.js.map +7 -0
  20. package/dist/generated/entities/customer_label_assignment/index.js +17 -0
  21. package/dist/generated/entities/customer_label_assignment/index.js.map +7 -0
  22. package/dist/generated/entities/customer_person_company_link/index.js +21 -0
  23. package/dist/generated/entities/customer_person_company_link/index.js.map +7 -0
  24. package/dist/generated/entities/customer_person_company_role/index.js +17 -0
  25. package/dist/generated/entities/customer_person_company_role/index.js.map +7 -0
  26. package/dist/generated/entities/dictionary_entry/index.js +4 -0
  27. package/dist/generated/entities/dictionary_entry/index.js.map +2 -2
  28. package/dist/generated/entities.ids.generated.js +9 -1
  29. package/dist/generated/entities.ids.generated.js.map +2 -2
  30. package/dist/generated/entity-fields-registry.js +116 -1
  31. package/dist/generated/entity-fields-registry.js.map +2 -2
  32. package/dist/modules/attachments/api/route.js +46 -8
  33. package/dist/modules/attachments/api/route.js.map +2 -2
  34. package/dist/modules/audit_logs/api/audit-logs/actions/export/route.js +208 -0
  35. package/dist/modules/audit_logs/api/audit-logs/actions/export/route.js.map +7 -0
  36. package/dist/modules/audit_logs/api/audit-logs/actions/route.js +52 -6
  37. package/dist/modules/audit_logs/api/audit-logs/actions/route.js.map +2 -2
  38. package/dist/modules/audit_logs/cli.js +62 -0
  39. package/dist/modules/audit_logs/cli.js.map +7 -0
  40. package/dist/modules/audit_logs/data/entities.js +21 -1
  41. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  42. package/dist/modules/audit_logs/data/validators.js +9 -1
  43. package/dist/modules/audit_logs/data/validators.js.map +2 -2
  44. package/dist/modules/audit_logs/lib/changeRows.js +34 -0
  45. package/dist/modules/audit_logs/lib/changeRows.js.map +7 -0
  46. package/dist/modules/audit_logs/lib/display-helpers.js +2 -20
  47. package/dist/modules/audit_logs/lib/display-helpers.js.map +3 -3
  48. package/dist/modules/audit_logs/lib/projections.js +58 -0
  49. package/dist/modules/audit_logs/lib/projections.js.map +7 -0
  50. package/dist/modules/audit_logs/migrations/Migration20260412160533.js +21 -0
  51. package/dist/modules/audit_logs/migrations/Migration20260412160533.js.map +7 -0
  52. package/dist/modules/audit_logs/services/actionLogService.js +313 -79
  53. package/dist/modules/audit_logs/services/actionLogService.js.map +2 -2
  54. package/dist/modules/customers/acl.js +3 -1
  55. package/dist/modules/customers/acl.js.map +2 -2
  56. package/dist/modules/customers/api/activities/route.js +4 -0
  57. package/dist/modules/customers/api/activities/route.js.map +2 -2
  58. package/dist/modules/customers/api/assignable-staff/route.js +208 -0
  59. package/dist/modules/customers/api/assignable-staff/route.js.map +7 -0
  60. package/dist/modules/customers/api/companies/[id]/people/route.js +205 -0
  61. package/dist/modules/customers/api/companies/[id]/people/route.js.map +7 -0
  62. package/dist/modules/customers/api/companies/[id]/roles/route.js +22 -0
  63. package/dist/modules/customers/api/companies/[id]/roles/route.js.map +7 -0
  64. package/dist/modules/customers/api/companies/[id]/route.js +374 -32
  65. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  66. package/dist/modules/customers/api/companies/route.js +82 -7
  67. package/dist/modules/customers/api/companies/route.js.map +2 -2
  68. package/dist/modules/customers/api/deals/[id]/companies/route.js +172 -0
  69. package/dist/modules/customers/api/deals/[id]/companies/route.js.map +7 -0
  70. package/dist/modules/customers/api/deals/[id]/people/route.js +156 -0
  71. package/dist/modules/customers/api/deals/[id]/people/route.js.map +7 -0
  72. package/dist/modules/customers/api/deals/[id]/route.js +459 -53
  73. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  74. package/dist/modules/customers/api/deals/[id]/stats/route.js +195 -0
  75. package/dist/modules/customers/api/deals/[id]/stats/route.js.map +7 -0
  76. package/dist/modules/customers/api/deals/route.js +20 -10
  77. package/dist/modules/customers/api/deals/route.js.map +3 -3
  78. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +105 -4
  79. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  80. package/dist/modules/customers/api/dictionaries/[kind]/route.js +118 -42
  81. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  82. package/dist/modules/customers/api/dictionaries/context.js +30 -6
  83. package/dist/modules/customers/api/dictionaries/context.js.map +2 -2
  84. package/dist/modules/customers/api/dictionaries/kind-settings/route.js +207 -0
  85. package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +7 -0
  86. package/dist/modules/customers/api/entity-roles-factory.js +471 -0
  87. package/dist/modules/customers/api/entity-roles-factory.js.map +7 -0
  88. package/dist/modules/customers/api/interactions/conflicts/route.js +158 -0
  89. package/dist/modules/customers/api/interactions/conflicts/route.js.map +7 -0
  90. package/dist/modules/customers/api/interactions/counts/route.js +92 -0
  91. package/dist/modules/customers/api/interactions/counts/route.js.map +7 -0
  92. package/dist/modules/customers/api/interactions/route.js +83 -4
  93. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  94. package/dist/modules/customers/api/labels/assign/route.js +189 -0
  95. package/dist/modules/customers/api/labels/assign/route.js.map +7 -0
  96. package/dist/modules/customers/api/labels/auth.js +17 -0
  97. package/dist/modules/customers/api/labels/auth.js.map +7 -0
  98. package/dist/modules/customers/api/labels/route.js +281 -0
  99. package/dist/modules/customers/api/labels/route.js.map +7 -0
  100. package/dist/modules/customers/api/labels/table-errors.js +38 -0
  101. package/dist/modules/customers/api/labels/table-errors.js.map +7 -0
  102. package/dist/modules/customers/api/labels/unassign/route.js +184 -0
  103. package/dist/modules/customers/api/labels/unassign/route.js.map +7 -0
  104. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +292 -0
  105. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +7 -0
  106. package/dist/modules/customers/api/people/[id]/companies/context.js +66 -0
  107. package/dist/modules/customers/api/people/[id]/companies/context.js.map +7 -0
  108. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +334 -0
  109. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +7 -0
  110. package/dist/modules/customers/api/people/[id]/companies/route.js +205 -0
  111. package/dist/modules/customers/api/people/[id]/companies/route.js.map +7 -0
  112. package/dist/modules/customers/api/people/[id]/roles/route.js +22 -0
  113. package/dist/modules/customers/api/people/[id]/roles/route.js.map +7 -0
  114. package/dist/modules/customers/api/people/[id]/route.js +134 -21
  115. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  116. package/dist/modules/customers/api/people/route.js +122 -23
  117. package/dist/modules/customers/api/people/route.js.map +2 -2
  118. package/dist/modules/customers/api/todos/route.js +4 -0
  119. package/dist/modules/customers/api/todos/route.js.map +2 -2
  120. package/dist/modules/customers/api/utils.js +22 -0
  121. package/dist/modules/customers/api/utils.js.map +2 -2
  122. package/dist/modules/customers/backend/config/customers/page.js +2 -6
  123. package/dist/modules/customers/backend/config/customers/page.js.map +2 -2
  124. package/dist/modules/customers/backend/customers/companies/page.js +37 -26
  125. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  126. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +265 -262
  127. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +3 -3
  128. package/dist/modules/customers/backend/customers/deals/[id]/hooks/formatters.js +23 -0
  129. package/dist/modules/customers/backend/customers/deals/[id]/hooks/formatters.js.map +7 -0
  130. package/dist/modules/customers/backend/customers/deals/[id]/hooks/types.js +1 -0
  131. package/dist/modules/customers/backend/customers/deals/[id]/hooks/types.js.map +7 -0
  132. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +43 -0
  133. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +7 -0
  134. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +264 -0
  135. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +7 -0
  136. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +88 -0
  137. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +7 -0
  138. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +41 -0
  139. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +7 -0
  140. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js +66 -0
  141. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js.map +7 -0
  142. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.js +39 -0
  143. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.js.map +7 -0
  144. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealMutationContext.js +49 -0
  145. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealMutationContext.js.map +7 -0
  146. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +43 -0
  147. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +7 -0
  148. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useScheduleDialog.js +28 -0
  149. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useScheduleDialog.js.map +7 -0
  150. package/dist/modules/customers/backend/customers/deals/[id]/page.js +556 -503
  151. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +3 -3
  152. package/dist/modules/customers/backend/customers/deals/page.js +66 -21
  153. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  154. package/dist/modules/customers/backend/customers/people/page.js +36 -28
  155. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  156. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +318 -203
  157. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +3 -3
  158. package/dist/modules/customers/cli.js +105 -13
  159. package/dist/modules/customers/cli.js.map +2 -2
  160. package/dist/modules/customers/commands/activities.js +6 -0
  161. package/dist/modules/customers/commands/activities.js.map +2 -2
  162. package/dist/modules/customers/commands/deals.js +315 -107
  163. package/dist/modules/customers/commands/deals.js.map +2 -2
  164. package/dist/modules/customers/commands/dictionaries.js +166 -32
  165. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  166. package/dist/modules/customers/commands/dictionaryKindSettings.js +208 -0
  167. package/dist/modules/customers/commands/dictionaryKindSettings.js.map +7 -0
  168. package/dist/modules/customers/commands/entity-roles.js +415 -0
  169. package/dist/modules/customers/commands/entity-roles.js.map +7 -0
  170. package/dist/modules/customers/commands/index.js +4 -0
  171. package/dist/modules/customers/commands/index.js.map +2 -2
  172. package/dist/modules/customers/commands/interactions.js +108 -21
  173. package/dist/modules/customers/commands/interactions.js.map +2 -2
  174. package/dist/modules/customers/commands/labels.js +539 -0
  175. package/dist/modules/customers/commands/labels.js.map +7 -0
  176. package/dist/modules/customers/commands/people.js +560 -463
  177. package/dist/modules/customers/commands/people.js.map +3 -3
  178. package/dist/modules/customers/commands/personCompanyLinks.js +568 -0
  179. package/dist/modules/customers/commands/personCompanyLinks.js.map +7 -0
  180. package/dist/modules/customers/commands/shared.js +12 -4
  181. package/dist/modules/customers/commands/shared.js.map +2 -2
  182. package/dist/modules/customers/commands/todos.js +10 -1
  183. package/dist/modules/customers/commands/todos.js.map +2 -2
  184. package/dist/modules/customers/components/AddressEditor.js +1 -1
  185. package/dist/modules/customers/components/AddressEditor.js.map +2 -2
  186. package/dist/modules/customers/components/CustomersConfigurationSections.js +31 -0
  187. package/dist/modules/customers/components/CustomersConfigurationSections.js.map +7 -0
  188. package/dist/modules/customers/components/DictionarySettings.js +37 -2
  189. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  190. package/dist/modules/customers/components/detail/ActiveDealCard.js +121 -0
  191. package/dist/modules/customers/components/detail/ActiveDealCard.js.map +7 -0
  192. package/dist/modules/customers/components/detail/ActivitiesSection.js +222 -331
  193. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +3 -3
  194. package/dist/modules/customers/components/detail/ActivityAiActions.js +36 -0
  195. package/dist/modules/customers/components/detail/ActivityAiActions.js.map +7 -0
  196. package/dist/modules/customers/components/detail/ActivityCard.js +126 -0
  197. package/dist/modules/customers/components/detail/ActivityCard.js.map +7 -0
  198. package/dist/modules/customers/components/detail/ActivityHistorySection.js +340 -0
  199. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +7 -0
  200. package/dist/modules/customers/components/detail/ActivityLogTab.js +56 -0
  201. package/dist/modules/customers/components/detail/ActivityLogTab.js.map +7 -0
  202. package/dist/modules/customers/components/detail/ActivityTimeline.js +108 -0
  203. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +7 -0
  204. package/dist/modules/customers/components/detail/ActivityTimelineFilters.js +139 -0
  205. package/dist/modules/customers/components/detail/ActivityTimelineFilters.js.map +7 -0
  206. package/dist/modules/customers/components/detail/ActivityTypeSelector.js +42 -0
  207. package/dist/modules/customers/components/detail/ActivityTypeSelector.js.map +7 -0
  208. package/dist/modules/customers/components/detail/AiActionChips.js +38 -0
  209. package/dist/modules/customers/components/detail/AiActionChips.js.map +7 -0
  210. package/dist/modules/customers/components/detail/AssignRoleDialog.js +534 -0
  211. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +7 -0
  212. package/dist/modules/customers/components/detail/ChangelogEntryRow.js +79 -0
  213. package/dist/modules/customers/components/detail/ChangelogEntryRow.js.map +7 -0
  214. package/dist/modules/customers/components/detail/ChangelogFilters.js +176 -0
  215. package/dist/modules/customers/components/detail/ChangelogFilters.js.map +7 -0
  216. package/dist/modules/customers/components/detail/ChangelogKpiCards.js +88 -0
  217. package/dist/modules/customers/components/detail/ChangelogKpiCards.js.map +7 -0
  218. package/dist/modules/customers/components/detail/ChangelogTab.js +470 -0
  219. package/dist/modules/customers/components/detail/ChangelogTab.js.map +7 -0
  220. package/dist/modules/customers/components/detail/ComingSoonPlaceholder.js +16 -0
  221. package/dist/modules/customers/components/detail/ComingSoonPlaceholder.js.map +7 -0
  222. package/dist/modules/customers/components/detail/CompanyCard.js +283 -0
  223. package/dist/modules/customers/components/detail/CompanyCard.js.map +7 -0
  224. package/dist/modules/customers/components/detail/CompanyDashboardTab.js +133 -0
  225. package/dist/modules/customers/components/detail/CompanyDashboardTab.js.map +7 -0
  226. package/dist/modules/customers/components/detail/CompanyDetailHeader.js +191 -0
  227. package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +7 -0
  228. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +123 -0
  229. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +7 -0
  230. package/dist/modules/customers/components/detail/CompanyKpiBar.js +174 -0
  231. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +7 -0
  232. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +514 -230
  233. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  234. package/dist/modules/customers/components/detail/CompanyTagsDialog.js +22 -0
  235. package/dist/modules/customers/components/detail/CompanyTagsDialog.js.map +7 -0
  236. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +159 -0
  237. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +7 -0
  238. package/dist/modules/customers/components/detail/CreatePersonDialog.js +135 -0
  239. package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +7 -0
  240. package/dist/modules/customers/components/detail/DealClosureActionBar.js +59 -0
  241. package/dist/modules/customers/components/detail/DealClosureActionBar.js.map +7 -0
  242. package/dist/modules/customers/components/detail/DealDetailHeader.js +237 -0
  243. package/dist/modules/customers/components/detail/DealDetailHeader.js.map +7 -0
  244. package/dist/modules/customers/components/detail/DealDetailTabs.js +109 -0
  245. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +7 -0
  246. package/dist/modules/customers/components/detail/DealForm.js +219 -92
  247. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  248. package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js +295 -0
  249. package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js.map +7 -0
  250. package/dist/modules/customers/components/detail/DealLostSummaryDialog.js +107 -0
  251. package/dist/modules/customers/components/detail/DealLostSummaryDialog.js.map +7 -0
  252. package/dist/modules/customers/components/detail/DealWonPopup.js +113 -0
  253. package/dist/modules/customers/components/detail/DealWonPopup.js.map +7 -0
  254. package/dist/modules/customers/components/detail/DealsSection.js +206 -193
  255. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  256. package/dist/modules/customers/components/detail/DecisionMakersFooter.js +39 -0
  257. package/dist/modules/customers/components/detail/DecisionMakersFooter.js.map +7 -0
  258. package/dist/modules/customers/components/detail/EntityTagsDialog.js +1096 -0
  259. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +7 -0
  260. package/dist/modules/customers/components/detail/InlineActivityComposer.js +197 -0
  261. package/dist/modules/customers/components/detail/InlineActivityComposer.js.map +7 -0
  262. package/dist/modules/customers/components/detail/ManageTagsDialog.js +1091 -0
  263. package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +7 -0
  264. package/dist/modules/customers/components/detail/MiniWeekCalendar.js +272 -0
  265. package/dist/modules/customers/components/detail/MiniWeekCalendar.js.map +7 -0
  266. package/dist/modules/customers/components/detail/MobilePersonDetail.js +106 -0
  267. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +7 -0
  268. package/dist/modules/customers/components/detail/NextStepCard.js +72 -0
  269. package/dist/modules/customers/components/detail/NextStepCard.js.map +7 -0
  270. package/dist/modules/customers/components/detail/PersonCard.js +192 -0
  271. package/dist/modules/customers/components/detail/PersonCard.js.map +7 -0
  272. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +345 -0
  273. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +7 -0
  274. package/dist/modules/customers/components/detail/PersonDetailHeader.js +220 -0
  275. package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +7 -0
  276. package/dist/modules/customers/components/detail/PersonDetailTabs.js +122 -0
  277. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +7 -0
  278. package/dist/modules/customers/components/detail/PersonTagsDialog.js +24 -0
  279. package/dist/modules/customers/components/detail/PersonTagsDialog.js.map +7 -0
  280. package/dist/modules/customers/components/detail/PipelineStepper.js +191 -0
  281. package/dist/modules/customers/components/detail/PipelineStepper.js.map +7 -0
  282. package/dist/modules/customers/components/detail/PlannedActivitiesSection.js +222 -0
  283. package/dist/modules/customers/components/detail/PlannedActivitiesSection.js.map +7 -0
  284. package/dist/modules/customers/components/detail/RelationshipHealthCard.js +49 -0
  285. package/dist/modules/customers/components/detail/RelationshipHealthCard.js.map +7 -0
  286. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +189 -0
  287. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +7 -0
  288. package/dist/modules/customers/components/detail/RolesSection.js +234 -0
  289. package/dist/modules/customers/components/detail/RolesSection.js.map +7 -0
  290. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +410 -0
  291. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +7 -0
  292. package/dist/modules/customers/components/detail/aiActionCatalog.js +41 -0
  293. package/dist/modules/customers/components/detail/aiActionCatalog.js.map +7 -0
  294. package/dist/modules/customers/components/detail/assignableStaff.js +48 -0
  295. package/dist/modules/customers/components/detail/assignableStaff.js.map +7 -0
  296. package/dist/modules/customers/components/detail/dashboard/ActiveDealWidget.js +48 -0
  297. package/dist/modules/customers/components/detail/dashboard/ActiveDealWidget.js.map +7 -0
  298. package/dist/modules/customers/components/detail/dashboard/OpenTasksWidget.js +86 -0
  299. package/dist/modules/customers/components/detail/dashboard/OpenTasksWidget.js.map +7 -0
  300. package/dist/modules/customers/components/detail/dashboard/RecentActivityWidget.js +53 -0
  301. package/dist/modules/customers/components/detail/dashboard/RecentActivityWidget.js.map +7 -0
  302. package/dist/modules/customers/components/detail/dashboard/RelationshipHealthWidget.js +30 -0
  303. package/dist/modules/customers/components/detail/dashboard/RelationshipHealthWidget.js.map +7 -0
  304. package/dist/modules/customers/components/detail/dashboard/UpcomingMeetingsWidget.js +43 -0
  305. package/dist/modules/customers/components/detail/dashboard/UpcomingMeetingsWidget.js.map +7 -0
  306. package/dist/modules/customers/components/detail/dashboard/helpers.js +71 -0
  307. package/dist/modules/customers/components/detail/dashboard/helpers.js.map +7 -0
  308. package/dist/modules/customers/components/detail/healthScoreUtils.js +69 -0
  309. package/dist/modules/customers/components/detail/healthScoreUtils.js.map +7 -0
  310. package/dist/modules/customers/components/detail/hooks/useCurrencyDictionary.js +5 -5
  311. package/dist/modules/customers/components/detail/hooks/useCurrencyDictionary.js.map +2 -2
  312. package/dist/modules/customers/components/detail/hooks/useCustomerDictionary.js +9 -8
  313. package/dist/modules/customers/components/detail/hooks/useCustomerDictionary.js.map +3 -3
  314. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js +65 -0
  315. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js.map +7 -0
  316. package/dist/modules/customers/components/detail/notesAdapter.js +70 -30
  317. package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
  318. package/dist/modules/customers/components/detail/pipelineStageUtils.js +26 -0
  319. package/dist/modules/customers/components/detail/pipelineStageUtils.js.map +7 -0
  320. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +144 -0
  321. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +7 -0
  322. package/dist/modules/customers/components/detail/schedule/FooterFields.js +60 -0
  323. package/dist/modules/customers/components/detail/schedule/FooterFields.js.map +7 -0
  324. package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js +216 -0
  325. package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js.map +7 -0
  326. package/dist/modules/customers/components/detail/schedule/LocationField.js +34 -0
  327. package/dist/modules/customers/components/detail/schedule/LocationField.js.map +7 -0
  328. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +226 -0
  329. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +7 -0
  330. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +69 -0
  331. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +7 -0
  332. package/dist/modules/customers/components/detail/schedule/index.js +21 -0
  333. package/dist/modules/customers/components/detail/schedule/index.js.map +7 -0
  334. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +172 -0
  335. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +7 -0
  336. package/dist/modules/customers/components/detail/utils.js +23 -0
  337. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  338. package/dist/modules/customers/components/formConfig.js +144 -22
  339. package/dist/modules/customers/components/formConfig.js.map +2 -2
  340. package/dist/modules/customers/components/linking/LinkEntityDialog.js +661 -0
  341. package/dist/modules/customers/components/linking/LinkEntityDialog.js.map +7 -0
  342. package/dist/modules/customers/components/linking/adapters/companyAdapter.js +252 -0
  343. package/dist/modules/customers/components/linking/adapters/companyAdapter.js.map +7 -0
  344. package/dist/modules/customers/components/linking/adapters/dealAdapter.js +384 -0
  345. package/dist/modules/customers/components/linking/adapters/dealAdapter.js.map +7 -0
  346. package/dist/modules/customers/components/linking/adapters/personAdapter.js +324 -0
  347. package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +7 -0
  348. package/dist/modules/customers/components/list/CollectionPreviewCell.js +53 -0
  349. package/dist/modules/customers/components/list/CollectionPreviewCell.js.map +7 -0
  350. package/dist/modules/customers/data/entities.js +407 -1
  351. package/dist/modules/customers/data/entities.js.map +2 -2
  352. package/dist/modules/customers/data/validators.js +139 -21
  353. package/dist/modules/customers/data/validators.js.map +2 -2
  354. package/dist/modules/customers/events.js +19 -1
  355. package/dist/modules/customers/events.js.map +2 -2
  356. package/dist/modules/customers/lib/customerRoleTypes.js +19 -0
  357. package/dist/modules/customers/lib/customerRoleTypes.js.map +7 -0
  358. package/dist/modules/customers/lib/dealClosureNotification.js +39 -0
  359. package/dist/modules/customers/lib/dealClosureNotification.js.map +7 -0
  360. package/dist/modules/customers/lib/dealStageTransitionTable.js +29 -0
  361. package/dist/modules/customers/lib/dealStageTransitionTable.js.map +7 -0
  362. package/dist/modules/customers/lib/dictionaries.js +25 -0
  363. package/dist/modules/customers/lib/dictionaries.js.map +2 -2
  364. package/dist/modules/customers/lib/interactionReadModel.js +10 -0
  365. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  366. package/dist/modules/customers/lib/personCompanies.js +235 -0
  367. package/dist/modules/customers/lib/personCompanies.js.map +7 -0
  368. package/dist/modules/customers/lib/personCompanyLinkTable.js +42 -0
  369. package/dist/modules/customers/lib/personCompanyLinkTable.js.map +7 -0
  370. package/dist/modules/customers/lib/roleTypeUsage.js +104 -0
  371. package/dist/modules/customers/lib/roleTypeUsage.js.map +7 -0
  372. package/dist/modules/customers/migrations/Migration20260406214502.js +18 -0
  373. package/dist/modules/customers/migrations/Migration20260406214502.js.map +7 -0
  374. package/dist/modules/customers/migrations/Migration20260408135736.js +17 -0
  375. package/dist/modules/customers/migrations/Migration20260408135736.js.map +7 -0
  376. package/dist/modules/customers/migrations/Migration20260408225345.js +21 -0
  377. package/dist/modules/customers/migrations/Migration20260408225345.js.map +7 -0
  378. package/dist/modules/customers/migrations/Migration20260411075533.js +27 -0
  379. package/dist/modules/customers/migrations/Migration20260411075533.js.map +7 -0
  380. package/dist/modules/customers/migrations/Migration20260411103551.js +13 -0
  381. package/dist/modules/customers/migrations/Migration20260411103551.js.map +7 -0
  382. package/dist/modules/customers/migrations/Migration20260411130944.js +26 -0
  383. package/dist/modules/customers/migrations/Migration20260411130944.js.map +7 -0
  384. package/dist/modules/customers/migrations/Migration20260415095203.js +13 -0
  385. package/dist/modules/customers/migrations/Migration20260415095203.js.map +7 -0
  386. package/dist/modules/customers/migrations/Migration20260415135056.js +20 -0
  387. package/dist/modules/customers/migrations/Migration20260415135056.js.map +7 -0
  388. package/dist/modules/customers/migrations/Migration20260417140000.js +15 -0
  389. package/dist/modules/customers/migrations/Migration20260417140000.js.map +7 -0
  390. package/dist/modules/customers/migrations/Migration20260417160000.js +17 -0
  391. package/dist/modules/customers/migrations/Migration20260417160000.js.map +7 -0
  392. package/dist/modules/customers/migrations/Migration20260417235407.js +13 -0
  393. package/dist/modules/customers/migrations/Migration20260417235407.js.map +7 -0
  394. package/dist/modules/customers/setup.js +16 -1
  395. package/dist/modules/customers/setup.js.map +2 -2
  396. package/dist/modules/customers/subscribers/deal-closure-notification.js +16 -0
  397. package/dist/modules/customers/subscribers/deal-closure-notification.js.map +7 -0
  398. package/dist/modules/customers/subscribers/deal-lost-notification.js +16 -0
  399. package/dist/modules/customers/subscribers/deal-lost-notification.js.map +7 -0
  400. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +2 -0
  401. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  402. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +154 -0
  403. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +7 -0
  404. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +6 -2
  405. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  406. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +154 -0
  407. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +7 -0
  408. package/dist/modules/dictionaries/api/context.js +8 -1
  409. package/dist/modules/dictionaries/api/context.js.map +2 -2
  410. package/dist/modules/dictionaries/api/openapi.js +18 -1
  411. package/dist/modules/dictionaries/api/openapi.js.map +2 -2
  412. package/dist/modules/dictionaries/commands/entry-operations.js +388 -0
  413. package/dist/modules/dictionaries/commands/entry-operations.js.map +7 -0
  414. package/dist/modules/dictionaries/commands/factory.js +24 -3
  415. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  416. package/dist/modules/dictionaries/commands/index.js +1 -0
  417. package/dist/modules/dictionaries/commands/index.js.map +2 -2
  418. package/dist/modules/dictionaries/components/DictionaryTable.js +6 -3
  419. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  420. package/dist/modules/dictionaries/data/entities.js +11 -1
  421. package/dist/modules/dictionaries/data/entities.js.map +2 -2
  422. package/dist/modules/dictionaries/data/validators.js +28 -2
  423. package/dist/modules/dictionaries/data/validators.js.map +2 -2
  424. package/dist/modules/dictionaries/events.js +18 -0
  425. package/dist/modules/dictionaries/events.js.map +7 -0
  426. package/dist/modules/dictionaries/lib/clientEntries.js +43 -0
  427. package/dist/modules/dictionaries/lib/clientEntries.js.map +7 -0
  428. package/dist/modules/dictionaries/migrations/Migration20260410171544.js +45 -0
  429. package/dist/modules/dictionaries/migrations/Migration20260410171544.js.map +7 -0
  430. package/dist/modules/inbox_ops/api/proposals/[id]/route.js +4 -1
  431. package/dist/modules/inbox_ops/api/proposals/[id]/route.js.map +2 -2
  432. package/dist/modules/query_index/lib/engine.js +1 -1
  433. package/dist/modules/query_index/lib/engine.js.map +2 -2
  434. package/dist/modules/sales/components/documents/AddressesSection.js +82 -42
  435. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  436. package/dist/modules/sales/lib/dictionaries.js +16 -0
  437. package/dist/modules/sales/lib/dictionaries.js.map +2 -2
  438. package/dist/modules/sales/widgets/injection-table.js +5 -1
  439. package/dist/modules/sales/widgets/injection-table.js.map +2 -2
  440. package/generated/entities/action_log/index.ts +4 -0
  441. package/generated/entities/customer_company_billing/index.ts +10 -0
  442. package/generated/entities/customer_deal/index.ts +4 -0
  443. package/generated/entities/customer_deal_stage_transition/index.ts +14 -0
  444. package/generated/entities/customer_dictionary_kind_setting/index.ts +9 -0
  445. package/generated/entities/customer_entity/index.ts +4 -0
  446. package/generated/entities/customer_entity_role/index.ts +10 -0
  447. package/generated/entities/customer_interaction/index.ts +11 -0
  448. package/generated/entities/customer_label/index.ts +8 -0
  449. package/generated/entities/customer_label_assignment/index.ts +7 -0
  450. package/generated/entities/customer_person_company_link/index.ts +9 -0
  451. package/generated/entities/customer_person_company_role/index.ts +7 -0
  452. package/generated/entities/dictionary_entry/index.ts +2 -0
  453. package/generated/entities.ids.generated.ts +9 -1
  454. package/generated/entity-fields-registry.ts +116 -1
  455. package/package.json +3 -3
  456. package/src/modules/attachments/api/route.ts +48 -6
  457. package/src/modules/attachments/i18n/de.json +4 -0
  458. package/src/modules/attachments/i18n/en.json +4 -0
  459. package/src/modules/attachments/i18n/es.json +4 -0
  460. package/src/modules/attachments/i18n/pl.json +4 -0
  461. package/src/modules/audit_logs/api/audit-logs/actions/export/route.ts +260 -0
  462. package/src/modules/audit_logs/api/audit-logs/actions/route.ts +81 -6
  463. package/src/modules/audit_logs/cli.ts +79 -0
  464. package/src/modules/audit_logs/data/entities.ts +17 -0
  465. package/src/modules/audit_logs/data/validators.ts +9 -1
  466. package/src/modules/audit_logs/lib/changeRows.ts +47 -0
  467. package/src/modules/audit_logs/lib/display-helpers.tsx +4 -30
  468. package/src/modules/audit_logs/lib/projections.ts +110 -0
  469. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +325 -2
  470. package/src/modules/audit_logs/migrations/Migration20260412160533.ts +21 -0
  471. package/src/modules/audit_logs/services/actionLogService.ts +455 -85
  472. package/src/modules/catalog/i18n/de.json +1 -0
  473. package/src/modules/catalog/i18n/en.json +1 -0
  474. package/src/modules/catalog/i18n/es.json +1 -0
  475. package/src/modules/catalog/i18n/pl.json +1 -0
  476. package/src/modules/customer_accounts/i18n/de.json +2 -0
  477. package/src/modules/customer_accounts/i18n/en.json +2 -0
  478. package/src/modules/customer_accounts/i18n/es.json +2 -0
  479. package/src/modules/customer_accounts/i18n/pl.json +2 -0
  480. package/src/modules/customers/acl.ts +2 -0
  481. package/src/modules/customers/api/activities/route.ts +4 -0
  482. package/src/modules/customers/api/assignable-staff/route.ts +250 -0
  483. package/src/modules/customers/api/companies/[id]/people/route.ts +244 -0
  484. package/src/modules/customers/api/companies/[id]/roles/route.ts +15 -0
  485. package/src/modules/customers/api/companies/[id]/route.ts +458 -40
  486. package/src/modules/customers/api/companies/route.ts +93 -15
  487. package/src/modules/customers/api/deals/[id]/companies/route.ts +203 -0
  488. package/src/modules/customers/api/deals/[id]/people/route.ts +182 -0
  489. package/src/modules/customers/api/deals/[id]/route.ts +554 -57
  490. package/src/modules/customers/api/deals/[id]/stats/route.ts +221 -0
  491. package/src/modules/customers/api/deals/route.ts +35 -46
  492. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +105 -3
  493. package/src/modules/customers/api/dictionaries/[kind]/route.ts +143 -44
  494. package/src/modules/customers/api/dictionaries/context.ts +45 -16
  495. package/src/modules/customers/api/dictionaries/kind-settings/route.ts +232 -0
  496. package/src/modules/customers/api/entity-roles-factory.ts +520 -0
  497. package/src/modules/customers/api/interactions/conflicts/route.ts +196 -0
  498. package/src/modules/customers/api/interactions/counts/route.ts +112 -0
  499. package/src/modules/customers/api/interactions/route.ts +95 -2
  500. package/src/modules/customers/api/labels/assign/route.ts +202 -0
  501. package/src/modules/customers/api/labels/auth.ts +19 -0
  502. package/src/modules/customers/api/labels/route.ts +310 -0
  503. package/src/modules/customers/api/labels/table-errors.ts +36 -0
  504. package/src/modules/customers/api/labels/unassign/route.ts +197 -0
  505. package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +331 -0
  506. package/src/modules/customers/api/people/[id]/companies/context.ts +70 -0
  507. package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +384 -0
  508. package/src/modules/customers/api/people/[id]/companies/route.ts +215 -0
  509. package/src/modules/customers/api/people/[id]/roles/route.ts +15 -0
  510. package/src/modules/customers/api/people/[id]/route.ts +153 -26
  511. package/src/modules/customers/api/people/route.ts +134 -31
  512. package/src/modules/customers/api/todos/route.ts +4 -0
  513. package/src/modules/customers/api/utils.ts +36 -0
  514. package/src/modules/customers/backend/config/customers/page.tsx +2 -6
  515. package/src/modules/customers/backend/customers/companies/page.tsx +36 -26
  516. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +277 -262
  517. package/src/modules/customers/backend/customers/deals/[id]/hooks/formatters.ts +19 -0
  518. package/src/modules/customers/backend/customers/deals/[id]/hooks/types.ts +104 -0
  519. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +60 -0
  520. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +362 -0
  521. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +113 -0
  522. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +52 -0
  523. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.ts +86 -0
  524. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx +60 -0
  525. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealMutationContext.ts +76 -0
  526. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +56 -0
  527. package/src/modules/customers/backend/customers/deals/[id]/hooks/useScheduleDialog.ts +38 -0
  528. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +587 -624
  529. package/src/modules/customers/backend/customers/deals/page.tsx +71 -28
  530. package/src/modules/customers/backend/customers/people/page.tsx +35 -29
  531. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +343 -209
  532. package/src/modules/customers/cli.ts +107 -12
  533. package/src/modules/customers/commands/activities.ts +13 -0
  534. package/src/modules/customers/commands/deals.ts +386 -114
  535. package/src/modules/customers/commands/dictionaries.ts +175 -32
  536. package/src/modules/customers/commands/dictionaryKindSettings.ts +268 -0
  537. package/src/modules/customers/commands/entity-roles.ts +494 -0
  538. package/src/modules/customers/commands/index.ts +4 -0
  539. package/src/modules/customers/commands/interactions.ts +125 -21
  540. package/src/modules/customers/commands/labels.ts +626 -0
  541. package/src/modules/customers/commands/people.ts +373 -259
  542. package/src/modules/customers/commands/personCompanyLinks.ts +654 -0
  543. package/src/modules/customers/commands/shared.ts +16 -15
  544. package/src/modules/customers/commands/todos.ts +17 -1
  545. package/src/modules/customers/components/AddressEditor.tsx +1 -1
  546. package/src/modules/customers/components/CustomersConfigurationSections.tsx +36 -0
  547. package/src/modules/customers/components/DictionarySettings.tsx +43 -2
  548. package/src/modules/customers/components/detail/ActiveDealCard.tsx +175 -0
  549. package/src/modules/customers/components/detail/ActivitiesSection.tsx +267 -361
  550. package/src/modules/customers/components/detail/ActivityAiActions.tsx +49 -0
  551. package/src/modules/customers/components/detail/ActivityCard.tsx +154 -0
  552. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +412 -0
  553. package/src/modules/customers/components/detail/ActivityLogTab.tsx +67 -0
  554. package/src/modules/customers/components/detail/ActivityTimeline.tsx +158 -0
  555. package/src/modules/customers/components/detail/ActivityTimelineFilters.tsx +163 -0
  556. package/src/modules/customers/components/detail/ActivityTypeSelector.tsx +53 -0
  557. package/src/modules/customers/components/detail/AiActionChips.tsx +48 -0
  558. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +672 -0
  559. package/src/modules/customers/components/detail/ChangelogEntryRow.tsx +132 -0
  560. package/src/modules/customers/components/detail/ChangelogFilters.tsx +193 -0
  561. package/src/modules/customers/components/detail/ChangelogKpiCards.tsx +107 -0
  562. package/src/modules/customers/components/detail/ChangelogTab.tsx +629 -0
  563. package/src/modules/customers/components/detail/ComingSoonPlaceholder.tsx +21 -0
  564. package/src/modules/customers/components/detail/CompanyCard.tsx +419 -0
  565. package/src/modules/customers/components/detail/CompanyDashboardTab.tsx +161 -0
  566. package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +243 -0
  567. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +172 -0
  568. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +206 -0
  569. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +582 -288
  570. package/src/modules/customers/components/detail/CompanyTagsDialog.tsx +23 -0
  571. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +210 -0
  572. package/src/modules/customers/components/detail/CreatePersonDialog.tsx +178 -0
  573. package/src/modules/customers/components/detail/DealClosureActionBar.tsx +63 -0
  574. package/src/modules/customers/components/detail/DealDetailHeader.tsx +335 -0
  575. package/src/modules/customers/components/detail/DealDetailTabs.tsx +154 -0
  576. package/src/modules/customers/components/detail/DealForm.tsx +253 -101
  577. package/src/modules/customers/components/detail/DealLinkedEntitiesTab.tsx +349 -0
  578. package/src/modules/customers/components/detail/DealLostSummaryDialog.tsx +156 -0
  579. package/src/modules/customers/components/detail/DealWonPopup.tsx +164 -0
  580. package/src/modules/customers/components/detail/DealsSection.tsx +276 -221
  581. package/src/modules/customers/components/detail/DecisionMakersFooter.tsx +56 -0
  582. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +1372 -0
  583. package/src/modules/customers/components/detail/InlineActivityComposer.tsx +239 -0
  584. package/src/modules/customers/components/detail/ManageTagsDialog.tsx +1331 -0
  585. package/src/modules/customers/components/detail/MiniWeekCalendar.tsx +338 -0
  586. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +124 -0
  587. package/src/modules/customers/components/detail/NextStepCard.tsx +104 -0
  588. package/src/modules/customers/components/detail/PersonCard.tsx +238 -0
  589. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +426 -0
  590. package/src/modules/customers/components/detail/PersonDetailHeader.tsx +294 -0
  591. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +172 -0
  592. package/src/modules/customers/components/detail/PersonTagsDialog.tsx +26 -0
  593. package/src/modules/customers/components/detail/PipelineStepper.tsx +245 -0
  594. package/src/modules/customers/components/detail/PlannedActivitiesSection.tsx +255 -0
  595. package/src/modules/customers/components/detail/RelationshipHealthCard.tsx +63 -0
  596. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +248 -0
  597. package/src/modules/customers/components/detail/RolesSection.tsx +311 -0
  598. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +481 -0
  599. package/src/modules/customers/components/detail/aiActionCatalog.ts +77 -0
  600. package/src/modules/customers/components/detail/assignableStaff.ts +124 -0
  601. package/src/modules/customers/components/detail/dashboard/ActiveDealWidget.tsx +63 -0
  602. package/src/modules/customers/components/detail/dashboard/OpenTasksWidget.tsx +114 -0
  603. package/src/modules/customers/components/detail/dashboard/RecentActivityWidget.tsx +69 -0
  604. package/src/modules/customers/components/detail/dashboard/RelationshipHealthWidget.tsx +40 -0
  605. package/src/modules/customers/components/detail/dashboard/UpcomingMeetingsWidget.tsx +64 -0
  606. package/src/modules/customers/components/detail/dashboard/helpers.ts +78 -0
  607. package/src/modules/customers/components/detail/healthScoreUtils.ts +91 -0
  608. package/src/modules/customers/components/detail/hooks/useCurrencyDictionary.ts +8 -8
  609. package/src/modules/customers/components/detail/hooks/useCustomerDictionary.ts +10 -6
  610. package/src/modules/customers/components/detail/hooks/useInteractionMutations.ts +91 -0
  611. package/src/modules/customers/components/detail/notesAdapter.ts +91 -30
  612. package/src/modules/customers/components/detail/pipelineStageUtils.ts +29 -0
  613. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +187 -0
  614. package/src/modules/customers/components/detail/schedule/FooterFields.tsx +79 -0
  615. package/src/modules/customers/components/detail/schedule/LinkedEntitiesField.tsx +277 -0
  616. package/src/modules/customers/components/detail/schedule/LocationField.tsx +42 -0
  617. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +255 -0
  618. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +70 -0
  619. package/src/modules/customers/components/detail/schedule/index.ts +9 -0
  620. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +221 -0
  621. package/src/modules/customers/components/detail/types.ts +16 -0
  622. package/src/modules/customers/components/detail/utils.ts +25 -0
  623. package/src/modules/customers/components/formConfig.tsx +223 -28
  624. package/src/modules/customers/components/linking/LinkEntityDialog.tsx +920 -0
  625. package/src/modules/customers/components/linking/adapters/companyAdapter.tsx +398 -0
  626. package/src/modules/customers/components/linking/adapters/dealAdapter.tsx +578 -0
  627. package/src/modules/customers/components/linking/adapters/personAdapter.tsx +512 -0
  628. package/src/modules/customers/components/list/CollectionPreviewCell.tsx +66 -0
  629. package/src/modules/customers/data/entities.ts +353 -1
  630. package/src/modules/customers/data/validators.ts +170 -19
  631. package/src/modules/customers/events.ts +22 -0
  632. package/src/modules/customers/i18n/de.json +841 -2
  633. package/src/modules/customers/i18n/en.json +841 -2
  634. package/src/modules/customers/i18n/es.json +840 -1
  635. package/src/modules/customers/i18n/pl.json +841 -2
  636. package/src/modules/customers/lib/customerRoleTypes.ts +24 -0
  637. package/src/modules/customers/lib/dealClosureNotification.ts +64 -0
  638. package/src/modules/customers/lib/dealStageTransitionTable.ts +32 -0
  639. package/src/modules/customers/lib/dictionaries.ts +26 -10
  640. package/src/modules/customers/lib/interactionReadModel.ts +10 -0
  641. package/src/modules/customers/lib/personCompanies.ts +317 -0
  642. package/src/modules/customers/lib/personCompanyLinkTable.ts +58 -0
  643. package/src/modules/customers/lib/roleTypeUsage.ts +146 -0
  644. package/src/modules/customers/migrations/.snapshot-open-mercato.json +2747 -798
  645. package/src/modules/customers/migrations/Migration20260406214502.ts +19 -0
  646. package/src/modules/customers/migrations/Migration20260408135736.ts +15 -0
  647. package/src/modules/customers/migrations/Migration20260408225345.ts +23 -0
  648. package/src/modules/customers/migrations/Migration20260411075533.ts +30 -0
  649. package/src/modules/customers/migrations/Migration20260411103551.ts +13 -0
  650. package/src/modules/customers/migrations/Migration20260411130944.ts +30 -0
  651. package/src/modules/customers/migrations/Migration20260415095203.ts +13 -0
  652. package/src/modules/customers/migrations/Migration20260415135056.ts +22 -0
  653. package/src/modules/customers/migrations/Migration20260417140000.ts +15 -0
  654. package/src/modules/customers/migrations/Migration20260417160000.ts +17 -0
  655. package/src/modules/customers/migrations/Migration20260417235407.ts +13 -0
  656. package/src/modules/customers/setup.ts +15 -0
  657. package/src/modules/customers/subscribers/deal-closure-notification.ts +22 -0
  658. package/src/modules/customers/subscribers/deal-lost-notification.ts +22 -0
  659. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +2 -0
  660. package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +162 -0
  661. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +6 -2
  662. package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +162 -0
  663. package/src/modules/dictionaries/api/context.ts +9 -0
  664. package/src/modules/dictionaries/api/openapi.ts +17 -0
  665. package/src/modules/dictionaries/commands/entry-operations.ts +457 -0
  666. package/src/modules/dictionaries/commands/factory.ts +31 -3
  667. package/src/modules/dictionaries/commands/index.ts +1 -0
  668. package/src/modules/dictionaries/components/DictionaryTable.tsx +15 -6
  669. package/src/modules/dictionaries/data/entities.ts +9 -0
  670. package/src/modules/dictionaries/data/validators.ts +34 -0
  671. package/src/modules/dictionaries/events.ts +20 -0
  672. package/src/modules/dictionaries/i18n/de.json +2 -0
  673. package/src/modules/dictionaries/i18n/en.json +2 -0
  674. package/src/modules/dictionaries/i18n/es.json +2 -0
  675. package/src/modules/dictionaries/i18n/pl.json +2 -0
  676. package/src/modules/dictionaries/lib/clientEntries.ts +66 -0
  677. package/src/modules/dictionaries/migrations/.snapshot-open-mercato.json +185 -3
  678. package/src/modules/dictionaries/migrations/Migration20260410171544.ts +49 -0
  679. package/src/modules/inbox_ops/api/proposals/[id]/route.ts +4 -1
  680. package/src/modules/query_index/lib/engine.ts +9 -1
  681. package/src/modules/sales/components/documents/AddressesSection.tsx +92 -42
  682. package/src/modules/sales/i18n/de.json +28 -0
  683. package/src/modules/sales/i18n/en.json +28 -0
  684. package/src/modules/sales/i18n/es.json +28 -0
  685. package/src/modules/sales/i18n/pl.json +28 -0
  686. package/src/modules/sales/lib/dictionaries.ts +18 -0
  687. package/src/modules/sales/widgets/injection-table.ts +4 -0
@@ -2,349 +2,289 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import Link from 'next/link'
5
- import { useRouter } from 'next/navigation'
6
- import { Pencil, MousePointerClick } from 'lucide-react'
5
+ import { Building2, Users } from 'lucide-react'
6
+ import { useRouter, useSearchParams } from 'next/navigation'
7
7
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
8
8
  import { Button } from '@open-mercato/ui/primitives/button'
9
- import { FormHeader } from '@open-mercato/ui/backend/forms'
10
- import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
11
- import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
12
- import { Spinner } from '@open-mercato/ui/primitives/spinner'
13
- import { flash } from '@open-mercato/ui/backend/FlashMessages'
14
- import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
15
- import { useT } from '@open-mercato/shared/lib/i18n/context'
16
- import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
9
+ import { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection } from '@open-mercato/ui/backend/detail'
10
+ import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
17
11
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
18
- import { NotesSection, type SectionAction } from '@open-mercato/ui/backend/detail'
19
- import { ActivitiesSection } from '../../../../components/detail/ActivitiesSection'
20
- import { DealForm, type DealFormSubmitPayload } from '../../../../components/detail/DealForm'
21
- import { useCustomerDictionary } from '../../../../components/detail/hooks/useCustomerDictionary'
22
- import type { CustomerDictionaryMap } from '../../../../lib/dictionaries'
12
+ import { CollapsibleZoneLayout } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
13
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
23
14
  import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
24
- import { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
25
- import { ICON_SUGGESTIONS } from '../../../../lib/dictionaries'
15
+ import { E } from '#generated/entities.ids.generated'
16
+
17
+ import { ActivitiesSection } from '../../../../components/detail/ActivitiesSection'
18
+ import { ChangelogTab } from '../../../../components/detail/ChangelogTab'
19
+ import { DealClosureActionBar } from '../../../../components/detail/DealClosureActionBar'
20
+ import { DealDetailHeader } from '../../../../components/detail/DealDetailHeader'
21
+ import { DealDetailTabs, resolveLegacyTab, type DealTabId } from '../../../../components/detail/DealDetailTabs'
22
+ import { DealForm, useDealAssociationLookups } from '../../../../components/detail/DealForm'
23
+ import { DealLinkedEntitiesTab } from '../../../../components/detail/DealLinkedEntitiesTab'
24
+ import { ConfirmDealLostDialog } from '../../../../components/detail/ConfirmDealLostDialog'
25
+ import { DealLostSummaryDialog } from '../../../../components/detail/DealLostSummaryDialog'
26
+ import { DealWonPopup } from '../../../../components/detail/DealWonPopup'
27
+ import { InlineActivityComposer } from '../../../../components/detail/InlineActivityComposer'
28
+ import { PipelineStepper } from '../../../../components/detail/PipelineStepper'
29
+ import { PlannedActivitiesSection } from '../../../../components/detail/PlannedActivitiesSection'
30
+ import { ScheduleActivityDialog } from '../../../../components/detail/ScheduleActivityDialog'
26
31
  import { createCustomerNotesAdapter } from '../../../../components/detail/notesAdapter'
32
+ import type { InteractionSummary } from '../../../../components/detail/types'
27
33
  import { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '../../../../lib/markdownPreference'
34
+ import { ICON_SUGGESTIONS } from '../../../../lib/dictionaries'
35
+ import { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
28
36
 
29
- type DealAssociation = {
30
- id: string
31
- label: string
32
- subtitle: string | null
33
- kind: 'person' | 'company'
34
- }
35
-
36
- type DealDetailPayload = {
37
- deal: {
38
- id: string
39
- title: string
40
- description: string | null
41
- status: string | null
42
- pipelineStage: string | null
43
- pipelineId: string | null
44
- pipelineStageId: string | null
45
- valueAmount: string | null
46
- valueCurrency: string | null
47
- probability: number | null
48
- expectedCloseAt: string | null
49
- ownerUserId: string | null
50
- source: string | null
51
- organizationId: string | null
52
- tenantId: string | null
53
- createdAt: string
54
- updatedAt: string
55
- }
56
- people: DealAssociation[]
57
- companies: DealAssociation[]
58
- customFields: Record<string, unknown>
59
- viewer?: {
60
- userId: string | null
61
- name?: string | null
62
- email?: string | null
63
- } | null
64
- }
65
-
66
- const CRUD_FOCUSABLE_SELECTOR =
67
- '[data-crud-focus-target], input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
68
-
69
- function formatCurrency(amount: string | null, currency: string | null): string | null {
70
- if (!amount) return null
71
- const value = Number(amount)
72
- if (!Number.isFinite(value)) return currency ? `${amount} ${currency}` : amount
73
- if (!currency) return value.toLocaleString()
74
- try {
75
- return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value)
76
- } catch {
77
- return `${value.toLocaleString()} ${currency}`
78
- }
79
- }
80
-
81
- function formatDate(value: string | null, fallback: string): string {
82
- if (!value) return fallback
83
- const date = new Date(value)
84
- if (Number.isNaN(date.getTime())) return fallback
85
- return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
86
- }
87
-
88
- function resolveDictionaryLabel(
89
- value: string | null | undefined,
90
- map: CustomerDictionaryMap | null | undefined,
91
- ): string | null {
92
- if (!value) return null
93
- const entry = map?.[value]
94
- if (entry && entry.label && entry.label.length) return entry.label
95
- return value
96
- }
37
+ import { formatCurrency, startOfNextQuarter } from './hooks/formatters'
38
+ import type { DealDetailPayload } from './hooks/types'
39
+ import { useDealActivities } from './hooks/useDealActivities'
40
+ import { useDealAssociations } from './hooks/useDealAssociations'
41
+ import { useDealClosure } from './hooks/useDealClosure'
42
+ import { useDealData } from './hooks/useDealData'
43
+ import { useDealFormHandlers } from './hooks/useDealFormHandlers'
44
+ import { useDealInjectedTabs } from './hooks/useDealInjectedTabs'
45
+ import { useDealMutationContext } from './hooks/useDealMutationContext'
46
+ import { useDealPipeline } from './hooks/useDealPipeline'
47
+ import { useScheduleDialog } from './hooks/useScheduleDialog'
97
48
 
98
49
  export default function DealDetailPage({ params }: { params?: { id?: string } }) {
50
+ const id = params?.id ?? ''
99
51
  const t = useT()
52
+ const router = useRouter()
53
+ const searchParams = useSearchParams()
100
54
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
101
55
  const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
102
- const notesAdapter = React.useMemo(() => createCustomerNotesAdapter(detailTranslator), [detailTranslator])
103
- const router = useRouter()
104
- const id = params?.id ?? ''
105
- const scopeVersion = useOrganizationScopeVersion()
106
- const statusDictionaryQuery = useCustomerDictionary('deal-statuses', scopeVersion)
107
- const pipelineDictionaryQuery = useCustomerDictionary('pipeline-stages', scopeVersion)
108
- const statusDictionaryMap = statusDictionaryQuery.data?.map ?? null
109
- const pipelineDictionaryMap = pipelineDictionaryQuery.data?.map ?? null
110
- const [data, setData] = React.useState<DealDetailPayload | null>(null)
111
- const [isLoading, setIsLoading] = React.useState(true)
112
- const [error, setError] = React.useState<string | null>(null)
113
- const [isSaving, setIsSaving] = React.useState(false)
114
- const [isDeleting, setIsDeleting] = React.useState(false)
115
- const [reloadToken, setReloadToken] = React.useState(0)
116
- const [activeTab, setActiveTab] = React.useState<'notes' | 'activities'>('notes')
117
- const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)
118
- const handleNotesLoadingChange = React.useCallback(() => {}, [])
119
- const handleActivitiesLoadingChange = React.useCallback(() => {}, [])
120
- const focusDealField = React.useCallback(
121
- (fieldId: 'title' | 'personIds' | 'companyIds') => {
122
- if (typeof window === 'undefined' || typeof document === 'undefined') return
123
- const focusOnce = () => {
124
- const container = document.querySelector<HTMLElement>(`[data-crud-field-id="${fieldId}"]`)
125
- if (!container) return false
126
- const target =
127
- container.querySelector<HTMLElement>(CRUD_FOCUSABLE_SELECTOR) ?? container
128
- if (!target || typeof target.focus !== 'function') return false
129
- if (typeof container.scrollIntoView === 'function') {
130
- container.scrollIntoView({ behavior: 'smooth', block: 'center' })
131
- }
132
- target.focus()
133
- if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
134
- try {
135
- target.select()
136
- } catch {}
137
- }
138
- return true
139
- }
140
-
141
- const schedule = () => {
142
- const focused = focusOnce()
143
- if (focused) return
144
- window.setTimeout(() => {
145
- focusOnce()
146
- }, 60)
147
- }
148
-
149
- if (typeof window.requestAnimationFrame === 'function') {
150
- window.requestAnimationFrame(schedule)
151
- } else {
152
- schedule()
153
- }
154
- },
155
- [],
156
- )
157
- const dealSettingsRef = React.useRef<HTMLDivElement | null>(null)
158
- const scrollToDealSettings = React.useCallback(() => {
159
- if (typeof window === 'undefined') return
160
- if (dealSettingsRef.current && typeof dealSettingsRef.current.scrollIntoView === 'function') {
161
- dealSettingsRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
162
- }
163
- window.setTimeout(() => {
164
- focusDealField('title')
165
- }, 160)
166
- }, [focusDealField])
56
+
57
+ const { data, setData, isLoading, error, loadData } = useDealData(id)
58
+ const [isDirty, setIsDirty] = React.useState(false)
59
+ const {
60
+ scheduleDialogOpen,
61
+ scheduleEditData,
62
+ openSchedule,
63
+ openEdit: openScheduleEdit,
64
+ closeSchedule,
65
+ } = useScheduleDialog()
66
+ const formWrapperRef = React.useRef<HTMLDivElement>(null)
67
+
68
+ const initialTab = React.useMemo(() => resolveLegacyTab(searchParams?.get('tab')), [searchParams])
69
+ const [activeTab, setActiveTab] = React.useState<DealTabId>(initialTab)
167
70
 
168
71
  React.useEffect(() => {
169
- if (!id) {
170
- setError(t('customers.deals.detail.missingId', 'Deal id is required.'))
171
- setIsLoading(false)
172
- return
173
- }
174
- let cancelled = false
175
- async function loadDeal() {
176
- setIsLoading(true)
177
- setError(null)
178
- try {
179
- const payload = await readApiResultOrThrow<DealDetailPayload>(
180
- `/api/customers/deals/${encodeURIComponent(id)}`,
181
- undefined,
182
- { errorMessage: t('customers.deals.detail.loadError', 'Failed to load deal.') },
183
- )
184
- if (cancelled) return
185
- setData(payload as DealDetailPayload)
186
- } catch (err) {
187
- if (cancelled) return
188
- const message =
189
- err instanceof Error && err.message
190
- ? err.message
191
- : t('customers.deals.detail.loadError', 'Failed to load deal.')
192
- setError(message)
193
- setData(null)
194
- } finally {
195
- if (!cancelled) setIsLoading(false)
196
- }
197
- }
198
- loadDeal().catch(() => {})
199
- return () => {
200
- cancelled = true
201
- }
202
- }, [id, reloadToken, t])
203
-
204
- const handleFormSubmit = React.useCallback(
205
- async ({ base, custom }: DealFormSubmitPayload) => {
206
- if (!data || isSaving) return
207
- setIsSaving(true)
208
- try {
209
- const payload: Record<string, unknown> = {
210
- id: data.deal.id,
211
- title: base.title,
212
- status: base.status ?? undefined,
213
- pipelineStage: base.pipelineStage ?? undefined,
214
- pipelineId: base.pipelineId ?? undefined,
215
- pipelineStageId: base.pipelineStageId ?? undefined,
216
- valueAmount: typeof base.valueAmount === 'number' ? base.valueAmount : undefined,
217
- valueCurrency: base.valueCurrency ?? undefined,
218
- probability: typeof base.probability === 'number' ? base.probability : undefined,
219
- expectedCloseAt: base.expectedCloseAt ?? undefined,
220
- description: base.description ?? undefined,
221
- personIds: base.personIds && base.personIds.length ? base.personIds : undefined,
222
- companyIds: base.companyIds && base.companyIds.length ? base.companyIds : undefined,
223
- }
224
- if (Object.keys(custom).length) payload.customFields = custom
225
-
226
- await apiCallOrThrow(
227
- '/api/customers/deals',
228
- {
229
- method: 'PUT',
230
- headers: { 'content-type': 'application/json' },
231
- body: JSON.stringify(payload),
232
- },
233
- { errorMessage: t('customers.deals.detail.saveError', 'Failed to update deal.') },
234
- )
235
- flash(t('customers.deals.detail.saveSuccess', 'Deal updated.'), 'success')
236
- setReloadToken((token) => token + 1)
237
- } catch (err) {
238
- const message =
239
- err instanceof Error && err.message
240
- ? err.message
241
- : t('customers.deals.detail.saveError', 'Failed to update deal.')
242
- flash(message, 'error')
243
- throw err instanceof Error ? err : new Error(message)
244
- } finally {
245
- setIsSaving(false)
246
- }
247
- },
248
- [data, isSaving, t],
72
+ setActiveTab(initialTab)
73
+ }, [initialTab])
74
+
75
+ const currentDealId = data?.deal.id ?? id
76
+ const { injectionContext, runMutationWithContext } = useDealMutationContext({
77
+ currentDealId,
78
+ fallbackId: id,
79
+ data,
80
+ })
81
+
82
+ const notesAdapter = React.useMemo(
83
+ () => createCustomerNotesAdapter(detailTranslator, { runMutation: runMutationWithContext }),
84
+ [detailTranslator, runMutationWithContext],
249
85
  )
250
86
 
251
- const handleDelete = React.useCallback(async () => {
252
- if (!data || isDeleting) return
253
- const confirmed = await confirm({
254
- title: t(
255
- 'customers.deals.detail.deleteConfirm',
256
- 'Delete this deal? This action cannot be undone.',
257
- ),
258
- variant: 'destructive',
259
- })
260
- if (!confirmed) return
87
+ const { injectedTabs, injectedTabMap } = useDealInjectedTabs({
88
+ injectionContext,
89
+ data,
90
+ setData,
91
+ })
261
92
 
262
- setIsDeleting(true)
263
- try {
264
- await apiCallOrThrow(
265
- '/api/customers/deals',
266
- {
267
- method: 'DELETE',
268
- headers: { 'content-type': 'application/json' },
269
- body: JSON.stringify({ id: data.deal.id }),
270
- },
271
- { errorMessage: t('customers.deals.detail.deleteError', 'Failed to delete deal.') },
272
- )
273
- flash(t('customers.deals.detail.deleteSuccess', 'Deal deleted.'), 'success')
274
- router.push('/backend/customers/deals')
275
- } catch (err) {
276
- const message =
277
- err instanceof Error && err.message
278
- ? err.message
279
- : t('customers.deals.detail.deleteError', 'Failed to delete deal.')
280
- flash(message, 'error')
281
- } finally {
282
- setIsDeleting(false)
283
- }
284
- }, [confirm, data, isDeleting, router, t])
93
+ const { searchPeoplePage, fetchPeopleByIds, searchCompaniesPage, fetchCompaniesByIds } = useDealAssociationLookups({
94
+ excludeLinkedDealId: data?.deal.id ?? null,
95
+ })
96
+
97
+ const {
98
+ plannedActivities,
99
+ activityRefreshKey,
100
+ loadPlannedActivities,
101
+ handleActivityCreated,
102
+ handleMarkDone,
103
+ handleCancelActivity,
104
+ } = useDealActivities({ dealId: id, runMutationWithContext })
285
105
 
286
106
  React.useEffect(() => {
287
- setSectionAction(null)
288
- }, [activeTab])
107
+ void Promise.all([loadData(), loadPlannedActivities()])
108
+ }, [loadData, loadPlannedActivities])
289
109
 
290
- const handleSectionAction = React.useCallback(() => {
291
- if (!sectionAction || sectionAction.disabled) return
292
- sectionAction.onClick()
293
- }, [sectionAction])
110
+ const activityEntities = React.useMemo(
111
+ () => (data
112
+ ? [...data.people, ...data.companies].map((entry) => ({
113
+ id: entry.id,
114
+ label: entry.subtitle ? `${entry.label} · ${entry.subtitle}` : entry.label,
115
+ kind: entry.kind,
116
+ }))
117
+ : []),
118
+ [data],
119
+ )
120
+ const [selectedActivityEntityId, setSelectedActivityEntityId] = React.useState<string | null>(null)
121
+
122
+ React.useEffect(() => {
123
+ setSelectedActivityEntityId((current) => {
124
+ if (activityEntities.length === 1) return activityEntities[0].id
125
+ if (current && activityEntities.some((entry) => entry.id === current)) return current
126
+ return null
127
+ })
128
+ }, [activityEntities])
129
+
130
+ const selectedActivityEntity = React.useMemo(
131
+ () => activityEntities.find((entry) => entry.id === selectedActivityEntityId) ?? null,
132
+ [activityEntities, selectedActivityEntityId],
133
+ )
294
134
 
295
135
  const dealOptions = React.useMemo(
296
- () =>
297
- data
298
- ? [
299
- {
300
- id: data.deal.id,
301
- label:
302
- data.deal.title && data.deal.title.length
303
- ? data.deal.title
304
- : t('customers.deals.detail.untitled', 'Untitled deal'),
305
- },
306
- ]
307
- : [],
136
+ () => data ? [{ id: data.deal.id, label: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal') }] : [],
308
137
  [data, t],
309
138
  )
310
139
 
311
- const entityOptions = React.useMemo(() => {
312
- if (!data) return []
313
- const entries: { id: string; label: string }[] = []
314
- data.people.forEach((person) => {
315
- if (!person.id) return
316
- const suffix = person.subtitle ? ` · ${person.subtitle}` : ''
317
- entries.push({ id: person.id, label: `${person.label}${suffix}` })
140
+ const entityOptions = React.useMemo(
141
+ () => activityEntities.map(({ id, label }) => ({ id, label })),
142
+ [activityEntities],
143
+ )
144
+
145
+ const confirmDiscardIfDirty = React.useCallback(async () => {
146
+ if (!isDirty) return true
147
+ return confirm({
148
+ title: t('customers.deals.detail.unsavedTitle', 'Discard unsaved changes?'),
149
+ description: t(
150
+ 'customers.deals.detail.unsavedDescription',
151
+ 'You have unsaved edits in this deal. Save them first or continue to discard them.',
152
+ ),
153
+ confirmText: t('customers.deals.detail.unsavedConfirm', 'Discard changes'),
154
+ cancelText: t('customers.deals.detail.unsavedCancel', 'Keep editing'),
155
+ variant: 'destructive',
318
156
  })
319
- data.companies.forEach((company) => {
320
- if (!company.id) return
321
- const suffix = company.subtitle ? ` · ${company.subtitle}` : ''
322
- entries.push({ id: company.id, label: `${company.label}${suffix}` })
157
+ }, [confirm, isDirty, t])
158
+
159
+ const {
160
+ peopleEditorIds,
161
+ companiesEditorIds,
162
+ peopleSaving,
163
+ companiesSaving,
164
+ handlePeopleAssociationsChange,
165
+ handleCompaniesAssociationsChange,
166
+ loadLinkedPeoplePage,
167
+ loadLinkedCompaniesPage,
168
+ } = useDealAssociations({
169
+ currentDealId,
170
+ data,
171
+ setData,
172
+ runMutationWithContext,
173
+ })
174
+
175
+ const { isStageSaving, handleStageChange } = useDealPipeline({
176
+ currentDealId,
177
+ data,
178
+ runMutationWithContext,
179
+ confirmDiscardIfDirty,
180
+ onStageChanged: loadData,
181
+ })
182
+
183
+ const {
184
+ lostDialogOpen,
185
+ wonPopupOpen,
186
+ lostPopupOpen,
187
+ wonStats,
188
+ lostStats,
189
+ openLostDialog,
190
+ closeLostDialog,
191
+ closeWonPopup,
192
+ closeLostPopup,
193
+ handleWon,
194
+ handleLostConfirm,
195
+ } = useDealClosure({
196
+ currentDealId,
197
+ runMutationWithContext,
198
+ confirmDiscardIfDirty,
199
+ onClosed: loadData,
200
+ })
201
+
202
+ const handleTabChange = React.useCallback(async (tab: DealTabId) => {
203
+ if (!(await confirmDiscardIfDirty())) return
204
+ setActiveTab(tab)
205
+ const nextParams = new URLSearchParams(searchParams?.toString() ?? '')
206
+ nextParams.set('tab', tab)
207
+ router.replace(`/backend/customers/deals/${encodeURIComponent(id)}?${nextParams.toString()}`, { scroll: false })
208
+ }, [confirmDiscardIfDirty, id, router, searchParams])
209
+
210
+ const { isSaving, handleFormSubmit, handleDelete, handleHeaderSave } = useDealFormHandlers({
211
+ data,
212
+ currentDealId,
213
+ loadData,
214
+ runMutationWithContext,
215
+ formWrapperRef,
216
+ confirm,
217
+ })
218
+
219
+ const handleEditActivity = React.useCallback((activity: InteractionSummary) => {
220
+ if (activity.entityId && activityEntities.some((entry) => entry.id === activity.entityId)) {
221
+ setSelectedActivityEntityId(activity.entityId)
222
+ }
223
+ openScheduleEdit({
224
+ id: activity.id,
225
+ interactionType: activity.interactionType,
226
+ title: activity.title ?? null,
227
+ body: activity.body ?? null,
228
+ scheduledAt: activity.scheduledAt ?? null,
229
+ durationMinutes: activity.duration ?? null,
230
+ location: activity.location ?? null,
231
+ allDay: activity.allDay ?? null,
232
+ recurrenceRule: activity.recurrenceRule ?? null,
233
+ recurrenceEnd: activity.recurrenceEnd ?? null,
234
+ participants: activity.participants ?? null,
235
+ reminderMinutes: activity.reminderMinutes ?? null,
236
+ visibility: activity.visibility ?? null,
237
+ linkedEntities: activity.linkedEntities ?? null,
238
+ guestPermissions: activity.guestPermissions ?? null,
323
239
  })
324
- return entries
325
- }, [data])
326
-
327
- const defaultEntityId = React.useMemo(() => {
328
- if (entityOptions.length) return entityOptions[0].id
329
- return null
330
- }, [entityOptions])
331
-
332
- const tabs = React.useMemo(
333
- () => [
334
- { id: 'notes' as const, label: t('customers.deals.detail.tabs.notes', 'Notes') },
335
- { id: 'activities' as const, label: t('customers.deals.detail.tabs.activities', 'Activities') },
336
- ],
337
- [t],
338
- )
240
+ }, [activityEntities, openScheduleEdit])
241
+
242
+ const handleViewDashboard = React.useCallback(() => {
243
+ closeWonPopup()
244
+ router.push('/backend')
245
+ }, [closeWonPopup, router])
246
+
247
+ const handleBackToPipeline = React.useCallback(() => {
248
+ closeWonPopup()
249
+ closeLostPopup()
250
+ router.push('/backend/customers/deals/pipeline')
251
+ }, [closeLostPopup, closeWonPopup, router])
252
+
253
+ const handleScheduleLostFollowUp = React.useCallback(() => {
254
+ if (!data || !selectedActivityEntity) return
255
+ const nextQuarterDate = startOfNextQuarter(new Date())
256
+ closeLostPopup()
257
+ openScheduleEdit({
258
+ id: '',
259
+ interactionType: 'task',
260
+ title: data.deal.title
261
+ ? t('customers.deals.detail.lost.followUpTitle', 'Revisit {{title}}', { title: data.deal.title })
262
+ : t('customers.deals.detail.lost.followUpFallbackTitle', 'Revisit closed deal'),
263
+ body: data.deal.lossNotes ?? null,
264
+ scheduledAt: nextQuarterDate.toISOString(),
265
+ durationMinutes: 30,
266
+ location: null,
267
+ allDay: false,
268
+ recurrenceRule: null,
269
+ recurrenceEnd: null,
270
+ participants: null,
271
+ reminderMinutes: 1440,
272
+ visibility: 'team',
273
+ linkedEntities: [
274
+ {
275
+ id: data.deal.id,
276
+ type: 'deal',
277
+ label: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal'),
278
+ },
279
+ ],
280
+ })
281
+ }, [closeLostPopup, data, openScheduleEdit, selectedActivityEntity, t])
339
282
 
340
283
  if (isLoading) {
341
284
  return (
342
285
  <Page>
343
286
  <PageBody>
344
- <div className="flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground">
345
- <Spinner className="h-6 w-6" />
346
- <span>{t('customers.deals.detail.loading', 'Loading deal…')}</span>
347
- </div>
287
+ <LoadingMessage label={t('customers.deals.detail.loading', 'Loading deal…')} />
348
288
  </PageBody>
349
289
  </Page>
350
290
  )
@@ -354,338 +294,361 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
354
294
  return (
355
295
  <Page>
356
296
  <PageBody>
357
- <div className="flex h-[50vh] flex-col items-center justify-center gap-3 text-muted-foreground">
358
- <p>{error || t('customers.deals.detail.notFound', 'Deal not found.')}</p>
359
- <Button variant="outline" asChild>
360
- <Link href="/backend/customers/deals">
361
- {t('customers.deals.detail.backToList', 'Back to deals')}
362
- </Link>
363
- </Button>
364
- </div>
297
+ <ErrorMessage
298
+ label={error || t('customers.deals.detail.error.notFound', 'Deal not found.')}
299
+ action={(
300
+ <Button asChild variant="outline">
301
+ <Link href="/backend/customers/deals">
302
+ {t('customers.deals.detail.actions.backToList', 'Back to deals')}
303
+ </Link>
304
+ </Button>
305
+ )}
306
+ />
365
307
  </PageBody>
366
308
  </Page>
367
309
  )
368
310
  }
369
311
 
370
- const probabilityLabel = data.deal.probability !== null && data.deal.probability !== undefined
371
- ? `${data.deal.probability}%`
372
- : t('customers.deals.detail.noValue', 'Not provided')
373
- const valueLabel =
374
- formatCurrency(data.deal.valueAmount, data.deal.valueCurrency) ??
375
- t('customers.deals.detail.noValue', 'Not provided')
376
- const expectedCloseLabel = formatDate(data.deal.expectedCloseAt, t('customers.deals.detail.noValue', 'Not provided'))
377
- const statusLabel =
378
- resolveDictionaryLabel(data.deal.status, statusDictionaryMap) ??
379
- t('customers.deals.detail.noStatus', 'No status')
380
- const statusDictEntry = data.deal.status ? statusDictionaryMap?.[data.deal.status] ?? null : null
381
- const pipelineLabel = resolveDictionaryLabel(data.deal.pipelineStage, pipelineDictionaryMap)
382
- const pipelineDictEntry = data.deal.pipelineStage ? pipelineDictionaryMap?.[data.deal.pipelineStage] ?? null : null
383
- const previewValueAmount = formatCurrency(data.deal.valueAmount, data.deal.valueCurrency)
384
- const previewProbability = data.deal.probability !== null && data.deal.probability !== undefined
385
- ? `${data.deal.probability}%`
386
- : null
387
- const dealPreviewMetadata: Record<string, string> = {}
388
- if (previewValueAmount) dealPreviewMetadata[t('customers.deals.detail.fields.value')] = previewValueAmount
389
- if (previewProbability) dealPreviewMetadata[t('customers.deals.detail.fields.probability')] = previewProbability
390
-
391
- const peopleSummaryLabel =
392
- data.people.length === 1
393
- ? t('customers.deals.detail.peopleSummaryOne')
394
- : t('customers.deals.detail.peopleSummaryMany', undefined, { count: data.people.length })
395
- const companiesSummaryLabel =
396
- data.companies.length === 1
397
- ? t('customers.deals.detail.companiesSummaryOne')
398
- : t('customers.deals.detail.companiesSummaryMany', undefined, { count: data.companies.length })
399
-
400
- const viewer = data.viewer ?? null
312
+ const amountLabel = formatCurrency(data.deal.valueAmount, data.deal.valueCurrency)
313
+ const currentPipelineName = data.pipelineName ?? wonStats?.pipelineName ?? lostStats?.pipelineName ?? null
314
+ const dealName = data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')
401
315
 
402
- return (
403
- <Page>
404
- <PageBody>
405
- <div className="flex flex-col gap-6">
406
- <FormHeader
407
- mode="detail"
408
- backHref="/backend/customers/deals"
409
- backLabel={t('customers.deals.detail.backToList', 'Back to deals')}
410
- utilityActions={(
411
- <>
412
- <SendObjectMessageDialog
413
- object={{
414
- entityModule: 'customers',
415
- entityType: 'deal',
416
- entityId: data.deal.id,
417
- sourceEntityType: 'customers.deal',
418
- sourceEntityId: data.deal.id,
419
- previewData: {
420
- title: data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal'),
421
- status: data.deal.status ? statusLabel : undefined,
422
- metadata: Object.keys(dealPreviewMetadata).length > 0 ? dealPreviewMetadata : undefined,
423
- },
424
- }}
425
- viewHref={`/backend/customers/deals/${data.deal.id}`}
426
- defaultValues={{
427
- sourceEntityType: 'customers.deal',
428
- sourceEntityId: data.deal.id,
429
- }}
430
- />
431
- <VersionHistoryAction
432
- config={{ resourceKind: 'customers.deal', resourceId: data.deal.id }}
433
- t={t}
434
- />
435
- </>
436
- )}
437
- title={
438
- <div className="flex flex-wrap items-center gap-2">
439
- <span>{data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}</span>
440
- <Button
441
- type="button"
442
- variant="ghost"
443
- size="sm"
444
- className="gap-1 text-muted-foreground hover:text-foreground"
445
- onClick={scrollToDealSettings}
446
- >
447
- <Pencil className="h-4 w-4" aria-hidden />
448
- <MousePointerClick className="h-4 w-4" aria-hidden />
449
- <span>{t('customers.deals.detail.goToSettings', 'Edit deal details')}</span>
450
- </Button>
451
- </div>
452
- }
453
- subtitle={t('customers.deals.detail.summary', undefined, {
454
- status: statusLabel,
455
- pipeline: pipelineLabel ?? t('customers.deals.detail.noPipeline', 'No pipeline'),
456
- })}
457
- onDelete={handleDelete}
458
- isDeleting={isDeleting}
459
- deleteLabel={t('ui.actions.delete', 'Delete')}
460
- />
461
- <div className="grid gap-6 lg:grid-cols-[minmax(0,2fr),minmax(0,1.1fr)]">
462
- <div className="space-y-6">
463
- <div className="rounded-lg border bg-card p-4">
464
- <h2 className="mb-4 text-sm font-semibold uppercase text-muted-foreground">
465
- {t('customers.deals.detail.highlights', 'Highlights')}
466
- </h2>
467
- <div className="grid gap-4 sm:grid-cols-2">
468
- <div>
469
- <p className="text-xs font-medium uppercase text-muted-foreground">
470
- {t('customers.deals.detail.fields.value', 'Deal value')}
471
- </p>
472
- <p className="text-base font-semibold text-foreground">{valueLabel}</p>
473
- </div>
474
- <div>
475
- <p className="text-xs font-medium uppercase text-muted-foreground">
476
- {t('customers.deals.detail.fields.probability', 'Probability')}
477
- </p>
478
- <p className="text-base font-semibold text-foreground">{probabilityLabel}</p>
479
- </div>
480
- <div>
481
- <p className="text-xs font-medium uppercase text-muted-foreground">
482
- {t('customers.deals.detail.fields.status', 'Status')}
483
- </p>
484
- <p className="text-base text-foreground flex items-center gap-2">
485
- {statusDictEntry?.color ? renderDictionaryColor(statusDictEntry.color) : null}
486
- {statusDictEntry?.icon ? renderDictionaryIcon(statusDictEntry.icon) : null}
487
- {statusLabel}
488
- </p>
489
- </div>
490
- <div>
491
- <p className="text-xs font-medium uppercase text-muted-foreground">
492
- {t('customers.deals.detail.fields.pipeline', 'Pipeline stage')}
493
- </p>
494
- <p className="text-base text-foreground flex items-center gap-2">
495
- {pipelineDictEntry?.color ? renderDictionaryColor(pipelineDictEntry.color) : null}
496
- {pipelineDictEntry?.icon ? renderDictionaryIcon(pipelineDictEntry.icon) : null}
497
- {pipelineLabel ?? t('customers.deals.detail.noValue', 'Not provided')}
498
- </p>
499
- </div>
500
- <div>
501
- <p className="text-xs font-medium uppercase text-muted-foreground">
502
- {t('customers.deals.detail.fields.expectedClose', 'Expected close')}
503
- </p>
504
- <p className="text-base text-foreground">{expectedCloseLabel}</p>
505
- </div>
506
- </div>
316
+ const zone1Content = (
317
+ <div ref={formWrapperRef}>
318
+ <DealForm
319
+ mode="edit"
320
+ embedded
321
+ trackDirtyWhenEmbedded
322
+ hideFooterActions
323
+ singleColumnGroups
324
+ showAssociationsGroup={false}
325
+ showVersionHistory={false}
326
+ showCancelAction={false}
327
+ onDirtyChange={setIsDirty}
328
+ collapsibleGroups={{ pageType: 'deal-detail-v3', chevronPosition: 'left' }}
329
+ sortableGroups={{ pageType: 'deal-detail-v3' }}
330
+ initialValues={{
331
+ ...data.deal,
332
+ valueAmount:
333
+ typeof data.deal.valueAmount === 'string' && data.deal.valueAmount.trim().length
334
+ ? Number(data.deal.valueAmount)
335
+ : null,
336
+ personIds: data.linkedPersonIds,
337
+ companyIds: data.linkedCompanyIds,
338
+ customFields: data.customFields,
339
+ ...Object.fromEntries(Object.entries(data.customFields ?? {}).map(([key, value]) => [`cf_${key}`, value])),
340
+ }}
341
+ onSubmit={handleFormSubmit}
342
+ onCancel={() => { void loadData() }}
343
+ onDelete={handleDelete}
344
+ />
345
+ </div>
346
+ )
347
+
348
+ const zone2Content = (
349
+ <div className="rounded-[10px] border border-border bg-card px-5 py-5">
350
+ {(() => {
351
+ const injected = injectedTabMap.get(activeTab)
352
+ if (injected) return injected()
353
+
354
+ if (activeTab === 'activities') {
355
+ const activityEntitySelection = activityEntities.length > 1 ? (
356
+ <div className="rounded-[10px] border border-border bg-muted/20 px-5 py-5">
357
+ <label htmlFor="deal-activity-entity" className="text-sm font-semibold text-foreground">
358
+ {t('customers.deals.detail.activities.selectEntityLabel', 'Choose customer record')}
359
+ </label>
360
+ <div className="mt-1 text-sm text-muted-foreground">
361
+ {t(
362
+ 'customers.deals.detail.activities.selectEntityDescription',
363
+ 'Pick the person or company that should own new deal activities and follow-ups.',
364
+ )}
507
365
  </div>
366
+ <select
367
+ id="deal-activity-entity"
368
+ aria-label={t('customers.deals.detail.activities.selectEntityLabel', 'Choose customer record')}
369
+ className="mt-4 h-9 w-full rounded border border-muted-foreground/40 bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
370
+ value={selectedActivityEntityId ?? ''}
371
+ onChange={(event) => setSelectedActivityEntityId(event.target.value || null)}
372
+ >
373
+ <option value="">
374
+ {t('customers.deals.detail.activities.selectEntityPlaceholder', 'Select a person or company')}
375
+ </option>
376
+ {activityEntities.map((entry) => (
377
+ <option key={entry.id} value={entry.id}>
378
+ {entry.label}
379
+ </option>
380
+ ))}
381
+ </select>
382
+ </div>
383
+ ) : null
508
384
 
509
- <div className="rounded-lg border bg-card p-4">
510
- <div className="flex flex-wrap items-center justify-between gap-3">
511
- <div className="flex gap-2">
512
- {tabs.map((tab) => (
513
- <Button
514
- key={tab.id}
515
- variant="ghost"
516
- size="sm"
517
- onClick={() => setActiveTab(tab.id)}
518
- className={`h-auto rounded-none border-b-2 px-0 py-1 ${
519
- activeTab === tab.id
520
- ? 'border-primary text-foreground'
521
- : 'border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent'
522
- }`}
523
- >
524
- {tab.label}
525
- </Button>
526
- ))}
385
+ return (
386
+ <div className="space-y-4">
387
+ {activityEntities.length > 1 ? activityEntitySelection : null}
388
+ {activityEntities.length === 0 ? (
389
+ <div className="rounded-[10px] border border-border bg-muted/20 px-5 py-5">
390
+ <div className="text-sm font-semibold text-foreground">
391
+ {t('customers.deals.detail.activities.linkEntityTitle', 'Link a person or company first')}
527
392
  </div>
528
- {sectionAction ? (
529
- <Button
530
- type="button"
531
- size="sm"
532
- disabled={sectionAction.disabled}
533
- onClick={handleSectionAction}
534
- >
535
- {sectionAction.icon ?? null}
536
- {sectionAction.label}
393
+ <div className="mt-1 text-sm text-muted-foreground">
394
+ {t('customers.deals.detail.activities.linkEntityDescription', 'Activities on a deal still need a customer record for timeline ownership.')}
395
+ </div>
396
+ <div className="mt-4 flex gap-2">
397
+ <Button type="button" variant="outline" size="sm" onClick={() => handleTabChange('people')}>
398
+ {t('customers.deals.detail.tabs.people', 'People')}
399
+ </Button>
400
+ <Button type="button" variant="outline" size="sm" onClick={() => handleTabChange('companies')}>
401
+ {t('customers.deals.detail.tabs.companies', 'Companies')}
537
402
  </Button>
538
- ) : null}
539
- </div>
540
- {activeTab === 'notes' ? (
541
- <NotesSection
542
- entityId={null}
543
- dealId={data.deal.id}
544
- dealOptions={dealOptions}
545
- entityOptions={entityOptions}
546
- emptyLabel={t('customers.deals.detail.notesEmpty', 'No notes yet.')}
547
- viewerUserId={viewer?.userId ?? null}
548
- viewerName={viewer?.name ?? null}
549
- viewerEmail={viewer?.email ?? null}
550
- addActionLabel={t('customers.deals.detail.notesAdd', 'Add note')}
551
- emptyState={{
552
- title: t('customers.deals.detail.notesEmptyTitle', 'Keep everyone in the loop'),
553
- actionLabel: t('customers.deals.detail.notesEmptyAction', 'Add a note'),
554
- }}
555
- onActionChange={setSectionAction}
556
- translator={detailTranslator}
557
- onLoadingChange={handleNotesLoadingChange}
558
- dataAdapter={notesAdapter}
559
- renderIcon={renderDictionaryIcon}
560
- renderColor={renderDictionaryColor}
561
- iconSuggestions={ICON_SUGGESTIONS}
562
- readMarkdownPreference={readMarkdownPreferenceCookie}
563
- writeMarkdownPreference={writeMarkdownPreferenceCookie}
564
- />
565
- ) : null}
566
- {activeTab === 'activities' ? (
567
- <ActivitiesSection
568
- entityId={null}
569
- dealId={data.deal.id}
570
- dealOptions={dealOptions}
571
- entityOptions={entityOptions}
572
- defaultEntityId={defaultEntityId ?? undefined}
573
- addActionLabel={t('customers.deals.detail.activitiesAdd', 'Log activity')}
574
- emptyState={{
575
- title: t('customers.deals.detail.activitiesEmptyTitle', 'No activities yet'),
576
- actionLabel: t('customers.deals.detail.activitiesEmptyAction', 'Add an activity'),
577
- }}
578
- onActionChange={setSectionAction}
579
- onLoadingChange={handleActivitiesLoadingChange}
580
- />
581
- ) : null}
582
- </div>
583
-
584
- <div className="grid gap-4 md:grid-cols-2">
585
- <div className="rounded-lg border bg-card p-4">
586
- <div className="mb-3 space-y-1">
587
- <h3 className="text-sm font-semibold text-foreground">
588
- {t('customers.deals.detail.peopleSection', 'People')}
589
- </h3>
590
- <p className="text-xs text-muted-foreground">
591
- {peopleSummaryLabel}
592
- </p>
593
403
  </div>
594
- {data.people.length ? (
595
- <ul className="space-y-2 text-sm">
596
- {data.people.map((person) => (
597
- <li key={person.id} className="flex flex-col gap-1">
598
- <Link href={`/backend/customers/people-v2/${encodeURIComponent(person.id)}`} className="font-medium text-foreground hover:underline">
599
- {person.label}
600
- </Link>
601
- <span className="text-xs text-muted-foreground">
602
- {person.subtitle ?? t('customers.deals.detail.peopleNoDetails', 'No additional details')}
603
- </span>
604
- </li>
605
- ))}
606
- </ul>
607
- ) : (
608
- <p className="text-sm text-muted-foreground">
609
- {t('customers.deals.detail.noPeople', 'No people linked to this deal yet.')}
610
- </p>
611
- )}
612
404
  </div>
613
- <div className="rounded-lg border bg-card p-4">
614
- <div className="mb-3 space-y-1">
615
- <h3 className="text-sm font-semibold text-foreground">
616
- {t('customers.deals.detail.companiesSection', 'Companies')}
617
- </h3>
618
- <p className="text-xs text-muted-foreground">
619
- {companiesSummaryLabel}
620
- </p>
405
+ ) : selectedActivityEntity ? (
406
+ <InlineActivityComposer
407
+ entityType={selectedActivityEntity.kind}
408
+ entityId={selectedActivityEntity.id}
409
+ dealId={data.deal.id}
410
+ onActivityCreated={() => { void handleActivityCreated() }}
411
+ runGuardedMutation={runMutationWithContext}
412
+ onScheduleRequested={openSchedule}
413
+ />
414
+ ) : (
415
+ <div className="rounded-[10px] border border-dashed border-border bg-muted/10 px-5 py-5">
416
+ <div className="text-sm font-semibold text-foreground">
417
+ {t('customers.deals.detail.activities.selectEntityRequiredTitle', 'Choose a person or company to continue')}
418
+ </div>
419
+ <div className="mt-1 text-sm text-muted-foreground">
420
+ {t(
421
+ 'customers.deals.detail.activities.selectEntityRequiredDescription',
422
+ 'Select the customer record that should receive new deal activities before logging or scheduling anything.',
423
+ )}
621
424
  </div>
622
- {data.companies.length ? (
623
- <ul className="space-y-2 text-sm">
624
- {data.companies.map((company) => (
625
- <li key={company.id} className="flex flex-col gap-1">
626
- <Link href={`/backend/customers/companies-v2/${encodeURIComponent(company.id)}`} className="font-medium text-foreground hover:underline">
627
- {company.label}
628
- </Link>
629
- <span className="text-xs text-muted-foreground">
630
- {company.subtitle ?? t('customers.deals.detail.companiesNoDetails', 'No additional details')}
631
- </span>
632
- </li>
633
- ))}
634
- </ul>
635
- ) : (
636
- <p className="text-sm text-muted-foreground">
637
- {t('customers.deals.detail.noCompanies', 'No companies linked to this deal yet.')}
638
- </p>
639
- )}
640
425
  </div>
641
- </div>
642
- </div>
643
-
644
- <div className="space-y-6">
645
- <div
646
- ref={dealSettingsRef}
647
- id="deal-settings"
648
- className="rounded-lg border bg-card p-4"
649
- >
650
- <h2 className="mb-4 text-sm font-semibold uppercase text-muted-foreground">
651
- {t('customers.deals.detail.formTitle', 'Deal settings')}
652
- </h2>
653
- <DealForm
654
- key={data.deal.updatedAt}
655
- mode="edit"
656
- initialValues={{
657
- id: data.deal.id,
658
- title: data.deal.title ?? '',
659
- status: data.deal.status ?? '',
660
- pipelineStage: data.deal.pipelineStage ?? '',
661
- pipelineId: data.deal.pipelineId ?? '',
662
- pipelineStageId: data.deal.pipelineStageId ?? '',
663
- valueAmount: data.deal.valueAmount ? Number(data.deal.valueAmount) : null,
664
- valueCurrency: data.deal.valueCurrency ?? undefined,
665
- probability: data.deal.probability ?? null,
666
- expectedCloseAt: data.deal.expectedCloseAt ?? null,
667
- description: data.deal.description ?? '',
668
- personIds: data.people.map((person) => person.id),
669
- companyIds: data.companies.map((company) => company.id),
670
- people: data.people.map((person) => ({ id: person.id, label: person.label })),
671
- companies: data.companies.map((company) => ({ id: company.id, label: company.label })),
672
- ...Object.fromEntries(
673
- Object.entries(data.customFields)
674
- .filter(([key]) => key.startsWith('cf_'))
675
- .map(([key, value]) => [key, value]),
676
- ),
426
+ )}
427
+ <PlannedActivitiesSection
428
+ activities={plannedActivities}
429
+ onComplete={(interactionId) => { void handleMarkDone(interactionId) }}
430
+ onSchedule={selectedActivityEntity ? openSchedule : undefined}
431
+ onEdit={handleEditActivity}
432
+ onCancel={(interactionId) => { void handleCancelActivity(interactionId) }}
433
+ />
434
+ {selectedActivityEntity ? (
435
+ <ActivitiesSection
436
+ entityId={selectedActivityEntity.id}
437
+ entityName={selectedActivityEntity.label}
438
+ dealId={data.deal.id}
439
+ dealOptions={dealOptions}
440
+ entityOptions={entityOptions}
441
+ defaultEntityId={selectedActivityEntity.id}
442
+ addActionLabel={t('customers.deals.detail.activitiesAdd', 'Log activity')}
443
+ emptyState={{
444
+ title: t('customers.deals.detail.activitiesEmptyTitle', 'No activities yet'),
445
+ actionLabel: t('customers.deals.detail.activitiesEmptyAction', 'Log activity'),
677
446
  }}
678
- onSubmit={handleFormSubmit}
679
- onCancel={() => setReloadToken((token) => token + 1)}
680
- onDelete={handleDelete}
681
- isSubmitting={isSaving || isDeleting}
447
+ runGuardedMutation={runMutationWithContext}
448
+ onDataRefresh={() => { void handleActivityCreated() }}
449
+ refreshKey={activityRefreshKey}
450
+ onEditActivity={handleEditActivity}
682
451
  />
683
- </div>
452
+ ) : null}
684
453
  </div>
685
- </div>
454
+ )
455
+ }
456
+
457
+ if (activeTab === 'people') {
458
+ return (
459
+ <DealLinkedEntitiesTab
460
+ entityLabel={t('customers.deals.detail.tabs.peopleSingular', 'Person')}
461
+ entityLabelPlural={t('customers.deals.detail.tabs.people', 'People')}
462
+ manageLabel={t('customers.deals.detail.peopleEditorTitle', 'Manage linked people')}
463
+ searchPlaceholder={t('customers.deals.detail.peopleSearch', 'Search linked people…')}
464
+ linkedItems={data.people}
465
+ linkedCount={data.counts.people}
466
+ selectedIds={peopleEditorIds}
467
+ disabled={peopleSaving || isSaving}
468
+ savePending={peopleSaving}
469
+ hrefBuilder={(personId) => `/backend/customers/people-v2/${encodeURIComponent(personId)}`}
470
+ onSaveSelection={(next) => handlePeopleAssociationsChange(next)}
471
+ loadLinkedPage={loadLinkedPeoplePage}
472
+ searchEntities={searchPeoplePage}
473
+ fetchEntitiesByIds={fetchPeopleByIds}
474
+ icon={<Users className="size-4" />}
475
+ />
476
+ )
477
+ }
478
+
479
+ if (activeTab === 'companies') {
480
+ return (
481
+ <DealLinkedEntitiesTab
482
+ entityLabel={t('customers.deals.detail.tabs.companySingular', 'Company')}
483
+ entityLabelPlural={t('customers.deals.detail.tabs.companies', 'Companies')}
484
+ manageLabel={t('customers.deals.detail.companiesEditorTitle', 'Manage linked companies')}
485
+ searchPlaceholder={t('customers.deals.detail.companiesSearch', 'Search linked companies…')}
486
+ linkedItems={data.companies}
487
+ linkedCount={data.counts.companies}
488
+ selectedIds={companiesEditorIds}
489
+ disabled={companiesSaving || isSaving}
490
+ savePending={companiesSaving}
491
+ hrefBuilder={(companyId) => `/backend/customers/companies-v2/${encodeURIComponent(companyId)}`}
492
+ onSaveSelection={(next) => handleCompaniesAssociationsChange(next)}
493
+ loadLinkedPage={loadLinkedCompaniesPage}
494
+ searchEntities={searchCompaniesPage}
495
+ fetchEntitiesByIds={fetchCompaniesByIds}
496
+ icon={<Building2 className="size-4" />}
497
+ />
498
+ )
499
+ }
500
+
501
+ if (activeTab === 'notes') {
502
+ return (
503
+ <NotesSection
504
+ entityId={null}
505
+ dealId={data.deal.id}
506
+ dealOptions={dealOptions}
507
+ entityOptions={entityOptions}
508
+ emptyLabel={t('customers.deals.detail.notesEmpty', 'No notes yet.')}
509
+ viewerUserId={data.viewer?.userId ?? null}
510
+ viewerName={data.viewer?.name ?? null}
511
+ viewerEmail={data.viewer?.email ?? null}
512
+ addActionLabel={t('customers.deals.detail.notesAdd', 'Add note')}
513
+ emptyState={{
514
+ title: t('customers.deals.detail.notesEmptyTitle', 'Keep everyone in the loop'),
515
+ actionLabel: t('customers.deals.detail.notesEmptyAction', 'Add a note'),
516
+ }}
517
+ translator={detailTranslator}
518
+ dataAdapter={notesAdapter}
519
+ renderIcon={renderDictionaryIcon}
520
+ renderColor={renderDictionaryColor}
521
+ iconSuggestions={ICON_SUGGESTIONS}
522
+ readMarkdownPreference={readMarkdownPreferenceCookie}
523
+ writeMarkdownPreference={writeMarkdownPreferenceCookie}
524
+ />
525
+ )
526
+ }
527
+
528
+ if (activeTab === 'files') {
529
+ return (
530
+ <AttachmentsSection
531
+ entityId={E.customers.customer_deal}
532
+ recordId={data.deal.id}
533
+ title={t('customers.deals.detail.tabs.files', 'Files')}
534
+ description={t('customers.deals.detail.files.subtitle', 'Upload and manage files linked to this deal.')}
535
+ />
536
+ )
537
+ }
538
+
539
+ if (activeTab === 'changelog') {
540
+ return <ChangelogTab entityId={data.deal.id} entityType="deal" />
541
+ }
542
+
543
+ return null
544
+ })()}
545
+ </div>
546
+ )
547
+
548
+ return (
549
+ <Page>
550
+ <PageBody>
551
+ <div className="space-y-6">
552
+ <InjectionSpot spotId="detail:customers.deal:header" context={injectionContext} data={data} />
553
+
554
+ <DealDetailHeader
555
+ deal={data.deal}
556
+ owner={data.owner}
557
+ people={data.people}
558
+ companies={data.companies}
559
+ pipelineName={currentPipelineName}
560
+ stageOptions={data.pipelineStages}
561
+ currentStageId={data.deal.pipelineStageId}
562
+ onStageChange={handleStageChange}
563
+ isStageSaving={isStageSaving}
564
+ onSave={handleHeaderSave}
565
+ onDelete={handleDelete}
566
+ isDirty={isDirty}
567
+ isSaving={isSaving}
568
+ />
569
+
570
+ <InjectionSpot spotId="detail:customers.deal:status-badges" context={injectionContext} data={data} />
571
+
572
+ <PipelineStepper
573
+ stages={data.pipelineStages}
574
+ transitions={data.stageTransitions}
575
+ currentStageId={data.deal.pipelineStageId}
576
+ pipelineName={currentPipelineName}
577
+ closureOutcome={data.deal.closureOutcome}
578
+ footer={data.deal.closureOutcome ? null : (
579
+ <DealClosureActionBar
580
+ embedded
581
+ closureOutcome={data.deal.closureOutcome}
582
+ onWon={() => { void handleWon() }}
583
+ onLost={openLostDialog}
584
+ />
585
+ )}
586
+ />
587
+
588
+ <DealDetailTabs
589
+ activeTab={activeTab}
590
+ onTabChange={handleTabChange}
591
+ injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}
592
+ peopleCount={data.counts.people}
593
+ companiesCount={data.counts.companies}
594
+ >
595
+ <CollapsibleZoneLayout
596
+ pageType="deal-detail-v3"
597
+ entityName={dealName}
598
+ isDirty={isDirty}
599
+ zone1DefaultWidth="540px"
600
+ zone1={zone1Content}
601
+ zone2={zone2Content}
602
+ />
603
+ </DealDetailTabs>
604
+
605
+ <InjectionSpot spotId="detail:customers.deal:footer" context={injectionContext} data={data} />
686
606
  </div>
607
+
608
+ {ConfirmDialogElement}
609
+
610
+ {selectedActivityEntity ? (
611
+ <ScheduleActivityDialog
612
+ open={scheduleDialogOpen}
613
+ onClose={closeSchedule}
614
+ entityId={selectedActivityEntity.id}
615
+ dealId={data.deal.id}
616
+ entityType={selectedActivityEntity.kind}
617
+ entityName={selectedActivityEntity.label}
618
+ companyName={selectedActivityEntity.kind === 'company' ? selectedActivityEntity.label : data.companies[0]?.label ?? null}
619
+ onActivityCreated={() => { void handleActivityCreated() }}
620
+ editData={scheduleEditData}
621
+ />
622
+ ) : null}
623
+
624
+ <ConfirmDealLostDialog
625
+ open={lostDialogOpen}
626
+ onClose={closeLostDialog}
627
+ dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}
628
+ dealValue={amountLabel}
629
+ companyName={data.companies[0]?.label ?? null}
630
+ onConfirm={handleLostConfirm}
631
+ />
632
+
633
+ <DealWonPopup
634
+ open={wonPopupOpen}
635
+ onClose={closeWonPopup}
636
+ dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}
637
+ stats={wonStats}
638
+ onViewDashboard={handleViewDashboard}
639
+ onBackToPipeline={handleBackToPipeline}
640
+ />
641
+
642
+ <DealLostSummaryDialog
643
+ open={lostPopupOpen}
644
+ onClose={closeLostPopup}
645
+ dealTitle={data.deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}
646
+ lossNotes={data.deal.lossNotes}
647
+ stats={lostStats}
648
+ onBackToPipeline={handleBackToPipeline}
649
+ onScheduleFollowUp={selectedActivityEntity ? handleScheduleLostFollowUp : undefined}
650
+ />
687
651
  </PageBody>
688
- {ConfirmDialogElement}
689
652
  </Page>
690
653
  )
691
654
  }