@open-mercato/core 0.9.0 → 0.9.1-develop.7459.1.99e3accd30

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 (243) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
  3. package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
  4. package/dist/generated/entities/customer_interaction/index.js +2 -0
  5. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/commands/categories.js +1 -1
  9. package/dist/modules/catalog/commands/categories.js.map +2 -2
  10. package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
  11. package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
  12. package/dist/modules/catalog/commands/products.js +52 -17
  13. package/dist/modules/catalog/commands/products.js.map +2 -2
  14. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
  15. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  16. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
  17. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
  18. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
  19. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
  20. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
  21. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  22. package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
  23. package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
  24. package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
  25. package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
  26. package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
  27. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
  28. package/dist/modules/customers/api/activity-types/route.js +151 -0
  29. package/dist/modules/customers/api/activity-types/route.js.map +7 -0
  30. package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
  31. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  32. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
  33. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  34. package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
  35. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  36. package/dist/modules/customers/api/interactions/route.js +3 -0
  37. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  38. package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
  39. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  40. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
  41. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
  42. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
  43. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  44. package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
  45. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  46. package/dist/modules/customers/calendar-event-types.js +548 -0
  47. package/dist/modules/customers/calendar-event-types.js.map +7 -0
  48. package/dist/modules/customers/commands/dictionaries.js +70 -10
  49. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +187 -22
  51. package/dist/modules/customers/commands/interactions.js.map +3 -3
  52. package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
  53. package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
  54. package/dist/modules/customers/components/DictionarySettings.js +3 -1
  55. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  56. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
  57. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
  58. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
  59. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  60. package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
  61. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
  63. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
  65. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
  66. package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
  67. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  68. package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
  69. package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
  70. package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
  71. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
  72. package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
  73. package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
  74. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
  75. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  76. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
  77. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
  78. package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
  79. package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
  80. package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
  81. package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
  82. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
  83. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
  85. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/types.js +1 -0
  87. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  88. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
  89. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  90. package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
  91. package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
  92. package/dist/modules/customers/components/detail/DealForm.js +7 -3
  93. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  94. package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
  95. package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
  96. package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
  97. package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
  98. package/dist/modules/customers/components/detail/PersonCard.js +5 -2
  99. package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
  100. package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
  101. package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
  102. package/dist/modules/customers/data/entities.js +6 -0
  103. package/dist/modules/customers/data/entities.js.map +2 -2
  104. package/dist/modules/customers/data/validators.js +39 -10
  105. package/dist/modules/customers/data/validators.js.map +2 -2
  106. package/dist/modules/customers/di.js +20 -1
  107. package/dist/modules/customers/di.js.map +2 -2
  108. package/dist/modules/customers/extension-points.js +13 -0
  109. package/dist/modules/customers/extension-points.js.map +2 -2
  110. package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
  111. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  112. package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
  113. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  114. package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
  115. package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
  117. package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
  119. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  120. package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
  121. package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
  122. package/dist/modules/customers/lib/calendar/timezone.js +111 -0
  123. package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
  124. package/dist/modules/customers/lib/interactionReadModel.js +1 -0
  125. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  126. package/dist/modules/customers/lib/todoCompatibility.js +1 -0
  127. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  128. package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
  129. package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
  130. package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
  131. package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
  132. package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
  133. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  134. package/dist/modules/messages/subscribers/message-notification.js +13 -1
  135. package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
  136. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
  137. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  138. package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
  139. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  140. package/dist/modules/planner/lib/availabilityMerge.js +101 -38
  141. package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
  142. package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
  143. package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
  144. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  145. package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
  146. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  147. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
  148. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  149. package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
  150. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  151. package/generated/entities/customer_dictionary_entry/index.ts +1 -0
  152. package/generated/entities/customer_interaction/index.ts +1 -0
  153. package/generated/entity-fields-registry.ts +2 -0
  154. package/package.json +9 -8
  155. package/src/modules/auth/i18n/de.json +3 -0
  156. package/src/modules/auth/i18n/en.json +3 -0
  157. package/src/modules/auth/i18n/es.json +3 -0
  158. package/src/modules/auth/i18n/ko.json +3 -0
  159. package/src/modules/auth/i18n/pl.json +3 -0
  160. package/src/modules/catalog/commands/categories.ts +1 -1
  161. package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
  162. package/src/modules/catalog/commands/products.ts +58 -17
  163. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
  164. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
  165. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
  166. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
  167. package/src/modules/communication_channels/i18n/de.json +4 -0
  168. package/src/modules/communication_channels/i18n/en.json +4 -0
  169. package/src/modules/communication_channels/i18n/es.json +4 -0
  170. package/src/modules/communication_channels/i18n/ko.json +4 -0
  171. package/src/modules/communication_channels/i18n/pl.json +4 -0
  172. package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
  173. package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
  174. package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
  175. package/src/modules/customers/api/activity-types/route.ts +154 -0
  176. package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
  177. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
  178. package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
  179. package/src/modules/customers/api/interactions/route.ts +4 -0
  180. package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
  181. package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
  182. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
  183. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
  184. package/src/modules/customers/calendar-event-types.ts +674 -0
  185. package/src/modules/customers/commands/dictionaries.ts +84 -8
  186. package/src/modules/customers/commands/interactions.ts +244 -21
  187. package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
  188. package/src/modules/customers/components/DictionarySettings.tsx +5 -3
  189. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
  190. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
  191. package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
  192. package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
  193. package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
  194. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
  195. package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
  196. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
  197. package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
  198. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
  199. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
  200. package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
  201. package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
  202. package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
  203. package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
  204. package/src/modules/customers/components/calendar/types.ts +1 -0
  205. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
  206. package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
  207. package/src/modules/customers/components/detail/DealForm.tsx +7 -3
  208. package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
  209. package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
  210. package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
  211. package/src/modules/customers/components/detail/types.ts +1 -0
  212. package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
  213. package/src/modules/customers/data/entities.ts +8 -1
  214. package/src/modules/customers/data/validators.ts +42 -7
  215. package/src/modules/customers/di.ts +21 -0
  216. package/src/modules/customers/extension-points.ts +13 -0
  217. package/src/modules/customers/i18n/de.json +104 -8
  218. package/src/modules/customers/i18n/en.json +104 -8
  219. package/src/modules/customers/i18n/es.json +104 -8
  220. package/src/modules/customers/i18n/ko.json +104 -8
  221. package/src/modules/customers/i18n/pl.json +104 -8
  222. package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
  223. package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
  224. package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
  225. package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
  226. package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
  227. package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
  228. package/src/modules/customers/lib/calendar/timezone.ts +117 -0
  229. package/src/modules/customers/lib/interactionReadModel.ts +1 -0
  230. package/src/modules/customers/lib/todoCompatibility.ts +2 -0
  231. package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
  232. package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
  233. package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
  234. package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
  235. package/src/modules/messages/subscribers/message-notification.ts +13 -3
  236. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
  237. package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
  238. package/src/modules/planner/lib/availabilityMerge.ts +106 -41
  239. package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
  240. package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
  241. package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
  242. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
  243. package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
