@kelpie/ui 0.11.0 → 0.13.0

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 (333) hide show
  1. package/dist/api/resources/attendances.d.ts +13 -0
  2. package/dist/api/resources/attendances.d.ts.map +1 -0
  3. package/dist/api/resources/attendances.js +34 -0
  4. package/dist/api/resources/attendances.js.map +1 -0
  5. package/dist/api/resources/eventAssociations.d.ts +12 -0
  6. package/dist/api/resources/eventAssociations.d.ts.map +1 -0
  7. package/dist/api/resources/eventAssociations.js +25 -0
  8. package/dist/api/resources/eventAssociations.js.map +1 -0
  9. package/dist/api/resources/events.d.ts +17 -0
  10. package/dist/api/resources/events.d.ts.map +1 -0
  11. package/dist/api/resources/events.js +40 -0
  12. package/dist/api/resources/events.js.map +1 -0
  13. package/dist/api/resources/handbookSeed.d.ts +11 -0
  14. package/dist/api/resources/handbookSeed.d.ts.map +1 -0
  15. package/dist/api/resources/handbookSeed.js +43 -0
  16. package/dist/api/resources/handbookSeed.js.map +1 -0
  17. package/dist/api/resources/moduleSettings.d.ts +10 -0
  18. package/dist/api/resources/moduleSettings.d.ts.map +1 -1
  19. package/dist/api/resources/moduleSettings.js +21 -0
  20. package/dist/api/resources/moduleSettings.js.map +1 -1
  21. package/dist/api/resources/planItems.d.ts +4 -4
  22. package/dist/api/resources/planItems.d.ts.map +1 -1
  23. package/dist/api/resources/planItems.js +1 -1
  24. package/dist/api/resources/planItems.js.map +1 -1
  25. package/dist/api/resources/publicConfig.js +3 -3
  26. package/dist/api/resources/sampleData.d.ts.map +1 -1
  27. package/dist/api/resources/sampleData.js +2 -0
  28. package/dist/api/resources/sampleData.js.map +1 -1
  29. package/dist/api/resources/session.d.ts +22 -1
  30. package/dist/api/resources/session.d.ts.map +1 -1
  31. package/dist/api/resources/session.js +43 -1
  32. package/dist/api/resources/session.js.map +1 -1
  33. package/dist/api/resources/workspace.d.ts.map +1 -1
  34. package/dist/api/resources/workspace.js +2 -3
  35. package/dist/api/resources/workspace.js.map +1 -1
  36. package/dist/app/KelpieApp.d.ts.map +1 -1
  37. package/dist/app/KelpieApp.js +5 -1
  38. package/dist/app/KelpieApp.js.map +1 -1
  39. package/dist/components/ActivitiesPanel.d.ts.map +1 -1
  40. package/dist/components/ActivitiesPanel.js +2 -0
  41. package/dist/components/ActivitiesPanel.js.map +1 -1
  42. package/dist/components/AddressesField.d.ts +23 -0
  43. package/dist/components/AddressesField.d.ts.map +1 -0
  44. package/dist/components/AddressesField.js +215 -0
  45. package/dist/components/AddressesField.js.map +1 -0
  46. package/dist/components/CountrySearch.d.ts +16 -0
  47. package/dist/components/CountrySearch.d.ts.map +1 -0
  48. package/dist/components/CountrySearch.js +30 -0
  49. package/dist/components/CountrySearch.js.map +1 -0
  50. package/dist/components/EnvironmentBanner.d.ts +0 -12
  51. package/dist/components/EnvironmentBanner.d.ts.map +1 -1
  52. package/dist/components/EnvironmentBanner.js +21 -6
  53. package/dist/components/EnvironmentBanner.js.map +1 -1
  54. package/dist/components/InlineEdit.d.ts +7 -1
  55. package/dist/components/InlineEdit.d.ts.map +1 -1
  56. package/dist/components/InlineEdit.js +8 -1
  57. package/dist/components/InlineEdit.js.map +1 -1
  58. package/dist/components/LinkedEventsPanel.d.ts +11 -0
  59. package/dist/components/LinkedEventsPanel.d.ts.map +1 -0
  60. package/dist/components/LinkedEventsPanel.js +18 -0
  61. package/dist/components/LinkedEventsPanel.js.map +1 -0
  62. package/dist/components/NotesPanel.d.ts +4 -6
  63. package/dist/components/NotesPanel.d.ts.map +1 -1
  64. package/dist/components/NotesPanel.js +39 -3
  65. package/dist/components/NotesPanel.js.map +1 -1
  66. package/dist/components/PlanAttention.d.ts.map +1 -1
  67. package/dist/components/PlanAttention.js +3 -2
  68. package/dist/components/PlanAttention.js.map +1 -1
  69. package/dist/components/PlanPanel.d.ts +3 -3
  70. package/dist/components/PlanPanel.d.ts.map +1 -1
  71. package/dist/components/Shell.d.ts.map +1 -1
  72. package/dist/components/Shell.js +8 -2
  73. package/dist/components/Shell.js.map +1 -1
  74. package/dist/components/SocialNetworkIcon.d.ts +17 -0
  75. package/dist/components/SocialNetworkIcon.d.ts.map +1 -0
  76. package/dist/components/SocialNetworkIcon.js +39 -0
  77. package/dist/components/SocialNetworkIcon.js.map +1 -0
  78. package/dist/components/SocialProfilesField.d.ts +8 -0
  79. package/dist/components/SocialProfilesField.d.ts.map +1 -1
  80. package/dist/components/SocialProfilesField.js +19 -5
  81. package/dist/components/SocialProfilesField.js.map +1 -1
  82. package/dist/components/TimezoneSearch.d.ts +21 -0
  83. package/dist/components/TimezoneSearch.d.ts.map +1 -0
  84. package/dist/components/TimezoneSearch.js +27 -0
  85. package/dist/components/TimezoneSearch.js.map +1 -0
  86. package/dist/components/WorkspaceSwitcher.d.ts +8 -0
  87. package/dist/components/WorkspaceSwitcher.d.ts.map +1 -0
  88. package/dist/components/WorkspaceSwitcher.js +67 -0
  89. package/dist/components/WorkspaceSwitcher.js.map +1 -0
  90. package/dist/components/environmentTone.d.ts +10 -0
  91. package/dist/components/environmentTone.d.ts.map +1 -0
  92. package/dist/components/environmentTone.js +35 -0
  93. package/dist/components/environmentTone.js.map +1 -0
  94. package/dist/index.d.ts +25 -6
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +17 -3
  97. package/dist/index.js.map +1 -1
  98. package/dist/lib/timezones.d.ts +17 -0
  99. package/dist/lib/timezones.d.ts.map +1 -0
  100. package/dist/lib/timezones.js +39 -0
  101. package/dist/lib/timezones.js.map +1 -0
  102. package/dist/pages/CompaniesPage.d.ts.map +1 -1
  103. package/dist/pages/CompaniesPage.js +12 -11
  104. package/dist/pages/CompaniesPage.js.map +1 -1
  105. package/dist/pages/CompanyDetail.d.ts.map +1 -1
  106. package/dist/pages/CompanyDetail.js +23 -12
  107. package/dist/pages/CompanyDetail.js.map +1 -1
  108. package/dist/pages/DealDetail.d.ts.map +1 -1
  109. package/dist/pages/DealDetail.js +5 -1
  110. package/dist/pages/DealDetail.js.map +1 -1
  111. package/dist/pages/DecisionsPage.d.ts.map +1 -1
  112. package/dist/pages/DecisionsPage.js +7 -0
  113. package/dist/pages/DecisionsPage.js.map +1 -1
  114. package/dist/pages/EnquiryDetail.d.ts.map +1 -1
  115. package/dist/pages/EnquiryDetail.js +5 -1
  116. package/dist/pages/EnquiryDetail.js.map +1 -1
  117. package/dist/pages/EventDetail.d.ts +2 -0
  118. package/dist/pages/EventDetail.d.ts.map +1 -0
  119. package/dist/pages/EventDetail.js +332 -0
  120. package/dist/pages/EventDetail.js.map +1 -0
  121. package/dist/pages/EventsPage.d.ts +2 -0
  122. package/dist/pages/EventsPage.d.ts.map +1 -0
  123. package/dist/pages/EventsPage.js +176 -0
  124. package/dist/pages/EventsPage.js.map +1 -0
  125. package/dist/pages/ListDetail.d.ts.map +1 -1
  126. package/dist/pages/ListDetail.js +17 -0
  127. package/dist/pages/ListDetail.js.map +1 -1
  128. package/dist/pages/OpportunityDetail.d.ts.map +1 -1
  129. package/dist/pages/OpportunityDetail.js +5 -1
  130. package/dist/pages/OpportunityDetail.js.map +1 -1
  131. package/dist/pages/PartnershipDetail.d.ts.map +1 -1
  132. package/dist/pages/PartnershipDetail.js +5 -1
  133. package/dist/pages/PartnershipDetail.js.map +1 -1
  134. package/dist/pages/PeoplePage.d.ts.map +1 -1
  135. package/dist/pages/PeoplePage.js +13 -5
  136. package/dist/pages/PeoplePage.js.map +1 -1
  137. package/dist/pages/PersonDetail.d.ts.map +1 -1
  138. package/dist/pages/PersonDetail.js +94 -28
  139. package/dist/pages/PersonDetail.js.map +1 -1
  140. package/dist/pages/PlanningPage.d.ts.map +1 -1
  141. package/dist/pages/PlanningPage.js +6 -3
  142. package/dist/pages/PlanningPage.js.map +1 -1
  143. package/dist/pages/RaiseDetail.d.ts.map +1 -1
  144. package/dist/pages/RaiseDetail.js +5 -1
  145. package/dist/pages/RaiseDetail.js.map +1 -1
  146. package/dist/pages/RoleDetail.d.ts.map +1 -1
  147. package/dist/pages/RoleDetail.js +4 -1
  148. package/dist/pages/RoleDetail.js.map +1 -1
  149. package/dist/pages/SearchPage.d.ts.map +1 -1
  150. package/dist/pages/SearchPage.js +2 -1
  151. package/dist/pages/SearchPage.js.map +1 -1
  152. package/dist/pages/account/PreferencesPage.d.ts.map +1 -1
  153. package/dist/pages/account/PreferencesPage.js +2 -28
  154. package/dist/pages/account/PreferencesPage.js.map +1 -1
  155. package/dist/pages/account/SecurityPage.js +2 -2
  156. package/dist/pages/account/SecurityPage.js.map +1 -1
  157. package/dist/pages/admin/DataPage.js +10 -9
  158. package/dist/pages/admin/DataPage.js.map +1 -1
  159. package/dist/pages/admin/FieldsPage.js +1 -1
  160. package/dist/pages/admin/FieldsPage.js.map +1 -1
  161. package/dist/pages/admin/ModulesPage.d.ts.map +1 -1
  162. package/dist/pages/admin/ModulesPage.js +1 -0
  163. package/dist/pages/admin/ModulesPage.js.map +1 -1
  164. package/dist/pages/admin/WorkspaceSettingsPage.d.ts +12 -0
  165. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
  166. package/dist/pages/admin/WorkspaceSettingsPage.js +2 -21
  167. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
  168. package/dist/pages/auth/AuthForm.d.ts +18 -0
  169. package/dist/pages/auth/AuthForm.d.ts.map +1 -1
  170. package/dist/pages/auth/AuthForm.js +17 -0
  171. package/dist/pages/auth/AuthForm.js.map +1 -1
  172. package/dist/pages/auth/AuthLayout.d.ts +5 -4
  173. package/dist/pages/auth/AuthLayout.d.ts.map +1 -1
  174. package/dist/pages/auth/AuthLayout.js +7 -5
  175. package/dist/pages/auth/AuthLayout.js.map +1 -1
  176. package/dist/pages/auth/AuthMethods.d.ts +12 -0
  177. package/dist/pages/auth/AuthMethods.d.ts.map +1 -0
  178. package/dist/pages/auth/AuthMethods.js +20 -0
  179. package/dist/pages/auth/AuthMethods.js.map +1 -0
  180. package/dist/pages/auth/RegionSwitcher.d.ts +10 -0
  181. package/dist/pages/auth/RegionSwitcher.d.ts.map +1 -0
  182. package/dist/pages/auth/RegionSwitcher.js +28 -0
  183. package/dist/pages/auth/RegionSwitcher.js.map +1 -0
  184. package/dist/pages/auth/SignInPage.d.ts +7 -0
  185. package/dist/pages/auth/SignInPage.d.ts.map +1 -1
  186. package/dist/pages/auth/SignInPage.js +5 -8
  187. package/dist/pages/auth/SignInPage.js.map +1 -1
  188. package/dist/pages/auth/SignUpPage.d.ts.map +1 -1
  189. package/dist/pages/auth/SignUpPage.js +9 -1
  190. package/dist/pages/auth/SignUpPage.js.map +1 -1
  191. package/dist/pages/auth/nextPath.d.ts +15 -0
  192. package/dist/pages/auth/nextPath.d.ts.map +1 -0
  193. package/dist/pages/auth/nextPath.js +17 -0
  194. package/dist/pages/auth/nextPath.js.map +1 -0
  195. package/dist/pages/auth/regionHref.d.ts +6 -0
  196. package/dist/pages/auth/regionHref.d.ts.map +1 -0
  197. package/dist/pages/auth/regionHref.js +8 -0
  198. package/dist/pages/auth/regionHref.js.map +1 -0
  199. package/dist/pages/dashboard/DashboardPage.d.ts +8 -0
  200. package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -1
  201. package/dist/pages/dashboard/DashboardPage.js +5 -13
  202. package/dist/pages/dashboard/DashboardPage.js.map +1 -1
  203. package/dist/pages/dashboard/attention.d.ts +1 -8
  204. package/dist/pages/dashboard/attention.d.ts.map +1 -1
  205. package/dist/pages/dashboard/attention.js +21 -54
  206. package/dist/pages/dashboard/attention.js.map +1 -1
  207. package/dist/pages/dashboard/sections.d.ts.map +1 -1
  208. package/dist/pages/dashboard/sections.js +3 -2
  209. package/dist/pages/dashboard/sections.js.map +1 -1
  210. package/dist/pages/forms/FormActions.d.ts.map +1 -1
  211. package/dist/pages/forms/FormActions.js +7 -3
  212. package/dist/pages/forms/FormActions.js.map +1 -1
  213. package/dist/pages/onboarding/HandbookStepPage.d.ts +4 -8
  214. package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -1
  215. package/dist/pages/onboarding/HandbookStepPage.js +13 -13
  216. package/dist/pages/onboarding/HandbookStepPage.js.map +1 -1
  217. package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -1
  218. package/dist/pages/onboarding/InvitesStepPage.js +13 -7
  219. package/dist/pages/onboarding/InvitesStepPage.js.map +1 -1
  220. package/dist/pages/onboarding/ModulesStepPage.d.ts +11 -0
  221. package/dist/pages/onboarding/ModulesStepPage.d.ts.map +1 -0
  222. package/dist/pages/onboarding/ModulesStepPage.js +80 -0
  223. package/dist/pages/onboarding/ModulesStepPage.js.map +1 -0
  224. package/dist/pages/onboarding/OnboardingNav.d.ts +17 -0
  225. package/dist/pages/onboarding/OnboardingNav.d.ts.map +1 -0
  226. package/dist/pages/onboarding/OnboardingNav.js +37 -0
  227. package/dist/pages/onboarding/OnboardingNav.js.map +1 -0
  228. package/dist/pages/onboarding/OrganisationStepPage.d.ts +13 -0
  229. package/dist/pages/onboarding/OrganisationStepPage.d.ts.map +1 -0
  230. package/dist/pages/onboarding/OrganisationStepPage.js +59 -0
  231. package/dist/pages/onboarding/OrganisationStepPage.js.map +1 -0
  232. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
  233. package/dist/pages/onboarding/WorkspaceStepPage.js +25 -20
  234. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
  235. package/dist/pages/onboarding/handbookTemplates.d.ts +19 -0
  236. package/dist/pages/onboarding/handbookTemplates.d.ts.map +1 -0
  237. package/dist/pages/onboarding/handbookTemplates.js +57 -0
  238. package/dist/pages/onboarding/handbookTemplates.js.map +1 -0
  239. package/dist/pages/onboarding/onboardingModules.d.ts +23 -0
  240. package/dist/pages/onboarding/onboardingModules.d.ts.map +1 -0
  241. package/dist/pages/onboarding/onboardingModules.js +153 -0
  242. package/dist/pages/onboarding/onboardingModules.js.map +1 -0
  243. package/dist/pages/onboarding/onboardingRerun.d.ts +21 -0
  244. package/dist/pages/onboarding/onboardingRerun.d.ts.map +1 -0
  245. package/dist/pages/onboarding/onboardingRerun.js +33 -0
  246. package/dist/pages/onboarding/onboardingRerun.js.map +1 -0
  247. package/dist/registry/context.d.ts +3 -1
  248. package/dist/registry/context.d.ts.map +1 -1
  249. package/dist/registry/context.js +4 -0
  250. package/dist/registry/context.js.map +1 -1
  251. package/dist/registry/contributions.d.ts +24 -0
  252. package/dist/registry/contributions.d.ts.map +1 -1
  253. package/dist/registry/contributions.js.map +1 -1
  254. package/dist/registry/registry.d.ts +4 -1
  255. package/dist/registry/registry.d.ts.map +1 -1
  256. package/dist/registry/registry.js +6 -0
  257. package/dist/registry/registry.js.map +1 -1
  258. package/package.json +4 -3
  259. package/src/api/resources/attendances.ts +54 -0
  260. package/src/api/resources/eventAssociations.ts +43 -0
  261. package/src/api/resources/events.ts +70 -0
  262. package/src/api/resources/handbookSeed.ts +63 -0
  263. package/src/api/resources/moduleSettings.ts +25 -0
  264. package/src/api/resources/planItems.ts +5 -5
  265. package/src/api/resources/publicConfig.ts +3 -3
  266. package/src/api/resources/sampleData.ts +2 -0
  267. package/src/api/resources/session.ts +59 -0
  268. package/src/api/resources/workspace.ts +2 -3
  269. package/src/app/KelpieApp.tsx +11 -3
  270. package/src/components/ActivitiesPanel.tsx +2 -0
  271. package/src/components/AddressesField.tsx +502 -0
  272. package/src/components/CountrySearch.tsx +71 -0
  273. package/src/components/EnvironmentBanner.tsx +23 -6
  274. package/src/components/InlineEdit.tsx +35 -1
  275. package/src/components/LinkedEventsPanel.tsx +50 -0
  276. package/src/components/NotesPanel.tsx +132 -15
  277. package/src/components/PlanAttention.tsx +5 -4
  278. package/src/components/PlanPanel.tsx +3 -3
  279. package/src/components/Shell.tsx +16 -1
  280. package/src/components/SocialNetworkIcon.tsx +88 -0
  281. package/src/components/SocialProfilesField.tsx +57 -10
  282. package/src/components/TimezoneSearch.tsx +67 -0
  283. package/src/components/WorkspaceSwitcher.tsx +136 -0
  284. package/src/components/environmentTone.ts +40 -0
  285. package/src/index.ts +49 -3
  286. package/src/lib/timezones.ts +43 -0
  287. package/src/pages/CompaniesPage.tsx +14 -13
  288. package/src/pages/CompanyDetail.tsx +59 -27
  289. package/src/pages/DealDetail.tsx +5 -0
  290. package/src/pages/DecisionsPage.tsx +7 -0
  291. package/src/pages/EnquiryDetail.tsx +7 -0
  292. package/src/pages/EventDetail.tsx +794 -0
  293. package/src/pages/EventsPage.tsx +382 -0
  294. package/src/pages/ListDetail.tsx +24 -0
  295. package/src/pages/OpportunityDetail.tsx +7 -0
  296. package/src/pages/PartnershipDetail.tsx +7 -0
  297. package/src/pages/PeoplePage.tsx +18 -5
  298. package/src/pages/PersonDetail.tsx +300 -100
  299. package/src/pages/PlanningPage.tsx +17 -14
  300. package/src/pages/RaiseDetail.tsx +5 -0
  301. package/src/pages/RoleDetail.tsx +8 -0
  302. package/src/pages/SearchPage.tsx +2 -1
  303. package/src/pages/account/PreferencesPage.tsx +2 -37
  304. package/src/pages/account/SecurityPage.tsx +6 -1
  305. package/src/pages/admin/DataPage.tsx +63 -21
  306. package/src/pages/admin/FieldsPage.tsx +1 -1
  307. package/src/pages/admin/ModulesPage.tsx +1 -0
  308. package/src/pages/admin/WorkspaceSettingsPage.tsx +10 -33
  309. package/src/pages/auth/AuthForm.tsx +38 -0
  310. package/src/pages/auth/AuthLayout.tsx +8 -4
  311. package/src/pages/auth/AuthMethods.tsx +29 -0
  312. package/src/pages/auth/RegionSwitcher.tsx +56 -0
  313. package/src/pages/auth/SignInPage.tsx +13 -14
  314. package/src/pages/auth/SignUpPage.tsx +32 -0
  315. package/src/pages/auth/nextPath.ts +17 -0
  316. package/src/pages/auth/regionHref.ts +10 -0
  317. package/src/pages/dashboard/DashboardPage.tsx +16 -48
  318. package/src/pages/dashboard/attention.ts +24 -78
  319. package/src/pages/dashboard/sections.tsx +8 -2
  320. package/src/pages/forms/FormActions.tsx +14 -8
  321. package/src/pages/onboarding/HandbookStepPage.tsx +21 -24
  322. package/src/pages/onboarding/InvitesStepPage.tsx +27 -19
  323. package/src/pages/onboarding/ModulesStepPage.tsx +145 -0
  324. package/src/pages/onboarding/OnboardingNav.tsx +97 -0
  325. package/src/pages/onboarding/OrganisationStepPage.tsx +130 -0
  326. package/src/pages/onboarding/WorkspaceStepPage.tsx +53 -38
  327. package/src/pages/onboarding/handbookTemplates.ts +77 -0
  328. package/src/pages/onboarding/onboardingModules.ts +202 -0
  329. package/src/pages/onboarding/onboardingRerun.ts +41 -0
  330. package/src/registry/context.ts +6 -0
  331. package/src/registry/contributions.ts +26 -0
  332. package/src/registry/registry.ts +12 -0
  333. package/src/styles.css +30 -0
