@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,382 @@
1
+ import {
2
+ EVENT_FORMAT_LABELS,
3
+ EVENT_STATUS_LABELS,
4
+ } from '@kelpie/schemas'
5
+ import type { Event as CrmEvent, EventFormat, EventStatus } from '@kelpie/schemas'
6
+ import { useMemo, useState } from 'react'
7
+ import type { FormEvent } from 'react'
8
+ import { Link, useNavigate } from 'react-router'
9
+
10
+ import { useTimezone } from '../api/resources/account.ts'
11
+ import { useCreateEvent, useEvents } from '../api/resources/events.ts'
12
+ import { Chip } from '../components/Chip.tsx'
13
+ import type { ChipTone } from '../components/Chip.tsx'
14
+ import { ColumnPicker } from '../components/ColumnPicker.tsx'
15
+ import { DataTable } from '../components/DataTable.tsx'
16
+ import type { Column } from '../components/DataTable.tsx'
17
+ import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
18
+ import { Paginator } from '../components/Paginator.tsx'
19
+ import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
20
+ import { SegmentedControl } from '../components/SegmentedControl.tsx'
21
+ import { formatDateTime } from '../lib/dates.ts'
22
+ import { useListView } from '../lib/listView.ts'
23
+ import { serverSortOnly } from '../lib/sort.ts'
24
+
25
+ /**
26
+ * Events list and month calendar. Upcoming is the default. Not a kanban.
27
+ */
28
+
29
+ type Scope = 'upcoming' | 'past' | 'all'
30
+ type BoardView = 'list' | 'calendar'
31
+
32
+ const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'starts', 'format', 'status', 'location']
33
+ const SERVER_SORT_KEYS: readonly string[] = ['name', 'starts_at', 'created_at', 'updated_at']
34
+ const MONTH_AND_YEAR = new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' })
35
+
36
+ const STATUS_TONES: Readonly<Record<EventStatus, ChipTone>> = {
37
+ draft: 'neutral',
38
+ scheduled: 'accent',
39
+ cancelled: 'danger',
40
+ }
41
+
42
+ function dayKey(value: Date, timezone: string): string {
43
+ return new Intl.DateTimeFormat('en-CA', {
44
+ timeZone: timezone,
45
+ year: 'numeric',
46
+ month: '2-digit',
47
+ day: '2-digit',
48
+ }).format(value)
49
+ }
50
+
51
+ export function EventsPage(): React.JSX.Element {
52
+ const [term, setTerm] = useState('')
53
+ const [adding, setAdding] = useState(false)
54
+ const [name, setName] = useState('')
55
+ const [scope, setScope] = useState<Scope>('upcoming')
56
+ const [board, setBoard] = useState<BoardView>('list')
57
+ const [sort, setSort] = useState<string | undefined>(undefined)
58
+ const navigate = useNavigate()
59
+ const timezone = useTimezone()
60
+ const now = new Date()
61
+ const [month, setMonth] = useState({ year: now.getFullYear(), month: now.getMonth() })
62
+ const createEvent = useCreateEvent()
63
+
64
+ const fromTo =
65
+ board === 'calendar'
66
+ ? monthBounds(month.year, month.month)
67
+ : scope === 'upcoming'
68
+ ? { from: now.toISOString(), to: undefined }
69
+ : scope === 'past'
70
+ ? { from: undefined, to: now.toISOString() }
71
+ : { from: undefined, to: undefined }
72
+
73
+ const events = useEvents({
74
+ term: term.trim().length > 0 ? term.trim() : undefined,
75
+ from: fromTo.from,
76
+ to: fromTo.to,
77
+ sort: serverSortOnly(
78
+ sort ?? (scope === 'past' ? '-starts_at' : 'starts_at'),
79
+ SERVER_SORT_KEYS,
80
+ ),
81
+ limit: board === 'calendar' ? 200 : undefined,
82
+ })
83
+
84
+ async function addEvent(event: FormEvent): Promise<void> {
85
+ event.preventDefault()
86
+
87
+ const trimmed = name.trim()
88
+
89
+ if (trimmed.length === 0) {
90
+ return
91
+ }
92
+
93
+ const startsAt = new Date(Date.now() + 24 * 60 * 60 * 1000)
94
+ const endsAt = new Date(startsAt.getTime() + 60 * 60 * 1000)
95
+ const created = await createEvent.runAsync({
96
+ name: trimmed,
97
+ startsAt,
98
+ endsAt,
99
+ format: 'virtual',
100
+ })
101
+
102
+ setAdding(false)
103
+ setName('')
104
+ await navigate(`/events/${created.id}`)
105
+ }
106
+
107
+ const columns: readonly Column<CrmEvent>[] = [
108
+ {
109
+ key: 'name',
110
+ header: 'Event',
111
+ sortKey: 'name',
112
+ render: (record) => <span className="font-medium text-ink">{record.name}</span>,
113
+ },
114
+ {
115
+ key: 'starts',
116
+ header: 'Starts',
117
+ sortKey: 'starts_at',
118
+ render: (record) => (
119
+ <span className="tabular-nums text-ink-muted">
120
+ {formatDateTime(record.startsAt, timezone)}
121
+ </span>
122
+ ),
123
+ },
124
+ {
125
+ key: 'format',
126
+ header: 'Format',
127
+ getSortValue: (record) => record.format,
128
+ render: (record) => EVENT_FORMAT_LABELS[record.format as EventFormat] ?? record.format,
129
+ },
130
+ {
131
+ key: 'status',
132
+ header: 'Status',
133
+ getSortValue: (record) => record.status,
134
+ render: (record) => (
135
+ <Chip tone={STATUS_TONES[record.status] ?? 'neutral'}>
136
+ {EVENT_STATUS_LABELS[record.status] ?? record.status}
137
+ </Chip>
138
+ ),
139
+ },
140
+ {
141
+ key: 'location',
142
+ header: 'Location',
143
+ getSortValue: (record) => record.location,
144
+ render: (record) => (
145
+ <span className="text-ink-muted">{record.location.length > 0 ? record.location : '—'}</span>
146
+ ),
147
+ },
148
+ ]
149
+
150
+ const supportedKeys = columns.map((column) => column.key)
151
+ const listView = useListView('events', supportedKeys, DEFAULT_VISIBLE_KEYS)
152
+ const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
153
+
154
+ return (
155
+ <div className="animate-fade-in">
156
+ <PageHeader
157
+ title="Events"
158
+ onAdd={() => {
159
+ setAdding(true)
160
+ }}
161
+ addLabel="Add event"
162
+ actions={
163
+ <>
164
+ <SegmentedControl
165
+ ariaLabel="Upcoming or past"
166
+ value={scope}
167
+ onChange={setScope}
168
+ options={[
169
+ { id: 'upcoming', label: 'Upcoming' },
170
+ { id: 'past', label: 'Past' },
171
+ { id: 'all', label: 'All' },
172
+ ]}
173
+ />
174
+ <SegmentedControl
175
+ ariaLabel="List or calendar"
176
+ value={board}
177
+ onChange={setBoard}
178
+ options={[
179
+ { id: 'list', label: 'List' },
180
+ { id: 'calendar', label: 'Calendar' },
181
+ ]}
182
+ />
183
+ <ColumnPicker
184
+ options={pickerOptions}
185
+ visibleKeys={listView.visibleKeys}
186
+ onChange={listView.setVisibleKeys}
187
+ />
188
+ </>
189
+ }
190
+ />
191
+
192
+ <FilterBar value={term} onChange={setTerm} placeholder="Search events…" />
193
+
194
+ {adding && (
195
+ <form
196
+ onSubmit={(event) => {
197
+ void addEvent(event)
198
+ }}
199
+ className="mb-4 flex flex-wrap items-end gap-2 rounded-md border border-border p-3"
200
+ >
201
+ <label className="min-w-[220px] flex-1 text-[12px] font-medium text-ink-muted">
202
+ Name
203
+ <input
204
+ value={name}
205
+ onChange={(event) => {
206
+ setName(event.target.value)
207
+ }}
208
+ className="mt-1 w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] text-ink outline-none focus:border-accent"
209
+ autoFocus
210
+ />
211
+ </label>
212
+ <button
213
+ type="submit"
214
+ disabled={createEvent.isPending || name.trim().length === 0}
215
+ className="rounded-md bg-accent px-3 py-2 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
216
+ >
217
+ Create
218
+ </button>
219
+ <button
220
+ type="button"
221
+ onClick={() => {
222
+ setAdding(false)
223
+ setName('')
224
+ }}
225
+ className="text-[12px] font-medium text-ink-muted hover:text-ink"
226
+ >
227
+ Cancel
228
+ </button>
229
+ {createEvent.error !== null && <ErrorPanel error={createEvent.error} />}
230
+ </form>
231
+ )}
232
+
233
+ {events.error !== null ? (
234
+ <ErrorPanel error={events.error} />
235
+ ) : events.isLoading ? (
236
+ <LoadingPanel label="Loading events…" />
237
+ ) : board === 'calendar' ? (
238
+ <EventCalendar
239
+ year={month.year}
240
+ month={month.month}
241
+ timezone={timezone}
242
+ events={events.records}
243
+ onShift={(delta) => {
244
+ setMonth((current) => {
245
+ const next = new Date(current.year, current.month + delta, 1)
246
+
247
+ return { year: next.getFullYear(), month: next.getMonth() }
248
+ })
249
+ }}
250
+ />
251
+ ) : (
252
+ <>
253
+ <Paginator list={events} placement="top" />
254
+ <DataTable
255
+ columns={columns}
256
+ rows={events.records}
257
+ getRowId={(record) => record.id}
258
+ onRowClick={(record) => {
259
+ void navigate(`/events/${record.id}`)
260
+ }}
261
+ emptyMessage={term.trim().length > 0 ? 'No events match this filter' : 'No events yet'}
262
+ sort={sort}
263
+ onSortChange={setSort}
264
+ visibleColumnKeys={listView.visibleKeys}
265
+ />
266
+ <Paginator list={events} />
267
+ </>
268
+ )}
269
+ </div>
270
+ )
271
+ }
272
+
273
+ function monthBounds(year: number, month: number): { from: string; to: string } {
274
+ const from = new Date(Date.UTC(year, month, 1))
275
+ const to = new Date(Date.UTC(year, month + 1, 1))
276
+
277
+ return { from: from.toISOString(), to: to.toISOString() }
278
+ }
279
+
280
+ function calendarCells(year: number, month: number): readonly { key: string; date: string | null; day: number | null }[] {
281
+ const firstWeekday = (new Date(year, month, 1).getDay() + 6) % 7
282
+ const dayCount = new Date(year, month + 1, 0).getDate()
283
+ const cells: { key: string; date: string | null; day: number | null }[] = []
284
+
285
+ for (let index = 0; index < firstWeekday; index++) {
286
+ cells.push({ key: `lead-${String(index)}`, date: null, day: null })
287
+ }
288
+
289
+ for (let day = 1; day <= dayCount; day++) {
290
+ const date = `${String(year)}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
291
+
292
+ cells.push({ key: date, date, day })
293
+ }
294
+
295
+ while (cells.length % 7 !== 0) {
296
+ cells.push({ key: `trail-${String(cells.length)}`, date: null, day: null })
297
+ }
298
+
299
+ return cells
300
+ }
301
+
302
+ function EventCalendar({
303
+ year,
304
+ month,
305
+ timezone,
306
+ events,
307
+ onShift,
308
+ }: {
309
+ readonly year: number
310
+ readonly month: number
311
+ readonly timezone: string
312
+ readonly events: readonly CrmEvent[]
313
+ readonly onShift: (delta: number) => void
314
+ }): React.JSX.Element {
315
+ const byDay = useMemo(() => {
316
+ const grouped = new Map<string, CrmEvent[]>()
317
+
318
+ for (const record of events) {
319
+ const key = dayKey(record.startsAt, timezone)
320
+ const held = grouped.get(key) ?? []
321
+
322
+ held.push(record)
323
+ grouped.set(key, held)
324
+ }
325
+
326
+ return grouped
327
+ }, [events, timezone])
328
+
329
+ return (
330
+ <div>
331
+ <div className="mb-4 flex items-center gap-2">
332
+ <button
333
+ type="button"
334
+ onClick={() => {
335
+ onShift(-1)
336
+ }}
337
+ aria-label="Previous month"
338
+ className="rounded-md border border-border px-2 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
339
+ >
340
+
341
+ </button>
342
+ <span className="min-w-[140px] text-center text-[13px] font-semibold text-ink">
343
+ {MONTH_AND_YEAR.format(new Date(year, month, 1))}
344
+ </span>
345
+ <button
346
+ type="button"
347
+ onClick={() => {
348
+ onShift(1)
349
+ }}
350
+ aria-label="Next month"
351
+ className="rounded-md border border-border px-2 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
352
+ >
353
+
354
+ </button>
355
+ </div>
356
+ <div className="grid grid-cols-7 gap-px overflow-hidden rounded-md border border-border bg-border">
357
+ {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((label) => (
358
+ <div key={label} className="bg-surface px-2 py-1 text-[11px] font-medium text-ink-muted">
359
+ {label}
360
+ </div>
361
+ ))}
362
+ {calendarCells(year, month).map((cell) => (
363
+ <div key={cell.key} className="min-h-[88px] bg-surface p-1.5">
364
+ {cell.day !== null && (
365
+ <div className="text-[11px] font-medium text-ink-muted">{cell.day}</div>
366
+ )}
367
+ {cell.date !== null &&
368
+ (byDay.get(cell.date) ?? []).map((record) => (
369
+ <Link
370
+ key={record.id}
371
+ to={`/events/${record.id}`}
372
+ className="mt-1 block truncate text-[11px] font-medium text-accent hover:underline"
373
+ >
374
+ {record.name}
375
+ </Link>
376
+ ))}
377
+ </div>
378
+ ))}
379
+ </div>
380
+ </div>
381
+ )
382
+ }
@@ -14,6 +14,7 @@ import {
14
14
  useRemoveListMember,
15
15
  } from '../api/resources/listMembers.ts'
16
16
  import { useEnquiries } from '../api/resources/enquiries.ts'
17
+ import { useEvents } from '../api/resources/events.ts'
17
18
  import { useOpportunities } from '../api/resources/opportunities.ts'
18
19
  import { usePartnerships } from '../api/resources/partnerships.ts'
19
20
  import { usePeople } from '../api/resources/people.ts'
@@ -274,9 +275,13 @@ function detailPathFor(targetType: RecordTargetType, targetId: string): string {
274
275
  return `/fundraising/${targetId}`
275
276
  case 'enquiry':
276
277
  return `/enquiries/${targetId}`
278
+ case 'event':
279
+ return `/events/${targetId}`
277
280
  case 'candidate':
278
281
  // A candidate has no page of its own; the person's does.
279
282
  return `/hiring`
283
+ case 'attendance':
284
+ return `/events`
280
285
  }
281
286
  }
282
287
 
@@ -309,6 +314,8 @@ function MemberPicker({
309
314
  return <RaisesPicker attached={attached} onPick={onPick} />
310
315
  case 'enquiry':
311
316
  return <EnquiriesPicker attached={attached} onPick={onPick} />
317
+ case 'event':
318
+ return <EventsPicker attached={attached} onPick={onPick} />
312
319
  case 'candidate':
313
320
  return (
314
321
  <p className="text-[12px] text-ink-faint">
@@ -316,6 +323,13 @@ function MemberPicker({
316
323
  page, then attach them here through the API.
317
324
  </p>
318
325
  )
326
+ case 'attendance':
327
+ return (
328
+ <p className="text-[12px] text-ink-faint">
329
+ Attendance lists have no inline picker in this release. Register people on an Event,
330
+ then attach them here through the API.
331
+ </p>
332
+ )
319
333
  }
320
334
  }
321
335
 
@@ -394,6 +408,16 @@ function EnquiriesPicker({ attached, onPick }: PickerProps): React.JSX.Element {
394
408
  return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
395
409
  }
396
410
 
411
+ function EventsPicker({ attached, onPick }: PickerProps): React.JSX.Element {
412
+ const [search, setSearch] = useState('')
413
+ const records = useEvents({ term: trimmedOrUndefined(search) })
414
+ const options: readonly SearchOption[] = records.records
415
+ .filter((row) => !attached.has(row.id))
416
+ .map((row) => ({ id: row.id, label: row.name }))
417
+
418
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
419
+ }
420
+
397
421
  function PickerBox({
398
422
  onQueryChange,
399
423
  onPick,
@@ -12,6 +12,7 @@ import {
12
12
  } from '../api/resources/conversions.ts'
13
13
  import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
14
14
  import { useMembers } from '../api/resources/members.ts'
15
+ import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
15
16
  import {
16
17
  useDeleteOpportunity,
17
18
  useOpportunity,
@@ -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'
@@ -70,6 +72,7 @@ export function OpportunityDetail(): React.JSX.Element {
70
72
  const deleteOpportunity = useDeleteOpportunity()
71
73
  const moduleTabs = inSlotOrder(useRecordTabs('opportunity'))
72
74
  const hasCustomFields = useHasCustomFields('opportunity')
75
+ const eventsEnabled = useModuleEnabled('events')
73
76
  const [activeTab, setActiveTab] = useState('overview')
74
77
  const [showConvert, setShowConvert] = useState(false)
75
78
  const formSubmissions = useFormSubmissionsForRecord('opportunity', id)
@@ -94,6 +97,7 @@ export function OpportunityDetail(): React.JSX.Element {
94
97
  { id: 'notes', label: 'Notes' },
95
98
  { id: 'decisions', label: 'Decisions' },
96
99
  { id: 'lists', label: 'Lists' },
100
+ ...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
97
101
  ...(formSubmissions.records.length === 0
98
102
  ? []
99
103
  : [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
@@ -176,6 +180,9 @@ export function OpportunityDetail(): React.JSX.Element {
176
180
  <DecisionsPanel targetType="opportunity" targetId={record.id} />
177
181
  )}
178
182
  {active === 'lists' && <ListsPanel targetType="opportunity" targetId={record.id} />}
183
+ {active === 'events' && (
184
+ <LinkedEventsPanel targetType="opportunity" targetId={record.id} />
185
+ )}
179
186
  {active === 'forms' && <FormsPanel targetType="opportunity" targetId={record.id} />}
180
187
  {moduleTab?.render({ objectType: 'opportunity', recordId: record.id })}
181
188
  </RecordTabs>
@@ -12,6 +12,7 @@ import {
12
12
  } from '../api/resources/conversions.ts'
13
13
  import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
14
14
  import { useMembers } from '../api/resources/members.ts'
15
+ import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
15
16
  import {
16
17
  useDeletePartnership,
17
18
  usePartnership,
@@ -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'
@@ -70,6 +72,7 @@ export function PartnershipDetail(): React.JSX.Element {
70
72
  const deletePartnership = useDeletePartnership()
71
73
  const moduleTabs = inSlotOrder(useRecordTabs('partnership'))
72
74
  const hasCustomFields = useHasCustomFields('partnership')
75
+ const eventsEnabled = useModuleEnabled('events')
73
76
  const [activeTab, setActiveTab] = useState('overview')
74
77
  const [showConvert, setShowConvert] = useState(false)
75
78
  const formSubmissions = useFormSubmissionsForRecord('partnership', id)
@@ -94,6 +97,7 @@ export function PartnershipDetail(): React.JSX.Element {
94
97
  { id: 'notes', label: 'Notes' },
95
98
  { id: 'decisions', label: 'Decisions' },
96
99
  { id: 'lists', label: 'Lists' },
100
+ ...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
97
101
  ...(formSubmissions.records.length === 0
98
102
  ? []
99
103
  : [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
@@ -176,6 +180,9 @@ export function PartnershipDetail(): React.JSX.Element {
176
180
  <DecisionsPanel targetType="partnership" targetId={record.id} />
177
181
  )}
178
182
  {active === 'lists' && <ListsPanel targetType="partnership" targetId={record.id} />}
183
+ {active === 'events' && (
184
+ <LinkedEventsPanel targetType="partnership" targetId={record.id} />
185
+ )}
179
186
  {active === 'forms' && <FormsPanel targetType="partnership" targetId={record.id} />}
180
187
  {moduleTab?.render({ objectType: 'partnership', recordId: record.id })}
181
188
  </RecordTabs>
@@ -1,3 +1,4 @@
1
+ import { SOCIAL_NETWORK_LABELS, formatAddress, primaryAddress } from '@kelpie/schemas'
1
2
  import type { Person } from '@kelpie/schemas'
2
3
  import { useState } from 'react'
3
4
  import { useNavigate } from 'react-router'
@@ -5,6 +6,7 @@ import { useNavigate } from 'react-router'
5
6
  import { useTimezone } from '../api/resources/account.ts'
6
7
  import { useCreatePerson, usePeople } from '../api/resources/people.ts'
7
8
  import { Chip } from '../components/Chip.tsx'
9
+ import { SocialNetworkIcon } from '../components/SocialNetworkIcon.tsx'
8
10
  import { ColumnPicker } from '../components/ColumnPicker.tsx'
9
11
  import { DataTable } from '../components/DataTable.tsx'
10
12
  import type { Column } from '../components/DataTable.tsx'
@@ -130,17 +132,28 @@ export function PeoplePage(): React.JSX.Element {
130
132
  <span className="flex flex-wrap gap-1">
131
133
  {person.socialProfiles.map((entry) => (
132
134
  <Chip key={entry.network}>
133
- <span className="text-[10px]">{entry.network}</span>
135
+ <span className="inline-flex items-center gap-1">
136
+ <SocialNetworkIcon network={entry.network} className="size-3" />
137
+ <span className="text-[10px]">{SOCIAL_NETWORK_LABELS[entry.network]}</span>
138
+ </span>
134
139
  </Chip>
135
140
  ))}
136
141
  </span>
137
142
  ),
138
143
  },
139
144
  {
140
- key: 'location',
141
- header: 'Location',
142
- getSortValue: (person) => person.location,
143
- render: (person) => person.location ?? '—',
145
+ key: 'addresses',
146
+ header: 'Address',
147
+ getSortValue: (person) => {
148
+ const address = primaryAddress(person.addresses)
149
+
150
+ return address === undefined ? null : formatAddress(address)
151
+ },
152
+ render: (person) => {
153
+ const address = primaryAddress(person.addresses)
154
+
155
+ return address === undefined ? '—' : formatAddress(address)
156
+ },
144
157
  },
145
158
  {
146
159
  key: 'timezone',