@kelpie/ui 0.12.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 (300) 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 +21 -4
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +12 -2
  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/admin/DataPage.js +10 -9
  156. package/dist/pages/admin/DataPage.js.map +1 -1
  157. package/dist/pages/admin/FieldsPage.js +1 -1
  158. package/dist/pages/admin/FieldsPage.js.map +1 -1
  159. package/dist/pages/admin/ModulesPage.d.ts.map +1 -1
  160. package/dist/pages/admin/ModulesPage.js +1 -0
  161. package/dist/pages/admin/ModulesPage.js.map +1 -1
  162. package/dist/pages/admin/WorkspaceSettingsPage.d.ts +12 -0
  163. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
  164. package/dist/pages/admin/WorkspaceSettingsPage.js +2 -21
  165. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
  166. package/dist/pages/auth/AuthLayout.d.ts +5 -4
  167. package/dist/pages/auth/AuthLayout.d.ts.map +1 -1
  168. package/dist/pages/auth/AuthLayout.js +7 -5
  169. package/dist/pages/auth/AuthLayout.js.map +1 -1
  170. package/dist/pages/auth/RegionSwitcher.d.ts +10 -0
  171. package/dist/pages/auth/RegionSwitcher.d.ts.map +1 -0
  172. package/dist/pages/auth/RegionSwitcher.js +28 -0
  173. package/dist/pages/auth/RegionSwitcher.js.map +1 -0
  174. package/dist/pages/auth/SignInPage.d.ts.map +1 -1
  175. package/dist/pages/auth/SignInPage.js +3 -1
  176. package/dist/pages/auth/SignInPage.js.map +1 -1
  177. package/dist/pages/auth/SignUpPage.d.ts.map +1 -1
  178. package/dist/pages/auth/SignUpPage.js +7 -0
  179. package/dist/pages/auth/SignUpPage.js.map +1 -1
  180. package/dist/pages/auth/regionHref.d.ts +6 -0
  181. package/dist/pages/auth/regionHref.d.ts.map +1 -0
  182. package/dist/pages/auth/regionHref.js +8 -0
  183. package/dist/pages/auth/regionHref.js.map +1 -0
  184. package/dist/pages/dashboard/DashboardPage.d.ts +8 -0
  185. package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -1
  186. package/dist/pages/dashboard/DashboardPage.js +5 -13
  187. package/dist/pages/dashboard/DashboardPage.js.map +1 -1
  188. package/dist/pages/dashboard/attention.d.ts +1 -8
  189. package/dist/pages/dashboard/attention.d.ts.map +1 -1
  190. package/dist/pages/dashboard/attention.js +21 -54
  191. package/dist/pages/dashboard/attention.js.map +1 -1
  192. package/dist/pages/dashboard/sections.d.ts.map +1 -1
  193. package/dist/pages/dashboard/sections.js +3 -2
  194. package/dist/pages/dashboard/sections.js.map +1 -1
  195. package/dist/pages/forms/FormActions.d.ts.map +1 -1
  196. package/dist/pages/forms/FormActions.js +7 -3
  197. package/dist/pages/forms/FormActions.js.map +1 -1
  198. package/dist/pages/onboarding/HandbookStepPage.d.ts +4 -8
  199. package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -1
  200. package/dist/pages/onboarding/HandbookStepPage.js +13 -13
  201. package/dist/pages/onboarding/HandbookStepPage.js.map +1 -1
  202. package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -1
  203. package/dist/pages/onboarding/InvitesStepPage.js +13 -7
  204. package/dist/pages/onboarding/InvitesStepPage.js.map +1 -1
  205. package/dist/pages/onboarding/ModulesStepPage.d.ts +11 -0
  206. package/dist/pages/onboarding/ModulesStepPage.d.ts.map +1 -0
  207. package/dist/pages/onboarding/ModulesStepPage.js +80 -0
  208. package/dist/pages/onboarding/ModulesStepPage.js.map +1 -0
  209. package/dist/pages/onboarding/OnboardingNav.d.ts +17 -0
  210. package/dist/pages/onboarding/OnboardingNav.d.ts.map +1 -0
  211. package/dist/pages/onboarding/OnboardingNav.js +37 -0
  212. package/dist/pages/onboarding/OnboardingNav.js.map +1 -0
  213. package/dist/pages/onboarding/OrganisationStepPage.d.ts +13 -0
  214. package/dist/pages/onboarding/OrganisationStepPage.d.ts.map +1 -0
  215. package/dist/pages/onboarding/OrganisationStepPage.js +59 -0
  216. package/dist/pages/onboarding/OrganisationStepPage.js.map +1 -0
  217. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
  218. package/dist/pages/onboarding/WorkspaceStepPage.js +25 -20
  219. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
  220. package/dist/pages/onboarding/handbookTemplates.d.ts +19 -0
  221. package/dist/pages/onboarding/handbookTemplates.d.ts.map +1 -0
  222. package/dist/pages/onboarding/handbookTemplates.js +57 -0
  223. package/dist/pages/onboarding/handbookTemplates.js.map +1 -0
  224. package/dist/pages/onboarding/onboardingModules.d.ts +23 -0
  225. package/dist/pages/onboarding/onboardingModules.d.ts.map +1 -0
  226. package/dist/pages/onboarding/onboardingModules.js +153 -0
  227. package/dist/pages/onboarding/onboardingModules.js.map +1 -0
  228. package/dist/pages/onboarding/onboardingRerun.d.ts +21 -0
  229. package/dist/pages/onboarding/onboardingRerun.d.ts.map +1 -0
  230. package/dist/pages/onboarding/onboardingRerun.js +33 -0
  231. package/dist/pages/onboarding/onboardingRerun.js.map +1 -0
  232. package/package.json +4 -3
  233. package/src/api/resources/attendances.ts +54 -0
  234. package/src/api/resources/eventAssociations.ts +43 -0
  235. package/src/api/resources/events.ts +70 -0
  236. package/src/api/resources/handbookSeed.ts +63 -0
  237. package/src/api/resources/moduleSettings.ts +25 -0
  238. package/src/api/resources/planItems.ts +5 -5
  239. package/src/api/resources/publicConfig.ts +3 -3
  240. package/src/api/resources/sampleData.ts +2 -0
  241. package/src/api/resources/session.ts +59 -0
  242. package/src/api/resources/workspace.ts +2 -3
  243. package/src/app/KelpieApp.tsx +11 -3
  244. package/src/components/ActivitiesPanel.tsx +2 -0
  245. package/src/components/AddressesField.tsx +502 -0
  246. package/src/components/CountrySearch.tsx +71 -0
  247. package/src/components/EnvironmentBanner.tsx +23 -6
  248. package/src/components/InlineEdit.tsx +35 -1
  249. package/src/components/LinkedEventsPanel.tsx +50 -0
  250. package/src/components/NotesPanel.tsx +132 -15
  251. package/src/components/PlanAttention.tsx +5 -4
  252. package/src/components/PlanPanel.tsx +3 -3
  253. package/src/components/Shell.tsx +16 -1
  254. package/src/components/SocialNetworkIcon.tsx +88 -0
  255. package/src/components/SocialProfilesField.tsx +57 -10
  256. package/src/components/TimezoneSearch.tsx +67 -0
  257. package/src/components/WorkspaceSwitcher.tsx +136 -0
  258. package/src/components/environmentTone.ts +40 -0
  259. package/src/index.ts +42 -3
  260. package/src/lib/timezones.ts +43 -0
  261. package/src/pages/CompaniesPage.tsx +14 -13
  262. package/src/pages/CompanyDetail.tsx +59 -27
  263. package/src/pages/DealDetail.tsx +5 -0
  264. package/src/pages/DecisionsPage.tsx +7 -0
  265. package/src/pages/EnquiryDetail.tsx +7 -0
  266. package/src/pages/EventDetail.tsx +794 -0
  267. package/src/pages/EventsPage.tsx +382 -0
  268. package/src/pages/ListDetail.tsx +24 -0
  269. package/src/pages/OpportunityDetail.tsx +7 -0
  270. package/src/pages/PartnershipDetail.tsx +7 -0
  271. package/src/pages/PeoplePage.tsx +18 -5
  272. package/src/pages/PersonDetail.tsx +300 -100
  273. package/src/pages/PlanningPage.tsx +17 -14
  274. package/src/pages/RaiseDetail.tsx +5 -0
  275. package/src/pages/RoleDetail.tsx +8 -0
  276. package/src/pages/SearchPage.tsx +2 -1
  277. package/src/pages/account/PreferencesPage.tsx +2 -37
  278. package/src/pages/admin/DataPage.tsx +63 -21
  279. package/src/pages/admin/FieldsPage.tsx +1 -1
  280. package/src/pages/admin/ModulesPage.tsx +1 -0
  281. package/src/pages/admin/WorkspaceSettingsPage.tsx +10 -33
  282. package/src/pages/auth/AuthLayout.tsx +8 -4
  283. package/src/pages/auth/RegionSwitcher.tsx +56 -0
  284. package/src/pages/auth/SignInPage.tsx +10 -6
  285. package/src/pages/auth/SignUpPage.tsx +25 -0
  286. package/src/pages/auth/regionHref.ts +10 -0
  287. package/src/pages/dashboard/DashboardPage.tsx +16 -48
  288. package/src/pages/dashboard/attention.ts +24 -78
  289. package/src/pages/dashboard/sections.tsx +8 -2
  290. package/src/pages/forms/FormActions.tsx +14 -8
  291. package/src/pages/onboarding/HandbookStepPage.tsx +21 -24
  292. package/src/pages/onboarding/InvitesStepPage.tsx +27 -19
  293. package/src/pages/onboarding/ModulesStepPage.tsx +145 -0
  294. package/src/pages/onboarding/OnboardingNav.tsx +97 -0
  295. package/src/pages/onboarding/OrganisationStepPage.tsx +130 -0
  296. package/src/pages/onboarding/WorkspaceStepPage.tsx +53 -38
  297. package/src/pages/onboarding/handbookTemplates.ts +77 -0
  298. package/src/pages/onboarding/onboardingModules.ts +202 -0
  299. package/src/pages/onboarding/onboardingRerun.ts +41 -0
  300. package/src/styles.css +30 -0