@@ -0,0 +1,794 @@
1
+ import {
2
+ ATTENDANCE_STATUS_LABELS,
3
+ ATTENDANCE_STATUSES,
4
+ EVENT_ASSOCIATION_TARGET_TYPE_LABELS,
5
+ EVENT_ASSOCIATION_TARGET_TYPES,
6
+ EVENT_FORMAT_LABELS,
7
+ EVENT_FORMATS,
8
+ EVENT_STATUS_LABELS,
9
+ EVENT_STATUSES,
10
+ } from '@kelpie/schemas'
11
+ import type {
12
+ Attendance,
13
+ AttendanceStatus,
14
+ Event as CrmEvent,
15
+ EventAssociation,
16
+ EventAssociationTargetType,
17
+ EventFormat,
18
+ EventInput,
19
+ EventStatus,
20
+ } from '@kelpie/schemas'
21
+ import { useState } from 'react'
22
+ import type { FormEvent } from 'react'
23
+ import { Link, useNavigate, useParams } from 'react-router'
24
+
25
+ import { usePatch } from '../api/resource.ts'
26
+ import type { PatchResult } from '../api/resource.ts'
27
+ import { useCandidates } from '../api/resources/candidates.ts'
28
+ import { useCompanies } from '../api/resources/companies.ts'
29
+ import { useDeals } from '../api/resources/deals.ts'
30
+ import { useEnquiries } from '../api/resources/enquiries.ts'
31
+ import {
32
+ useAttendances,
33
+ useCreateAttendance,
34
+ useDeleteAttendance,
35
+ useUpdateAttendance,
36
+ } from '../api/resources/attendances.ts'
37
+ import { useDeleteEvent, useEvent, useUpdateEvent } from '../api/resources/events.ts'
38
+ import { useMembers } from '../api/resources/members.ts'
39
+ import { useOpportunities } from '../api/resources/opportunities.ts'
40
+ import { usePartnerships } from '../api/resources/partnerships.ts'
41
+ import { usePeople } from '../api/resources/people.ts'
42
+ import { useRecordPlanItems } from '../api/resources/planItems.ts'
43
+ import { useRaises } from '../api/resources/raises.ts'
44
+ import { useRoles } from '../api/resources/roles.ts'
45
+ import { ActivitiesPanel, LatestActivity } from '../components/ActivitiesPanel.tsx'
46
+ import { AgentTasks } from '../components/AgentTasks.tsx'
47
+ import { Chip } from '../components/Chip.tsx'
48
+ import type { ChipTone } from '../components/Chip.tsx'
49
+ import { DecisionsPanel } from '../components/DecisionsPanel.tsx'
50
+ import { DeleteRecord } from '../components/DeleteRecord.tsx'
51
+ import { EntitySearch } from '../components/EntitySearch.tsx'
52
+ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
53
+ import { useHasCustomFields } from '../components/useHasCustomFields.ts'
54
+ import { InlineEdit } from '../components/InlineEdit.tsx'
55
+ import { ListsPanel } from '../components/ListsPanel.tsx'
56
+ import { NotesPanel } from '../components/NotesPanel.tsx'
57
+ import { PlanAttention } from '../components/PlanAttention.tsx'
58
+ import { PlanPanel } from '../components/PlanPanel.tsx'
59
+ import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
60
+ import { RecordTabs } from '../components/RecordTabs.tsx'
61
+ import type { RecordTabDescriptor } from '../components/RecordTabs.tsx'
62
+ import { SectionHeader } from '../components/SectionHeader.tsx'
63
+ import { SidebarField } from '../components/SidebarField.tsx'
64
+ import { SummaryBlock } from '../components/SummaryBlock.tsx'
65
+ import { TimezoneSearch } from '../components/TimezoneSearch.tsx'
66
+ import { formatDateTime } from '../lib/dates.ts'
67
+ import { useRecordTabs } from '../registry/context.ts'
68
+ import { inSlotOrder } from '../registry/registry.ts'
69
+ import { toTags } from './fields.ts'
70
+
71
+ /**
72
+ * One dated gathering. Not a pipeline: no stages, no convert, no kanban.
73
+ *
74
+ * People register as Attendances. Other records attach through Event-only
75
+ * associations. Prep work lives on Plan items on this Event.
76
+ */
77
+
78
+ const STATUS_TONES: Readonly<Record<EventStatus, ChipTone>> = {
79
+ draft: 'neutral',
80
+ scheduled: 'accent',
81
+ cancelled: 'danger',
82
+ }
83
+
84
+ const ATTENDANCE_TONES: Readonly<Record<AttendanceStatus, ChipTone>> = {
85
+ registered: 'accent',
86
+ attended: 'success',
87
+ no_show: 'warning',
88
+ cancelled: 'danger',
89
+ }
90
+
91
+ const ASSOCIATION_ROUTES: Readonly<Partial<Record<EventAssociationTargetType, string>>> = {
92
+ person: '/people',
93
+ company: '/companies',
94
+ deal: '/deals',
95
+ opportunity: '/opportunities',
96
+ partnership: '/partnerships',
97
+ raise: '/fundraising',
98
+ enquiry: '/enquiries',
99
+ role: '/hiring',
100
+ }
101
+
102
+ export function EventDetail(): React.JSX.Element {
103
+ const { id } = useParams()
104
+ const navigate = useNavigate()
105
+ const { record, isLoading, isNotFound, error } = useEvent(id)
106
+ const deleteEvent = useDeleteEvent()
107
+ const moduleTabs = inSlotOrder(useRecordTabs('event'))
108
+ const hasCustomFields = useHasCustomFields('event')
109
+ const [activeTab, setActiveTab] = useState('overview')
110
+
111
+ if (isNotFound) {
112
+ return <NotFoundPanel label="Event" backTo="/events" />
113
+ }
114
+
115
+ if (error !== null) {
116
+ return <ErrorPanel error={error} />
117
+ }
118
+
119
+ if (isLoading || record === undefined || id === undefined) {
120
+ return <LoadingPanel label="Loading event…" />
121
+ }
122
+
123
+ const tabs: readonly RecordTabDescriptor<string>[] = [
124
+ { id: 'overview', label: 'Overview' },
125
+ ...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
126
+ { id: 'plan', label: 'Plan' },
127
+ { id: 'attendees', label: 'Attendees' },
128
+ { id: 'activity', label: 'Activity' },
129
+ { id: 'notes', label: 'Notes' },
130
+ { id: 'decisions', label: 'Decisions' },
131
+ { id: 'lists', label: 'Lists' },
132
+ ...moduleTabs.map((tab) => ({ id: tab.id, label: tab.label })),
133
+ ]
134
+ const active = tabs.some((tab) => tab.id === activeTab) ? activeTab : 'overview'
135
+ const moduleTab = moduleTabs.find((tab) => tab.id === active)
136
+
137
+ return (
138
+ <div className="animate-fade-in mx-auto max-w-6xl">
139
+ <Link
140
+ to="/events"
141
+ className="mb-4 inline-flex text-[12px] font-medium text-ink-muted transition hover:text-accent"
142
+ >
143
+ ← Events
144
+ </Link>
145
+
146
+ <div className="grid items-start gap-8 lg:grid-cols-[minmax(0,1fr)_280px]">
147
+ <div className="min-w-0 space-y-8">
148
+ <EventHeading event={record} />
149
+
150
+ <div className="flex justify-end gap-2">
151
+ <AgentTasks targetType="event" targetId={record.id} targetLabel={record.name} />
152
+ <DeleteRecord
153
+ recordLabel="Event"
154
+ recordName={record.name}
155
+ isPending={deleteEvent.isPending}
156
+ error={deleteEvent.error}
157
+ onConfirm={() => {
158
+ deleteEvent
159
+ .runAsync(record.id)
160
+ .then(() => navigate('/events'))
161
+ .catch(() => undefined)
162
+ }}
163
+ />
164
+ </div>
165
+
166
+ <RecordTabs
167
+ tabs={tabs}
168
+ active={active}
169
+ onChange={setActiveTab}
170
+ ariaLabel="Event sections"
171
+ >
172
+ {active === 'overview' && <EventOverview event={record} />}
173
+ {active === 'fields' && <EventFields event={record} />}
174
+ {active === 'plan' && <PlanPanel targetType="event" targetId={record.id} />}
175
+ {active === 'attendees' && <EventAttendees event={record} />}
176
+ {active === 'activity' && <ActivitiesPanel targetType="event" targetId={record.id} />}
177
+ {active === 'notes' && <NotesPanel targetType="event" targetId={record.id} />}
178
+ {active === 'decisions' && <DecisionsPanel targetType="event" targetId={record.id} />}
179
+ {active === 'lists' && <ListsPanel targetType="event" targetId={record.id} />}
180
+ {moduleTab?.render({ objectType: 'event', recordId: record.id })}
181
+ </RecordTabs>
182
+ </div>
183
+
184
+ <aside className="space-y-4 text-[12px] lg:sticky lg:top-6">
185
+ <EventSidebar event={record} />
186
+ <EventAssociationsCard event={record} />
187
+ </aside>
188
+ </div>
189
+ </div>
190
+ )
191
+ }
192
+
193
+ function useEventPatch(event: CrmEvent): PatchResult<EventInput> {
194
+ return usePatch(useUpdateEvent, event)
195
+ }
196
+
197
+ function EventHeading({ event }: { readonly event: CrmEvent }): React.JSX.Element {
198
+ const { patch, error } = useEventPatch(event)
199
+
200
+ return (
201
+ <div className="min-w-0 flex-1">
202
+ {error !== null && (
203
+ <div className="mb-2">
204
+ <ErrorPanel error={error} />
205
+ </div>
206
+ )}
207
+ <InlineEdit
208
+ value={event.name}
209
+ onChange={(name) => {
210
+ patch({ name })
211
+ }}
212
+ displayClassName="text-[22px] font-semibold tracking-tight text-ink not-italic"
213
+ emptyLabel="Untitled event"
214
+ />
215
+ <div className="mt-1">
216
+ <InlineEdit
217
+ value={event.kind}
218
+ onChange={(kind) => {
219
+ patch({ kind })
220
+ }}
221
+ displayClassName="text-[13px] text-ink-muted not-italic"
222
+ emptyLabel="Add kind…"
223
+ />
224
+ </div>
225
+ </div>
226
+ )
227
+ }
228
+
229
+ function EventOverview({ event }: { readonly event: CrmEvent }): React.JSX.Element {
230
+ const { patch, error } = useEventPatch(event)
231
+ const planItems = useRecordPlanItems('event', event.id)
232
+
233
+ return (
234
+ <div className="space-y-8">
235
+ {error !== null && <ErrorPanel error={error} />}
236
+ <SummaryBlock
237
+ value={event.summary}
238
+ onChange={(summary) => {
239
+ patch({ summary })
240
+ }}
241
+ />
242
+ <SidebarField label="Details">
243
+ <InlineEdit
244
+ value={event.details}
245
+ onChange={(details) => {
246
+ patch({ details })
247
+ }}
248
+ multiline
249
+ displayClassName="not-italic normal-case text-[13px]"
250
+ emptyLabel="Add agenda or description…"
251
+ />
252
+ </SidebarField>
253
+ <PlanAttention items={planItems.records} isLoading={planItems.isLoading} />
254
+ <LatestActivity targetType="event" targetId={event.id} />
255
+ </div>
256
+ )
257
+ }
258
+
259
+ function EventFields({ event }: { readonly event: CrmEvent }): React.JSX.Element {
260
+ const { patch, error } = useEventPatch(event)
261
+
262
+ return (
263
+ <div className="space-y-4">
264
+ {error !== null && <ErrorPanel error={error} />}
265
+ <CustomFieldsPanel
266
+ objectType="event"
267
+ values={event.customFields}
268
+ onPatch={(customFields) => {
269
+ patch({ customFields })
270
+ }}
271
+ />
272
+ </div>
273
+ )
274
+ }
275
+
276
+ function EventSidebar({ event }: { readonly event: CrmEvent }): React.JSX.Element {
277
+ const { patch, error } = useEventPatch(event)
278
+ const members = useMembers()
279
+
280
+ return (
281
+ <section className="rounded-md border border-border p-3">
282
+ {error !== null && (
283
+ <div className="mb-2">
284
+ <ErrorPanel error={error} />
285
+ </div>
286
+ )}
287
+ <div className="mb-2">
288
+ <div className="mb-0.5 text-[10px] font-semibold tracking-wide text-ink-faint uppercase">
289
+ Status
290
+ </div>
291
+ <InlineEdit
292
+ value={event.status}
293
+ onChange={(status) => {
294
+ patch({ status: status as EventStatus })
295
+ }}
296
+ options={EVENT_STATUSES.map((status) => ({
297
+ value: status,
298
+ label: EVENT_STATUS_LABELS[status],
299
+ }))}
300
+ display={
301
+ <Chip tone={STATUS_TONES[event.status]}>
302
+ <span className="text-[10px]">{EVENT_STATUS_LABELS[event.status]}</span>
303
+ </Chip>
304
+ }
305
+ displayClassName="not-italic inline-flex"
306
+ className="!w-auto"
307
+ />
308
+ </div>
309
+ <SidebarField label="Format">
310
+ <InlineEdit
311
+ value={event.format}
312
+ onChange={(format) => {
313
+ patch({ format: format as EventFormat })
314
+ }}
315
+ options={EVENT_FORMATS.map((format) => ({
316
+ value: format,
317
+ label: EVENT_FORMAT_LABELS[format],
318
+ }))}
319
+ displayClassName="not-italic text-[12px]"
320
+ />
321
+ </SidebarField>
322
+ <SidebarField label="Starts">
323
+ <InlineEdit
324
+ value={toDatetimeLocal(event.startsAt)}
325
+ onChange={(startsAt) => {
326
+ if (startsAt.length === 0) {
327
+ return
328
+ }
329
+
330
+ patch({ startsAt: new Date(startsAt) })
331
+ }}
332
+ type="datetime-local"
333
+ display={formatDateTime(event.startsAt, event.timezone)}
334
+ displayClassName="not-italic text-[12px]"
335
+ />
336
+ </SidebarField>
337
+ <SidebarField label="Ends">
338
+ <InlineEdit
339
+ value={toDatetimeLocal(event.endsAt)}
340
+ onChange={(endsAt) => {
341
+ if (endsAt.length === 0) {
342
+ return
343
+ }
344
+
345
+ patch({ endsAt: new Date(endsAt) })
346
+ }}
347
+ type="datetime-local"
348
+ display={formatDateTime(event.endsAt, event.timezone)}
349
+ displayClassName="not-italic text-[12px]"
350
+ />
351
+ </SidebarField>
352
+ <SidebarField label="Timezone">
353
+ <TimezoneSearch
354
+ value={event.timezone}
355
+ onChange={(timezone) => {
356
+ patch({ timezone })
357
+ }}
358
+ required
359
+ />
360
+ </SidebarField>
361
+ <SidebarField label="Location">
362
+ <InlineEdit
363
+ value={event.location}
364
+ onChange={(location) => {
365
+ patch({ location })
366
+ }}
367
+ displayClassName="not-italic text-[12px]"
368
+ emptyLabel="Add venue or Online…"
369
+ />
370
+ </SidebarField>
371
+ <SidebarField label="Meeting URL">
372
+ <InlineEdit
373
+ value={event.meetingUrl ?? ''}
374
+ onChange={(meetingUrl) => {
375
+ patch({ meetingUrl: meetingUrl.length > 0 ? meetingUrl : null })
376
+ }}
377
+ type="url"
378
+ displayClassName="not-italic text-[12px]"
379
+ emptyLabel="Add meeting URL…"
380
+ />
381
+ </SidebarField>
382
+ <SidebarField label="Owner">
383
+ <EntitySearch
384
+ options={members.members.map((member) => ({
385
+ id: member.id,
386
+ label: member.name,
387
+ meta: member.email,
388
+ }))}
389
+ value={event.ownerId ?? ''}
390
+ onChange={(ownerId) => {
391
+ patch({ ownerId: ownerId.length > 0 ? ownerId : null })
392
+ }}
393
+ placeholder="Search owners…"
394
+ size="sm"
395
+ />
396
+ </SidebarField>
397
+ <SidebarField label="Tags">
398
+ <InlineEdit
399
+ value={event.tags.join(', ')}
400
+ onChange={(value) => {
401
+ patch({ tags: toTags(value) })
402
+ }}
403
+ display={
404
+ event.tags.length > 0 ? (
405
+ <span className="flex flex-wrap gap-1">
406
+ {event.tags.map((tag) => (
407
+ <Chip key={tag}>
408
+ <span className="text-[10px]">{tag}</span>
409
+ </Chip>
410
+ ))}
411
+ </span>
412
+ ) : undefined
413
+ }
414
+ emptyLabel="Add tags…"
415
+ displayClassName="not-italic"
416
+ />
417
+ </SidebarField>
418
+ </section>
419
+ )
420
+ }
421
+
422
+ function EventAttendees({ event }: { readonly event: CrmEvent }): React.JSX.Element {
423
+ const attendances = useAttendances({ eventIds: [event.id], limit: 200 })
424
+ const createAttendance = useCreateAttendance()
425
+ const [personId, setPersonId] = useState('')
426
+ const [search, setSearch] = useState('')
427
+ const [openNotesId, setOpenNotesId] = useState<string | null>(null)
428
+ const people = usePeople({
429
+ term: search.trim().length > 0 ? search.trim() : undefined,
430
+ })
431
+ const directory = usePeople({ limit: 200 })
432
+ const nameById = new Map(directory.records.map((person) => [person.id, person.name]))
433
+ const registered = new Set(attendances.records.map((row) => row.personId))
434
+
435
+ function add(submitEvent: FormEvent): void {
436
+ submitEvent.preventDefault()
437
+
438
+ if (personId.length === 0 || registered.has(personId)) {
439
+ return
440
+ }
441
+
442
+ createAttendance.run({ eventId: event.id, personId, source: 'manual' })
443
+ setPersonId('')
444
+ setSearch('')
445
+ }
446
+
447
+ return (
448
+ <section>
449
+ <SectionHeader title="Attendees" />
450
+ {createAttendance.error !== null && (
451
+ <div className="mb-3">
452
+ <ErrorPanel error={createAttendance.error} />
453
+ </div>
454
+ )}
455
+ {attendances.error !== null && <ErrorPanel error={attendances.error} />}
456
+ {attendances.isLoading ? (
457
+ <p className="text-[13px] text-ink-faint">Loading attendees…</p>
458
+ ) : (
459
+ <ul className="divide-y divide-border overflow-hidden rounded-md border border-border">
460
+ {attendances.records.length === 0 && (
461
+ <li className="px-3 py-3 text-[13px] text-ink-faint">No attendees yet.</li>
462
+ )}
463
+ {attendances.records.map((row) => (
464
+ <AttendanceRow
465
+ key={row.id}
466
+ attendance={row}
467
+ personName={nameById.get(row.personId) ?? row.personId}
468
+ notesOpen={openNotesId === row.id}
469
+ onToggleNotes={() => {
470
+ setOpenNotesId((current) => (current === row.id ? null : row.id))
471
+ }}
472
+ />
473
+ ))}
474
+ </ul>
475
+ )}
476
+ <form onSubmit={add} className="mt-3 flex flex-wrap items-end gap-2">
477
+ <div className="min-w-[220px] flex-1">
478
+ <EntitySearch
479
+ options={people.records
480
+ .filter((person) => !registered.has(person.id))
481
+ .map((person) => ({
482
+ id: person.id,
483
+ label: person.name,
484
+ meta: person.email ?? undefined,
485
+ }))}
486
+ value={personId}
487
+ onChange={setPersonId}
488
+ onQueryChange={setSearch}
489
+ placeholder="Add a person…"
490
+ size="sm"
491
+ />
492
+ </div>
493
+ <button
494
+ type="submit"
495
+ disabled={createAttendance.isPending || personId.length === 0}
496
+ className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
497
+ >
498
+ Register
499
+ </button>
500
+ </form>
501
+ </section>
502
+ )
503
+ }
504
+
505
+ function AttendanceRow({
506
+ attendance,
507
+ personName,
508
+ notesOpen,
509
+ onToggleNotes,
510
+ }: {
511
+ readonly attendance: Attendance
512
+ readonly personName: string
513
+ readonly notesOpen: boolean
514
+ readonly onToggleNotes: () => void
515
+ }): React.JSX.Element {
516
+ const updateAttendance = useUpdateAttendance()
517
+ const deleteAttendance = useDeleteAttendance()
518
+ const error = updateAttendance.error ?? deleteAttendance.error
519
+
520
+ return (
521
+ <li className="px-3 py-2">
522
+ <div className="flex flex-wrap items-center gap-2">
523
+ <Link
524
+ to={`/people/${attendance.personId}`}
525
+ className="min-w-0 flex-1 text-[13px] font-medium text-accent hover:underline"
526
+ >
527
+ {personName}
528
+ </Link>
529
+ <select
530
+ value={attendance.status}
531
+ aria-label={`Status for ${personName}`}
532
+ onChange={(event) => {
533
+ updateAttendance.run({
534
+ id: attendance.id,
535
+ changes: { status: event.target.value as AttendanceStatus },
536
+ })
537
+ }}
538
+ className="rounded-md border border-border bg-surface-raised px-2 py-1 text-[12px] outline-none focus:border-accent"
539
+ >
540
+ {ATTENDANCE_STATUSES.map((status) => (
541
+ <option key={status} value={status}>
542
+ {ATTENDANCE_STATUS_LABELS[status]}
543
+ </option>
544
+ ))}
545
+ </select>
546
+ <Chip tone={ATTENDANCE_TONES[attendance.status]}>
547
+ {ATTENDANCE_STATUS_LABELS[attendance.status]}
548
+ </Chip>
549
+ <span className="text-[11px] text-ink-faint">{attendance.source}</span>
550
+ <button
551
+ type="button"
552
+ onClick={onToggleNotes}
553
+ className="text-[11px] font-medium text-ink-muted hover:text-ink"
554
+ >
555
+ Notes
556
+ </button>
557
+ <button
558
+ type="button"
559
+ onClick={() => {
560
+ deleteAttendance.run(attendance.id)
561
+ }}
562
+ className="text-[11px] font-medium text-danger hover:underline"
563
+ >
564
+ Remove
565
+ </button>
566
+ </div>
567
+ {error !== null && (
568
+ <div className="mt-2">
569
+ <ErrorPanel error={error} />
570
+ </div>
571
+ )}
572
+ {notesOpen && (
573
+ <div className="mt-3">
574
+ <NotesPanel targetType="attendance" targetId={attendance.id} />
575
+ </div>
576
+ )}
577
+ </li>
578
+ )
579
+ }
580
+
581
+ function EventAssociationsCard({ event }: { readonly event: CrmEvent }): React.JSX.Element {
582
+ const { patch, error } = useEventPatch(event)
583
+ const [kind, setKind] = useState<EventAssociationTargetType>('deal')
584
+ const [pick, setPick] = useState('')
585
+ const [search, setSearch] = useState('')
586
+ const options = useAssociationOptions(kind, search)
587
+ const names = useAssociationNames()
588
+ const chosenKey = new Set(event.associations.map((row) => `${row.targetType}:${row.targetId}`))
589
+
590
+ function add(): void {
591
+ if (pick.length === 0 || chosenKey.has(`${kind}:${pick}`)) {
592
+ return
593
+ }
594
+
595
+ patch({
596
+ associations: [...event.associations, { targetType: kind, targetId: pick }],
597
+ })
598
+ setPick('')
599
+ setSearch('')
600
+ }
601
+
602
+ function remove(target: EventAssociation): void {
603
+ patch({
604
+ associations: event.associations.filter(
605
+ (row) => row.targetType !== target.targetType || row.targetId !== target.targetId,
606
+ ),
607
+ })
608
+ }
609
+
610
+ return (
611
+ <section className="rounded-md border border-border p-3">
612
+ <SectionHeader title="Linked records" />
613
+ {error !== null && (
614
+ <div className="mb-2">
615
+ <ErrorPanel error={error} />
616
+ </div>
617
+ )}
618
+ <ul className="mb-3 divide-y divide-border">
619
+ {event.associations.length === 0 && (
620
+ <li className="py-2 text-[12px] text-ink-faint">No linked records yet.</li>
621
+ )}
622
+ {event.associations.map((row) => {
623
+ const route = ASSOCIATION_ROUTES[row.targetType]
624
+ const label = names.get(`${row.targetType}:${row.targetId}`) ?? row.targetId
625
+
626
+ return (
627
+ <li
628
+ key={`${row.targetType}:${row.targetId}`}
629
+ className="flex items-center justify-between gap-2 py-2"
630
+ >
631
+ {route === undefined ? (
632
+ <span className="text-[12px] text-ink">
633
+ {EVENT_ASSOCIATION_TARGET_TYPE_LABELS[row.targetType]} · {label}
634
+ </span>
635
+ ) : (
636
+ <Link
637
+ to={`${route}/${row.targetId}`}
638
+ className="text-[12px] font-medium text-accent hover:underline"
639
+ >
640
+ {EVENT_ASSOCIATION_TARGET_TYPE_LABELS[row.targetType]} · {label}
641
+ </Link>
642
+ )}
643
+ <button
644
+ type="button"
645
+ onClick={() => {
646
+ remove(row)
647
+ }}
648
+ className="text-[11px] font-medium text-danger hover:underline"
649
+ >
650
+ Remove
651
+ </button>
652
+ </li>
653
+ )
654
+ })}
655
+ </ul>
656
+ <div className="space-y-2">
657
+ <select
658
+ value={kind}
659
+ aria-label="Record type"
660
+ onChange={(event) => {
661
+ setKind(event.target.value as EventAssociationTargetType)
662
+ setPick('')
663
+ setSearch('')
664
+ }}
665
+ className="w-full rounded-md border border-border bg-surface-raised px-2 py-1.5 text-[12px] outline-none focus:border-accent"
666
+ >
667
+ {EVENT_ASSOCIATION_TARGET_TYPES.map((type) => (
668
+ <option key={type} value={type}>
669
+ {EVENT_ASSOCIATION_TARGET_TYPE_LABELS[type]}
670
+ </option>
671
+ ))}
672
+ </select>
673
+ <EntitySearch
674
+ options={options.filter((option) => !chosenKey.has(`${kind}:${option.id}`))}
675
+ value={pick}
676
+ onChange={setPick}
677
+ onQueryChange={setSearch}
678
+ placeholder={`Search ${EVENT_ASSOCIATION_TARGET_TYPE_LABELS[kind].toLowerCase()}s…`}
679
+ size="sm"
680
+ />
681
+ <button
682
+ type="button"
683
+ onClick={add}
684
+ disabled={pick.length === 0}
685
+ className="w-full rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
686
+ >
687
+ Link
688
+ </button>
689
+ </div>
690
+ </section>
691
+ )
692
+ }
693
+
694
+ interface NamedOption {
695
+ readonly id: string
696
+ readonly label: string
697
+ }
698
+
699
+ function useAssociationOptions(kind: EventAssociationTargetType, search: string): readonly NamedOption[] {
700
+ const term = search.trim().length > 0 ? search.trim() : undefined
701
+ const people = usePeople({ term }, { enabled: kind === 'person' })
702
+ const companies = useCompanies({ term }, { enabled: kind === 'company' })
703
+ const deals = useDeals({ term }, { enabled: kind === 'deal' })
704
+ const opportunities = useOpportunities({ term }, { enabled: kind === 'opportunity' })
705
+ const partnerships = usePartnerships({ term }, { enabled: kind === 'partnership' })
706
+ const raises = useRaises({ term }, { enabled: kind === 'raise' })
707
+ const enquiries = useEnquiries({ term }, { enabled: kind === 'enquiry' })
708
+ const roles = useRoles({ term }, { enabled: kind === 'role' })
709
+ const candidates = useCandidates({}, { enabled: kind === 'candidate' })
710
+ const candidatePeople = usePeople({ limit: 200 }, { enabled: kind === 'candidate' })
711
+ const personName = new Map(candidatePeople.records.map((person) => [person.id, person.name]))
712
+
713
+ switch (kind) {
714
+ case 'person':
715
+ return people.records.map((record) => ({ id: record.id, label: record.name }))
716
+ case 'company':
717
+ return companies.records.map((record) => ({ id: record.id, label: record.name }))
718
+ case 'deal':
719
+ return deals.records.map((record) => ({ id: record.id, label: record.name }))
720
+ case 'opportunity':
721
+ return opportunities.records.map((record) => ({ id: record.id, label: record.name }))
722
+ case 'partnership':
723
+ return partnerships.records.map((record) => ({ id: record.id, label: record.name }))
724
+ case 'raise':
725
+ return raises.records.map((record) => ({ id: record.id, label: record.name }))
726
+ case 'enquiry':
727
+ return enquiries.records.map((record) => ({ id: record.id, label: record.name }))
728
+ case 'role':
729
+ return roles.records.map((record) => ({ id: record.id, label: record.title }))
730
+ case 'candidate':
731
+ return candidates.records.map((record) => ({
732
+ id: record.id,
733
+ label: personName.get(record.personId) ?? record.id,
734
+ }))
735
+ }
736
+ }
737
+
738
+ function useAssociationNames(): ReadonlyMap<string, string> {
739
+ const people = usePeople({ limit: 200 })
740
+ const companies = useCompanies({ limit: 200 })
741
+ const deals = useDeals({ limit: 200 })
742
+ const opportunities = useOpportunities({ limit: 200 })
743
+ const partnerships = usePartnerships({ limit: 200 })
744
+ const raises = useRaises({ limit: 200 })
745
+ const enquiries = useEnquiries({ limit: 200 })
746
+ const roles = useRoles({ limit: 200 })
747
+ const candidates = useCandidates({ limit: 200 })
748
+ const personName = new Map(people.records.map((person) => [person.id, person.name]))
749
+ const names = new Map<string, string>()
750
+
751
+ for (const record of people.records) {
752
+ names.set(`person:${record.id}`, record.name)
753
+ }
754
+
755
+ for (const record of companies.records) {
756
+ names.set(`company:${record.id}`, record.name)
757
+ }
758
+
759
+ for (const record of deals.records) {
760
+ names.set(`deal:${record.id}`, record.name)
761
+ }
762
+
763
+ for (const record of opportunities.records) {
764
+ names.set(`opportunity:${record.id}`, record.name)
765
+ }
766
+
767
+ for (const record of partnerships.records) {
768
+ names.set(`partnership:${record.id}`, record.name)
769
+ }
770
+
771
+ for (const record of raises.records) {
772
+ names.set(`raise:${record.id}`, record.name)
773
+ }
774
+
775
+ for (const record of enquiries.records) {
776
+ names.set(`enquiry:${record.id}`, record.name)
777
+ }
778
+
779
+ for (const record of roles.records) {
780
+ names.set(`role:${record.id}`, record.title)
781
+ }
782
+
783
+ for (const record of candidates.records) {
784
+ names.set(`candidate:${record.id}`, personName.get(record.personId) ?? record.id)
785
+ }
786
+
787
+ return names
788
+ }
789
+
790
+ function toDatetimeLocal(value: Date): string {
791
+ const pad = (digit: number): string => String(digit).padStart(2, '0')
792
+
793
+ return `${String(value.getFullYear())}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`
794
+ }