@@ -1,28 +1,19 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Users, Link2, Plus, Filter } from "lucide-react";
5
- import { EmptyState } from "@open-mercato/ui/backend/EmptyState";
6
- import { Button } from "@open-mercato/ui/primitives/button";
7
- import { Badge } from "@open-mercato/ui/primitives/badge";
8
4
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
9
5
  import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
10
- import {
11
- readVersionedIdSet,
12
- writeVersionedIdSet
13
- } from "@open-mercato/shared/lib/browser/versionedPreference";
14
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
15
7
  import { createTranslatorWithFallback } from "@open-mercato/shared/lib/i18n/translate";
16
8
  import { useAppEvent } from "@open-mercato/ui/backend/injection/useAppEvent";
17
9
  import { CreatePersonDialog } from "./CreatePersonDialog.js";
18
- import { PersonCard } from "./PersonCard.js";
19
10
  import { coerceDisplayName } from "../../lib/displayName.js";
20
11
  import { DecisionMakersFooter } from "./DecisionMakersFooter.js";
21
12
  import { RolesSection } from "./RolesSection.js";
22
- import { LinkEntityDialog } from "../linking/LinkEntityDialog.js";
13
+ import {
14
+ LinkedPeopleSection
15
+ } from "./LinkedPeopleSection.js";
23
16
  import { createPersonLinkAdapter } from "../linking/adapters/personAdapter.js";