@@ -0,0 +1,136 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { useNavigate } from 'react-router'
3
+
4
+ import { useAccountWorkspaces, useSession, useSwitchWorkspace } from '../api/resources/session.ts'
5
+
6
+ /**
7
+ * Header control: the workspace this session is in, and a menu to move it.
8
+ *
9
+ * The list and the switch both live under `/v1/auth`, so a suspended current
10
+ * workspace still lets the reader see the other memberships and leave.
11
+ */
12
+
13
+ export function WorkspaceSwitcher(): React.JSX.Element | null {
14
+ const navigate = useNavigate()
15
+ const { session } = useSession()
16
+ const { workspaces, isLoading } = useAccountWorkspaces()
17
+ const switchWorkspace = useSwitchWorkspace()
18
+ const [open, setOpen] = useState(false)
19
+ const menuRef = useRef<HTMLDivElement>(null)
20
+ const currentId = session?.workspaceId ?? null
21
+ const current = workspaces.find((workspace) => workspace.id === currentId)
22
+
23
+ useEffect(() => {
24
+ if (!open) {
25
+ return
26
+ }
27
+
28
+ function onPointerDown(event: MouseEvent): void {
29
+ if (menuRef.current?.contains(event.target as Node) !== true) {
30
+ setOpen(false)
31
+ }
32
+ }
33
+
34
+ function onKeyDown(event: KeyboardEvent): void {
35
+ if (event.key === 'Escape') {
36
+ setOpen(false)
37
+ }
38
+ }
39
+
40
+ document.addEventListener('mousedown', onPointerDown)
41
+ document.addEventListener('keydown', onKeyDown)
42
+
43
+ return () => {
44
+ document.removeEventListener('mousedown', onPointerDown)
45
+ document.removeEventListener('keydown', onKeyDown)
46
+ }
47
+ }, [open])
48
+
49
+ if (isLoading && workspaces.length === 0) {
50
+ return (
51
+ <span className="max-w-[10rem] truncate px-2 py-1 text-[12px] text-ink-faint" aria-hidden>
52
+ Workspace
53
+ </span>
54
+ )
55
+ }
56
+
57
+ if (workspaces.length === 0) {
58
+ return null
59
+ }
60
+
61
+ const label = current?.name ?? 'Workspace'
62
+
63
+ return (
64
+ <div className="relative" ref={menuRef}>
65
+ <button
66
+ type="button"
67
+ onClick={() => {
68
+ setOpen((value) => !value)
69
+ }}
70
+ aria-expanded={open}
71
+ aria-haspopup="menu"
72
+ aria-label="Switch workspace"
73
+ title={label}
74
+ className="inline-flex max-w-[12rem] items-center gap-1 rounded-md px-2 py-1 text-[12px] text-ink-muted transition hover:bg-surface-sunken hover:text-ink"
75
+ >
76
+ <span className="truncate">{label}</span>
77
+ <svg viewBox="0 0 12 12" aria-hidden className="h-2.5 w-2.5 shrink-0">
78
+ <path
79
+ d="M2.5 4.5 L6 8 L9.5 4.5"
80
+ fill="none"
81
+ stroke="currentColor"
82
+ strokeWidth="1.5"
83
+ strokeLinecap="round"
84
+ strokeLinejoin="round"
85
+ />
86
+ </svg>
87
+ </button>
88
+ {open && (
89
+ <div
90
+ role="menu"
91
+ className="absolute right-0 z-20 mt-1.5 w-56 overflow-hidden rounded-md border border-border bg-surface-raised py-1"
92
+ >
93
+ {workspaces.map((workspace) => {
94
+ const isCurrent = workspace.id === currentId
95
+
96
+ return (
97
+ <button
98
+ key={workspace.id}
99
+ type="button"
100
+ role="menuitem"
101
+ aria-current={isCurrent ? 'true' : undefined}
102
+ disabled={switchWorkspace.isPending}
103
+ onClick={() => {
104
+ if (isCurrent) {
105
+ setOpen(false)
106
+ return
107
+ }
108
+
109
+ switchWorkspace
110
+ .runAsync({ workspaceId: workspace.id })
111
+ .then(() => {
112
+ setOpen(false)
113
+ void navigate('/dashboard', { replace: true })
114
+ })
115
+ .catch(() => undefined)
116
+ }}
117
+ className="block w-full px-3 py-1.5 text-left hover:bg-surface-sunken disabled:opacity-60"
118
+ >
119
+ <div className="truncate text-[13px] text-ink">{workspace.name}</div>
120
+ <div className="truncate text-[11px] text-ink-faint capitalize">
121
+ {workspace.role}
122
+ {isCurrent ? ' · current' : ''}
123
+ </div>
124
+ </button>
125
+ )
126
+ })}
127
+ {switchWorkspace.error !== null && (
128
+ <p className="border-t border-border px-3 py-1.5 text-[12px] text-danger">
129
+ {switchWorkspace.error.message}
130
+ </p>
131
+ )}
132
+ </div>
133
+ )}
134
+ </div>
135
+ )
136
+ }
@@ -0,0 +1,40 @@
1
+ export type EnvironmentTone = 'dev' | 'demo' | 'staging' | 'test' | 'cloud'
2
+
3
+ const NAMED_TONES: Readonly<Record<string, EnvironmentTone>> = {
4
+ development: 'dev',
5
+ dev: 'dev',
6
+ local: 'dev',
7
+ demo: 'demo',
8
+ staging: 'staging',
9
+ stage: 'staging',
10
+ preview: 'staging',
11
+ test: 'test',
12
+ cloud: 'cloud',
13
+ }
14
+
15
+ const TONES: readonly EnvironmentTone[] = ['dev', 'demo', 'staging', 'test', 'cloud']
16
+
17
+ /**
18
+ * Picks a banner colour from a site name or runtime-mode fallback.
19
+ *
20
+ * Known names keep a stable colour so four tabs (dev, demo, cloud, staging)
21
+ * never collide. Anything else hashes onto the same five colours so an
22
+ * unknown name still looks different from local.
23
+ */
24
+ export function environmentTone(label: string): EnvironmentTone {
25
+ const key = label.trim().toLowerCase()
26
+ const named = NAMED_TONES[key]
27
+ if (named !== undefined) {
28
+ return named
29
+ }
30
+
31
+ return TONES[hashLabel(key) % TONES.length] ?? 'dev'
32
+ }
33
+
34
+ function hashLabel(value: string): number {
35
+ let hash = 0
36
+ for (const char of value) {
37
+ hash = (hash * 31 + char.charCodeAt(0)) >>> 0
38
+ }
39
+ return hash
40
+ }
package/src/index.ts CHANGED
@@ -44,6 +44,26 @@ export {
44
44
  } from './api/resources/people.ts'
