@kelpie/ui 0.7.0 → 0.9.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 (349) hide show
  1. package/dist/api/resource.d.ts +59 -6
  2. package/dist/api/resource.d.ts.map +1 -1
  3. package/dist/api/resource.js +106 -24
  4. package/dist/api/resource.js.map +1 -1
  5. package/dist/api/resources/companies.d.ts +2 -0
  6. package/dist/api/resources/companies.d.ts.map +1 -1
  7. package/dist/api/resources/companies.js +5 -1
  8. package/dist/api/resources/companies.js.map +1 -1
  9. package/dist/api/resources/consentPurposes.d.ts +13 -0
  10. package/dist/api/resources/consentPurposes.d.ts.map +1 -0
  11. package/dist/api/resources/consentPurposes.js +39 -0
  12. package/dist/api/resources/consentPurposes.js.map +1 -0
  13. package/dist/api/resources/customFields.d.ts +15 -0
  14. package/dist/api/resources/customFields.d.ts.map +1 -0
  15. package/dist/api/resources/customFields.js +41 -0
  16. package/dist/api/resources/customFields.js.map +1 -0
  17. package/dist/api/resources/decisions.d.ts +2 -0
  18. package/dist/api/resources/decisions.d.ts.map +1 -1
  19. package/dist/api/resources/decisions.js +1 -0
  20. package/dist/api/resources/decisions.js.map +1 -1
  21. package/dist/api/resources/emailDomainLinker.d.ts +16 -0
  22. package/dist/api/resources/emailDomainLinker.d.ts.map +1 -0
  23. package/dist/api/resources/emailDomainLinker.js +16 -0
  24. package/dist/api/resources/emailDomainLinker.js.map +1 -0
  25. package/dist/api/resources/enquiries.d.ts +39 -0
  26. package/dist/api/resources/enquiries.d.ts.map +1 -0
  27. package/dist/api/resources/enquiries.js +68 -0
  28. package/dist/api/resources/enquiries.js.map +1 -0
  29. package/dist/api/resources/forms.d.ts +21 -1
  30. package/dist/api/resources/forms.d.ts.map +1 -1
  31. package/dist/api/resources/forms.js +44 -17
  32. package/dist/api/resources/forms.js.map +1 -1
  33. package/dist/api/resources/handbookPages.d.ts +1 -1
  34. package/dist/api/resources/handbookPages.js +1 -1
  35. package/dist/api/resources/importJobs.d.ts +5 -0
  36. package/dist/api/resources/importJobs.d.ts.map +1 -1
  37. package/dist/api/resources/importJobs.js +3 -0
  38. package/dist/api/resources/importJobs.js.map +1 -1
  39. package/dist/api/resources/listMembers.d.ts +27 -0
  40. package/dist/api/resources/listMembers.d.ts.map +1 -0
  41. package/dist/api/resources/listMembers.js +108 -0
  42. package/dist/api/resources/listMembers.js.map +1 -0
  43. package/dist/api/resources/lists.d.ts +14 -0
  44. package/dist/api/resources/lists.d.ts.map +1 -0
  45. package/dist/api/resources/lists.js +34 -0
  46. package/dist/api/resources/lists.js.map +1 -0
  47. package/dist/api/resources/opportunities.d.ts +2 -0
  48. package/dist/api/resources/opportunities.d.ts.map +1 -1
  49. package/dist/api/resources/opportunities.js +1 -0
  50. package/dist/api/resources/opportunities.js.map +1 -1
  51. package/dist/api/resources/people.d.ts.map +1 -1
  52. package/dist/api/resources/people.js +4 -1
  53. package/dist/api/resources/people.js.map +1 -1
  54. package/dist/api/resources/roles.d.ts +2 -0
  55. package/dist/api/resources/roles.d.ts.map +1 -1
  56. package/dist/api/resources/roles.js +1 -1
  57. package/dist/api/resources/roles.js.map +1 -1
  58. package/dist/api/resources/sampleData.d.ts +17 -0
  59. package/dist/api/resources/sampleData.d.ts.map +1 -0
  60. package/dist/api/resources/sampleData.js +31 -0
  61. package/dist/api/resources/sampleData.js.map +1 -0
  62. package/dist/api/resources/webhooks.d.ts.map +1 -1
  63. package/dist/api/resources/webhooks.js +7 -22
  64. package/dist/api/resources/webhooks.js.map +1 -1
  65. package/dist/app/KelpieApp.d.ts.map +1 -1
  66. package/dist/app/KelpieApp.js +9 -1
  67. package/dist/app/KelpieApp.js.map +1 -1
  68. package/dist/components/ActivitiesPanel.d.ts.map +1 -1
  69. package/dist/components/ActivitiesPanel.js +3 -1
  70. package/dist/components/ActivitiesPanel.js.map +1 -1
  71. package/dist/components/ColumnPicker.d.ts +24 -0
  72. package/dist/components/ColumnPicker.d.ts.map +1 -0
  73. package/dist/components/ColumnPicker.js +56 -0
  74. package/dist/components/ColumnPicker.js.map +1 -0
  75. package/dist/components/CustomFieldsPanel.d.ts +23 -0
  76. package/dist/components/CustomFieldsPanel.d.ts.map +1 -0
  77. package/dist/components/CustomFieldsPanel.js +116 -0
  78. package/dist/components/CustomFieldsPanel.js.map +1 -0
  79. package/dist/components/DataTable.d.ts +16 -1
  80. package/dist/components/DataTable.d.ts.map +1 -1
  81. package/dist/components/DataTable.js +51 -10
  82. package/dist/components/DataTable.js.map +1 -1
  83. package/dist/components/DecisionsPanel.d.ts.map +1 -1
  84. package/dist/components/DecisionsPanel.js +4 -3
  85. package/dist/components/DecisionsPanel.js.map +1 -1
  86. package/dist/components/EntitySearch.d.ts.map +1 -1
  87. package/dist/components/EntitySearch.js +11 -5
  88. package/dist/components/EntitySearch.js.map +1 -1
  89. package/dist/components/FormsPanel.d.ts +18 -0
  90. package/dist/components/FormsPanel.d.ts.map +1 -0
  91. package/dist/components/FormsPanel.js +37 -0
  92. package/dist/components/FormsPanel.js.map +1 -0
  93. package/dist/components/ListsPanel.d.ts +14 -0
  94. package/dist/components/ListsPanel.d.ts.map +1 -0
  95. package/dist/components/ListsPanel.js +56 -0
  96. package/dist/components/ListsPanel.js.map +1 -0
  97. package/dist/components/NotesPanel.d.ts.map +1 -1
  98. package/dist/components/NotesPanel.js +6 -5
  99. package/dist/components/NotesPanel.js.map +1 -1
  100. package/dist/components/Paginator.d.ts +26 -0
  101. package/dist/components/Paginator.d.ts.map +1 -0
  102. package/dist/components/Paginator.js +16 -0
  103. package/dist/components/Paginator.js.map +1 -0
  104. package/dist/components/PlanAttention.d.ts +3 -2
  105. package/dist/components/PlanAttention.d.ts.map +1 -1
  106. package/dist/components/PlanAttention.js +7 -3
  107. package/dist/components/PlanAttention.js.map +1 -1
  108. package/dist/components/PlanPanel.d.ts.map +1 -1
  109. package/dist/components/PlanPanel.js +2 -1
  110. package/dist/components/PlanPanel.js.map +1 -1
  111. package/dist/components/Shell.d.ts.map +1 -1
  112. package/dist/components/Shell.js +4 -0
  113. package/dist/components/Shell.js.map +1 -1
  114. package/dist/components/useHasCustomFields.d.ts +13 -0
  115. package/dist/components/useHasCustomFields.d.ts.map +1 -0
  116. package/dist/components/useHasCustomFields.js +16 -0
  117. package/dist/components/useHasCustomFields.js.map +1 -0
  118. package/dist/index.d.ts +8 -2
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +4 -1
  121. package/dist/index.js.map +1 -1
  122. package/dist/lib/listView.d.ts +15 -0
  123. package/dist/lib/listView.d.ts.map +1 -0
  124. package/dist/lib/listView.js +66 -0
  125. package/dist/lib/listView.js.map +1 -0
  126. package/dist/lib/sort.d.ts +43 -0
  127. package/dist/lib/sort.d.ts.map +1 -0
  128. package/dist/lib/sort.js +102 -0
  129. package/dist/lib/sort.js.map +1 -0
  130. package/dist/pages/CompaniesPage.d.ts.map +1 -1
  131. package/dist/pages/CompaniesPage.js +88 -3
  132. package/dist/pages/CompaniesPage.js.map +1 -1
  133. package/dist/pages/CompanyDetail.d.ts.map +1 -1
  134. package/dist/pages/CompanyDetail.js +40 -15
  135. package/dist/pages/CompanyDetail.js.map +1 -1
  136. package/dist/pages/DealDetail.d.ts.map +1 -1
  137. package/dist/pages/DealDetail.js +20 -1
  138. package/dist/pages/DealDetail.js.map +1 -1
  139. package/dist/pages/DealsPage.d.ts.map +1 -1
  140. package/dist/pages/DealsPage.js +134 -14
  141. package/dist/pages/DealsPage.js.map +1 -1
  142. package/dist/pages/DecisionsPage.d.ts.map +1 -1
  143. package/dist/pages/DecisionsPage.js +52 -8
  144. package/dist/pages/DecisionsPage.js.map +1 -1
  145. package/dist/pages/EnquiriesPage.d.ts +2 -0
  146. package/dist/pages/EnquiriesPage.d.ts.map +1 -0
  147. package/dist/pages/EnquiriesPage.js +251 -0
  148. package/dist/pages/EnquiriesPage.js.map +1 -0
  149. package/dist/pages/EnquiryDetail.d.ts +2 -0
  150. package/dist/pages/EnquiryDetail.d.ts.map +1 -0
  151. package/dist/pages/EnquiryDetail.js +207 -0
  152. package/dist/pages/EnquiryDetail.js.map +1 -0
  153. package/dist/pages/EnquiryStageSettingsPage.d.ts +3 -0
  154. package/dist/pages/EnquiryStageSettingsPage.d.ts.map +1 -0
  155. package/dist/pages/EnquiryStageSettingsPage.js +34 -0
  156. package/dist/pages/EnquiryStageSettingsPage.js.map +1 -0
  157. package/dist/pages/FormDetail.d.ts.map +1 -1
  158. package/dist/pages/FormDetail.js +19 -1
  159. package/dist/pages/FormDetail.js.map +1 -1
  160. package/dist/pages/FormsPage.d.ts.map +1 -1
  161. package/dist/pages/FormsPage.js +53 -2
  162. package/dist/pages/FormsPage.js.map +1 -1
  163. package/dist/pages/FundraisingPage.d.ts.map +1 -1
  164. package/dist/pages/FundraisingPage.js +116 -14
  165. package/dist/pages/FundraisingPage.js.map +1 -1
  166. package/dist/pages/HiringPage.d.ts.map +1 -1
  167. package/dist/pages/HiringPage.js +27 -4
  168. package/dist/pages/HiringPage.js.map +1 -1
  169. package/dist/pages/ListDetail.d.ts +11 -0
  170. package/dist/pages/ListDetail.d.ts.map +1 -0
  171. package/dist/pages/ListDetail.js +214 -0
  172. package/dist/pages/ListDetail.js.map +1 -0
  173. package/dist/pages/ListsPage.d.ts +9 -0
  174. package/dist/pages/ListsPage.d.ts.map +1 -0
  175. package/dist/pages/ListsPage.js +125 -0
  176. package/dist/pages/ListsPage.js.map +1 -0
  177. package/dist/pages/OpportunitiesPage.d.ts.map +1 -1
  178. package/dist/pages/OpportunitiesPage.js +97 -14
  179. package/dist/pages/OpportunitiesPage.js.map +1 -1
  180. package/dist/pages/OpportunityDetail.d.ts.map +1 -1
  181. package/dist/pages/OpportunityDetail.js +64 -3
  182. package/dist/pages/OpportunityDetail.js.map +1 -1
  183. package/dist/pages/PartnershipDetail.d.ts.map +1 -1
  184. package/dist/pages/PartnershipDetail.js +19 -1
  185. package/dist/pages/PartnershipDetail.js.map +1 -1
  186. package/dist/pages/PartnershipsPage.d.ts.map +1 -1
  187. package/dist/pages/PartnershipsPage.js +111 -14
  188. package/dist/pages/PartnershipsPage.js.map +1 -1
  189. package/dist/pages/PeoplePage.d.ts.map +1 -1
  190. package/dist/pages/PeoplePage.js +119 -3
  191. package/dist/pages/PeoplePage.js.map +1 -1
  192. package/dist/pages/PersonDetail.d.ts.map +1 -1
  193. package/dist/pages/PersonDetail.js +67 -5
  194. package/dist/pages/PersonDetail.js.map +1 -1
  195. package/dist/pages/PlanningPage.d.ts.map +1 -1
  196. package/dist/pages/PlanningPage.js +88 -17
  197. package/dist/pages/PlanningPage.js.map +1 -1
  198. package/dist/pages/RaiseDetail.d.ts.map +1 -1
  199. package/dist/pages/RaiseDetail.js +13 -1
  200. package/dist/pages/RaiseDetail.js.map +1 -1
  201. package/dist/pages/RoleDetail.d.ts.map +1 -1
  202. package/dist/pages/RoleDetail.js +5 -4
  203. package/dist/pages/RoleDetail.js.map +1 -1
  204. package/dist/pages/SearchPage.d.ts.map +1 -1
  205. package/dist/pages/SearchPage.js +1 -0
  206. package/dist/pages/SearchPage.js.map +1 -1
  207. package/dist/pages/admin/DataPage.d.ts.map +1 -1
  208. package/dist/pages/admin/DataPage.js +126 -13
  209. package/dist/pages/admin/DataPage.js.map +1 -1
  210. package/dist/pages/admin/FieldsPage.d.ts +2 -0
  211. package/dist/pages/admin/FieldsPage.d.ts.map +1 -0
  212. package/dist/pages/admin/FieldsPage.js +164 -0
  213. package/dist/pages/admin/FieldsPage.js.map +1 -0
  214. package/dist/pages/admin/PrivacyPage.d.ts +9 -0
  215. package/dist/pages/admin/PrivacyPage.d.ts.map +1 -0
  216. package/dist/pages/admin/PrivacyPage.js +170 -0
  217. package/dist/pages/admin/PrivacyPage.js.map +1 -0
  218. package/dist/pages/admin/WebhookDeliveries.d.ts.map +1 -1
  219. package/dist/pages/admin/WebhookDeliveries.js +3 -2
  220. package/dist/pages/admin/WebhookDeliveries.js.map +1 -1
  221. package/dist/pages/admin/WebhooksPage.d.ts.map +1 -1
  222. package/dist/pages/admin/WebhooksPage.js +3 -2
  223. package/dist/pages/admin/WebhooksPage.js.map +1 -1
  224. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
  225. package/dist/pages/admin/WorkspaceSettingsPage.js +45 -2
  226. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
  227. package/dist/pages/dashboard/attention.d.ts.map +1 -1
  228. package/dist/pages/dashboard/attention.js +3 -0
  229. package/dist/pages/dashboard/attention.js.map +1 -1
  230. package/dist/pages/forms/AddFieldMenu.d.ts +17 -0
  231. package/dist/pages/forms/AddFieldMenu.d.ts.map +1 -0
  232. package/dist/pages/forms/AddFieldMenu.js +43 -0
  233. package/dist/pages/forms/AddFieldMenu.js.map +1 -0
  234. package/dist/pages/forms/EmbedPanel.d.ts +4 -0
  235. package/dist/pages/forms/EmbedPanel.d.ts.map +1 -1
  236. package/dist/pages/forms/EmbedPanel.js +62 -3
  237. package/dist/pages/forms/EmbedPanel.js.map +1 -1
  238. package/dist/pages/forms/FieldsEditor.d.ts.map +1 -1
  239. package/dist/pages/forms/FieldsEditor.js +179 -36
  240. package/dist/pages/forms/FieldsEditor.js.map +1 -1
  241. package/dist/pages/forms/FormActions.d.ts +6 -0
  242. package/dist/pages/forms/FormActions.d.ts.map +1 -0
  243. package/dist/pages/forms/FormActions.js +149 -0
  244. package/dist/pages/forms/FormActions.js.map +1 -0
  245. package/dist/pages/forms/FormSettings.d.ts.map +1 -1
  246. package/dist/pages/forms/FormSettings.js +15 -19
  247. package/dist/pages/forms/FormSettings.js.map +1 -1
  248. package/dist/pages/forms/SubmissionDetail.d.ts +2 -0
  249. package/dist/pages/forms/SubmissionDetail.d.ts.map +1 -0
  250. package/dist/pages/forms/SubmissionDetail.js +76 -0
  251. package/dist/pages/forms/SubmissionDetail.js.map +1 -0
  252. package/dist/pages/forms/SubmissionsTable.d.ts.map +1 -1
  253. package/dist/pages/forms/SubmissionsTable.js +57 -8
  254. package/dist/pages/forms/SubmissionsTable.js.map +1 -1
  255. package/dist/pages/forms/fieldList.d.ts +11 -5
  256. package/dist/pages/forms/fieldList.d.ts.map +1 -1
  257. package/dist/pages/forms/fieldList.js +66 -6
  258. package/dist/pages/forms/fieldList.js.map +1 -1
  259. package/dist/pages/forms/template.d.ts +17 -0
  260. package/dist/pages/forms/template.d.ts.map +1 -1
  261. package/dist/pages/forms/template.js +93 -0
  262. package/dist/pages/forms/template.js.map +1 -1
  263. package/dist/pages/handbook/HandbookPage.js +1 -1
  264. package/dist/pages/hiringDirectory.js +2 -2
  265. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
  266. package/dist/pages/onboarding/WorkspaceStepPage.js +15 -2
  267. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
  268. package/dist/pages/positionDirectory.js +2 -2
  269. package/package.json +2 -2
  270. package/src/api/resource.ts +155 -32
  271. package/src/api/resources/companies.ts +7 -1
  272. package/src/api/resources/consentPurposes.ts +83 -0
  273. package/src/api/resources/customFields.ts +88 -0
  274. package/src/api/resources/decisions.ts +3 -0
  275. package/src/api/resources/emailDomainLinker.ts +43 -0
  276. package/src/api/resources/enquiries.ts +118 -0
  277. package/src/api/resources/forms.ts +59 -19
  278. package/src/api/resources/handbookPages.ts +1 -1
  279. package/src/api/resources/importJobs.ts +9 -0
  280. package/src/api/resources/listMembers.ts +173 -0
  281. package/src/api/resources/lists.ts +61 -0
  282. package/src/api/resources/opportunities.ts +3 -0
  283. package/src/api/resources/people.ts +4 -1
  284. package/src/api/resources/roles.ts +3 -1
  285. package/src/api/resources/sampleData.ts +57 -0
  286. package/src/api/resources/webhooks.ts +8 -24
  287. package/src/app/KelpieApp.tsx +16 -0
  288. package/src/components/ActivitiesPanel.tsx +3 -10
  289. package/src/components/ColumnPicker.tsx +124 -0
  290. package/src/components/CustomFieldsPanel.tsx +330 -0
  291. package/src/components/DataTable.tsx +89 -10
  292. package/src/components/DecisionsPanel.tsx +4 -12
  293. package/src/components/EntitySearch.tsx +12 -5
  294. package/src/components/FormsPanel.tsx +86 -0
  295. package/src/components/ListsPanel.tsx +176 -0
  296. package/src/components/NotesPanel.tsx +6 -14
  297. package/src/components/Paginator.tsx +78 -0
  298. package/src/components/PlanAttention.tsx +20 -3
  299. package/src/components/PlanPanel.tsx +2 -10
  300. package/src/components/Shell.tsx +4 -0
  301. package/src/components/useHasCustomFields.ts +18 -0
  302. package/src/index.ts +10 -1
  303. package/src/lib/listView.ts +149 -0
  304. package/src/lib/sort.ts +143 -0
  305. package/src/pages/CompaniesPage.tsx +125 -11
  306. package/src/pages/CompanyDetail.tsx +98 -63
  307. package/src/pages/DealDetail.tsx +34 -0
  308. package/src/pages/DealsPage.tsx +178 -24
  309. package/src/pages/DecisionsPage.tsx +75 -18
  310. package/src/pages/EnquiriesPage.tsx +434 -0
  311. package/src/pages/EnquiryDetail.tsx +554 -0
  312. package/src/pages/EnquiryStageSettingsPage.tsx +63 -0
  313. package/src/pages/FormDetail.tsx +23 -1
  314. package/src/pages/FormsPage.tsx +81 -17
  315. package/src/pages/FundraisingPage.tsx +152 -24
  316. package/src/pages/HiringPage.tsx +39 -11
  317. package/src/pages/ListDetail.tsx +470 -0
  318. package/src/pages/ListsPage.tsx +281 -0
  319. package/src/pages/OpportunitiesPage.tsx +130 -24
  320. package/src/pages/OpportunityDetail.tsx +168 -2
  321. package/src/pages/PartnershipDetail.tsx +35 -0
  322. package/src/pages/PartnershipsPage.tsx +149 -24
  323. package/src/pages/PeoplePage.tsx +157 -11
  324. package/src/pages/PersonDetail.tsx +157 -6
  325. package/src/pages/PlanningPage.tsx +259 -29
  326. package/src/pages/RaiseDetail.tsx +24 -0
  327. package/src/pages/RoleDetail.tsx +21 -26
  328. package/src/pages/SearchPage.tsx +1 -0
  329. package/src/pages/admin/DataPage.tsx +324 -13
  330. package/src/pages/admin/FieldsPage.tsx +455 -0
  331. package/src/pages/admin/PrivacyPage.tsx +461 -0
  332. package/src/pages/admin/WebhookDeliveries.tsx +10 -15
  333. package/src/pages/admin/WebhooksPage.tsx +14 -19
  334. package/src/pages/admin/WorkspaceSettingsPage.tsx +134 -2
  335. package/src/pages/dashboard/attention.ts +3 -0
  336. package/src/pages/forms/AddFieldMenu.tsx +121 -0
  337. package/src/pages/forms/EmbedPanel.tsx +163 -2
  338. package/src/pages/forms/FieldsEditor.tsx +539 -165
  339. package/src/pages/forms/FormActions.tsx +552 -0
  340. package/src/pages/forms/FormSettings.tsx +29 -70
  341. package/src/pages/forms/SubmissionDetail.tsx +249 -0
  342. package/src/pages/forms/SubmissionsTable.tsx +112 -26
  343. package/src/pages/forms/fieldList.ts +68 -5
  344. package/src/pages/forms/template.ts +103 -0
  345. package/src/pages/handbook/HandbookPage.tsx +1 -1
  346. package/src/pages/hiringDirectory.ts +2 -2
  347. package/src/pages/onboarding/WorkspaceStepPage.tsx +31 -3
  348. package/src/pages/positionDirectory.ts +2 -2
  349. package/src/styles.css +3 -3
