@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
@@ -22,10 +22,13 @@ import type {
22
22
  ImportRowError,
23
23
  ImportSource,
24
24
  OnMissingCompany,
25
+ SampleDataCounts,
25
26
  } from '@kelpie/schemas'
26
- import { useState } from 'react'
27
- import type { ChangeEvent, FormEvent, ReactNode } from 'react'
27
+ import { useEffect, useState } from 'react'
28
+ import type { ChangeEvent, DragEvent, FormEvent, ReactNode } from 'react'
28
29
 
30
+ import { useConsentPurposes } from '../../api/resources/consentPurposes.ts'
31
+ import { useRelinkEmailDomains } from '../../api/resources/emailDomainLinker.ts'
29
32
  import {
30
33
  saveCsv,
31
34
  useCommitImportJob,
@@ -34,6 +37,8 @@ import {
34
37
  useExportCsv,
35
38
  useImportJob,
36
39
  } from '../../api/resources/importJobs.ts'
40
+ import { useInstallSampleData } from '../../api/resources/sampleData.ts'
41
+ import { useSession } from '../../api/resources/session.ts'
37
42
  import { PageHeader } from '../../components/PageHeader.tsx'
38
43
 
39
44
  /**
@@ -49,13 +54,14 @@ import { PageHeader } from '../../components/PageHeader.tsx'
49
54
  * `public/fixtures`, and none of those are part of the application.
50
55
  */
51
56
 
52
- type WizardStep = 'source' | 'upload' | 'map' | 'result'
57
+ type WizardStep = 'source' | 'upload' | 'map' | 'result' | 'done'
53
58
 
54
59
  const STEPS: readonly { readonly id: WizardStep; readonly label: string }[] = [
55
60
  { id: 'source', label: 'Source' },
56
61
  { id: 'upload', label: 'Upload' },
57
62
  { id: 'map', label: 'Map' },
58
63
  { id: 'result', label: 'Dry-run' },
64
+ { id: 'done', label: 'Done' },
59
65
  ]
60
66
 
61
67
  export function DataPage(): ReactNode {
@@ -68,6 +74,7 @@ export function DataPage(): ReactNode {
68
74
  const [columnMap, setColumnMap] = useState<ImportColumnMap>({})
69
75
  const [conflictMode, setConflictMode] = useState<ImportConflictMode>('skip')
70
76
  const [onMissingCompany, setOnMissingCompany] = useState<OnMissingCompany>('skip')
77
+ const [consentPurposeId, setConsentPurposeId] = useState<string | null>(null)
71
78
  const [matchKeyId, setMatchKeyId] = useState<string>(() => defaultMatchKeyId('companies'))
72
79
  const [jobId, setJobId] = useState<string | undefined>(undefined)
73
80
  const [problem, setProblem] = useState<string | null>(null)
@@ -120,6 +127,7 @@ export function DataPage(): ReactNode {
120
127
  setColumnMap({})
121
128
  setConflictMode('skip')
122
129
  setOnMissingCompany('skip')
130
+ setConsentPurposeId(null)
123
131
  setMatchKeyId(defaultMatchKeyId(object))
124
132
  setJobId(undefined)
125
133
  setProblem(null)
@@ -140,6 +148,7 @@ export function DataPage(): ReactNode {
140
148
  conflictMode,
141
149
  onMissingCompany,
142
150
  matchKeyId,
151
+ consentPurposeId,
143
152
  ...(map === undefined ? {} : { columnMap: map }),
144
153
  })
145
154
 
@@ -153,6 +162,19 @@ export function DataPage(): ReactNode {
153
162
  return created
154
163
  }
155
164
 
165
+ async function processChosenFile(chosen: File): Promise<void> {
166
+ setProblem(null)
167
+ setFile(chosen)
168
+
169
+ const created = await runDryRun(chosen, undefined)
170
+
171
+ // The first upload takes the server's own reading of the file, which is what
172
+ // the mapping table is then built from.
173
+ setColumnMap(created.columnMap)
174
+ setMatchKeyId(created.matchKey)
175
+ setStep('map')
176
+ }
177
+
156
178
  async function onFileChange(event: ChangeEvent<HTMLInputElement>): Promise<void> {
157
179
  const chosen = event.target.files?.[0]
158
180
 
@@ -164,16 +186,22 @@ export function DataPage(): ReactNode {
164
186
  return
165
187
  }
166
188
 
167
- setProblem(null)
168
- setFile(chosen)
189
+ await processChosenFile(chosen)
190
+ }
169
191
 
170
- const created = await runDryRun(chosen, undefined)
192
+ async function onFileDrop(event: DragEvent<HTMLLabelElement>): Promise<void> {
193
+ event.preventDefault()
171
194
 
172
- // The first upload takes the server's own reading of the file, which is what
173
- // the mapping table is then built from.
174
- setColumnMap(created.columnMap)
175
- setMatchKeyId(created.matchKey)
176
- setStep('map')
195
+ if (createJob.isPending) {
196
+ return
197
+ }
198
+
199
+ const chosen = event.dataTransfer.files[0]
200
+ if (chosen === undefined) {
201
+ return
202
+ }
203
+
204
+ await processChosenFile(chosen)
177
205
  }
178
206
 
179
207
  async function onRerun(event: FormEvent): Promise<void> {
@@ -199,6 +227,19 @@ export function DataPage(): ReactNode {
199
227
  setStep('result')
200
228
  }
201
229
 
230
+ // Commit finishes on the server, not the click, so the wizard waits for the
231
+ // job to reach `completed` before showing the outcome step. `failed` lands
232
+ // here too — the outcome is that the commit did not go through.
233
+ useEffect(() => {
234
+ if (step !== 'result' || job === undefined) {
235
+ return
236
+ }
237
+
238
+ if (job.status === 'completed' || job.status === 'failed') {
239
+ setStep('done')
240
+ }
241
+ }, [step, job])
242
+
202
243
  function onCommit(): void {
203
244
  // The file goes back with the commit: the server kept its digest, not its
204
245
  // bytes. This is the same `File` every dry run was run against.
@@ -223,7 +264,11 @@ export function DataPage(): ReactNode {
223
264
  </p>
224
265
  )}
225
266
 
226
- <section className="space-y-4">
267
+ <SampleDataSection />
268
+
269
+ <RelinkEmailDomainsSection />
270
+
271
+ <section className="space-y-4 border-t border-border pt-8">
227
272
  <div>
228
273
  <h2 className="text-[15px] font-semibold text-ink">Export</h2>
229
274
  <p className="mt-1 text-[13px] text-ink-muted">
@@ -348,7 +393,14 @@ export function DataPage(): ReactNode {
348
393
  <p className="text-[13px] text-ink-muted">
349
394
  {SOURCE_LABELS[source]} → {OBJECT_LABELS[object]}
350
395
  </p>
351
- <label className="flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-border bg-surface-raised px-6 py-10 transition hover:border-accent">
396
+ <label
397
+ className="flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-border bg-surface-raised px-6 py-10 transition hover:border-accent"
398
+ onDragOver={(event) => {
399
+ event.preventDefault()
400
+ event.dataTransfer.dropEffect = 'copy'
401
+ }}
402
+ onDrop={(event) => runStep(onFileDrop(event))}
403
+ >
352
404
  <span className="text-[13px] font-medium text-ink">
353
405
  {createJob.isPending ? 'Reading the file…' : 'Drop a CSV here or click to browse'}
354
406
  </span>
@@ -380,11 +432,13 @@ export function DataPage(): ReactNode {
380
432
  columnMap={columnMap}
381
433
  conflictMode={conflictMode}
382
434
  onMissingCompany={onMissingCompany}
435
+ consentPurposeId={consentPurposeId}
383
436
  matchKeyId={matchKeyId}
384
437
  isPending={createJob.isPending}
385
438
  onColumnMapChange={setColumnMap}
386
439
  onConflictModeChange={setConflictMode}
387
440
  onMissingCompanyChange={setOnMissingCompany}
441
+ onConsentPurposeChange={setConsentPurposeId}
388
442
  onMatchKeyChange={setMatchKeyId}
389
443
  onSubmit={(event) => runStep(onRerun(event))}
390
444
  onBack={() => {
@@ -405,6 +459,10 @@ export function DataPage(): ReactNode {
405
459
  onBack={() => setStep('map')}
406
460
  />
407
461
  ) : null}
462
+
463
+ {step === 'done' && job !== undefined ? (
464
+ <DonePanel job={job} object={object} onAgain={resetWizard} />
465
+ ) : null}
408
466
  </section>
409
467
  </div>
410
468
  )
@@ -416,11 +474,13 @@ interface MappingFormProps {
416
474
  readonly columnMap: ImportColumnMap
417
475
  readonly conflictMode: ImportConflictMode
418
476
  readonly onMissingCompany: OnMissingCompany
477
+ readonly consentPurposeId: string | null
419
478
  readonly matchKeyId: string
420
479
  readonly isPending: boolean
421
480
  readonly onColumnMapChange: (map: ImportColumnMap) => void
422
481
  readonly onConflictModeChange: (mode: ImportConflictMode) => void
423
482
  readonly onMissingCompanyChange: (mode: OnMissingCompany) => void
483
+ readonly onConsentPurposeChange: (id: string | null) => void
424
484
  readonly onMatchKeyChange: (id: string) => void
425
485
  readonly onSubmit: (event: FormEvent) => void
426
486
  readonly onBack: () => void
@@ -483,6 +543,12 @@ function MappingForm(props: MappingFormProps): ReactNode {
483
543
  </select>
484
544
  </Field>
485
545
  ) : null}
546
+ {showMissingCompany ? (
547
+ <ConsentPurposeField
548
+ value={props.consentPurposeId}
549
+ onChange={props.onConsentPurposeChange}
550
+ />
551
+ ) : null}
486
552
  </div>
487
553
  <p className="text-[12px] text-ink-muted">
488
554
  {props.conflictMode === 'update'
@@ -661,6 +727,81 @@ function ResultPanel({ job, object, isCommitting, onCommit, onBack }: ResultPane
661
727
  )
662
728
  }
663
729
 
730
+ interface DonePanelProps {
731
+ readonly job: ImportJob
732
+ readonly object: ImportObject
733
+ readonly onAgain: () => void
734
+ }
735
+
736
+ function DonePanel({ job, object, onAgain }: DonePanelProps): ReactNode {
737
+ const failed = job.status === 'failed'
738
+ const wrote = job.counts.create + job.counts.update
739
+
740
+ return (
741
+ <div className="space-y-4">
742
+ <div>
743
+ <h3 className="text-[15px] font-semibold text-ink">
744
+ {failed ? 'Import failed' : 'Import complete'}
745
+ </h3>
746
+ <p className="mt-1 text-[13px] text-ink-muted">
747
+ {failed
748
+ ? 'The server refused the commit. Nothing was written.'
749
+ : `${wrote} ${OBJECT_LABELS[object].toLowerCase()} row${wrote === 1 ? '' : 's'} written to this workspace.`}
750
+ </p>
751
+ </div>
752
+ <p className="text-[12px] text-ink-muted">
753
+ Job <span className="font-mono text-[12px] text-ink">{job.id}</span> · status {job.status}
754
+ </p>
755
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-5">
756
+ <Stat label="Total" value={job.counts.total} />
757
+ <Stat label="Created" value={job.counts.create} />
758
+ <Stat label="Updated" value={job.counts.update} />
759
+ <Stat label="Skipped" value={job.counts.skip} />
760
+ <Stat label="Errors" value={job.counts.error} danger />
761
+ </div>
762
+
763
+ {job.errors.length > 0 ? (
764
+ <div className="space-y-2">
765
+ <div>
766
+ <h3 className="text-[13px] font-semibold text-ink">Row errors</h3>
767
+ <p className="mt-0.5 text-[12px] text-ink-muted">
768
+ These rows were not written. Fix them in the CSV and import again.
769
+ </p>
770
+ </div>
771
+ <IssueTable rows={job.errors} />
772
+ {job.counts.error > job.errors.length ? (
773
+ <p className="text-[12px] text-ink-muted">
774
+ Showing {job.errors.length} of {job.counts.error} failing rows.
775
+ </p>
776
+ ) : null}
777
+ </div>
778
+ ) : null}
779
+
780
+ {job.warnings.length > 0 ? (
781
+ <div className="space-y-2">
782
+ <div>
783
+ <h3 className="text-[13px] font-semibold text-ink">Warnings</h3>
784
+ <p className="mt-0.5 text-[12px] text-ink-muted">
785
+ These rows were written. Each note says what was left out.
786
+ </p>
787
+ </div>
788
+ <IssueTable rows={job.warnings} />
789
+ </div>
790
+ ) : null}
791
+
792
+ <div className="flex flex-wrap gap-3">
793
+ <button
794
+ type="button"
795
+ onClick={onAgain}
796
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover"
797
+ >
798
+ Import another
799
+ </button>
800
+ </div>
801
+ </div>
802
+ )
803
+ }
804
+
664
805
  /** The row / field / message table shared by the error and warning lists. */
665
806
  function IssueTable({ rows }: { readonly rows: readonly ImportRowError[] }): ReactNode {
666
807
  return (
@@ -827,6 +968,176 @@ function Field({
827
968
  )
828
969
  }
829
970
 
971
+ /**
972
+ * People imports only. Required whenever the map names `consent_status` or
973
+ * `consent_at` — the server refuses the job otherwise, so the picker is next
974
+ * to the columns rather than tucked away.
975
+ */
976
+ function ConsentPurposeField({
977
+ value,
978
+ onChange,
979
+ }: {
980
+ readonly value: string | null
981
+ readonly onChange: (id: string | null) => void
982
+ }): ReactNode {
983
+ const purposes = useConsentPurposes({ sort: 'sort_order', limit: 200 })
984
+
985
+ return (
986
+ <Field label="Consent purpose">
987
+ <select
988
+ value={value ?? ''}
989
+ onChange={(event) => {
990
+ onChange(event.target.value.length === 0 ? null : event.target.value)
991
+ }}
992
+ className="w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent"
993
+ >
994
+ <option value="">— none —</option>
995
+ {purposes.records.map((purpose) => (
996
+ <option key={purpose.id} value={purpose.id}>
997
+ {purpose.label}
998
+ </option>
999
+ ))}
1000
+ </select>
1001
+ </Field>
1002
+ )
1003
+ }
1004
+
830
1005
  function messageOf(error: Error | null | undefined): string | null {
831
1006
  return error === null || error === undefined ? null : error.message
832
1007
  }
1008
+
1009
+ /**
1010
+ * One button that fills the workspace with a small demo fixture.
1011
+ *
1012
+ * The endpoint refuses on a workspace that already has data, so the button
1013
+ * never doubles the seed and the "already has data" answer surfaces here as
1014
+ * a plain error line.
1015
+ */
1016
+ function SampleDataSection(): ReactNode {
1017
+ const { session } = useSession()
1018
+ const workspaceId = session?.workspaceId ?? null
1019
+ const install = useInstallSampleData()
1020
+ const [counts, setCounts] = useState<SampleDataCounts | null>(null)
1021
+
1022
+ function onInstall(): void {
1023
+ if (workspaceId === null) {
1024
+ return
1025
+ }
1026
+
1027
+ install
1028
+ .runAsync({ workspaceId })
1029
+ .then((result) => {
1030
+ setCounts(result)
1031
+ })
1032
+ .catch(() => undefined)
1033
+ }
1034
+
1035
+ const failure = messageOf(install.error)
1036
+
1037
+ return (
1038
+ <section className="space-y-4">
1039
+ <div>
1040
+ <h2 className="text-[15px] font-semibold text-ink">Install sample data</h2>
1041
+ <p className="mt-1 text-[13px] text-ink-muted">
1042
+ A small set of companies, people, and deals so a fresh workspace has something
1043
+ to look at. Refuses if this workspace already has CRM data.
1044
+ </p>
1045
+ </div>
1046
+ <button
1047
+ type="button"
1048
+ onClick={onInstall}
1049
+ disabled={install.isPending || workspaceId === null || counts !== null}
1050
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover disabled:opacity-50"
1051
+ >
1052
+ {counts !== null
1053
+ ? 'Installed'
1054
+ : install.isPending
1055
+ ? 'Installing…'
1056
+ : 'Install sample data'}
1057
+ </button>
1058
+ {counts !== null ? (
1059
+ <p className="text-[12px] text-ink-muted">
1060
+ Added {counts.companies} companies, {counts.people} people, {counts.positions}{' '}
1061
+ positions, {counts.deals} deals, {counts.opportunities} opportunities,{' '}
1062
+ {counts.raises} raises, {counts.partnerships} partnerships, {counts.enquiries}{' '}
1063
+ enquiries, {counts.roles} roles, {counts.candidates} candidates,{' '}
1064
+ {counts.planItems} plan items, and {counts.notes} notes.
1065
+ </p>
1066
+ ) : null}
1067
+ {failure === null ? null : (
1068
+ <p role="alert" className="text-[12px] font-medium text-danger">
1069
+ {failure}
1070
+ </p>
1071
+ )}
1072
+ </section>
1073
+ )
1074
+ }
1075
+
1076
+ /**
1077
+ * Sweep every workspace Company that carries a domain and attach each Person
1078
+ * whose email is at that domain.
1079
+ *
1080
+ * Backfills links for records that predate the auto-linker or arrived through
1081
+ * a bulk import. Idempotent: a follow-up run adds nothing. Never removes.
1082
+ */
1083
+ function RelinkEmailDomainsSection(): ReactNode {
1084
+ const { session } = useSession()
1085
+ const workspaceId = session?.workspaceId ?? null
1086
+ const relink = useRelinkEmailDomains()
1087
+ const [outcome, setOutcome] = useState<{
1088
+ readonly companiesScanned: number
1089
+ readonly positionsCreated: number
1090
+ } | null>(null)
1091
+
1092
+ function onRun(): void {
1093
+ if (workspaceId === null) {
1094
+ return
1095
+ }
1096
+
1097
+ relink
1098
+ .runAsync({ workspaceId })
1099
+ .then((result) => {
1100
+ setOutcome({
1101
+ companiesScanned: result.companiesScanned,
1102
+ positionsCreated: result.positionsCreated,
1103
+ })
1104
+ })
1105
+ .catch(() => undefined)
1106
+ }
1107
+
1108
+ const failure = messageOf(relink.error)
1109
+
1110
+ return (
1111
+ <section className="space-y-4 border-t border-border pt-8">
1112
+ <div>
1113
+ <h2 className="text-[15px] font-semibold text-ink">Rebuild email-domain links</h2>
1114
+ <p className="mt-1 text-[13px] text-ink-muted">
1115
+ Attach every workspace Person to a Company whose domain matches their email. Safe to run
1116
+ more than once — a Person already at a Company is left alone, and consumer email hosts
1117
+ (gmail, hotmail, …) are skipped.
1118
+ </p>
1119
+ </div>
1120
+ <button
1121
+ type="button"
1122
+ onClick={onRun}
1123
+ disabled={relink.isPending || workspaceId === null}
1124
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover disabled:opacity-50"
1125
+ >
1126
+ {relink.isPending ? 'Rebuilding…' : 'Rebuild links'}
1127
+ </button>
1128
+ {outcome !== null ? (
1129
+ <p className="text-[12px] text-ink-muted">
1130
+ Scanned {outcome.companiesScanned}{' '}
1131
+ {outcome.companiesScanned === 1 ? 'company' : 'companies'}, added{' '}
1132
+ {outcome.positionsCreated}{' '}
1133
+ {outcome.positionsCreated === 1 ? 'position' : 'positions'}.
1134
+ </p>
1135
+ ) : null}
1136
+ {failure === null ? null : (
1137
+ <p role="alert" className="text-[12px] font-medium text-danger">
1138
+ {failure}
1139
+ </p>
1140
+ )}
1141
+ </section>
1142
+ )
1143
+ }