45
45
  export type { CreatePersonInput, PeopleFilters } from './api/resources/people.ts'
46
46
 
47
+ export {
48
+ useCreateEvent,
49
+ useDeleteEvent,
50
+ useEvent,
51
+ useEvents,
52
+ useUpdateEvent,
53
+ } from './api/resources/events.ts'
54
+ export type { EventFilters } from './api/resources/events.ts'
55
+
56
+ export {
57
+ useAttendances,
58
+ useCreateAttendance,
59
+ useDeleteAttendance,
60
+ useUpdateAttendance,
61
+ } from './api/resources/attendances.ts'
62
+ export type { AttendanceFilters } from './api/resources/attendances.ts'
63
+
64
+ export { useEventAssociationsFor } from './api/resources/eventAssociations.ts'
65
+ export type { EventAssociationsResult } from './api/resources/eventAssociations.ts'
66
+
47
67
  export {
48
68
  useCreatePosition,
49
69
  useDeletePosition,
@@ -73,6 +93,7 @@ export type { DecisionFilters } from './api/resources/decisions.ts'
73
93
 
74
94
  export {
75
95
  useAcceptInvite,
96
+ useAccountWorkspaces,
76
97
  useConfirmPasswordReset,
77
98
  useCreateWorkspace,
78
99
  useLogIn,
@@ -80,8 +101,9 @@ export {
80
101
  useRequestPasswordReset,
81
102
  useSession,
82
103
  useSignUp,
104
+ useSwitchWorkspace,
83
105
  } from './api/resources/session.ts'
84
- export type { SessionState } from './api/resources/session.ts'
106
+ export type { AccountWorkspacesState, SessionState } from './api/resources/session.ts'
85
107
 
86
108
  export { fetchServiceHealth } from './api/health.ts'
87
109
  export type { HealthRequestOptions, ServiceHealth } from './api/health.ts'
@@ -121,14 +143,30 @@ export type { AddButtonProps, SectionHeaderProps } from './components/SectionHea
121
143
  export { Shell } from './components/Shell.tsx'
122
144
  export { SidebarField } from './components/SidebarField.tsx'
123
145
  export type { SidebarFieldProps } from './components/SidebarField.tsx'
124
- export { SocialProfilesField } from './components/SocialProfilesField.tsx'
125
- export type { SocialProfilesFieldProps } from './components/SocialProfilesField.tsx'
146
+ export { AddressSidebarLink, AddressesField } from './components/AddressesField.tsx'
147
+ export type { AddressSidebarLinkProps, AddressesFieldProps } from './components/AddressesField.tsx'
148
+ export { CountrySearch } from './components/CountrySearch.tsx'
149
+ export type { CountrySearchProps } from './components/CountrySearch.tsx'
150
+ export { SocialNetworkIcon, SocialNetworkName } from './components/SocialNetworkIcon.tsx'
151
+ export type {
152
+ SocialNetworkIconProps,
153
+ SocialNetworkNameProps,
154
+ } from './components/SocialNetworkIcon.tsx'
155
+ export { SocialProfileIcons, SocialProfilesField } from './components/SocialProfilesField.tsx'
156
+ export type {
157
+ SocialProfileIconsProps,
158
+ SocialProfilesFieldProps,
159
+ } from './components/SocialProfilesField.tsx'
126
160
  export { SummaryBlock } from './components/SummaryBlock.tsx'
127
161
  export type { SummaryBlockProps } from './components/SummaryBlock.tsx'
162
+ export { TimezoneSearch } from './components/TimezoneSearch.tsx'
163
+ export type { TimezoneSearchProps } from './components/TimezoneSearch.tsx'
128
164
 
129
165
  export { CompaniesPage } from './pages/CompaniesPage.tsx'
130
166
  export { CompanyDetail } from './pages/CompanyDetail.tsx'
131
167
  export { DecisionsPage } from './pages/DecisionsPage.tsx'
168
+ export { EventDetail } from './pages/EventDetail.tsx'
169
+ export { EventsPage } from './pages/EventsPage.tsx'
132
170
  export { PeoplePage } from './pages/PeoplePage.tsx'
133
171
  export { PersonDetail } from './pages/PersonDetail.tsx'
134
172
  export { PlanningPage } from './pages/PlanningPage.tsx'
@@ -143,6 +181,7 @@ export { SignInPage } from './pages/auth/SignInPage.tsx'
143
181
  export { SignUpPage } from './pages/auth/SignUpPage.tsx'
144
182
  export { HandbookStepPage } from './pages/onboarding/HandbookStepPage.tsx'
145
183
  export { InvitesStepPage } from './pages/onboarding/InvitesStepPage.tsx'
184
+ export { OrganisationStepPage } from './pages/onboarding/OrganisationStepPage.tsx'
146
185
  export { WorkspaceStepPage } from './pages/onboarding/WorkspaceStepPage.tsx'
147
186
 
148
187
  export {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * IANA zone names this runtime can resolve, matching the server's
3
+ * `timezoneSchema`. UTC is always first: some engines omit it from
4
+ * `supportedValuesOf` even though `DateTimeFormat` accepts it.
5
+ *
6
+ * Falls back to the short list the mockup offered if `supportedValuesOf` is
7
+ * missing, for a browser old enough to lack it.
8
+ */
9
+
10
+ const FALLBACK_TIMEZONES: readonly string[] = [
11
+ 'UTC',
12
+ 'Australia/Sydney',
13
+ 'Australia/Melbourne',
14
+ 'America/Los_Angeles',
15
+ 'America/New_York',
16
+ 'Europe/London',
17
+ ]
18
+
19
+ function readTimezones(): readonly string[] {
20
+ try {
21
+ const supported = Intl.supportedValuesOf('timeZone')
22
+
23
+ return ['UTC', ...supported.filter((zone) => zone !== 'UTC')]
24
+ } catch {
25
+ return FALLBACK_TIMEZONES
26
+ }
27
+ }
28
+
29
+ export const ALL_TIMEZONES: readonly string[] = readTimezones()
30
+
31
+ /**
32
+ * The full list, with `current` prepended when a stored zone is not in it.
33
+ *
34
+ * A value the picker does not know must still render, rather than being
35
+ * rewritten to the first option on the next save.
36
+ */
37
+ export function timezonesIncluding(current: string): readonly string[] {
38
+ if (current.length === 0 || ALL_TIMEZONES.includes(current)) {
39
+ return ALL_TIMEZONES
40
+ }
41
+
42
+ return [current, ...ALL_TIMEZONES]
43
+ }
@@ -1,3 +1,4 @@
1
+ import { formatAddress, primaryAddress } from '@kelpie/schemas'
1
2
  import type { Company, IcpFit } from '@kelpie/schemas'
2
3
  import { useState } from 'react'
3
4
  import { useNavigate } from 'react-router'
@@ -32,7 +33,7 @@ const ICP_TONES: Readonly<Record<IcpFit, ChipTone>> = {
32
33
  unknown: 'neutral',
33
34
  }
34
35
 
35
- const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'domain', 'hq', 'type', 'updatedAt']
36
+ const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'domain', 'type', 'updatedAt']
36
37
 
37
38
  const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
38
39
 
@@ -70,14 +71,6 @@ export function CompaniesPage(): React.JSX.Element {
70
71
  <span className="font-mono text-[12px] text-ink-muted">{company.domain ?? '—'}</span>
71
72
  ),
72
73
  },
