@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/FormsPage.tsx
CHANGED
|
@@ -2,13 +2,19 @@ import type { Form } 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 { useCreateForm, useForms } from '../api/resources/forms.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'
|
|
11
14
|
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
15
|
+
import { formatDate } from '../lib/dates.ts'
|
|
16
|
+
import { useListView } from '../lib/listView.ts'
|
|
17
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
12
18
|
import { CONTACT_FORM_FIELDS } from './forms/template.ts'
|
|
13
19
|
|
|
14
20
|
/**
|
|
@@ -31,6 +37,10 @@ const STATUS_OPTIONS = [
|
|
|
31
37
|
{ id: 'paused' as const, label: 'Paused' },
|
|
32
38
|
]
|
|
33
39
|
|
|
40
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'status', 'fields', 'deal']
|
|
41
|
+
|
|
42
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
43
|
+
|
|
34
44
|
export function FormsPage(): React.JSX.Element {
|
|
35
45
|
const [term, setTerm] = useState('')
|
|
36
46
|
const [status, setStatus] = useState<StatusFilter>('all')
|
|
@@ -38,10 +48,11 @@ export function FormsPage(): React.JSX.Element {
|
|
|
38
48
|
const navigate = useNavigate()
|
|
39
49
|
const hasFilter = term.trim().length > 0 || status !== 'all'
|
|
40
50
|
const createForm = useCreateForm()
|
|
51
|
+
const timezone = useTimezone()
|
|
41
52
|
const forms = useForms({
|
|
42
53
|
term: term.trim().length > 0 ? term.trim() : undefined,
|
|
43
54
|
...(status === 'all' ? {} : { status }),
|
|
44
|
-
sort,
|
|
55
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
45
56
|
})
|
|
46
57
|
|
|
47
58
|
/**
|
|
@@ -70,10 +81,17 @@ export function FormsPage(): React.JSX.Element {
|
|
|
70
81
|
</>
|
|
71
82
|
),
|
|
72
83
|
},
|
|
84
|
+
{
|
|
85
|
+
key: 'description',
|
|
86
|
+
header: 'Description',
|
|
87
|
+
getSortValue: (form) => form.description,
|
|
88
|
+
render: (form) => form.description ?? '—',
|
|
89
|
+
},
|
|
73
90
|
{
|
|
74
91
|
key: 'status',
|
|
75
92
|
header: 'Status',
|
|
76
93
|
className: 'w-28',
|
|
94
|
+
getSortValue: (form) => form.status,
|
|
77
95
|
render: (form) => (
|
|
78
96
|
<Chip tone={form.status === 'active' ? 'success' : 'neutral'}>{form.status}</Chip>
|
|
79
97
|
),
|
|
@@ -82,12 +100,14 @@ export function FormsPage(): React.JSX.Element {
|
|
|
82
100
|
key: 'fields',
|
|
83
101
|
header: 'Fields',
|
|
84
102
|
className: 'w-20',
|
|
103
|
+
getSortValue: (form) => form.fields.length,
|
|
85
104
|
render: (form) => <span className="font-mono text-[12px]">{form.fields.length}</span>,
|
|
86
105
|
},
|
|
87
106
|
{
|
|
88
107
|
key: 'deal',
|
|
89
108
|
header: 'Deal',
|
|
90
109
|
className: 'w-32',
|
|
110
|
+
getSortValue: (form) => form.createDeal,
|
|
91
111
|
render: (form) =>
|
|
92
112
|
form.createDeal ? (
|
|
93
113
|
<Chip tone="accent">Creates deal</Chip>
|
|
@@ -95,8 +115,52 @@ export function FormsPage(): React.JSX.Element {
|
|
|
95
115
|
<span className="text-ink-faint">—</span>
|
|
96
116
|
),
|
|
97
117
|
},
|
|
118
|
+
{
|
|
119
|
+
key: 'dealNameTemplate',
|
|
120
|
+
header: 'Deal name template',
|
|
121
|
+
getSortValue: (form) => form.dealNameTemplate,
|
|
122
|
+
render: (form) =>
|
|
123
|
+
form.dealNameTemplate === null ? (
|
|
124
|
+
'—'
|
|
125
|
+
) : (
|
|
126
|
+
<span className="font-mono text-[12px] text-ink-muted">{form.dealNameTemplate}</span>
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: 'thankYouMessage',
|
|
131
|
+
header: 'Thank-you',
|
|
132
|
+
getSortValue: (form) => form.thankYouMessage || null,
|
|
133
|
+
render: (form) =>
|
|
134
|
+
form.thankYouMessage.length === 0 ? '—' : (
|
|
135
|
+
<span className="text-ink-muted">{form.thankYouMessage}</span>
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
key: 'publicKey',
|
|
140
|
+
header: 'Public key',
|
|
141
|
+
getSortValue: (form) => form.publicKey,
|
|
142
|
+
render: (form) => (
|
|
143
|
+
<span className="font-mono text-[12px] text-ink-muted">{form.publicKey}</span>
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: 'createdAt',
|
|
148
|
+
header: 'Created',
|
|
149
|
+
sortKey: 'created_at',
|
|
150
|
+
render: (form) => formatDate(form.createdAt, timezone),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: 'updatedAt',
|
|
154
|
+
header: 'Updated',
|
|
155
|
+
sortKey: 'updated_at',
|
|
156
|
+
render: (form) => formatDate(form.updatedAt, timezone),
|
|
157
|
+
},
|
|
98
158
|
]
|
|
99
159
|
|
|
160
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
161
|
+
const listView = useListView('forms', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
162
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
163
|
+
|
|
100
164
|
return (
|
|
101
165
|
<div className="animate-fade-in">
|
|
102
166
|
<PageHeader
|
|
@@ -105,12 +169,19 @@ export function FormsPage(): React.JSX.Element {
|
|
|
105
169
|
onAdd={addForm}
|
|
106
170
|
addLabel="New form"
|
|
107
171
|
actions={
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
<>
|
|
173
|
+
<SegmentedControl
|
|
174
|
+
value={status}
|
|
175
|
+
onChange={setStatus}
|
|
176
|
+
options={STATUS_OPTIONS}
|
|
177
|
+
ariaLabel="Filter by status"
|
|
178
|
+
/>
|
|
179
|
+
<ColumnPicker
|
|
180
|
+
options={pickerOptions}
|
|
181
|
+
visibleKeys={listView.visibleKeys}
|
|
182
|
+
onChange={listView.setVisibleKeys}
|
|
183
|
+
/>
|
|
184
|
+
</>
|
|
114
185
|
}
|
|
115
186
|
/>
|
|
116
187
|
<FilterBar value={term} onChange={setTerm} placeholder="Filter forms…" />
|
|
@@ -123,6 +194,7 @@ export function FormsPage(): React.JSX.Element {
|
|
|
123
194
|
<LoadingPanel label="Loading forms…" />
|
|
124
195
|
) : (
|
|
125
196
|
<>
|
|
197
|
+
<Paginator list={forms} placement="top" />
|
|
126
198
|
<DataTable
|
|
127
199
|
columns={columns}
|
|
128
200
|
rows={forms.records}
|
|
@@ -133,17 +205,9 @@ export function FormsPage(): React.JSX.Element {
|
|
|
133
205
|
emptyAction={hasFilter ? undefined : { label: 'New form', onClick: addForm }}
|
|
134
206
|
sort={sort}
|
|
135
207
|
onSortChange={setSort}
|
|
208
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
136
209
|
/>
|
|
137
|
-
{forms
|
|
138
|
-
<button
|
|
139
|
-
type="button"
|
|
140
|
-
onClick={forms.loadMore}
|
|
141
|
-
disabled={forms.isLoadingMore}
|
|
142
|
-
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"
|
|
143
|
-
>
|
|
144
|
-
{forms.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
145
|
-
</button>
|
|
146
|
-
)}
|
|
210
|
+
<Paginator list={forms} />
|
|
147
211
|
</>
|
|
148
212
|
)}
|
|
149
213
|
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { PipelineStage, Raise } from '@kelpie/schemas'
|
|
2
|
-
import { useState } from 'react'
|
|
3
2
|
import { Link, useNavigate } from 'react-router'
|
|
4
3
|
|
|
5
4
|
import { useCompanies } from '../api/resources/companies.ts'
|
|
@@ -7,17 +6,22 @@ import { useMembers } from '../api/resources/members.ts'
|
|
|
7
6
|
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
8
7
|
import { MAX_PAGE_SIZE, usePlanItems } from '../api/resources/planItems.ts'
|
|
9
8
|
import { useCreateRaise, useRaises, useUpdateRaise } from '../api/resources/raises.ts'
|
|
9
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
10
10
|
import { Chip } from '../components/Chip.tsx'
|
|
11
11
|
import type { ChipTone } from '../components/Chip.tsx'
|
|
12
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
12
13
|
import { DataTable } from '../components/DataTable.tsx'
|
|
13
14
|
import type { Column, DataTableGroup } from '../components/DataTable.tsx'
|
|
14
15
|
import { KanbanBoard } from '../components/KanbanBoard.tsx'
|
|
15
16
|
import { PageHeader } from '../components/PageHeader.tsx'
|
|
17
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
16
18
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
17
19
|
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
18
|
-
import { formatDay } from '../lib/dates.ts'
|
|
20
|
+
import { formatDate, formatDay } from '../lib/dates.ts'
|
|
21
|
+
import { useListView } from '../lib/listView.ts'
|
|
19
22
|
import { formatMoney } from '../lib/money.ts'
|
|
20
23
|
import { DUE_BUCKETS, byDateThenTitle, dueBucketFor, nextOpenByTarget } from '../lib/plan.ts'
|
|
24
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* The Fundraising board: one raise per firm per round, grouped by stage. The
|
|
@@ -29,6 +33,34 @@ type BoardView = 'list' | 'columns'
|
|
|
29
33
|
type PipelineScope = 'open' | 'all'
|
|
30
34
|
type ListGrouping = 'stage' | 'due'
|
|
31
35
|
|
|
36
|
+
const SCOPES: readonly PipelineScope[] = ['open', 'all']
|
|
37
|
+
const GROUPINGS: readonly ListGrouping[] = ['stage', 'due']
|
|
38
|
+
|
|
39
|
+
function readScope(stored: string | undefined): PipelineScope {
|
|
40
|
+
return SCOPES.includes(stored as PipelineScope) ? (stored as PipelineScope) : 'open'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readGrouping(stored: string | undefined): ListGrouping {
|
|
44
|
+
return GROUPINGS.includes(stored as ListGrouping) ? (stored as ListGrouping) : 'stage'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const COLUMN_KEYS: readonly string[] = [
|
|
48
|
+
'name',
|
|
49
|
+
'company',
|
|
50
|
+
'stage',
|
|
51
|
+
'check',
|
|
52
|
+
'currency',
|
|
53
|
+
'thesisFit',
|
|
54
|
+
'passReason',
|
|
55
|
+
'nextPlan',
|
|
56
|
+
'owner',
|
|
57
|
+
'close',
|
|
58
|
+
'tags',
|
|
59
|
+
'summary',
|
|
60
|
+
'createdAt',
|
|
61
|
+
'updatedAt',
|
|
62
|
+
]
|
|
63
|
+
|
|
32
64
|
/** Tones keyed by the seeded slugs. A renamed stage keeps its slug and its tone. */
|
|
33
65
|
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
34
66
|
closed: 'success',
|
|
@@ -43,19 +75,33 @@ function stageTone(stage: PipelineStage): ChipTone {
|
|
|
43
75
|
return STAGE_TONES[stage.slug] ?? 'neutral'
|
|
44
76
|
}
|
|
45
77
|
|
|
78
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = [
|
|
79
|
+
'name',
|
|
80
|
+
'company',
|
|
81
|
+
'check',
|
|
82
|
+
'nextPlan',
|
|
83
|
+
'owner',
|
|
84
|
+
'close',
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
88
|
+
|
|
46
89
|
export function FundraisingPage(): React.JSX.Element {
|
|
47
90
|
const navigate = useNavigate()
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
91
|
+
|
|
92
|
+
const listView = useListView('raises', COLUMN_KEYS, DEFAULT_VISIBLE_KEYS)
|
|
93
|
+
const view: BoardView = listView.mode ?? 'columns'
|
|
94
|
+
const scope = readScope(listView.scope)
|
|
95
|
+
const grouping = readGrouping(listView.grouping)
|
|
96
|
+
const sort = listView.sort
|
|
52
97
|
|
|
53
98
|
const stages = usePipelineStages('raise')
|
|
54
|
-
const raises = useRaises({ sort })
|
|
99
|
+
const raises = useRaises({ sort: serverSortOnly(sort, SERVER_SORT_KEYS) })
|
|
55
100
|
const companies = useCompanies({ limit: 200 })
|
|
56
101
|
const members = useMembers()
|
|
57
102
|
const createRaise = useCreateRaise()
|
|
58
103
|
const updateRaise = useUpdateRaise()
|
|
104
|
+
const timezone = useTimezone()
|
|
59
105
|
|
|
60
106
|
const allStages = [...stages.records].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
61
107
|
const visibleStages = scope === 'open' ? allStages.filter((stage) => stage.open) : allStages
|
|
@@ -76,7 +122,7 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
76
122
|
{ enabled: askedIds.length > 0 },
|
|
77
123
|
)
|
|
78
124
|
const nextPlanByRaise = nextOpenByTarget(planItems.records)
|
|
79
|
-
const plansTruncated = visibleRaises.length > MAX_PAGE_SIZE || planItems.
|
|
125
|
+
const plansTruncated = visibleRaises.length > MAX_PAGE_SIZE || planItems.hasNext
|
|
80
126
|
|
|
81
127
|
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
82
128
|
|
|
@@ -95,6 +141,8 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
95
141
|
await navigate(`/fundraising/${raise.id}`)
|
|
96
142
|
}
|
|
97
143
|
|
|
144
|
+
const stageLabelById = new Map(allStages.map((stage) => [stage.id, stage.label]))
|
|
145
|
+
|
|
98
146
|
const columns: readonly Column<Raise>[] = [
|
|
99
147
|
{
|
|
100
148
|
key: 'name',
|
|
@@ -105,12 +153,20 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
105
153
|
{
|
|
106
154
|
key: 'company',
|
|
107
155
|
header: 'Firm',
|
|
156
|
+
getSortValue: (raise) => companyNameById.get(raise.companyId) ?? null,
|
|
108
157
|
render: (raise) => companyNameById.get(raise.companyId) ?? '—',
|
|
109
158
|
},
|
|
159
|
+
{
|
|
160
|
+
key: 'stage',
|
|
161
|
+
header: 'Stage',
|
|
162
|
+
getSortValue: (raise) => stageLabelById.get(raise.stageId) ?? null,
|
|
163
|
+
render: (raise) => stageLabelById.get(raise.stageId) ?? '—',
|
|
164
|
+
},
|
|
110
165
|
{
|
|
111
166
|
key: 'check',
|
|
112
167
|
header: 'Check',
|
|
113
168
|
className: 'w-28',
|
|
169
|
+
getSortValue: (raise) => raise.checkSizeCents,
|
|
114
170
|
render: (raise) =>
|
|
115
171
|
raise.checkSizeCents === null ? (
|
|
116
172
|
'—'
|
|
@@ -120,9 +176,34 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
120
176
|
</span>
|
|
121
177
|
),
|
|
122
178
|
},
|
|
179
|
+
{
|
|
180
|
+
key: 'currency',
|
|
181
|
+
header: 'Currency',
|
|
182
|
+
getSortValue: (raise) => raise.currency,
|
|
183
|
+
render: (raise) => raise.currency ?? '—',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
key: 'thesisFit',
|
|
187
|
+
header: 'Thesis fit',
|
|
188
|
+
getSortValue: (raise) => raise.thesisFit || null,
|
|
189
|
+
render: (raise) =>
|
|
190
|
+
raise.thesisFit.length === 0 ? '—' : (
|
|
191
|
+
<span className="text-ink-muted">{raise.thesisFit}</span>
|
|
192
|
+
),
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
key: 'passReason',
|
|
196
|
+
header: 'Pass reason',
|
|
197
|
+
getSortValue: (raise) => raise.passReason,
|
|
198
|
+
render: (raise) =>
|
|
199
|
+
raise.passReason === null ? '—' : (
|
|
200
|
+
<span className="text-ink-muted">{raise.passReason}</span>
|
|
201
|
+
),
|
|
202
|
+
},
|
|
123
203
|
{
|
|
124
204
|
key: 'nextPlan',
|
|
125
205
|
header: 'Next plan',
|
|
206
|
+
getSortValue: (raise) => nextPlanByRaise.get(raise.id)?.date ?? null,
|
|
126
207
|
render: (raise) => {
|
|
127
208
|
const next = nextPlanByRaise.get(raise.id)
|
|
128
209
|
|
|
@@ -143,6 +224,8 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
143
224
|
{
|
|
144
225
|
key: 'owner',
|
|
145
226
|
header: 'Owner',
|
|
227
|
+
getSortValue: (raise) =>
|
|
228
|
+
raise.ownerId === null ? null : (members.nameById.get(raise.ownerId) ?? 'Unknown'),
|
|
146
229
|
render: (raise) =>
|
|
147
230
|
raise.ownerId === null ? '—' : (members.nameById.get(raise.ownerId) ?? 'Unknown'),
|
|
148
231
|
},
|
|
@@ -150,14 +233,51 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
150
233
|
key: 'close',
|
|
151
234
|
header: 'Close',
|
|
152
235
|
className: 'w-28',
|
|
236
|
+
getSortValue: (raise) => raise.expectedClose,
|
|
153
237
|
render: (raise) => (
|
|
154
238
|
<span className="font-mono text-[12px] text-ink-muted">
|
|
155
239
|
{raise.expectedClose === null ? '—' : formatDay(raise.expectedClose)}
|
|
156
240
|
</span>
|
|
157
241
|
),
|
|
158
242
|
},
|
|
243
|
+
{
|
|
244
|
+
key: 'tags',
|
|
245
|
+
header: 'Tags',
|
|
246
|
+
getSortValue: (raise) => raise.tags.join(', ') || null,
|
|
247
|
+
render: (raise) =>
|
|
248
|
+
raise.tags.length === 0 ? '—' : (
|
|
249
|
+
<span className="flex flex-wrap gap-1">
|
|
250
|
+
{raise.tags.map((tag) => (
|
|
251
|
+
<Chip key={tag}>
|
|
252
|
+
<span className="text-[10px]">{tag}</span>
|
|
253
|
+
</Chip>
|
|
254
|
+
))}
|
|
255
|
+
</span>
|
|
256
|
+
),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
key: 'summary',
|
|
260
|
+
header: 'Summary',
|
|
261
|
+
getSortValue: (raise) => raise.summary || null,
|
|
262
|
+
render: (raise) =>
|
|
263
|
+
raise.summary.length === 0 ? '—' : <span className="text-ink-muted">{raise.summary}</span>,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
key: 'createdAt',
|
|
267
|
+
header: 'Created',
|
|
268
|
+
sortKey: 'created_at',
|
|
269
|
+
render: (raise) => formatDate(raise.createdAt, timezone),
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: 'updatedAt',
|
|
273
|
+
header: 'Updated',
|
|
274
|
+
sortKey: 'updated_at',
|
|
275
|
+
render: (raise) => formatDate(raise.updatedAt, timezone),
|
|
276
|
+
},
|
|
159
277
|
]
|
|
160
278
|
|
|
279
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
280
|
+
|
|
161
281
|
const stageGroups: readonly DataTableGroup<Raise>[] = visibleStages.map((stage) => ({
|
|
162
282
|
id: stage.id,
|
|
163
283
|
label: <Chip tone={stageTone(stage)}>{stage.label}</Chip>,
|
|
@@ -221,7 +341,9 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
221
341
|
<SegmentedControl
|
|
222
342
|
ariaLabel="Stage scope"
|
|
223
343
|
value={scope}
|
|
224
|
-
onChange={
|
|
344
|
+
onChange={(next) => {
|
|
345
|
+
listView.setScope(next)
|
|
346
|
+
}}
|
|
225
347
|
options={[
|
|
226
348
|
{ id: 'open', label: 'Open' },
|
|
227
349
|
{ id: 'all', label: 'All' },
|
|
@@ -231,7 +353,9 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
231
353
|
<SegmentedControl
|
|
232
354
|
ariaLabel="Group the list"
|
|
233
355
|
value={grouping}
|
|
234
|
-
onChange={
|
|
356
|
+
onChange={(next) => {
|
|
357
|
+
listView.setGrouping(next)
|
|
358
|
+
}}
|
|
235
359
|
options={[
|
|
236
360
|
{ id: 'stage', label: 'By stage' },
|
|
237
361
|
{ id: 'due', label: 'By due' },
|
|
@@ -241,12 +365,21 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
241
365
|
<SegmentedControl
|
|
242
366
|
ariaLabel="Board or list"
|
|
243
367
|
value={view}
|
|
244
|
-
onChange={
|
|
368
|
+
onChange={(next) => {
|
|
369
|
+
listView.setMode(next)
|
|
370
|
+
}}
|
|
245
371
|
options={[
|
|
246
372
|
{ id: 'list', label: 'List' },
|
|
247
373
|
{ id: 'columns', label: 'Board' },
|
|
248
374
|
]}
|
|
249
375
|
/>
|
|
376
|
+
{view === 'list' && (
|
|
377
|
+
<ColumnPicker
|
|
378
|
+
options={pickerOptions}
|
|
379
|
+
visibleKeys={listView.visibleKeys}
|
|
380
|
+
onChange={listView.setVisibleKeys}
|
|
381
|
+
/>
|
|
382
|
+
)}
|
|
250
383
|
</>
|
|
251
384
|
}
|
|
252
385
|
/>
|
|
@@ -269,6 +402,7 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
269
402
|
<LoadingPanel label="Loading raises…" />
|
|
270
403
|
) : (
|
|
271
404
|
<>
|
|
405
|
+
{view === 'list' && <Paginator list={raises} placement="top" />}
|
|
272
406
|
{view === 'columns' ? (
|
|
273
407
|
<KanbanBoard
|
|
274
408
|
stages={visibleStages.map((stage) => ({ id: stage.id, label: stage.label }))}
|
|
@@ -313,13 +447,16 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
313
447
|
? {
|
|
314
448
|
label: 'Show all raises',
|
|
315
449
|
onClick: () => {
|
|
316
|
-
setScope('all')
|
|
450
|
+
listView.setScope('all')
|
|
317
451
|
},
|
|
318
452
|
}
|
|
319
453
|
: undefined
|
|
320
454
|
}
|
|
321
455
|
sort={sort}
|
|
322
|
-
onSortChange={
|
|
456
|
+
onSortChange={(next) => {
|
|
457
|
+
listView.setSort(next)
|
|
458
|
+
}}
|
|
459
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
323
460
|
/>
|
|
324
461
|
)}
|
|
325
462
|
{updateRaise.error !== null && (
|
|
@@ -327,17 +464,8 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
327
464
|
<ErrorPanel error={updateRaise.error} />
|
|
328
465
|
</div>
|
|
329
466
|
)}
|
|
330
|
-
{
|
|
331
|
-
|
|
332
|
-
type="button"
|
|
333
|
-
onClick={raises.loadMore}
|
|
334
|
-
disabled={raises.isLoadingMore}
|
|
335
|
-
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"
|
|
336
|
-
>
|
|
337
|
-
{raises.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
338
|
-
</button>
|
|
339
|
-
)}
|
|
340
|
-
{companies.hasMore && (
|
|
467
|
+
{view === 'list' && <Paginator list={raises} />}
|
|
468
|
+
{companies.hasNext && (
|
|
341
469
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
342
470
|
More companies exist than one page returns, so some firm names may show as “—”.
|
|
343
471
|
</p>
|
package/src/pages/HiringPage.tsx
CHANGED
|
@@ -6,13 +6,21 @@ import { useNavigate } from 'react-router'
|
|
|
6
6
|
import { useTimezone } from '../api/resources/account.ts'
|
|
7
7
|
import { useCreateRole, useRoles } from '../api/resources/roles.ts'
|
|
8
8
|
import { Chip } from '../components/Chip.tsx'
|
|
9
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
9
10
|
import { DataTable } from '../components/DataTable.tsx'
|
|
10
11
|
import type { Column } from '../components/DataTable.tsx'
|
|
11
12
|
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
13
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
12
14
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
13
15
|
import { formatDate } from '../lib/dates.ts'
|
|
16
|
+
import { useListView } from '../lib/listView.ts'
|
|
17
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
14
18
|
import { useRoleCandidateCounts } from './hiringDirectory.ts'
|
|
15
19
|
|
|
20
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['title', 'status', 'candidates', 'created']
|
|
21
|
+
|
|
22
|
+
const SERVER_SORT_KEYS: readonly string[] = ['title', 'created_at', 'updated_at']
|
|
23
|
+
|
|
16
24
|
/**
|
|
17
25
|
* The Hiring list: every opening, open or closed.
|
|
18
26
|
*
|
|
@@ -24,8 +32,12 @@ export function HiringPage(): React.JSX.Element {
|
|
|
24
32
|
const [term, setTerm] = useState('')
|
|
25
33
|
const [adding, setAdding] = useState(false)
|
|
26
34
|
const [title, setTitle] = useState('')
|
|
35
|
+
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
27
36
|
const navigate = useNavigate()
|
|
28
|
-
const roles = useRoles({
|
|
37
|
+
const roles = useRoles({
|
|
38
|
+
term: term.trim().length > 0 ? term.trim() : undefined,
|
|
39
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
40
|
+
})
|
|
29
41
|
const counts = useRoleCandidateCounts(roles.records.map((role) => role.id))
|
|
30
42
|
const createRole = useCreateRole()
|
|
31
43
|
const timezone = useTimezone()
|
|
@@ -54,11 +66,13 @@ export function HiringPage(): React.JSX.Element {
|
|
|
54
66
|
{
|
|
55
67
|
key: 'title',
|
|
56
68
|
header: 'Role',
|
|
69
|
+
sortKey: 'title',
|
|
57
70
|
render: (role) => <span className="font-medium text-ink">{role.title}</span>,
|
|
58
71
|
},
|
|
59
72
|
{
|
|
60
73
|
key: 'status',
|
|
61
74
|
header: 'Status',
|
|
75
|
+
getSortValue: (role) => role.status,
|
|
62
76
|
render: (role) => (
|
|
63
77
|
<Chip tone={role.status === 'open' ? 'accent' : 'neutral'}>
|
|
64
78
|
{role.status === 'open' ? 'Open' : 'Closed'}
|
|
@@ -69,6 +83,7 @@ export function HiringPage(): React.JSX.Element {
|
|
|
69
83
|
key: 'candidates',
|
|
70
84
|
header: 'Candidates',
|
|
71
85
|
className: 'w-28',
|
|
86
|
+
getSortValue: (role) => (counts.isLoading ? null : counts.countFor(role.id)),
|
|
72
87
|
render: (role) => (
|
|
73
88
|
<span className="font-mono text-[12px]">
|
|
74
89
|
{counts.isLoading ? '—' : counts.countFor(role.id)}
|
|
@@ -78,10 +93,21 @@ export function HiringPage(): React.JSX.Element {
|
|
|
78
93
|
{
|
|
79
94
|
key: 'created',
|
|
80
95
|
header: 'Created',
|
|
96
|
+
sortKey: 'created_at',
|
|
81
97
|
render: (role) => formatDate(role.createdAt, timezone),
|
|
82
98
|
},
|
|
99
|
+
{
|
|
100
|
+
key: 'updated',
|
|
101
|
+
header: 'Updated',
|
|
102
|
+
sortKey: 'updated_at',
|
|
103
|
+
render: (role) => formatDate(role.updatedAt, timezone),
|
|
104
|
+
},
|
|
83
105
|
]
|
|
84
106
|
|
|
107
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
108
|
+
const listView = useListView('roles', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
109
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
110
|
+
|
|
85
111
|
return (
|
|
86
112
|
<div className="animate-fade-in">
|
|
87
113
|
<PageHeader
|
|
@@ -90,6 +116,13 @@ export function HiringPage(): React.JSX.Element {
|
|
|
90
116
|
setAdding((current) => !current)
|
|
91
117
|
}}
|
|
92
118
|
addLabel="Add role"
|
|
119
|
+
actions={
|
|
120
|
+
<ColumnPicker
|
|
121
|
+
options={pickerOptions}
|
|
122
|
+
visibleKeys={listView.visibleKeys}
|
|
123
|
+
onChange={listView.setVisibleKeys}
|
|
124
|
+
/>
|
|
125
|
+
}
|
|
93
126
|
/>
|
|
94
127
|
|
|
95
128
|
{adding && (
|
|
@@ -140,6 +173,7 @@ export function HiringPage(): React.JSX.Element {
|
|
|
140
173
|
<LoadingPanel label="Loading roles…" />
|
|
141
174
|
) : (
|
|
142
175
|
<>
|
|
176
|
+
<Paginator list={roles} placement="top" />
|
|
143
177
|
<DataTable
|
|
144
178
|
columns={columns}
|
|
145
179
|
rows={roles.records}
|
|
@@ -148,22 +182,16 @@ export function HiringPage(): React.JSX.Element {
|
|
|
148
182
|
void navigate(`/hiring/${role.id}`)
|
|
149
183
|
}}
|
|
150
184
|
emptyMessage="No roles yet"
|
|
185
|
+
sort={sort}
|
|
186
|
+
onSortChange={setSort}
|
|
187
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
151
188
|
/>
|
|
152
189
|
{!counts.isComplete && !counts.isLoading && (
|
|
153
190
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
154
191
|
These roles hold more candidates than one page returns, so some counts read low.
|
|
155
192
|
</p>
|
|
156
193
|
)}
|
|
157
|
-
{roles
|
|
158
|
-
<button
|
|
159
|
-
type="button"
|
|
160
|
-
onClick={roles.loadMore}
|
|
161
|
-
disabled={roles.isLoadingMore}
|
|
162
|
-
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"
|
|
163
|
-
>
|
|
164
|
-
{roles.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
165
|
-
</button>
|
|
166
|
-
)}
|
|
194
|
+
<Paginator list={roles} />
|
|
167
195
|
</>
|
|
168
196
|
)}
|
|
169
197
|
</div>
|