@@ -0,0 +1,470 @@
1
+ import { RECORD_TARGET_TYPE_LABELS } from '@kelpie/schemas'
2
+ import type { ListMember, RecordTargetType } from '@kelpie/schemas'
3
+ import { useState } from 'react'
4
+ import { Link, useNavigate, useParams } from 'react-router'
5
+
6
+ import { usePatch } from '../api/resource.ts'
7
+ import type { PatchResult } from '../api/resource.ts'
8
+ import { useCompanies } from '../api/resources/companies.ts'
9
+ import { useConsentPurposes } from '../api/resources/consentPurposes.ts'
10
+ import { useDeals } from '../api/resources/deals.ts'
11
+ import { useDeleteList, useList, useUpdateList } from '../api/resources/lists.ts'
12
+ import {
13
+ useAddListMember,
14
+ useListMembers,
15
+ useRemoveListMember,
16
+ } from '../api/resources/listMembers.ts'
17
+ import { useEnquiries } from '../api/resources/enquiries.ts'
18
+ import { useOpportunities } from '../api/resources/opportunities.ts'
19
+ import { usePartnerships } from '../api/resources/partnerships.ts'
20
+ import { usePeople } from '../api/resources/people.ts'
21
+ import { useRaises } from '../api/resources/raises.ts'
22
+ import { Chip } from '../components/Chip.tsx'
23
+ import { DeleteRecord } from '../components/DeleteRecord.tsx'
24
+ import { EntitySearch } from '../components/EntitySearch.tsx'
25
+ import type { SearchOption } from '../components/EntitySearch.tsx'
26
+ import { InlineEdit } from '../components/InlineEdit.tsx'
27
+ import { Paginator } from '../components/Paginator.tsx'
28
+ import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
29
+ import { SectionHeader } from '../components/SectionHeader.tsx'
30
+ import type { List, ListInput } from '@kelpie/schemas'
31
+
32
+ /**
33
+ * One list, and the records on it.
34
+ *
35
+ * The type is fixed at creation, so the header shows it as a badge and the
36
+ * member picker below dispatches on it: a `person` list picks from people, a
37
+ * `company` list from companies, and so on. A candidate list has no picker in
38
+ * v1 because candidates carry no name of their own; add candidacies from the
39
+ * hiring pages.
40
+ */
41
+ export function ListDetail(): React.JSX.Element {
42
+ const { id } = useParams()
43
+ const navigate = useNavigate()
44
+ const { record, isLoading, isNotFound, error } = useList(id)
45
+ const deleteList = useDeleteList()
46
+
47
+ if (isNotFound) {
48
+ return <NotFoundPanel label="List" backTo="/lists" />
49
+ }
50
+
51
+ if (error !== null) {
52
+ return <ErrorPanel error={error} />
53
+ }
54
+
55
+ if (isLoading || record === undefined || id === undefined) {
56
+ return <LoadingPanel label="Loading list…" />
57
+ }
58
+
59
+ return (
60
+ <div className="animate-fade-in mx-auto max-w-4xl">
61
+ <Link
62
+ to="/lists"
63
+ className="mb-4 inline-flex text-[12px] font-medium text-ink-muted transition hover:text-accent"
64
+ >
65
+ ← Lists
66
+ </Link>
67
+
68
+ <div className="mb-6 flex flex-wrap items-start justify-between gap-3">
69
+ <ListHeading list={record} />
70
+ <div className="flex shrink-0 flex-wrap items-center gap-3">
71
+ <Chip>{RECORD_TARGET_TYPE_LABELS[record.targetType]}</Chip>
72
+ <DeleteRecord
73
+ recordLabel="List"
74
+ recordName={record.name}
75
+ isPending={deleteList.isPending}
76
+ error={deleteList.error}
77
+ onConfirm={() => {
78
+ deleteList
79
+ .runAsync(record.id)
80
+ .then(() => navigate('/lists'))
81
+ .catch(() => undefined)
82
+ }}
83
+ />
84
+ </div>
85
+ </div>
86
+
87
+ <ListDescription list={record} />
88
+
89
+ {record.targetType === 'person' && <ListConsentPurpose list={record} />}
90
+
91
+ <ListMembers listId={record.id} targetType={record.targetType} />
92
+ </div>
93
+ )
94
+ }
95
+
96
+ function ListConsentPurpose({ list }: { readonly list: List }): React.JSX.Element {
97
+ const { patch, error } = useListPatch(list)
98
+ const purposes = useConsentPurposes({ sort: 'sort_order', limit: 200 })
99
+ const options = [
100
+ { value: '', label: '— none —' },
101
+ ...purposes.records.map((purpose) => ({ value: purpose.id, label: purpose.label })),
102
+ ]
103
+
104
+ return (
105
+ <div className="mb-6 rounded-md border border-border bg-surface-raised p-4 text-[13px]">
106
+ {error !== null && (
107
+ <div className="mb-2">
108
+ <ErrorPanel error={error} />
109
+ </div>
110
+ )}
111
+ <div className="flex flex-wrap items-baseline justify-between gap-3">
112
+ <div>
113
+ <div className="text-[12px] font-medium text-ink">Consent purpose</div>
114
+ <p className="mt-0.5 text-[12px] text-ink-muted">
115
+ A form submit that adds a person to this list also grants this purpose. Manual and
116
+ API adds capture nothing.
117
+ </p>
118
+ </div>
119
+ <label className="shrink-0">
120
+ <select
121
+ value={list.consentPurposeId ?? ''}
122
+ onChange={(event) => {
123
+ const value = event.target.value
124
+ patch({ consentPurposeId: value.length === 0 ? null : value })
125
+ }}
126
+ className="rounded-md border border-border bg-transparent px-2 py-1 text-[12px] outline-none focus:border-accent"
127
+ >
128
+ {options.map((option) => (
129
+ <option key={option.value} value={option.value}>
130
+ {option.label}
131
+ </option>
132
+ ))}
133
+ </select>
134
+ </label>
135
+ </div>
136
+ </div>
137
+ )
138
+ }
139
+
140
+ function useListPatch(list: List): PatchResult<ListInput> {
141
+ return usePatch(useUpdateList, list)
142
+ }
143
+
144
+ function ListHeading({ list }: { readonly list: List }): React.JSX.Element {
145
+ const { patch, error } = useListPatch(list)
146
+
147
+ return (
148
+ <div className="min-w-0 flex-1">
149
+ {error !== null && (
150
+ <div className="mb-2">
151
+ <ErrorPanel error={error} />
152
+ </div>
153
+ )}
154
+ <InlineEdit
155
+ value={list.name}
156
+ onChange={(name) => {
157
+ patch({ name })
158
+ }}
159
+ displayClassName="text-[22px] font-semibold tracking-tight text-ink not-italic"
160
+ emptyLabel="Untitled list"
161
+ />
162
+ </div>
163
+ )
164
+ }
165
+
166
+ function ListDescription({ list }: { readonly list: List }): React.JSX.Element {
167
+ const { patch, error } = useListPatch(list)
168
+
169
+ return (
170
+ <div className="mb-6">
171
+ {error !== null && (
172
+ <div className="mb-2">
173
+ <ErrorPanel error={error} />
174
+ </div>
175
+ )}
176
+ <InlineEdit
177
+ value={list.description ?? ''}
178
+ onChange={(description) => {
179
+ patch({ description: description.length > 0 ? description : null })
180
+ }}
181
+ displayClassName="text-[13px] text-ink-muted not-italic"
182
+ emptyLabel="Add a description"
183
+ multiline
184
+ />
185
+ </div>
186
+ )
187
+ }
188
+
189
+ function ListMembers({
190
+ listId,
191
+ targetType,
192
+ }: {
193
+ readonly listId: string
194
+ readonly targetType: RecordTargetType
195
+ }): React.JSX.Element {
196
+ const members = useListMembers(listId)
197
+ const addMember = useAddListMember()
198
+ const [adding, setAdding] = useState(false)
199
+
200
+ const attached = new Set(members.records.map((member) => member.targetId))
201
+
202
+ function add(targetId: string): void {
203
+ if (attached.has(targetId)) {
204
+ return
205
+ }
206
+
207
+ addMember.run({ listId, input: { targetType, targetId } })
208
+ setAdding(false)
209
+ }
210
+
211
+ return (
212
+ <section>
213
+ <SectionHeader
214
+ title="Members"
215
+ onAdd={() => {
216
+ setAdding((current) => !current)
217
+ }}
218
+ addLabel={`Add ${RECORD_TARGET_TYPE_LABELS[targetType].toLowerCase()}`}
219
+ />
220
+
221
+ {adding && (
222
+ <div className="mb-3 flex gap-2">
223
+ <div className="min-w-0 flex-1">
224
+ <MemberPicker
225
+ targetType={targetType}
226
+ attached={attached}
227
+ onPick={add}
228
+ />
229
+ </div>
230
+ <button
231
+ type="button"
232
+ onClick={() => {
233
+ setAdding(false)
234
+ }}
235
+ className="shrink-0 rounded-md px-2.5 py-1.5 text-[12px] font-medium text-ink-muted hover:text-ink"
236
+ >
237
+ Cancel
238
+ </button>
239
+ </div>
240
+ )}
241
+
242
+ {addMember.error !== null && (
243
+ <div className="mb-3">
244
+ <ErrorPanel error={addMember.error} />
245
+ </div>
246
+ )}
247
+ {members.error !== null && <ErrorPanel error={members.error} />}
248
+
249
+ {members.isLoading ? (
250
+ <LoadingPanel label="Loading members…" />
251
+ ) : members.records.length === 0 && !adding ? (
252
+ <p className="text-[13px] text-ink-faint">No members yet.</p>
253
+ ) : (
254
+ <>
255
+ <Paginator list={members} placement="top" />
256
+ <ul className="overflow-hidden rounded-md border border-border">
257
+ {members.records.map((member) => (
258
+ <MemberRow key={member.id} listId={listId} member={member} />
259
+ ))}
260
+ </ul>
261
+ </>
262
+ )}
263
+
264
+ <Paginator list={members} />
265
+ </section>
266
+ )
267
+ }
268
+
269
+ function MemberRow({
270
+ listId,
271
+ member,
272
+ }: {
273
+ readonly listId: string
274
+ readonly member: ListMember
275
+ }): React.JSX.Element {
276
+ const remove = useRemoveListMember()
277
+
278
+ return (
279
+ <li className="border-b border-border px-4 py-3 last:border-0">
280
+ <div className="flex flex-wrap items-center justify-between gap-3">
281
+ <Link
282
+ to={detailPathFor(member.targetType, member.targetId)}
283
+ className="text-left text-[13px] font-medium text-ink hover:text-accent"
284
+ >
285
+ {member.targetName ?? member.targetId}
286
+ </Link>
287
+ <button
288
+ type="button"
289
+ onClick={() => {
290
+ remove.run({ listId, id: member.id })
291
+ }}
292
+ className="text-[11px] font-medium text-danger hover:underline"
293
+ >
294
+ Remove
295
+ </button>
296
+ </div>
297
+
298
+ {remove.error !== null && (
299
+ <div className="mt-2">
300
+ <ErrorPanel error={remove.error} />
301
+ </div>
302
+ )}
303
+ </li>
304
+ )
305
+ }
306
+
307
+ /** Where a link on a member row should go. Every type has its own detail path. */
308
+ function detailPathFor(targetType: RecordTargetType, targetId: string): string {
309
+ switch (targetType) {
310
+ case 'person':
311
+ return `/people/${targetId}`
312
+ case 'company':
313
+ return `/companies/${targetId}`
314
+ case 'deal':
315
+ return `/deals/${targetId}`
316
+ case 'opportunity':
317
+ return `/opportunities/${targetId}`
318
+ case 'partnership':
319
+ return `/partnerships/${targetId}`
320
+ case 'raise':
321
+ return `/fundraising/${targetId}`
322
+ case 'enquiry':
323
+ return `/enquiries/${targetId}`
324
+ case 'candidate':
325
+ // A candidate has no page of its own; the person's does.
326
+ return `/hiring`
327
+ }
328
+ }
329
+
330
+ /**
331
+ * A "search or pick" combobox that queries the collection matching the list's
332
+ * type. Each type gets its own component so React sees a stable set of hook
333
+ * calls per render.
334
+ */
335
+ function MemberPicker({
336
+ targetType,
337
+ attached,
338
+ onPick,
339
+ }: {
340
+ readonly targetType: RecordTargetType
341
+ readonly attached: ReadonlySet<string>
342
+ readonly onPick: (id: string) => void
343
+ }): React.JSX.Element {
344
+ switch (targetType) {
345
+ case 'person':
346
+ return <PeoplePicker attached={attached} onPick={onPick} />
347
+ case 'company':
348
+ return <CompaniesPicker attached={attached} onPick={onPick} />
349
+ case 'deal':
350
+ return <DealsPicker attached={attached} onPick={onPick} />
351
+ case 'opportunity':
352
+ return <OpportunitiesPicker attached={attached} onPick={onPick} />
353
+ case 'partnership':
354
+ return <PartnershipsPicker attached={attached} onPick={onPick} />
355
+ case 'raise':
356
+ return <RaisesPicker attached={attached} onPick={onPick} />
357
+ case 'enquiry':
358
+ return <EnquiriesPicker attached={attached} onPick={onPick} />
359
+ case 'candidate':
360
+ return (
361
+ <p className="text-[12px] text-ink-faint">
362
+ Candidate lists have no inline picker in this release. Add candidacies from a role's
363
+ page, then attach them here through the API.
364
+ </p>
365
+ )
366
+ }
367
+ }
368
+
369
+ interface PickerProps {
370
+ readonly attached: ReadonlySet<string>
371
+ readonly onPick: (id: string) => void
372
+ }
373
+
374
+ function PeoplePicker({ attached, onPick }: PickerProps): React.JSX.Element {
375
+ const [search, setSearch] = useState('')
376
+ const records = usePeople({ term: trimmedOrUndefined(search) })
377
+ const options: readonly SearchOption[] = records.records
378
+ .filter((row) => !attached.has(row.id))
379
+ .map((row) => ({ id: row.id, label: row.name, meta: row.email ?? undefined }))
380
+
381
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
382
+ }
383
+
384
+ function CompaniesPicker({ attached, onPick }: PickerProps): React.JSX.Element {
385
+ const [search, setSearch] = useState('')
386
+ const records = useCompanies({ term: trimmedOrUndefined(search) })
387
+ const options: readonly SearchOption[] = records.records
388
+ .filter((row) => !attached.has(row.id))
389
+ .map((row) => ({ id: row.id, label: row.name, meta: row.domain ?? undefined }))
390
+
391
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
392
+ }
393
+
394
+ function DealsPicker({ attached, onPick }: PickerProps): React.JSX.Element {
395
+ const [search, setSearch] = useState('')
396
+ const records = useDeals({ term: trimmedOrUndefined(search) })
397
+ const options: readonly SearchOption[] = records.records
398
+ .filter((row) => !attached.has(row.id))
399
+ .map((row) => ({ id: row.id, label: row.name }))
400
+
401
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
402
+ }
403
+
404
+ function OpportunitiesPicker({ attached, onPick }: PickerProps): React.JSX.Element {
405
+ const [search, setSearch] = useState('')
406
+ const records = useOpportunities({ term: trimmedOrUndefined(search) })
407
+ const options: readonly SearchOption[] = records.records
408
+ .filter((row) => !attached.has(row.id))
409
+ .map((row) => ({ id: row.id, label: row.name }))
410
+
411
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
412
+ }
413
+
414
+ function PartnershipsPicker({ attached, onPick }: PickerProps): React.JSX.Element {
415
+ const [search, setSearch] = useState('')
416
+ const records = usePartnerships({ term: trimmedOrUndefined(search) })
417
+ const options: readonly SearchOption[] = records.records
418
+ .filter((row) => !attached.has(row.id))
419
+ .map((row) => ({ id: row.id, label: row.name }))
420
+
421
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
422
+ }
423
+
424
+ function RaisesPicker({ attached, onPick }: PickerProps): React.JSX.Element {
425
+ const [search, setSearch] = useState('')
426
+ const records = useRaises({ term: trimmedOrUndefined(search) })
427
+ const options: readonly SearchOption[] = records.records
428
+ .filter((row) => !attached.has(row.id))
429
+ .map((row) => ({ id: row.id, label: row.name }))
430
+
431
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
432
+ }
433
+
434
+ function EnquiriesPicker({ attached, onPick }: PickerProps): React.JSX.Element {
435
+ const [search, setSearch] = useState('')
436
+ const records = useEnquiries({ term: trimmedOrUndefined(search) })
437
+ const options: readonly SearchOption[] = records.records
438
+ .filter((row) => !attached.has(row.id))
439
+ .map((row) => ({ id: row.id, label: row.name }))
440
+
441
+ return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
442
+ }
443
+
444
+ function PickerBox({
445
+ onQueryChange,
446
+ onPick,
447
+ options,
448
+ }: {
449
+ readonly onQueryChange: (value: string) => void
450
+ readonly onPick: (id: string) => void
451
+ readonly options: readonly SearchOption[]
452
+ }): React.JSX.Element {
453
+ return (
454
+ <EntitySearch
455
+ options={options}
456
+ value=""
457
+ onChange={onPick}
458
+ onQueryChange={onQueryChange}
459
+ placeholder="Search…"
460
+ emptyMessage="No matches"
461
+ size="md"
462
+ />
463
+ )
464
+ }
465
+
466
+ function trimmedOrUndefined(value: string): string | undefined {
467
+ const trimmed = value.trim()
468
+
469
+ return trimmed.length > 0 ? trimmed : undefined
470
+ }