73
- {
74
- key: 'website',
75
- header: 'Website',
76
- getSortValue: (company) => company.website,
77
- render: (company) => (
78
- <span className="font-mono text-[12px] text-ink-muted">{company.website ?? '—'}</span>
79
- ),
80
- },
81
74
  {
82
75
  key: 'industry',
83
76
  header: 'Industry',
@@ -85,10 +78,18 @@ export function CompaniesPage(): React.JSX.Element {
85
78
  render: (company) => company.industry ?? '—',
86
79
  },
87
80
  {
88
- key: 'hq',
89
- header: 'HQ',
90
- getSortValue: (company) => company.hq,
91
- render: (company) => company.hq ?? '—',
81
+ key: 'addresses',
82
+ header: 'Address',
83
+ getSortValue: (company) => {
84
+ const address = primaryAddress(company.addresses)
85
+
86
+ return address === undefined ? null : formatAddress(address)
87
+ },
88
+ render: (company) => {
89
+ const address = primaryAddress(company.addresses)
90
+
91
+ return address === undefined ? '—' : formatAddress(address)
92
+ },
92
93
  },
93
94
  {
94
95
  key: 'type',
@@ -1,4 +1,11 @@
1
- import { ACCOUNT_TYPES, COMPANY_STAGES, ICP_FITS, SIZE_BANDS } from '@kelpie/schemas'
1
+ import {
2
+ ACCOUNT_TYPES,
3
+ COMPANY_ADDRESS_KIND_LABELS,
4
+ COMPANY_ADDRESS_KINDS,
5
+ COMPANY_STAGES,
6
+ ICP_FITS,
7
+ SIZE_BANDS,
8
+ } from '@kelpie/schemas'
2
9
  import type { AccountType, Company, CompanyInput, CompanyStage, IcpFit, SizeBand } from '@kelpie/schemas'
3
10
  import { useState } from 'react'
4
11
  import type { FormEvent } from 'react'
@@ -14,6 +21,7 @@ import {
14
21
  import { useDeals } from '../api/resources/deals.ts'
15
22
  import { useEnquiries } from '../api/resources/enquiries.ts'
16
23
  import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
24
+ import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
17
25
  import { useOpportunities } from '../api/resources/opportunities.ts'
18
26
  import { usePartnerships } from '../api/resources/partnerships.ts'
19
27
  import { usePeople } from '../api/resources/people.ts'
@@ -24,6 +32,7 @@ import {
24
32
  useUpdatePositionTitle,
25
33
  } from '../api/resources/positions.ts'
26
34
  import { useRaises } from '../api/resources/raises.ts'
35
+ import { AddressSidebarLink, AddressesField } from '../components/AddressesField.tsx'
27
36
  import { ActivitiesPanel, LatestActivity } from '../components/ActivitiesPanel.tsx'
28
37
  import { AgentTasks } from '../components/AgentTasks.tsx'
29
38
  import { Chip } from '../components/Chip.tsx'
@@ -36,6 +45,7 @@ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
36
45
  import { useHasCustomFields } from '../components/useHasCustomFields.ts'
37
46
  import { InlineEdit } from '../components/InlineEdit.tsx'
38
47
  import { ListsPanel } from '../components/ListsPanel.tsx'
48
+ import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
39
49
  import { NotesPanel } from '../components/NotesPanel.tsx'
40
50
  import { RelatedPlanAttention } from '../components/PlanAttention.tsx'
41
51
  import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
@@ -51,9 +61,10 @@ import { toOptions, toTags } from './fields.ts'
51
61
  /**
52
62
  * One company.
53
63
  *
54
- * Overview, Activity, Notes, Decisions, Lists and People render today. The
55
- * tabs for Deals, Opportunities, Partnerships and Raises wait for their
56
- * endpoints. A UI module can add its own through the `company` record-tab slot.
64
+ * Overview, Addresses, Activity, Notes, Decisions, Lists and People render
65
+ * today. The tabs for Deals, Opportunities, Partnerships and Raises wait for
66
+ * their endpoints. A UI module can add its own through the `company`
67
+ * record-tab slot.
57
68
  */
58
69
 
59
70
  const STAGE_OPTIONS = toOptions(COMPANY_STAGES)
@@ -75,6 +86,7 @@ export function CompanyDetail(): React.JSX.Element {
75
86
  const deleteCompany = useDeleteCompany()
76
87
  const moduleTabs = inSlotOrder(useRecordTabs('company'))
77
88
  const hasCustomFields = useHasCustomFields('company')
89
+ const eventsEnabled = useModuleEnabled('events')
78
90
  const [activeTab, setActiveTab] = useState('overview')
79
91
  const formSubmissions = useFormSubmissionsForRecord('company', id)
80
92
 
@@ -92,12 +104,14 @@ export function CompanyDetail(): React.JSX.Element {
92
104
 
93
105
  const tabs: readonly RecordTabDescriptor<string>[] = [
94
106
  { id: 'overview', label: 'Overview' },
107
+ { id: 'addresses', label: 'Addresses', count: record.addresses.length },
95
108
  ...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
96
109
  { id: 'activity', label: 'Activity' },
97
110
  { id: 'people', label: 'People' },
98
111
  { id: 'notes', label: 'Notes' },
99
112
  { id: 'decisions', label: 'Decisions' },
100
113
  { id: 'lists', label: 'Lists' },
114
+ ...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
101
115
  ...(formSubmissions.records.length === 0
102
116
  ? []
103
117
  : [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
@@ -142,19 +156,28 @@ export function CompanyDetail(): React.JSX.Element {
142
156
  ariaLabel="Company sections"
143
157
  >
144
158
  {active === 'overview' && <CompanyOverview company={record} />}
159
+ {active === 'addresses' && <CompanyAddresses company={record} />}
145
160
  {active === 'fields' && <CompanyFields company={record} />}
146
161
  {active === 'activity' && <ActivitiesPanel targetType="company" targetId={record.id} />}
147
162
  {active === 'people' && <CompanyPeople company={record} />}
148
163
  {active === 'notes' && <NotesPanel targetType="company" targetId={record.id} />}
149
164
  {active === 'decisions' && <DecisionsPanel targetType="company" targetId={record.id} />}
150
165
  {active === 'lists' && <ListsPanel targetType="company" targetId={record.id} />}
166
+ {active === 'events' && (
167
+ <LinkedEventsPanel targetType="company" targetId={record.id} />
168
+ )}
151
169
  {active === 'forms' && <FormsPanel targetType="company" targetId={record.id} />}
152
170
  {moduleTab?.render({ objectType: 'company', recordId: record.id })}
153
171
  </RecordTabs>
154
172
  </div>
155
173
 
156
174
  <aside className="space-y-4 text-[12px] lg:sticky lg:top-6">
157
- <CompanySidebar company={record} />
175
+ <CompanySidebar
176
+ company={record}
177
+ onOpenAddresses={() => {
178
+ setActiveTab('addresses')
179
+ }}
180
+ />
158
181
  </aside>
159
182
  </div>
160
183
  </div>
@@ -189,6 +212,7 @@ function CompanyHeading({ company }: { readonly company: Company }): React.JSX.E
189
212
  onChange={(domain) => {
190
213
  patch({ domain: domain.length > 0 ? domain : null })
191
214
  }}
215
+ visitable
192
216
  displayClassName="text-[13px] font-mono text-ink-muted not-italic"
193
217
  emptyLabel="Add domain…"
194
218
  />
@@ -239,7 +263,13 @@ function CompanyOverview({ company }: { readonly company: Company }): React.JSX.
239
263
  )
240
264
  }
241
265
 
242
- function CompanySidebar({ company }: { readonly company: Company }): React.JSX.Element {
266
+ function CompanySidebar({
267
+ company,
268
+ onOpenAddresses,
269
+ }: {
270
+ readonly company: Company
271
+ readonly onOpenAddresses: () => void
272
+ }): React.JSX.Element {
243
273
  const { patch, error } = useCompanyPatch(company)
244
274
 
245
275
  return (
@@ -309,27 +339,7 @@ function CompanySidebar({ company }: { readonly company: Company }): React.JSX.E
309
339
  displayClassName="not-italic text-[12px]"
310
340
  />
311
341
  </SidebarField>
312
- <SidebarField label="HQ">
313
- <InlineEdit
314
- value={company.hq ?? ''}
315
- onChange={(hq) => {
316
- patch({ hq: hq.length > 0 ? hq : null })
317
- }}
318
- displayClassName="not-italic normal-case text-[12px]"
319
- emptyLabel="Add HQ…"
320
- />
321
- </SidebarField>
322
- <SidebarField label="Website">
323
- <InlineEdit
324
- value={company.website ?? ''}
325
- onChange={(website) => {
326
- patch({ website: website.length > 0 ? website : null })
327
- }}
328
- type="url"
329
- displayClassName="not-italic normal-case text-[12px]"
330
- emptyLabel="Add website…"
331
- />
332
- </SidebarField>
342
+ <AddressSidebarLink addresses={company.addresses} onOpen={onOpenAddresses} />
333
343
  <SidebarField label="Tech stack">
334
344
  <InlineEdit
335
345
  value={company.techStack.join(', ')}
@@ -376,6 +386,28 @@ function CompanySidebar({ company }: { readonly company: Company }): React.JSX.E
376
386
  )
377
387
  }
378
388
 
389
+ function CompanyAddresses({ company }: { readonly company: Company }): React.JSX.Element {
390
+ const { patch, error } = useCompanyPatch(company)
391
+
392
+ return (
393
+ <div className="max-w-lg space-y-4">
394
+ {error !== null && <ErrorPanel error={error} />}
395
+ <SectionHeader
396
+ title="Addresses"
397
+ description="One address per kind. Mark one as primary for lists and the sidebar."
398
+ />
399
+ <AddressesField
400
+ value={company.addresses}
401
+ kinds={COMPANY_ADDRESS_KINDS}
402
+ kindLabels={COMPANY_ADDRESS_KIND_LABELS}
403
+ onChange={(addresses) => {
404
+ patch({ addresses })
405
+ }}
406
+ />
407
+ </div>
408
+ )
409
+ }
410
+
379
411
  function CompanyFields({ company }: { readonly company: Company }): React.JSX.Element {
380
412
  const { patch, error } = useCompanyPatch(company)
381
413
  return (
@@ -13,6 +13,7 @@ import {
13
13
  import { useDeal, useDeleteDeal, useUpdateDeal } from '../api/resources/deals.ts'
14
14
  import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
15
15
  import { useMembers } from '../api/resources/members.ts'
16
+ import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
16
17
  import { usePeople } from '../api/resources/people.ts'
17
18
  import { usePipelineStages } from '../api/resources/pipelineStages.ts'
18
19
  import { useRecordPlanItems } from '../api/resources/planItems.ts'
@@ -29,6 +30,7 @@ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
29
30
  import { useHasCustomFields } from '../components/useHasCustomFields.ts'
30
31
  import { InlineEdit } from '../components/InlineEdit.tsx'
31
32
  import { ListsPanel } from '../components/ListsPanel.tsx'
33
+ import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
32
34
  import { NotesPanel } from '../components/NotesPanel.tsx'
33
35
  import { PlanAttention } from '../components/PlanAttention.tsx'
34
36
  import { PlanPanel } from '../components/PlanPanel.tsx'
@@ -65,6 +67,7 @@ export function DealDetail(): React.JSX.Element {
65
67
  const deleteDeal = useDeleteDeal()
66
68
  const moduleTabs = inSlotOrder(useRecordTabs('deal'))
67
69
  const hasCustomFields = useHasCustomFields('deal')
70
+ const eventsEnabled = useModuleEnabled('events')
68
71
  const [activeTab, setActiveTab] = useState('overview')
69
72
  const [showConvert, setShowConvert] = useState(false)
70
73
  const formSubmissions = useFormSubmissionsForRecord('deal', id)
@@ -89,6 +92,7 @@ export function DealDetail(): React.JSX.Element {
89
92
  { id: 'notes', label: 'Notes' },
90
93
  { id: 'decisions', label: 'Decisions' },
91
94
  { id: 'lists', label: 'Lists' },
95
+ ...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
92
96
  ...(formSubmissions.records.length === 0
93
97
  ? []
94
98
  : [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
@@ -164,6 +168,7 @@ export function DealDetail(): React.JSX.Element {
164
168
  {active === 'notes' && <NotesPanel targetType="deal" targetId={record.id} />}
165
169
  {active === 'decisions' && <DecisionsPanel targetType="deal" targetId={record.id} />}
166
170
  {active === 'lists' && <ListsPanel targetType="deal" targetId={record.id} />}
171
+ {active === 'events' && <LinkedEventsPanel targetType="deal" targetId={record.id} />}
167
172
  {active === 'forms' && <FormsPanel targetType="deal" targetId={record.id} />}
168
173
  {moduleTab?.render({ objectType: 'deal', recordId: record.id })}
169
174
  </RecordTabs>
@@ -6,6 +6,7 @@ import { useTimezone } from '../api/resources/account.ts'
6
6
  import { useCompanies } from '../api/resources/companies.ts'
7
7
  import { useDeals } from '../api/resources/deals.ts'
8
8
  import { useDecisions } from '../api/resources/decisions.ts'
9
+ import { useEvents } from '../api/resources/events.ts'
9
10
  import { useMembers } from '../api/resources/members.ts'
10
11
  import { useOpportunities } from '../api/resources/opportunities.ts'
11
12
  import { usePartnerships } from '../api/resources/partnerships.ts'
@@ -46,6 +47,8 @@ const TARGET_LABELS: Readonly<Record<RecordTargetType, string>> = {
46
47
  raise: 'Raise',
47
48
  enquiry: 'Enquiry',
48
49
  candidate: 'Candidate',
50
+ event: 'Event',
51
+ attendance: 'Attendance',
49
52
  }
50
53
 
51
54
  const TARGET_ROUTES: Readonly<Partial<Record<RecordTargetType, string>>> = {
@@ -56,6 +59,7 @@ const TARGET_ROUTES: Readonly<Partial<Record<RecordTargetType, string>>> = {
56
59
  raise: '/fundraising',
57
60
  partnership: '/partnerships',
58
61
  enquiry: '/enquiries',
62
+ event: '/events',
59
63
  }
60
64
 
61
65
  interface TargetDirectory {
@@ -69,6 +73,7 @@ function useTargetDirectory(): TargetDirectory {
69
73
  const opportunities = useOpportunities({ limit: MAX_PAGE })
70
74
  const raises = useRaises({ limit: MAX_PAGE })
71
75
  const partnerships = usePartnerships({ limit: MAX_PAGE })
76
+ const events = useEvents({ limit: MAX_PAGE })
72
77
 
73
78
  const nameById = useMemo(
74
79
  () =>
@@ -80,6 +85,7 @@ function useTargetDirectory(): TargetDirectory {
80
85
  ...opportunities.records,
81
86
  ...raises.records,
82
87
  ...partnerships.records,
88
+ ...events.records,
83
89
  ].map((record) => [record.id, record.name]),
84
90
  ),
85
91
  [
@@ -89,6 +95,7 @@ function useTargetDirectory(): TargetDirectory {
89
95
  opportunities.records,
90
96
  raises.records,
91
97
  partnerships.records,
98
+ events.records,
92
99
  ],
93
100
  )
94
101
 
@@ -17,6 +17,7 @@ import {
17
17
  } from '../api/resources/conversions.ts'
18
18
  import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
19
19
  import { useMembers } from '../api/resources/members.ts'
20
+ import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
20
21
  import { usePeople } from '../api/resources/people.ts'
21
22
  import { usePipelineStages } from '../api/resources/pipelineStages.ts'
22
23
  import { useRecordPlanItems } from '../api/resources/planItems.ts'
@@ -33,6 +34,7 @@ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
33
34
  import { useHasCustomFields } from '../components/useHasCustomFields.ts'
34
35
  import { InlineEdit } from '../components/InlineEdit.tsx'
35
36
  import { ListsPanel } from '../components/ListsPanel.tsx'
37
+ import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
36
38
  import { NotesPanel } from '../components/NotesPanel.tsx'
37
39
  import { PlanAttention } from '../components/PlanAttention.tsx'
38
40
  import { PlanPanel } from '../components/PlanPanel.tsx'
@@ -68,6 +70,7 @@ export function EnquiryDetail(): React.JSX.Element {
68
70
  const deleteEnquiry = useDeleteEnquiry()
69
71
  const moduleTabs = inSlotOrder(useRecordTabs('enquiry'))
70
72
  const hasCustomFields = useHasCustomFields('enquiry')
73
+ const eventsEnabled = useModuleEnabled('events')
71
74
  const [activeTab, setActiveTab] = useState('overview')
72
75
  const [showConvert, setShowConvert] = useState(false)
73
76
  const formSubmissions = useFormSubmissionsForRecord('enquiry', id)
@@ -92,6 +95,7 @@ export function EnquiryDetail(): React.JSX.Element {
92
95
  { id: 'notes', label: 'Notes' },
93
96
  { id: 'decisions', label: 'Decisions' },
94
97
  { id: 'lists', label: 'Lists' },
98
+ ...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
95
99
  ...(formSubmissions.records.length === 0
96
100
  ? []
97
101
  : [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
@@ -179,6 +183,9 @@ export function EnquiryDetail(): React.JSX.Element {
179
183
  <DecisionsPanel targetType="enquiry" targetId={record.id} />
180
184
  )}
181
185
  {active === 'lists' && <ListsPanel targetType="enquiry" targetId={record.id} />}
186
+ {active === 'events' && (
187
+ <LinkedEventsPanel targetType="enquiry" targetId={record.id} />
188
+ )}
182
189
  {active === 'forms' && <FormsPanel targetType="enquiry" targetId={record.id} />}
183
190
  {moduleTab?.render({ objectType: 'enquiry', recordId: record.id })}
184
191
  </RecordTabs>