@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.
- package/dist/api/resource.d.ts +59 -6
- package/dist/api/resource.d.ts.map +1 -1
- package/dist/api/resource.js +106 -24
- package/dist/api/resource.js.map +1 -1
- package/dist/api/resources/companies.d.ts +2 -0
- package/dist/api/resources/companies.d.ts.map +1 -1
- package/dist/api/resources/companies.js +5 -1
- package/dist/api/resources/companies.js.map +1 -1
- package/dist/api/resources/consentPurposes.d.ts +13 -0
- package/dist/api/resources/consentPurposes.d.ts.map +1 -0
- package/dist/api/resources/consentPurposes.js +39 -0
- package/dist/api/resources/consentPurposes.js.map +1 -0
- package/dist/api/resources/customFields.d.ts +15 -0
- package/dist/api/resources/customFields.d.ts.map +1 -0
- package/dist/api/resources/customFields.js +41 -0
- package/dist/api/resources/customFields.js.map +1 -0
- package/dist/api/resources/decisions.d.ts +2 -0
- package/dist/api/resources/decisions.d.ts.map +1 -1
- package/dist/api/resources/decisions.js +1 -0
- package/dist/api/resources/decisions.js.map +1 -1
- package/dist/api/resources/emailDomainLinker.d.ts +16 -0
- package/dist/api/resources/emailDomainLinker.d.ts.map +1 -0
- package/dist/api/resources/emailDomainLinker.js +16 -0
- package/dist/api/resources/emailDomainLinker.js.map +1 -0
- package/dist/api/resources/enquiries.d.ts +39 -0
- package/dist/api/resources/enquiries.d.ts.map +1 -0
- package/dist/api/resources/enquiries.js +68 -0
- package/dist/api/resources/enquiries.js.map +1 -0
- package/dist/api/resources/forms.d.ts +21 -1
- package/dist/api/resources/forms.d.ts.map +1 -1
- package/dist/api/resources/forms.js +44 -17
- package/dist/api/resources/forms.js.map +1 -1
- package/dist/api/resources/handbookPages.d.ts +1 -1
- package/dist/api/resources/handbookPages.js +1 -1
- package/dist/api/resources/importJobs.d.ts +5 -0
- package/dist/api/resources/importJobs.d.ts.map +1 -1
- package/dist/api/resources/importJobs.js +3 -0
- package/dist/api/resources/importJobs.js.map +1 -1
- package/dist/api/resources/listMembers.d.ts +27 -0
- package/dist/api/resources/listMembers.d.ts.map +1 -0
- package/dist/api/resources/listMembers.js +108 -0
- package/dist/api/resources/listMembers.js.map +1 -0
- package/dist/api/resources/lists.d.ts +14 -0
- package/dist/api/resources/lists.d.ts.map +1 -0
- package/dist/api/resources/lists.js +34 -0
- package/dist/api/resources/lists.js.map +1 -0
- package/dist/api/resources/opportunities.d.ts +2 -0
- package/dist/api/resources/opportunities.d.ts.map +1 -1
- package/dist/api/resources/opportunities.js +1 -0
- package/dist/api/resources/opportunities.js.map +1 -1
- package/dist/api/resources/people.d.ts.map +1 -1
- package/dist/api/resources/people.js +4 -1
- package/dist/api/resources/people.js.map +1 -1
- package/dist/api/resources/roles.d.ts +2 -0
- package/dist/api/resources/roles.d.ts.map +1 -1
- package/dist/api/resources/roles.js +1 -1
- package/dist/api/resources/roles.js.map +1 -1
- package/dist/api/resources/sampleData.d.ts +17 -0
- package/dist/api/resources/sampleData.d.ts.map +1 -0
- package/dist/api/resources/sampleData.js +31 -0
- package/dist/api/resources/sampleData.js.map +1 -0
- package/dist/api/resources/webhooks.d.ts.map +1 -1
- package/dist/api/resources/webhooks.js +7 -22
- package/dist/api/resources/webhooks.js.map +1 -1
- package/dist/app/KelpieApp.d.ts.map +1 -1
- package/dist/app/KelpieApp.js +9 -1
- package/dist/app/KelpieApp.js.map +1 -1
- package/dist/components/ActivitiesPanel.d.ts.map +1 -1
- package/dist/components/ActivitiesPanel.js +3 -1
- package/dist/components/ActivitiesPanel.js.map +1 -1
- package/dist/components/ColumnPicker.d.ts +24 -0
- package/dist/components/ColumnPicker.d.ts.map +1 -0
- package/dist/components/ColumnPicker.js +56 -0
- package/dist/components/ColumnPicker.js.map +1 -0
- package/dist/components/CustomFieldsPanel.d.ts +23 -0
- package/dist/components/CustomFieldsPanel.d.ts.map +1 -0
- package/dist/components/CustomFieldsPanel.js +116 -0
- package/dist/components/CustomFieldsPanel.js.map +1 -0
- package/dist/components/DataTable.d.ts +16 -1
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/DataTable.js +51 -10
- package/dist/components/DataTable.js.map +1 -1
- package/dist/components/DecisionsPanel.d.ts.map +1 -1
- package/dist/components/DecisionsPanel.js +4 -3
- package/dist/components/DecisionsPanel.js.map +1 -1
- package/dist/components/EntitySearch.d.ts.map +1 -1
- package/dist/components/EntitySearch.js +11 -5
- package/dist/components/EntitySearch.js.map +1 -1
- package/dist/components/FormsPanel.d.ts +18 -0
- package/dist/components/FormsPanel.d.ts.map +1 -0
- package/dist/components/FormsPanel.js +37 -0
- package/dist/components/FormsPanel.js.map +1 -0
- package/dist/components/ListsPanel.d.ts +14 -0
- package/dist/components/ListsPanel.d.ts.map +1 -0
- package/dist/components/ListsPanel.js +56 -0
- package/dist/components/ListsPanel.js.map +1 -0
- package/dist/components/NotesPanel.d.ts.map +1 -1
- package/dist/components/NotesPanel.js +6 -5
- package/dist/components/NotesPanel.js.map +1 -1
- package/dist/components/Paginator.d.ts +26 -0
- package/dist/components/Paginator.d.ts.map +1 -0
- package/dist/components/Paginator.js +16 -0
- package/dist/components/Paginator.js.map +1 -0
- package/dist/components/PlanAttention.d.ts +3 -2
- package/dist/components/PlanAttention.d.ts.map +1 -1
- package/dist/components/PlanAttention.js +7 -3
- package/dist/components/PlanAttention.js.map +1 -1
- package/dist/components/PlanPanel.d.ts.map +1 -1
- package/dist/components/PlanPanel.js +2 -1
- package/dist/components/PlanPanel.js.map +1 -1
- package/dist/components/Shell.d.ts.map +1 -1
- package/dist/components/Shell.js +4 -0
- package/dist/components/Shell.js.map +1 -1
- package/dist/components/useHasCustomFields.d.ts +13 -0
- package/dist/components/useHasCustomFields.d.ts.map +1 -0
- package/dist/components/useHasCustomFields.js +16 -0
- package/dist/components/useHasCustomFields.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/listView.d.ts +15 -0
- package/dist/lib/listView.d.ts.map +1 -0
- package/dist/lib/listView.js +66 -0
- package/dist/lib/listView.js.map +1 -0
- package/dist/lib/sort.d.ts +43 -0
- package/dist/lib/sort.d.ts.map +1 -0
- package/dist/lib/sort.js +102 -0
- package/dist/lib/sort.js.map +1 -0
- package/dist/pages/CompaniesPage.d.ts.map +1 -1
- package/dist/pages/CompaniesPage.js +88 -3
- package/dist/pages/CompaniesPage.js.map +1 -1
- package/dist/pages/CompanyDetail.d.ts.map +1 -1
- package/dist/pages/CompanyDetail.js +40 -15
- package/dist/pages/CompanyDetail.js.map +1 -1
- package/dist/pages/DealDetail.d.ts.map +1 -1
- package/dist/pages/DealDetail.js +20 -1
- package/dist/pages/DealDetail.js.map +1 -1
- package/dist/pages/DealsPage.d.ts.map +1 -1
- package/dist/pages/DealsPage.js +134 -14
- package/dist/pages/DealsPage.js.map +1 -1
- package/dist/pages/DecisionsPage.d.ts.map +1 -1
- package/dist/pages/DecisionsPage.js +52 -8
- package/dist/pages/DecisionsPage.js.map +1 -1
- package/dist/pages/EnquiriesPage.d.ts +2 -0
- package/dist/pages/EnquiriesPage.d.ts.map +1 -0
- package/dist/pages/EnquiriesPage.js +251 -0
- package/dist/pages/EnquiriesPage.js.map +1 -0
- package/dist/pages/EnquiryDetail.d.ts +2 -0
- package/dist/pages/EnquiryDetail.d.ts.map +1 -0
- package/dist/pages/EnquiryDetail.js +207 -0
- package/dist/pages/EnquiryDetail.js.map +1 -0
- package/dist/pages/EnquiryStageSettingsPage.d.ts +3 -0
- package/dist/pages/EnquiryStageSettingsPage.d.ts.map +1 -0
- package/dist/pages/EnquiryStageSettingsPage.js +34 -0
- package/dist/pages/EnquiryStageSettingsPage.js.map +1 -0
- package/dist/pages/FormDetail.d.ts.map +1 -1
- package/dist/pages/FormDetail.js +19 -1
- package/dist/pages/FormDetail.js.map +1 -1
- package/dist/pages/FormsPage.d.ts.map +1 -1
- package/dist/pages/FormsPage.js +53 -2
- package/dist/pages/FormsPage.js.map +1 -1
- package/dist/pages/FundraisingPage.d.ts.map +1 -1
- package/dist/pages/FundraisingPage.js +116 -14
- package/dist/pages/FundraisingPage.js.map +1 -1
- package/dist/pages/HiringPage.d.ts.map +1 -1
- package/dist/pages/HiringPage.js +27 -4
- package/dist/pages/HiringPage.js.map +1 -1
- package/dist/pages/ListDetail.d.ts +11 -0
- package/dist/pages/ListDetail.d.ts.map +1 -0
- package/dist/pages/ListDetail.js +214 -0
- package/dist/pages/ListDetail.js.map +1 -0
- package/dist/pages/ListsPage.d.ts +9 -0
- package/dist/pages/ListsPage.d.ts.map +1 -0
- package/dist/pages/ListsPage.js +125 -0
- package/dist/pages/ListsPage.js.map +1 -0
- package/dist/pages/OpportunitiesPage.d.ts.map +1 -1
- package/dist/pages/OpportunitiesPage.js +97 -14
- package/dist/pages/OpportunitiesPage.js.map +1 -1
- package/dist/pages/OpportunityDetail.d.ts.map +1 -1
- package/dist/pages/OpportunityDetail.js +64 -3
- package/dist/pages/OpportunityDetail.js.map +1 -1
- package/dist/pages/PartnershipDetail.d.ts.map +1 -1
- package/dist/pages/PartnershipDetail.js +19 -1
- package/dist/pages/PartnershipDetail.js.map +1 -1
- package/dist/pages/PartnershipsPage.d.ts.map +1 -1
- package/dist/pages/PartnershipsPage.js +111 -14
- package/dist/pages/PartnershipsPage.js.map +1 -1
- package/dist/pages/PeoplePage.d.ts.map +1 -1
- package/dist/pages/PeoplePage.js +119 -3
- package/dist/pages/PeoplePage.js.map +1 -1
- package/dist/pages/PersonDetail.d.ts.map +1 -1
- package/dist/pages/PersonDetail.js +67 -5
- package/dist/pages/PersonDetail.js.map +1 -1
- package/dist/pages/PlanningPage.d.ts.map +1 -1
- package/dist/pages/PlanningPage.js +88 -17
- package/dist/pages/PlanningPage.js.map +1 -1
- package/dist/pages/RaiseDetail.d.ts.map +1 -1
- package/dist/pages/RaiseDetail.js +13 -1
- package/dist/pages/RaiseDetail.js.map +1 -1
- package/dist/pages/RoleDetail.d.ts.map +1 -1
- package/dist/pages/RoleDetail.js +5 -4
- package/dist/pages/RoleDetail.js.map +1 -1
- package/dist/pages/SearchPage.d.ts.map +1 -1
- package/dist/pages/SearchPage.js +1 -0
- package/dist/pages/SearchPage.js.map +1 -1
- package/dist/pages/admin/DataPage.d.ts.map +1 -1
- package/dist/pages/admin/DataPage.js +126 -13
- package/dist/pages/admin/DataPage.js.map +1 -1
- package/dist/pages/admin/FieldsPage.d.ts +2 -0
- package/dist/pages/admin/FieldsPage.d.ts.map +1 -0
- package/dist/pages/admin/FieldsPage.js +164 -0
- package/dist/pages/admin/FieldsPage.js.map +1 -0
- package/dist/pages/admin/PrivacyPage.d.ts +9 -0
- package/dist/pages/admin/PrivacyPage.d.ts.map +1 -0
- package/dist/pages/admin/PrivacyPage.js +170 -0
- package/dist/pages/admin/PrivacyPage.js.map +1 -0
- package/dist/pages/admin/WebhookDeliveries.d.ts.map +1 -1
- package/dist/pages/admin/WebhookDeliveries.js +3 -2
- package/dist/pages/admin/WebhookDeliveries.js.map +1 -1
- package/dist/pages/admin/WebhooksPage.d.ts.map +1 -1
- package/dist/pages/admin/WebhooksPage.js +3 -2
- package/dist/pages/admin/WebhooksPage.js.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.js +45 -2
- package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/dashboard/attention.d.ts.map +1 -1
- package/dist/pages/dashboard/attention.js +3 -0
- package/dist/pages/dashboard/attention.js.map +1 -1
- package/dist/pages/forms/AddFieldMenu.d.ts +17 -0
- package/dist/pages/forms/AddFieldMenu.d.ts.map +1 -0
- package/dist/pages/forms/AddFieldMenu.js +43 -0
- package/dist/pages/forms/AddFieldMenu.js.map +1 -0
- package/dist/pages/forms/EmbedPanel.d.ts +4 -0
- package/dist/pages/forms/EmbedPanel.d.ts.map +1 -1
- package/dist/pages/forms/EmbedPanel.js +62 -3
- package/dist/pages/forms/EmbedPanel.js.map +1 -1
- package/dist/pages/forms/FieldsEditor.d.ts.map +1 -1
- package/dist/pages/forms/FieldsEditor.js +179 -36
- package/dist/pages/forms/FieldsEditor.js.map +1 -1
- package/dist/pages/forms/FormActions.d.ts +6 -0
- package/dist/pages/forms/FormActions.d.ts.map +1 -0
- package/dist/pages/forms/FormActions.js +149 -0
- package/dist/pages/forms/FormActions.js.map +1 -0
- package/dist/pages/forms/FormSettings.d.ts.map +1 -1
- package/dist/pages/forms/FormSettings.js +15 -19
- package/dist/pages/forms/FormSettings.js.map +1 -1
- package/dist/pages/forms/SubmissionDetail.d.ts +2 -0
- package/dist/pages/forms/SubmissionDetail.d.ts.map +1 -0
- package/dist/pages/forms/SubmissionDetail.js +76 -0
- package/dist/pages/forms/SubmissionDetail.js.map +1 -0
- package/dist/pages/forms/SubmissionsTable.d.ts.map +1 -1
- package/dist/pages/forms/SubmissionsTable.js +57 -8
- package/dist/pages/forms/SubmissionsTable.js.map +1 -1
- package/dist/pages/forms/fieldList.d.ts +11 -5
- package/dist/pages/forms/fieldList.d.ts.map +1 -1
- package/dist/pages/forms/fieldList.js +66 -6
- package/dist/pages/forms/fieldList.js.map +1 -1
- package/dist/pages/forms/template.d.ts +17 -0
- package/dist/pages/forms/template.d.ts.map +1 -1
- package/dist/pages/forms/template.js +93 -0
- package/dist/pages/forms/template.js.map +1 -1
- package/dist/pages/handbook/HandbookPage.js +1 -1
- package/dist/pages/hiringDirectory.js +2 -2
- package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/WorkspaceStepPage.js +15 -2
- package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
- package/dist/pages/positionDirectory.js +2 -2
- package/package.json +2 -2
- package/src/api/resource.ts +155 -32
- package/src/api/resources/companies.ts +7 -1
- package/src/api/resources/consentPurposes.ts +83 -0
- package/src/api/resources/customFields.ts +88 -0
- package/src/api/resources/decisions.ts +3 -0
- package/src/api/resources/emailDomainLinker.ts +43 -0
- package/src/api/resources/enquiries.ts +118 -0
- package/src/api/resources/forms.ts +59 -19
- package/src/api/resources/handbookPages.ts +1 -1
- package/src/api/resources/importJobs.ts +9 -0
- package/src/api/resources/listMembers.ts +173 -0
- package/src/api/resources/lists.ts +61 -0
- package/src/api/resources/opportunities.ts +3 -0
- package/src/api/resources/people.ts +4 -1
- package/src/api/resources/roles.ts +3 -1
- package/src/api/resources/sampleData.ts +57 -0
- package/src/api/resources/webhooks.ts +8 -24
- package/src/app/KelpieApp.tsx +16 -0
- package/src/components/ActivitiesPanel.tsx +3 -10
- package/src/components/ColumnPicker.tsx +124 -0
- package/src/components/CustomFieldsPanel.tsx +330 -0
- package/src/components/DataTable.tsx +89 -10
- package/src/components/DecisionsPanel.tsx +4 -12
- package/src/components/EntitySearch.tsx +12 -5
- package/src/components/FormsPanel.tsx +86 -0
- package/src/components/ListsPanel.tsx +176 -0
- package/src/components/NotesPanel.tsx +6 -14
- package/src/components/Paginator.tsx +78 -0
- package/src/components/PlanAttention.tsx +20 -3
- package/src/components/PlanPanel.tsx +2 -10
- package/src/components/Shell.tsx +4 -0
- package/src/components/useHasCustomFields.ts +18 -0
- package/src/index.ts +10 -1
- package/src/lib/listView.ts +149 -0
- package/src/lib/sort.ts +143 -0
- package/src/pages/CompaniesPage.tsx +125 -11
- package/src/pages/CompanyDetail.tsx +98 -63
- package/src/pages/DealDetail.tsx +34 -0
- package/src/pages/DealsPage.tsx +178 -24
- package/src/pages/DecisionsPage.tsx +75 -18
- package/src/pages/EnquiriesPage.tsx +434 -0
- package/src/pages/EnquiryDetail.tsx +554 -0
- package/src/pages/EnquiryStageSettingsPage.tsx +63 -0
- package/src/pages/FormDetail.tsx +23 -1
- package/src/pages/FormsPage.tsx +81 -17
- package/src/pages/FundraisingPage.tsx +152 -24
- package/src/pages/HiringPage.tsx +39 -11
- package/src/pages/ListDetail.tsx +470 -0
- package/src/pages/ListsPage.tsx +281 -0
- package/src/pages/OpportunitiesPage.tsx +130 -24
- package/src/pages/OpportunityDetail.tsx +168 -2
- package/src/pages/PartnershipDetail.tsx +35 -0
- package/src/pages/PartnershipsPage.tsx +149 -24
- package/src/pages/PeoplePage.tsx +157 -11
- package/src/pages/PersonDetail.tsx +157 -6
- package/src/pages/PlanningPage.tsx +259 -29
- package/src/pages/RaiseDetail.tsx +24 -0
- package/src/pages/RoleDetail.tsx +21 -26
- package/src/pages/SearchPage.tsx +1 -0
- package/src/pages/admin/DataPage.tsx +324 -13
- package/src/pages/admin/FieldsPage.tsx +455 -0
- package/src/pages/admin/PrivacyPage.tsx +461 -0
- package/src/pages/admin/WebhookDeliveries.tsx +10 -15
- package/src/pages/admin/WebhooksPage.tsx +14 -19
- package/src/pages/admin/WorkspaceSettingsPage.tsx +134 -2
- package/src/pages/dashboard/attention.ts +3 -0
- package/src/pages/forms/AddFieldMenu.tsx +121 -0
- package/src/pages/forms/EmbedPanel.tsx +163 -2
- package/src/pages/forms/FieldsEditor.tsx +539 -165
- package/src/pages/forms/FormActions.tsx +552 -0
- package/src/pages/forms/FormSettings.tsx +29 -70
- package/src/pages/forms/SubmissionDetail.tsx +249 -0
- package/src/pages/forms/SubmissionsTable.tsx +112 -26
- package/src/pages/forms/fieldList.ts +68 -5
- package/src/pages/forms/template.ts +103 -0
- package/src/pages/handbook/HandbookPage.tsx +1 -1
- package/src/pages/hiringDirectory.ts +2 -2
- package/src/pages/onboarding/WorkspaceStepPage.tsx +31 -3
- package/src/pages/positionDirectory.ts +2 -2
- package/src/styles.css +3 -3
package/src/pages/PeoplePage.tsx
CHANGED
|
@@ -2,14 +2,41 @@ import type { Person } from '@kelpie/schemas'
|
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { useNavigate } from 'react-router'
|
|
4
4
|
|
|
5
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
5
6
|
import { useCreatePerson, usePeople } from '../api/resources/people.ts'
|
|
6
7
|
import { Chip } from '../components/Chip.tsx'
|
|
8
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
7
9
|
import { DataTable } from '../components/DataTable.tsx'
|
|
8
10
|
import type { Column } from '../components/DataTable.tsx'
|
|
9
11
|
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
12
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
10
13
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
14
|
+
import { formatDate } from '../lib/dates.ts'
|
|
15
|
+
import { useListView } from '../lib/listView.ts'
|
|
16
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
11
17
|
import { usePeopleDirectory } from './positionDirectory.ts'
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Column catalog for People. Every field the wire schema carries appears here,
|
|
21
|
+
* plus the two joined-in columns (position title, company names) computed from
|
|
22
|
+
* the position directory. `sortKey` names the server field; `getSortValue`
|
|
23
|
+
* enables the DataTable's page-local sort for columns the server does not
|
|
24
|
+
* index. Everything not in `DEFAULT_VISIBLE_KEYS` starts hidden and comes back
|
|
25
|
+
* through the Columns picker.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'email', 'phones', 'company', 'updatedAt']
|
|
28
|
+
|
|
29
|
+
/** The sort fields `/v1/people` accepts. Anything else is a client-only sort. */
|
|
30
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
31
|
+
|
|
32
|
+
/** Written out because "Firstname" is what deriving these from the key would give. */
|
|
33
|
+
const NAME_PART_HEADERS = {
|
|
34
|
+
salutation: 'Salutation',
|
|
35
|
+
firstName: 'First name',
|
|
36
|
+
lastName: 'Last name',
|
|
37
|
+
suffix: 'Suffix',
|
|
38
|
+
} as const
|
|
39
|
+
|
|
13
40
|
/**
|
|
14
41
|
* The People list.
|
|
15
42
|
*
|
|
@@ -22,8 +49,12 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
22
49
|
const [term, setTerm] = useState('')
|
|
23
50
|
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
24
51
|
const navigate = useNavigate()
|
|
52
|
+
const timezone = useTimezone()
|
|
25
53
|
const hasFilter = term.trim().length > 0
|
|
26
|
-
const people = usePeople({
|
|
54
|
+
const people = usePeople({
|
|
55
|
+
term: hasFilter ? term.trim() : undefined,
|
|
56
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
57
|
+
})
|
|
27
58
|
const directory = usePeopleDirectory(people.records.map((person) => person.id))
|
|
28
59
|
const createPerson = useCreatePerson()
|
|
29
60
|
|
|
@@ -42,23 +73,115 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
42
73
|
sortKey: 'name',
|
|
43
74
|
render: (person) => <span className="font-medium text-ink">{person.name}</span>,
|
|
44
75
|
},
|
|
76
|
+
// Hidden by default, and sorted page-locally rather than by the server: the
|
|
77
|
+
// parts are nullable, and `?sort=` is keyset paged, which cannot seek past a
|
|
78
|
+
// null. The catalog carries them so a workspace that imported them can put
|
|
79
|
+
// them back through the Columns picker.
|
|
80
|
+
...(['salutation', 'firstName', 'lastName', 'suffix'] as const).map(
|
|
81
|
+
(key): Column<Person> => ({
|
|
82
|
+
key,
|
|
83
|
+
header: NAME_PART_HEADERS[key],
|
|
84
|
+
getSortValue: (person) => person[key],
|
|
85
|
+
render: (person) => person[key] ?? '—',
|
|
86
|
+
}),
|
|
87
|
+
),
|
|
45
88
|
{
|
|
46
89
|
key: 'position',
|
|
47
90
|
header: 'Position',
|
|
91
|
+
getSortValue: (person) => directory.titleFor(person.id) ?? null,
|
|
48
92
|
render: (person) => directory.titleFor(person.id) ?? '—',
|
|
49
93
|
},
|
|
50
94
|
{
|
|
51
95
|
key: 'company',
|
|
52
96
|
header: 'Company',
|
|
97
|
+
getSortValue: (person) => directory.companyNamesFor(person.id).join(', ') || null,
|
|
53
98
|
render: (person) => {
|
|
54
99
|
const names = directory.companyNamesFor(person.id)
|
|
55
100
|
|
|
56
101
|
return names.length > 0 ? names.join(', ') : '—'
|
|
57
102
|
},
|
|
58
103
|
},
|
|
104
|
+
{
|
|
105
|
+
key: 'email',
|
|
106
|
+
header: 'Email',
|
|
107
|
+
getSortValue: (person) => person.email,
|
|
108
|
+
render: (person) =>
|
|
109
|
+
person.email === null ? (
|
|
110
|
+
'—'
|
|
111
|
+
) : (
|
|
112
|
+
<span className="font-mono text-[12px] text-ink-muted">{person.email}</span>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: 'phones',
|
|
117
|
+
header: 'Phones',
|
|
118
|
+
getSortValue: (person) => person.phones.join(', ') || null,
|
|
119
|
+
render: (person) =>
|
|
120
|
+
person.phones.length === 0 ? '—' : (
|
|
121
|
+
<span className="font-mono text-[12px] text-ink-muted">{person.phones.join(', ')}</span>
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: 'socials',
|
|
126
|
+
header: 'Socials',
|
|
127
|
+
getSortValue: (person) => person.socialProfiles.map((entry) => entry.network).join(', ') || null,
|
|
128
|
+
render: (person) =>
|
|
129
|
+
person.socialProfiles.length === 0 ? '—' : (
|
|
130
|
+
<span className="flex flex-wrap gap-1">
|
|
131
|
+
{person.socialProfiles.map((entry) => (
|
|
132
|
+
<Chip key={entry.network}>
|
|
133
|
+
<span className="text-[10px]">{entry.network}</span>
|
|
134
|
+
</Chip>
|
|
135
|
+
))}
|
|
136
|
+
</span>
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: 'location',
|
|
141
|
+
header: 'Location',
|
|
142
|
+
getSortValue: (person) => person.location,
|
|
143
|
+
render: (person) => person.location ?? '—',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: 'timezone',
|
|
147
|
+
header: 'Timezone',
|
|
148
|
+
getSortValue: (person) => person.timezone,
|
|
149
|
+
render: (person) =>
|
|
150
|
+
person.timezone === null ? '—' : (
|
|
151
|
+
<span className="font-mono text-[12px] text-ink-muted">{person.timezone}</span>
|
|
152
|
+
),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
key: 'preferredChannel',
|
|
156
|
+
header: 'Preferred channel',
|
|
157
|
+
getSortValue: (person) => person.preferredChannel,
|
|
158
|
+
render: (person) => <span className="capitalize">{person.preferredChannel}</span>,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
key: 'influence',
|
|
162
|
+
header: 'Influence',
|
|
163
|
+
getSortValue: (person) => person.influence,
|
|
164
|
+
render: (person) => <span className="capitalize">{person.influence.replace('_', ' ')}</span>,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
key: 'relationship',
|
|
168
|
+
header: 'Relationship',
|
|
169
|
+
getSortValue: (person) => person.relationship,
|
|
170
|
+
render: (person) => <span className="capitalize">{person.relationship}</span>,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: 'summary',
|
|
174
|
+
header: 'Summary',
|
|
175
|
+
getSortValue: (person) => person.summary || null,
|
|
176
|
+
render: (person) =>
|
|
177
|
+
person.summary.length === 0 ? '—' : (
|
|
178
|
+
<span className="text-ink-muted">{person.summary}</span>
|
|
179
|
+
),
|
|
180
|
+
},
|
|
59
181
|
{
|
|
60
182
|
key: 'tags',
|
|
61
183
|
header: 'Tags',
|
|
184
|
+
getSortValue: (person) => person.tags.join(', ') || null,
|
|
62
185
|
render: (person) =>
|
|
63
186
|
person.tags.length > 0 ? (
|
|
64
187
|
<span className="flex flex-wrap gap-1">
|
|
@@ -72,8 +195,31 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
72
195
|
'—'
|
|
73
196
|
),
|
|
74
197
|
},
|
|
198
|
+
{
|
|
199
|
+
key: 'lastContactedAt',
|
|
200
|
+
header: 'Last contacted',
|
|
201
|
+
getSortValue: (person) => person.lastContactedAt,
|
|
202
|
+
render: (person) =>
|
|
203
|
+
person.lastContactedAt === null ? '—' : formatDate(person.lastContactedAt, timezone),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
key: 'createdAt',
|
|
207
|
+
header: 'Created',
|
|
208
|
+
sortKey: 'created_at',
|
|
209
|
+
render: (person) => formatDate(person.createdAt, timezone),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
key: 'updatedAt',
|
|
213
|
+
header: 'Updated',
|
|
214
|
+
sortKey: 'updated_at',
|
|
215
|
+
render: (person) => formatDate(person.updatedAt, timezone),
|
|
216
|
+
},
|
|
75
217
|
]
|
|
76
218
|
|
|
219
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
220
|
+
const listView = useListView('people', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
221
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
222
|
+
|
|
77
223
|
return (
|
|
78
224
|
<div className="animate-fade-in">
|
|
79
225
|
<PageHeader
|
|
@@ -82,6 +228,13 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
82
228
|
void addPerson()
|
|
83
229
|
}}
|
|
84
230
|
addLabel="Add person"
|
|
231
|
+
actions={
|
|
232
|
+
<ColumnPicker
|
|
233
|
+
options={pickerOptions}
|
|
234
|
+
visibleKeys={listView.visibleKeys}
|
|
235
|
+
onChange={listView.setVisibleKeys}
|
|
236
|
+
/>
|
|
237
|
+
}
|
|
85
238
|
/>
|
|
86
239
|
<FilterBar
|
|
87
240
|
value={term}
|
|
@@ -101,6 +254,7 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
101
254
|
<LoadingPanel label="Loading people…" />
|
|
102
255
|
) : (
|
|
103
256
|
<>
|
|
257
|
+
<Paginator list={people} placement="top" />
|
|
104
258
|
<DataTable
|
|
105
259
|
columns={columns}
|
|
106
260
|
rows={people.records}
|
|
@@ -122,22 +276,14 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
122
276
|
}
|
|
123
277
|
sort={sort}
|
|
124
278
|
onSortChange={setSort}
|
|
279
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
125
280
|
/>
|
|
126
281
|
{!directory.isComplete && !directory.isLoading && (
|
|
127
282
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
128
283
|
These people hold more positions than one page returns, so some rows may read “—”.
|
|
129
284
|
</p>
|
|
130
285
|
)}
|
|
131
|
-
{people
|
|
132
|
-
<button
|
|
133
|
-
type="button"
|
|
134
|
-
onClick={people.loadMore}
|
|
135
|
-
disabled={people.isLoadingMore}
|
|
136
|
-
className="mt-3 rounded-md border border-border px-3 py-1.5 text-[12px] font-medium text-ink transition hover:border-border-strong hover:bg-surface-sunken disabled:opacity-50"
|
|
137
|
-
>
|
|
138
|
-
{people.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
139
|
-
</button>
|
|
140
|
-
)}
|
|
286
|
+
<Paginator list={people} />
|
|
141
287
|
</>
|
|
142
288
|
)}
|
|
143
289
|
</div>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { IN_PROCESS, PREFERRED_CHANNELS } from '@kelpie/schemas'
|
|
2
|
-
import type {
|
|
1
|
+
import { CONSENT_PURPOSE_STATUS_LABELS, IN_PROCESS, PREFERRED_CHANNELS } from '@kelpie/schemas'
|
|
2
|
+
import type {
|
|
3
|
+
Candidate,
|
|
4
|
+
ConsentStatus,
|
|
5
|
+
Person,
|
|
6
|
+
PersonInput,
|
|
7
|
+
PreferredChannel,
|
|
8
|
+
} from '@kelpie/schemas'
|
|
3
9
|
import { useState } from 'react'
|
|
4
10
|
import type { FormEvent } from 'react'
|
|
5
11
|
import { Link, useNavigate, useParams } from 'react-router'
|
|
@@ -9,6 +15,8 @@ import type { PatchResult } from '../api/resource.ts'
|
|
|
9
15
|
import { useCandidates } from '../api/resources/candidates.ts'
|
|
10
16
|
import { useCompanies } from '../api/resources/companies.ts'
|
|
11
17
|
import { useDeals } from '../api/resources/deals.ts'
|
|
18
|
+
import { useEnquiries } from '../api/resources/enquiries.ts'
|
|
19
|
+
import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
|
|
12
20
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
13
21
|
import { useDeletePerson, usePerson, useUpdatePerson } from '../api/resources/people.ts'
|
|
14
22
|
import {
|
|
@@ -21,12 +29,16 @@ import { ActivitiesPanel, LatestActivity } from '../components/ActivitiesPanel.t
|
|
|
21
29
|
import { AgentTasks } from '../components/AgentTasks.tsx'
|
|
22
30
|
import { Chip } from '../components/Chip.tsx'
|
|
23
31
|
import { DecisionsPanel } from '../components/DecisionsPanel.tsx'
|
|
32
|
+
import { FormsPanel } from '../components/FormsPanel.tsx'
|
|
24
33
|
import { DeleteRecord } from '../components/DeleteRecord.tsx'
|
|
25
34
|
import { EntitySearch } from '../components/EntitySearch.tsx'
|
|
35
|
+
import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
36
|
+
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
26
37
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
27
38
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
28
39
|
import { RelatedPlanAttention } from '../components/PlanAttention.tsx'
|
|
29
40
|
import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
|
|
41
|
+
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
30
42
|
import { RecordTabs } from '../components/RecordTabs.tsx'
|
|
31
43
|
import type { RecordTabDescriptor } from '../components/RecordTabs.tsx'
|
|
32
44
|
import { SectionHeader } from '../components/SectionHeader.tsx'
|
|
@@ -69,10 +81,12 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
69
81
|
const { record, isLoading, isNotFound, error } = usePerson(id)
|
|
70
82
|
const deletePerson = useDeletePerson()
|
|
71
83
|
const moduleTabs = inSlotOrder(useRecordTabs('person'))
|
|
84
|
+
const hasCustomFields = useHasCustomFields('person')
|
|
72
85
|
const [activeTab, setActiveTab] = useState('overview')
|
|
73
86
|
const candidacies = useCandidates({ personIds: id === undefined ? [] : [id] }, {
|
|
74
87
|
enabled: id !== undefined,
|
|
75
88
|
})
|
|
89
|
+
const formSubmissions = useFormSubmissionsForRecord('person', id)
|
|
76
90
|
|
|
77
91
|
if (isNotFound) {
|
|
78
92
|
return <NotFoundPanel label="Person" backTo="/people" />
|
|
@@ -88,12 +102,17 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
88
102
|
|
|
89
103
|
const tabs: readonly RecordTabDescriptor<string>[] = [
|
|
90
104
|
{ id: 'overview', label: 'Overview' },
|
|
105
|
+
...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
|
|
91
106
|
{ id: 'activity', label: 'Activity' },
|
|
92
107
|
...(candidacies.records.length === 0
|
|
93
108
|
? []
|
|
94
109
|
: [{ id: 'hiring', label: 'Hiring', count: candidacies.records.length }]),
|
|
95
110
|
{ id: 'notes', label: 'Notes' },
|
|
96
111
|
{ id: 'decisions', label: 'Decisions' },
|
|
112
|
+
{ id: 'lists', label: 'Lists' },
|
|
113
|
+
...(formSubmissions.records.length === 0
|
|
114
|
+
? []
|
|
115
|
+
: [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
|
|
97
116
|
...moduleTabs.map((tab) => ({ id: tab.id, label: tab.label })),
|
|
98
117
|
]
|
|
99
118
|
const active = tabs.some((tab) => tab.id === activeTab) ? activeTab : 'overview'
|
|
@@ -130,18 +149,23 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
130
149
|
|
|
131
150
|
<RecordTabs tabs={tabs} active={active} onChange={setActiveTab} ariaLabel="Person sections">
|
|
132
151
|
{active === 'overview' && <PersonOverview person={record} />}
|
|
152
|
+
{active === 'fields' && <PersonFields person={record} />}
|
|
133
153
|
{active === 'activity' && <ActivitiesPanel targetType="person" targetId={record.id} />}
|
|
134
154
|
{active === 'hiring' && (
|
|
135
155
|
<PersonHiring candidacies={candidacies.records} personName={record.name} />
|
|
136
156
|
)}
|
|
137
157
|
{active === 'notes' && <NotesPanel targetType="person" targetId={record.id} />}
|
|
138
158
|
{active === 'decisions' && <DecisionsPanel targetType="person" targetId={record.id} />}
|
|
159
|
+
{active === 'lists' && <ListsPanel targetType="person" targetId={record.id} />}
|
|
160
|
+
{active === 'forms' && <FormsPanel targetType="person" targetId={record.id} />}
|
|
139
161
|
{moduleTab?.render({ objectType: 'person', recordId: record.id })}
|
|
140
162
|
</RecordTabs>
|
|
141
163
|
</div>
|
|
142
164
|
|
|
143
165
|
<aside className="space-y-4 text-[12px] lg:sticky lg:top-6">
|
|
166
|
+
<PersonNameParts person={record} />
|
|
144
167
|
<PersonSidebar person={record} />
|
|
168
|
+
<PersonConsents person={record} />
|
|
145
169
|
<PersonPositions person={record} />
|
|
146
170
|
</aside>
|
|
147
171
|
</div>
|
|
@@ -197,6 +221,7 @@ function PersonOverview({ person }: { readonly person: Person }): React.JSX.Elem
|
|
|
197
221
|
const { patch, error } = usePersonPatch(person)
|
|
198
222
|
const deals = useDeals({ personIds: [person.id] })
|
|
199
223
|
const partnerships = usePartnerships({ personIds: [person.id] })
|
|
224
|
+
const enquiries = useEnquiries({ personIds: [person.id] })
|
|
200
225
|
|
|
201
226
|
return (
|
|
202
227
|
<div className="space-y-8">
|
|
@@ -210,7 +235,8 @@ function PersonOverview({ person }: { readonly person: Person }): React.JSX.Elem
|
|
|
210
235
|
<RelatedPlanAttention
|
|
211
236
|
deals={deals.records}
|
|
212
237
|
partnerships={partnerships.records}
|
|
213
|
-
|
|
238
|
+
enquiries={enquiries.records}
|
|
239
|
+
isLoading={deals.isLoading || partnerships.isLoading || enquiries.isLoading}
|
|
214
240
|
/>
|
|
215
241
|
<LatestActivity targetType="person" targetId={person.id} />
|
|
216
242
|
</div>
|
|
@@ -297,6 +323,46 @@ function PersonHiring({
|
|
|
297
323
|
)
|
|
298
324
|
}
|
|
299
325
|
|
|
326
|
+
/**
|
|
327
|
+
* The parts of the name, beside the name itself rather than instead of it.
|
|
328
|
+
*
|
|
329
|
+
* Editing one of these does not rename the record: the heading is `name`, and
|
|
330
|
+
* `name` is only ever what somebody put there. Both halves are on this page at
|
|
331
|
+
* once so that is visible rather than surprising — a first name corrected here
|
|
332
|
+
* leaves the heading alone, and the heading is one click away when it should
|
|
333
|
+
* change too.
|
|
334
|
+
*/
|
|
335
|
+
function PersonNameParts({ person }: { readonly person: Person }): React.JSX.Element {
|
|
336
|
+
const { patch, error } = usePersonPatch(person)
|
|
337
|
+
const fields = [
|
|
338
|
+
{ label: 'Salutation', value: person.salutation, key: 'salutation' },
|
|
339
|
+
{ label: 'First name', value: person.firstName, key: 'firstName' },
|
|
340
|
+
{ label: 'Last name', value: person.lastName, key: 'lastName' },
|
|
341
|
+
{ label: 'Suffix', value: person.suffix, key: 'suffix' },
|
|
342
|
+
] as const
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<section className="rounded-md border border-border p-3">
|
|
346
|
+
{error !== null && (
|
|
347
|
+
<div className="mb-2">
|
|
348
|
+
<ErrorPanel error={error} />
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
{fields.map((field) => (
|
|
352
|
+
<SidebarField key={field.key} label={field.label}>
|
|
353
|
+
<InlineEdit
|
|
354
|
+
value={field.value ?? ''}
|
|
355
|
+
onChange={(next) => {
|
|
356
|
+
patch({ [field.key]: next.length > 0 ? next : null })
|
|
357
|
+
}}
|
|
358
|
+
displayClassName="not-italic normal-case text-[12px]"
|
|
359
|
+
/>
|
|
360
|
+
</SidebarField>
|
|
361
|
+
))}
|
|
362
|
+
</section>
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
300
366
|
function PersonSidebar({ person }: { readonly person: Person }): React.JSX.Element {
|
|
301
367
|
const { patch, error } = usePersonPatch(person)
|
|
302
368
|
|
|
@@ -376,6 +442,22 @@ function PersonSidebar({ person }: { readonly person: Person }): React.JSX.Eleme
|
|
|
376
442
|
)
|
|
377
443
|
}
|
|
378
444
|
|
|
445
|
+
function PersonFields({ person }: { readonly person: Person }): React.JSX.Element {
|
|
446
|
+
const { patch, error } = usePersonPatch(person)
|
|
447
|
+
return (
|
|
448
|
+
<div className="space-y-4">
|
|
449
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
450
|
+
<CustomFieldsPanel
|
|
451
|
+
objectType="person"
|
|
452
|
+
values={person.customFields}
|
|
453
|
+
onPatch={(customFields) => {
|
|
454
|
+
patch({ customFields })
|
|
455
|
+
}}
|
|
456
|
+
/>
|
|
457
|
+
</div>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
379
461
|
/**
|
|
380
462
|
* The companies this person holds a title at.
|
|
381
463
|
*
|
|
@@ -408,7 +490,7 @@ function PersonPositions({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
408
490
|
function submit(event: FormEvent): void {
|
|
409
491
|
event.preventDefault()
|
|
410
492
|
|
|
411
|
-
if (companyId.length === 0
|
|
493
|
+
if (companyId.length === 0) {
|
|
412
494
|
return
|
|
413
495
|
}
|
|
414
496
|
|
|
@@ -500,9 +582,8 @@ function PersonPositions({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
500
582
|
onChange={(event) => {
|
|
501
583
|
setTitle(event.target.value)
|
|
502
584
|
}}
|
|
503
|
-
placeholder="Position title"
|
|
585
|
+
placeholder="Position title (optional)"
|
|
504
586
|
className="w-full rounded-md border border-border bg-surface-raised px-2 py-1.5 text-[12px] outline-none focus:border-accent"
|
|
505
|
-
required
|
|
506
587
|
/>
|
|
507
588
|
<div className="flex justify-end gap-2">
|
|
508
589
|
<button
|
|
@@ -524,3 +605,73 @@ function PersonPositions({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
524
605
|
</section>
|
|
525
606
|
)
|
|
526
607
|
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The person's effective consent for every workspace purpose, plus the
|
|
611
|
+
* global `do_not_contact` toggle.
|
|
612
|
+
*
|
|
613
|
+
* An `inherited: true` entry has no explicit `person_consents` row and reads
|
|
614
|
+
* the purpose's default; picking a status writes an override, and the
|
|
615
|
+
* "Inherit" choice clears the override again. Do-not-contact is the global
|
|
616
|
+
* Article 21 objection, independent of per-purpose consent.
|
|
617
|
+
*/
|
|
618
|
+
function PersonConsents({ person }: { readonly person: Person }): React.JSX.Element {
|
|
619
|
+
const { patch, error } = usePersonPatch(person)
|
|
620
|
+
|
|
621
|
+
function setConsent(purposeSlug: string, status: ConsentStatus | null): void {
|
|
622
|
+
patch({ consents: [{ purposeSlug, status }] })
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return (
|
|
626
|
+
<section className="rounded-md border border-border p-3">
|
|
627
|
+
<h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wide text-ink-faint">
|
|
628
|
+
Privacy and marketing
|
|
629
|
+
</h3>
|
|
630
|
+
{error !== null && (
|
|
631
|
+
<div className="mb-2">
|
|
632
|
+
<ErrorPanel error={error} />
|
|
633
|
+
</div>
|
|
634
|
+
)}
|
|
635
|
+
<label className="flex items-center gap-2 py-1 text-[12px] text-ink">
|
|
636
|
+
<input
|
|
637
|
+
type="checkbox"
|
|
638
|
+
checked={person.doNotContact}
|
|
639
|
+
onChange={(event) => {
|
|
640
|
+
patch({ doNotContact: event.target.checked })
|
|
641
|
+
}}
|
|
642
|
+
/>
|
|
643
|
+
Do not contact
|
|
644
|
+
</label>
|
|
645
|
+
{person.consents.length === 0 ? (
|
|
646
|
+
<p className="mt-2 text-[11px] text-ink-muted">
|
|
647
|
+
No consent purposes yet. Define them on /admin/privacy.
|
|
648
|
+
</p>
|
|
649
|
+
) : (
|
|
650
|
+
person.consents.map((consent) => (
|
|
651
|
+
<SidebarField key={consent.purposeSlug} label={consent.purposeLabel}>
|
|
652
|
+
<div className="flex items-center gap-2">
|
|
653
|
+
<select
|
|
654
|
+
value={consent.inherited ? '' : consent.status}
|
|
655
|
+
onChange={(event) => {
|
|
656
|
+
const value = event.target.value
|
|
657
|
+
setConsent(
|
|
658
|
+
consent.purposeSlug,
|
|
659
|
+
value === '' ? null : (value as ConsentStatus),
|
|
660
|
+
)
|
|
661
|
+
}}
|
|
662
|
+
className="rounded-md border border-border bg-transparent px-2 py-1 text-[12px] outline-none focus:border-accent"
|
|
663
|
+
>
|
|
664
|
+
<option value="">Inherit ({CONSENT_PURPOSE_STATUS_LABELS[consent.status]})</option>
|
|
665
|
+
<option value="granted">Granted</option>
|
|
666
|
+
<option value="withdrawn">Withdrawn</option>
|
|
667
|
+
</select>
|
|
668
|
+
{!consent.inherited && consent.source !== null && (
|
|
669
|
+
<span className="text-[10px] text-ink-faint">via {consent.source}</span>
|
|
670
|
+
)}
|
|
671
|
+
</div>
|
|
672
|
+
</SidebarField>
|
|
673
|
+
))
|
|
674
|
+
)}
|
|
675
|
+
</section>
|
|
676
|
+
)
|
|
677
|
+
}
|