24
- const COMPANY_PEOPLE_PAGE_SIZE = 20;
25
- const STARRED_PEOPLE_STORAGE_VERSION = 1;
26
17
  function normalizeCompanyPerson(record) {
27
18
  const id = typeof record.id === "string" ? record.id : null;
28
19
  if (!id) return null;
@@ -95,21 +86,13 @@ function CompanyPeopleSection({
95
86
  );
96
87
  const translate = translator ?? fallbackTranslator;
97
88
  const [people, setPeople] = React.useState(initialPeople);
98
- const [removingId, setRemovingId] = React.useState(null);
99
- const [linkDialogOpen, setLinkDialogOpen] = React.useState(false);
100
89
  const [createDialogOpen, setCreateDialogOpen] = React.useState(false);
101
- const [searchQuery, setSearchQuery] = React.useState("");
102
- const [sortMode, setSortMode] = React.useState("name-asc");
103
- const [filtersOpen, setFiltersOpen] = React.useState(true);
104
- const [visiblePeople, setVisiblePeople] = React.useState([]);
105
- const [listPage, setListPage] = React.useState(1);
106
- const [listTotalPages, setListTotalPages] = React.useState(1);
107
- const [listTotalCount, setListTotalCount] = React.useState(initialPeople.length);
108
- const [listLoading, setListLoading] = React.useState(true);
109
- const [starredIds, setStarredIds] = React.useState(
110
- () => readVersionedIdSet(`om:starred-people:${companyId}`, STARRED_PEOPLE_STORAGE_VERSION)
111
- );
90
+ const [linkDialogOpen, setLinkDialogOpen] = React.useState(false);
91
+ const [refreshKey, setRefreshKey] = React.useState(0);
112
92
  const pendingPeopleChangeRef = React.useRef(false);
93
+ const requestRefresh = React.useCallback(() => {
94
+ setRefreshKey((current) => current + 1);
95
+ }, []);
113
96
  const runWriteMutation = React.useCallback(
114
97
  async (operation, mutationPayload) => {
115
98
  if (!runGuardedMutation) {
@@ -119,27 +102,6 @@ function CompanyPeopleSection({
119
102
  },
120
103
  [runGuardedMutation]
121
104
  );
122
- const toggleStar = React.useCallback(
123
- (personId) => {
124
- setStarredIds((prev) => {
125
- const next = new Set(prev);
126
- if (next.has(personId)) next.delete(personId);
127
- else next.add(personId);
128
- writeVersionedIdSet(`om:starred-people:${companyId}`, STARRED_PEOPLE_STORAGE_VERSION, next);
129
- return next;
130
- });
131
- },
132
- [companyId]
133
- );
134
- const displayedPeople = React.useMemo(
135
- () => visiblePeople.length > 0 ? visiblePeople : people,
136
- [people, visiblePeople]
137
- );
138
- const totalLinkedPeople = listTotalCount > 0 ? listTotalCount : displayedPeople.length;
139
- const decisionMakerNames = React.useMemo(
140
- () => displayedPeople.filter((person) => starredIds.has(person.id)).map((person) => person.displayName),
141
- [displayedPeople, starredIds]
142
- );
143
105
  React.useEffect(() => {
144
106
  onActionChange?.(null);
145
107
  return () => onActionChange?.(null);
@@ -153,18 +115,22 @@ function CompanyPeopleSection({
153
115
  pendingPeopleChangeRef.current = false;
154
116
  onPeopleChange?.(people);
155
117
  }, [onPeopleChange, people]);
156
- const loadVisiblePeople = React.useCallback(async () => {
157
- setListLoading(true);
158
- try {
118
+ const loadPage = React.useCallback(
119
+ async ({
120
+ page,
121
+ pageSize,
122
+ sort,
123
+ search
124
+ }) => {
159
125
  const params = new URLSearchParams({
160
- page: String(listPage),
161
- pageSize: String(COMPANY_PEOPLE_PAGE_SIZE),
162
- sort: sortMode
126
+ page: String(page),
127
+ pageSize: String(pageSize),
128
+ sort
163
129
  });
164
- if (searchQuery.trim().length > 0) {
165
- params.set("search", searchQuery.trim());
130
+ if (search.length > 0) {
131
+ params.set("search", search);
166
132
  }
167
- const payload = await readApiResultOrThrow(
133
+ return readApiResultOrThrow(
168
134
  `/api/customers/companies/${encodeURIComponent(companyId)}/people?${params.toString()}`,
169
135
  void 0,
170
136
  {
@@ -174,37 +140,17 @@ function CompanyPeopleSection({
174
140
  )
175
141
  }
176
142
  );
177
- const nextTotalCount = typeof payload.total === "number" ? payload.total : 0;
178
- setVisiblePeople(Array.isArray(payload.items) ? payload.items : []);
179
- setListPage(typeof payload.page === "number" ? payload.page : listPage);
180
- setListTotalCount(
181
- (current) => searchQuery.trim().length > 0 ? Math.max(current, nextTotalCount) : nextTotalCount
182
- );
183
- setListTotalPages(typeof payload.totalPages === "number" ? payload.totalPages : 1);
184
- } catch {
185
- setVisiblePeople([]);
186
- if (searchQuery.trim().length === 0) {
187
- setListTotalCount(0);
188
- }
189
- setListTotalPages(1);
190
- } finally {
191
- setListLoading(false);
192
- }
193
- }, [companyId, listPage, searchQuery, sortMode, translate]);
194
- React.useEffect(() => {
195
- void loadVisiblePeople();
196
- }, [loadVisiblePeople]);
143
+ },
144
+ [companyId, translate]
145
+ );
197
146
  const reloadOnCompanyDetach = React.useCallback((event) => {
198
147
  const payload = event.payload;
199
148
  if (payload && payload.companyEntityId === companyId) {
200
- void loadVisiblePeople();
149
+ requestRefresh();
201
150
  }
202
- }, [companyId, loadVisiblePeople]);
151
+ }, [companyId, requestRefresh]);
203
152
  useAppEvent("customers.person_company_link.deleted", reloadOnCompanyDetach, [reloadOnCompanyDetach]);
204
153
  useAppEvent("customers.person.company_assignment.detached", reloadOnCompanyDetach, [reloadOnCompanyDetach]);
205
- React.useEffect(() => {
206
- setListPage(1);
207
- }, [searchQuery, sortMode]);
208
154
  const applyPeopleChange = React.useCallback(
209
155
  (updater) => {
210
156
  setPeople((current) => {
@@ -223,7 +169,6 @@ function CompanyPeopleSection({
223
169
  optionsById
224
170
  }) => {
225
171
  if (!addedIds.length) return;
226
- onLoadingChange?.(true);
227
172
  try {
228
173
  for (const personId of addedIds) {
229
174
  await runWriteMutation(
@@ -263,9 +208,7 @@ function CompanyPeopleSection({
263
208
  }).filter((entry) => entry !== null);
264
209
  if (optimisticPeople.length > 0) {
265
210
  applyPeopleChange((current) => mergeCompanyPeople([...current, ...optimisticPeople]));
266
- setListTotalCount((current) => current + optimisticPeople.length);
267
211
  }
268
- await loadVisiblePeople();
269
212
  flash(
270
213
  addedIds.length === 1 ? translate(
271
214
  "customers.companies.detail.people.linkSuccess",
@@ -288,19 +231,9 @@ function CompanyPeopleSection({
288
231
  );
289
232
  flash(message, "error");
290
233
  throw err;
291
- } finally {
292
- onLoadingChange?.(false);
293
234
  }
294
235
  },
295
- [
296
- applyPeopleChange,
297
- companyId,
298
- loadVisiblePeople,
299
- onDataRefresh,
300
- onLoadingChange,
301
- runWriteMutation,
302
- translate
303
- ]
236
+ [applyPeopleChange, companyId, onDataRefresh, runWriteMutation, translate]
304
237
  );
305
238
  const personLinkAdapter = React.useMemo(
306
239
  () => createPersonLinkAdapter({
@@ -363,7 +296,7 @@ function CompanyPeopleSection({
363
296
  companyName: companyName ?? companyId,
364
297
  runGuardedMutation: runWriteMutation,
365
298
  onPersonCreated: () => {
366
- void loadVisiblePeople();
299
+ requestRefresh();
367
300
  void onDataRefresh?.();
368
301
  setLinkDialogOpen(false);
369
302
  onCancel();
@@ -372,13 +305,10 @@ function CompanyPeopleSection({
372
305
  )
373
306
  }
374
307
  }),
375
- [companyId, companyName, loadVisiblePeople, onDataRefresh, runWriteMutation, translate]
308
+ [companyId, companyName, onDataRefresh, requestRefresh, runWriteMutation, translate]
376
309
  );
377
310
  const handleRemove = React.useCallback(
378
311
  async (personId) => {
379
- if (!personId || removingId) return;
380
- setRemovingId(personId);
381
- onLoadingChange?.(true);
382
312
  try {
383
313
  await runWriteMutation(
384
314
  () => (
@@ -399,7 +329,6 @@ function CompanyPeopleSection({
399
329
  companyId
400
330
  }
401
331
  );
402
- await loadVisiblePeople();
403
332
  flash(
404
333
  translate(
405
334
  "customers.companies.detail.people.removeSuccess",
@@ -413,257 +342,63 @@ function CompanyPeopleSection({
413
342
  "Failed to unlink person from company."
414
343
  );
415
344
  flash(message, "error");
416
- } finally {
417
- setRemovingId(null);
418
- onLoadingChange?.(false);
419
345
  }
420
346
  },
421
- [
422
- companyId,
423
- loadVisiblePeople,
424
- onLoadingChange,
425
- removingId,
426
- runWriteMutation,
427
- translate
428
- ]
347
+ [companyId, runWriteMutation, translate]
429
348
  );
430
- const linkAction = /* @__PURE__ */ jsxs(
431
- Button,
432
- {
433
- type: "button",
434
- variant: "outline",
435
- size: "sm",
436
- onClick: () => setLinkDialogOpen(true),
437
- children: [
438
- /* @__PURE__ */ jsx(Link2, { className: "mr-1.5 h-4 w-4" }),
439
- translate(
349
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
350
+ /* @__PURE__ */ jsx(
351
+ LinkedPeopleSection,
352
+ {
353
+ scopeId: companyId,
354
+ fallbackPeople: people,
355
+ loadPage,
356
+ onUnlink: handleRemove,
357
+ linkAdapter: personLinkAdapter,
358
+ linkDialogOpen,
359
+ onLinkDialogOpenChange: setLinkDialogOpen,
360
+ onLinkConfirm: handleLinkConfirm,
361
+ sectionTitle: translate("customers.companies.detail.people.sectionTitle", "People"),
362
+ sectionSubtitle: translate(
363
+ "customers.companies.detail.people.sectionSubtitle",
364
+ "Employees and decision makers on the client side"
365
+ ),
366
+ searchPlaceholder: translate(
367
+ "customers.companies.detail.people.searchPlaceholder",
368
+ "Search by name, role, email..."
369
+ ),
370
+ linkActionLabel: translate(
440
371
  "customers.companies.detail.people.linkAction",
441
372
  "Link existing person"
442
- )
443
- ]
444
- }
445
- );
446
- const addPersonAction = /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: () => setCreateDialogOpen(true), children: [
447
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
448
- addActionLabel
449
- ] });
450
- if (!listLoading && totalLinkedPeople === 0) {
451
- return /* @__PURE__ */ jsxs(Fragment, { children: [
452
- /* @__PURE__ */ jsxs(
453
- EmptyState,
454
- {
455
- icon: /* @__PURE__ */ jsx(Users, { className: "h-10 w-10 text-muted-foreground" }),
456
- title: emptyState.title,
457
- actionLabel: emptyState.actionLabel,
458
- onAction: () => setCreateDialogOpen(true),
459
- children: [
460
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: emptyLabel }),
461
- /* @__PURE__ */ jsx("div", { className: "mt-4", children: linkAction })
462
- ]
463
- }
464
- ),
465
- /* @__PURE__ */ jsx(
466
- LinkEntityDialog,
467
- {
468
- open: linkDialogOpen,
469
- onOpenChange: setLinkDialogOpen,
470
- adapter: personLinkAdapter,
471
- initialSelectedIds: [],
472
- onConfirm: handleLinkConfirm,
473
- runGuardedMutation: runWriteMutation
474
- }
475
- ),
476
- /* @__PURE__ */ jsx(
477
- CreatePersonDialog,
478
- {
479
- open: createDialogOpen,
480
- onClose: () => setCreateDialogOpen(false),
481
- companyId,
482
- companyName: companyName ?? companyId,
483
- runGuardedMutation: runWriteMutation,
484
- onPersonCreated: () => {
485
- setCreateDialogOpen(false);
486
- void loadVisiblePeople();
487
- void onDataRefresh?.();
373
+ ),
374
+ addActionLabel,
375
+ onAddPerson: () => setCreateDialogOpen(true),
376
+ emptyLabel,
377
+ emptyState,
378
+ refreshKey,
379
+ translator: translate,
380
+ runGuardedMutation: runWriteMutation,
381
+ onLoadingChange,
382
+ header: /* @__PURE__ */ jsx(
383
+ RolesSection,
384
+ {
385
+ entityType: "company",
386
+ entityId: companyId,
387
+ entityName: companyName ?? null
488
388
  }
489
- }
490
- )
491
- ] });
492
- }
493
- return /* @__PURE__ */ jsxs(Fragment, { children: [
494
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
495
- /* @__PURE__ */ jsx(
496
- RolesSection,
497
- {
498
- entityType: "company",
499
- entityId: companyId,
500
- entityName: companyName ?? null
501
- }
502
- ),
503
- /* @__PURE__ */ jsx("section", { className: "rounded-lg border bg-card px-4 py-4 sm:px-5", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
504
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between", children: [
505
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
506
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
507
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: translate(
508
- "customers.companies.detail.people.sectionTitle",
509
- "People"
510
- ) }),
511
- /* @__PURE__ */ jsx(
512
- Badge,
513
- {
514
- variant: "secondary",
515
- className: "rounded-full px-2 py-0 text-xs font-semibold",
516
- children: totalLinkedPeople
517
- }
518
- )
519
- ] }),
520
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: translate(
521
- "customers.companies.detail.people.sectionSubtitle",
522
- "Employees and decision makers on the client side"
523
- ) })
524
- ] }),
525
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 xl:justify-end", children: [
526
- linkAction,
527
- addPersonAction
528
- ] })
529
- ] }),
530
- totalLinkedPeople > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 lg:flex-row lg:items-center", children: [
531
- filtersOpen ? /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
532
- "input",
533
- {
534
- type: "text",
535
- value: searchQuery,
536
- onChange: (event) => setSearchQuery(event.target.value),
537
- placeholder: translate(
538
- "customers.companies.detail.people.searchPlaceholder",
539
- "Search by name, role, email..."
540
- ),
541
- className: "h-10 w-full rounded-md border bg-background px-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
542
- }
543
- ) }) : null,
544
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 lg:justify-end", children: [
545
- /* @__PURE__ */ jsxs(
546
- Button,
547
- {
548
- type: "button",
549
- variant: "outline",
550
- size: "sm",
551
- onClick: () => setFiltersOpen((current) => !current),
552
- className: "h-10",
553
- children: [
554
- /* @__PURE__ */ jsx(Filter, { className: "mr-1.5 h-4 w-4" }),
555
- translate(
556
- "customers.companies.detail.people.filter",
557
- "Filters"
558
- )
559
- ]
560
- }
561
- ),
562
- filtersOpen ? /* @__PURE__ */ jsxs(
563
- "select",
564
- {
565
- value: sortMode,
566
- onChange: (event) => setSortMode(event.target.value),
567
- className: "h-10 min-w-[11rem] rounded-md border bg-background px-3 text-sm focus:outline-none focus:ring-1 focus:ring-ring",
568
- children: [
569
- /* @__PURE__ */ jsx("option", { value: "name-asc", children: translate(
570
- "customers.companies.detail.people.sortNameAsc",
571
- "Sort: Name A-Z"
572
- ) }),
573
- /* @__PURE__ */ jsx("option", { value: "name-desc", children: translate(
574
- "customers.companies.detail.people.sortNameDesc",
575
- "Sort: Name Z-A"
576
- ) }),
577
- /* @__PURE__ */ jsx("option", { value: "recent", children: translate(
578
- "customers.companies.detail.people.sortRecent",
579
- "Sort: Recently linked"
580
- ) })
581
- ]
582
- }
583
- ) : null
584
- ] })
585
- ] }) : null,
586
- listLoading ? /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: translate("customers.companies.detail.people.loading", "Loading people\u2026") }) : visiblePeople.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
587
- /* @__PURE__ */ jsx(
588
- "div",
589
- {
590
- className: "grid items-start gap-4",
591
- style: {
592
- gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 19.5rem), 1fr))"
593
- },
594
- children: visiblePeople.map((person) => /* @__PURE__ */ jsx(
595
- PersonCard,
596
- {
597
- person,
598
- isStarred: starredIds.has(person.id),
599
- onToggleStar: toggleStar,
600
- onUnlink: handleRemove
601
- },
602
- person.id
603
- ))
604
- }
605
- ),
606
- listTotalPages > 1 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border/60 pt-3 text-sm text-muted-foreground", children: [
607
- /* @__PURE__ */ jsx("span", { children: translate(
608
- "customers.companies.detail.people.pageSummary",
609
- "Page {{page}} of {{total}}",
610
- {
611
- page: listPage,
612
- total: listTotalPages
389
+ ),
390
+ renderFooter: ({ people: visible, starredIds }) => /* @__PURE__ */ jsx(
391
+ DecisionMakersFooter,
392
+ {
393
+ names: visible.filter((person) => starredIds.has(person.id)).map((person) => person.displayName),
394
+ onSendInvitation: () => {
395
+ const starredEmails = visible.filter((person) => starredIds.has(person.id) && person.primaryEmail).map((person) => person.primaryEmail);
396
+ if (starredEmails.length > 0) {
397
+ window.open(`mailto:${starredEmails.join(",")}`, "_blank");
613
398
  }
614
- ) }),
615
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
616
- /* @__PURE__ */ jsx(
617
- Button,
618
- {
619
- type: "button",
620
- variant: "outline",
621
- size: "sm",
622
- onClick: () => setListPage((current) => Math.max(1, current - 1)),
623
- disabled: listPage <= 1,
624
- children: translate("customers.companies.detail.people.previous", "Previous")
625
- }
626
- ),
627
- /* @__PURE__ */ jsx(
628
- Button,
629
- {
630
- type: "button",
631
- variant: "outline",
632
- size: "sm",
633
- onClick: () => setListPage((current) => Math.min(listTotalPages, current + 1)),
634
- disabled: listPage >= listTotalPages,
635
- children: translate("customers.companies.detail.people.next", "Next")
636
- }
637
- )
638
- ] })
639
- ] }) : null
640
- ] }) : totalLinkedPeople > 0 ? /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: translate(
641
- "customers.companies.detail.people.noSearchResults",
642
- "No people match your search."
643
- ) }) : null
644
- ] }) }),
645
- /* @__PURE__ */ jsx(
646
- DecisionMakersFooter,
647
- {
648
- names: decisionMakerNames,
649
- onSendInvitation: () => {
650
- const starredEmails = displayedPeople.filter((person) => starredIds.has(person.id) && person.primaryEmail).map((person) => person.primaryEmail);
651
- if (starredEmails.length > 0) {
652
- window.open(`mailto:${starredEmails.join(",")}`, "_blank");
653
399
  }
654
400
  }
655
- }
656
- )
657
- ] }),
658
- /* @__PURE__ */ jsx(
659
- LinkEntityDialog,
660
- {
661
- open: linkDialogOpen,
662
- onOpenChange: setLinkDialogOpen,
663
- adapter: personLinkAdapter,
664
- initialSelectedIds: [],
665
- onConfirm: handleLinkConfirm,
666
- runGuardedMutation: runWriteMutation
401
+ )
667
402
  }
668
403
  ),
669
404
  /* @__PURE__ */ jsx(
@@ -676,7 +411,7 @@ function CompanyPeopleSection({
676
411
  runGuardedMutation: runWriteMutation,
677
412
  onPersonCreated: () => {
678
413
  setCreateDialogOpen(false);
679
- void loadVisiblePeople();
414
+ requestRefresh();
680
415
  void onDataRefresh?.();
681
416
  }
682
417
  }