@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/DealsPage.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Deal, PipelineStage } 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 { useDeals, useCreateDeal, useUpdateDeal } from '../api/resources/deals.t
|
|
|
7
6
|
import { useMembers } from '../api/resources/members.ts'
|
|
8
7
|
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
9
8
|
import { MAX_PAGE_SIZE, usePlanItems } from '../api/resources/planItems.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 Deals pipeline: a board and a list over the same records, grouped by stage
|
|
@@ -31,6 +35,17 @@ type BoardView = 'list' | 'columns'
|
|
|
31
35
|
type PipelineScope = 'open' | 'all'
|
|
32
36
|
type ListGrouping = 'stage' | 'due'
|
|
33
37
|
|
|
38
|
+
const SCOPES: readonly PipelineScope[] = ['open', 'all']
|
|
39
|
+
const GROUPINGS: readonly ListGrouping[] = ['stage', 'due']
|
|
40
|
+
|
|
41
|
+
function readScope(stored: string | undefined): PipelineScope {
|
|
42
|
+
return SCOPES.includes(stored as PipelineScope) ? (stored as PipelineScope) : 'open'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readGrouping(stored: string | undefined): ListGrouping {
|
|
46
|
+
return GROUPINGS.includes(stored as ListGrouping) ? (stored as ListGrouping) : 'stage'
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
/** Tones keyed by the seeded slugs. A renamed stage keeps its slug and its tone. */
|
|
35
50
|
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
36
51
|
won: 'success',
|
|
@@ -43,19 +58,54 @@ function stageTone(stage: PipelineStage): ChipTone {
|
|
|
43
58
|
return STAGE_TONES[stage.slug] ?? 'neutral'
|
|
44
59
|
}
|
|
45
60
|
|
|
61
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = [
|
|
62
|
+
'name',
|
|
63
|
+
'company',
|
|
64
|
+
'value',
|
|
65
|
+
'nextPlan',
|
|
66
|
+
'owner',
|
|
67
|
+
'close',
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
71
|
+
|
|
46
72
|
export function DealsPage(): React.JSX.Element {
|
|
47
73
|
const navigate = useNavigate()
|
|
48
|
-
const [view, setView] = useState<BoardView>('list')
|
|
49
|
-
const [scope, setScope] = useState<PipelineScope>('open')
|
|
50
|
-
const [grouping, setGrouping] = useState<ListGrouping>('stage')
|
|
51
|
-
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
52
74
|
|
|
53
75
|
const stages = usePipelineStages('deal')
|
|
54
|
-
const deals = useDeals({ sort })
|
|
55
76
|
const companies = useCompanies({ limit: 200 })
|
|
77
|
+
// The list-view controller holds every persisted knob, so it is declared
|
|
78
|
+
// before the deals query — the sort it stores decides which page comes
|
|
79
|
+
// back. Board vs list defaults to the board here; the other three read
|
|
80
|
+
// their own default from the readers above.
|
|
81
|
+
const supportedKeys: readonly string[] = [
|
|
82
|
+
'name',
|
|
83
|
+
'company',
|
|
84
|
+
'stage',
|
|
85
|
+
'value',
|
|
86
|
+
'currency',
|
|
87
|
+
'nextPlan',
|
|
88
|
+
'owner',
|
|
89
|
+
'close',
|
|
90
|
+
'competitors',
|
|
91
|
+
'tags',
|
|
92
|
+
'summary',
|
|
93
|
+
'risks',
|
|
94
|
+
'whyWin',
|
|
95
|
+
'externalId',
|
|
96
|
+
'createdAt',
|
|
97
|
+
'updatedAt',
|
|
98
|
+
]
|
|
99
|
+
const listView = useListView('deals', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
100
|
+
const view: BoardView = listView.mode ?? 'columns'
|
|
101
|
+
const scope = readScope(listView.scope)
|
|
102
|
+
const grouping = readGrouping(listView.grouping)
|
|
103
|
+
const sort = listView.sort
|
|
104
|
+
const deals = useDeals({ sort: serverSortOnly(sort, SERVER_SORT_KEYS) })
|
|
56
105
|
const members = useMembers()
|
|
57
106
|
const createDeal = useCreateDeal()
|
|
58
107
|
const updateDeal = useUpdateDeal()
|
|
108
|
+
const timezone = useTimezone()
|
|
59
109
|
|
|
60
110
|
const allStages = [...stages.records].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
61
111
|
const visibleStages = scope === 'open' ? allStages.filter((stage) => stage.open) : allStages
|
|
@@ -71,7 +121,7 @@ export function DealsPage(): React.JSX.Element {
|
|
|
71
121
|
{ enabled: askedDealIds.length > 0 },
|
|
72
122
|
)
|
|
73
123
|
const nextPlanByDeal = nextOpenByTarget(planItems.records)
|
|
74
|
-
const plansTruncated = visibleDeals.length > MAX_PAGE_SIZE || planItems.
|
|
124
|
+
const plansTruncated = visibleDeals.length > MAX_PAGE_SIZE || planItems.hasNext
|
|
75
125
|
|
|
76
126
|
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
77
127
|
|
|
@@ -87,6 +137,8 @@ export function DealsPage(): React.JSX.Element {
|
|
|
87
137
|
await navigate(`/deals/${deal.id}`)
|
|
88
138
|
}
|
|
89
139
|
|
|
140
|
+
const stageLabelById = new Map(allStages.map((stage) => [stage.id, stage.label]))
|
|
141
|
+
|
|
90
142
|
const columns: readonly Column<Deal>[] = [
|
|
91
143
|
{
|
|
92
144
|
key: 'name',
|
|
@@ -97,21 +149,36 @@ export function DealsPage(): React.JSX.Element {
|
|
|
97
149
|
{
|
|
98
150
|
key: 'company',
|
|
99
151
|
header: 'Company',
|
|
152
|
+
getSortValue: (deal) => companyNameById.get(deal.companyId) ?? null,
|
|
100
153
|
render: (deal) => companyNameById.get(deal.companyId) ?? '—',
|
|
101
154
|
},
|
|
155
|
+
{
|
|
156
|
+
key: 'stage',
|
|
157
|
+
header: 'Stage',
|
|
158
|
+
getSortValue: (deal) => stageLabelById.get(deal.stageId) ?? null,
|
|
159
|
+
render: (deal) => stageLabelById.get(deal.stageId) ?? '—',
|
|
160
|
+
},
|
|
102
161
|
{
|
|
103
162
|
key: 'value',
|
|
104
163
|
header: 'Value',
|
|
105
164
|
className: 'w-28',
|
|
165
|
+
getSortValue: (deal) => deal.valueCents,
|
|
106
166
|
render: (deal) => (
|
|
107
167
|
<span className="font-mono text-[12px]">
|
|
108
168
|
{deal.valueCents === null ? '—' : formatMoney(deal.valueCents, deal.currency)}
|
|
109
169
|
</span>
|
|
110
170
|
),
|
|
111
171
|
},
|
|
172
|
+
{
|
|
173
|
+
key: 'currency',
|
|
174
|
+
header: 'Currency',
|
|
175
|
+
getSortValue: (deal) => deal.currency,
|
|
176
|
+
render: (deal) => deal.currency ?? '—',
|
|
177
|
+
},
|
|
112
178
|
{
|
|
113
179
|
key: 'nextPlan',
|
|
114
180
|
header: 'Next plan',
|
|
181
|
+
getSortValue: (deal) => nextPlanByDeal.get(deal.id)?.date ?? null,
|
|
115
182
|
render: (deal) => {
|
|
116
183
|
const next = nextPlanByDeal.get(deal.id)
|
|
117
184
|
|
|
@@ -132,6 +199,8 @@ export function DealsPage(): React.JSX.Element {
|
|
|
132
199
|
{
|
|
133
200
|
key: 'owner',
|
|
134
201
|
header: 'Owner',
|
|
202
|
+
getSortValue: (deal) =>
|
|
203
|
+
deal.ownerId === null ? null : (members.nameById.get(deal.ownerId) ?? 'Unknown'),
|
|
135
204
|
render: (deal) =>
|
|
136
205
|
deal.ownerId === null ? '—' : (members.nameById.get(deal.ownerId) ?? 'Unknown'),
|
|
137
206
|
},
|
|
@@ -139,14 +208,91 @@ export function DealsPage(): React.JSX.Element {
|
|
|
139
208
|
key: 'close',
|
|
140
209
|
header: 'Close',
|
|
141
210
|
className: 'w-28',
|
|
211
|
+
getSortValue: (deal) => deal.expectedClose,
|
|
142
212
|
render: (deal) => (
|
|
143
213
|
<span className="font-mono text-[12px] text-ink-muted">
|
|
144
214
|
{deal.expectedClose === null ? '—' : formatDay(deal.expectedClose)}
|
|
145
215
|
</span>
|
|
146
216
|
),
|
|
147
217
|
},
|
|
218
|
+
{
|
|
219
|
+
key: 'competitors',
|
|
220
|
+
header: 'Competitors',
|
|
221
|
+
getSortValue: (deal) => deal.competitors.join(', ') || null,
|
|
222
|
+
render: (deal) =>
|
|
223
|
+
deal.competitors.length === 0 ? '—' : (
|
|
224
|
+
<span className="flex flex-wrap gap-1">
|
|
225
|
+
{deal.competitors.map((entry) => (
|
|
226
|
+
<Chip key={entry}>
|
|
227
|
+
<span className="text-[10px]">{entry}</span>
|
|
228
|
+
</Chip>
|
|
229
|
+
))}
|
|
230
|
+
</span>
|
|
231
|
+
),
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
key: 'tags',
|
|
235
|
+
header: 'Tags',
|
|
236
|
+
getSortValue: (deal) => deal.tags.join(', ') || null,
|
|
237
|
+
render: (deal) =>
|
|
238
|
+
deal.tags.length === 0 ? '—' : (
|
|
239
|
+
<span className="flex flex-wrap gap-1">
|
|
240
|
+
{deal.tags.map((tag) => (
|
|
241
|
+
<Chip key={tag}>
|
|
242
|
+
<span className="text-[10px]">{tag}</span>
|
|
243
|
+
</Chip>
|
|
244
|
+
))}
|
|
245
|
+
</span>
|
|
246
|
+
),
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
key: 'summary',
|
|
250
|
+
header: 'Summary',
|
|
251
|
+
getSortValue: (deal) => deal.summary || null,
|
|
252
|
+
render: (deal) =>
|
|
253
|
+
deal.summary.length === 0 ? '—' : <span className="text-ink-muted">{deal.summary}</span>,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
key: 'risks',
|
|
257
|
+
header: 'Risks',
|
|
258
|
+
getSortValue: (deal) => deal.risks || null,
|
|
259
|
+
render: (deal) =>
|
|
260
|
+
deal.risks.length === 0 ? '—' : <span className="text-ink-muted">{deal.risks}</span>,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
key: 'whyWin',
|
|
264
|
+
header: 'Why win',
|
|
265
|
+
getSortValue: (deal) => deal.whyWin || null,
|
|
266
|
+
render: (deal) =>
|
|
267
|
+
deal.whyWin.length === 0 ? '—' : <span className="text-ink-muted">{deal.whyWin}</span>,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
key: 'externalId',
|
|
271
|
+
header: 'External ID',
|
|
272
|
+
getSortValue: (deal) => deal.externalId,
|
|
273
|
+
render: (deal) =>
|
|
274
|
+
deal.externalId === null ? (
|
|
275
|
+
'—'
|
|
276
|
+
) : (
|
|
277
|
+
<span className="font-mono text-[12px] text-ink-muted">{deal.externalId}</span>
|
|
278
|
+
),
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
key: 'createdAt',
|
|
282
|
+
header: 'Created',
|
|
283
|
+
sortKey: 'created_at',
|
|
284
|
+
render: (deal) => formatDate(deal.createdAt, timezone),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
key: 'updatedAt',
|
|
288
|
+
header: 'Updated',
|
|
289
|
+
sortKey: 'updated_at',
|
|
290
|
+
render: (deal) => formatDate(deal.updatedAt, timezone),
|
|
291
|
+
},
|
|
148
292
|
]
|
|
149
293
|
|
|
294
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
295
|
+
|
|
150
296
|
const stageGroups: readonly DataTableGroup<Deal>[] = visibleStages.map((stage) => ({
|
|
151
297
|
id: stage.id,
|
|
152
298
|
label: <Chip tone={stageTone(stage)}>{stage.label}</Chip>,
|
|
@@ -208,7 +354,9 @@ export function DealsPage(): React.JSX.Element {
|
|
|
208
354
|
<SegmentedControl
|
|
209
355
|
ariaLabel="Stage scope"
|
|
210
356
|
value={scope}
|
|
211
|
-
onChange={
|
|
357
|
+
onChange={(next) => {
|
|
358
|
+
listView.setScope(next)
|
|
359
|
+
}}
|
|
212
360
|
options={[
|
|
213
361
|
{ id: 'open', label: 'Open' },
|
|
214
362
|
{ id: 'all', label: 'All' },
|
|
@@ -218,7 +366,9 @@ export function DealsPage(): React.JSX.Element {
|
|
|
218
366
|
<SegmentedControl
|
|
219
367
|
ariaLabel="Group the list"
|
|
220
368
|
value={grouping}
|
|
221
|
-
onChange={
|
|
369
|
+
onChange={(next) => {
|
|
370
|
+
listView.setGrouping(next)
|
|
371
|
+
}}
|
|
222
372
|
options={[
|
|
223
373
|
{ id: 'stage', label: 'By stage' },
|
|
224
374
|
{ id: 'due', label: 'By due' },
|
|
@@ -228,12 +378,21 @@ export function DealsPage(): React.JSX.Element {
|
|
|
228
378
|
<SegmentedControl
|
|
229
379
|
ariaLabel="Board or list"
|
|
230
380
|
value={view}
|
|
231
|
-
onChange={
|
|
381
|
+
onChange={(next) => {
|
|
382
|
+
listView.setMode(next)
|
|
383
|
+
}}
|
|
232
384
|
options={[
|
|
233
385
|
{ id: 'list', label: 'List' },
|
|
234
386
|
{ id: 'columns', label: 'Board' },
|
|
235
387
|
]}
|
|
236
388
|
/>
|
|
389
|
+
{view === 'list' && (
|
|
390
|
+
<ColumnPicker
|
|
391
|
+
options={pickerOptions}
|
|
392
|
+
visibleKeys={listView.visibleKeys}
|
|
393
|
+
onChange={listView.setVisibleKeys}
|
|
394
|
+
/>
|
|
395
|
+
)}
|
|
237
396
|
</>
|
|
238
397
|
}
|
|
239
398
|
/>
|
|
@@ -255,6 +414,7 @@ export function DealsPage(): React.JSX.Element {
|
|
|
255
414
|
<LoadingPanel label="Loading deals…" />
|
|
256
415
|
) : (
|
|
257
416
|
<>
|
|
417
|
+
{view === 'list' && <Paginator list={deals} placement="top" />}
|
|
258
418
|
{view === 'columns' ? (
|
|
259
419
|
<KanbanBoard
|
|
260
420
|
stages={visibleStages.map((stage) => ({ id: stage.id, label: stage.label }))}
|
|
@@ -301,13 +461,16 @@ export function DealsPage(): React.JSX.Element {
|
|
|
301
461
|
? {
|
|
302
462
|
label: 'Show all deals',
|
|
303
463
|
onClick: () => {
|
|
304
|
-
setScope('all')
|
|
464
|
+
listView.setScope('all')
|
|
305
465
|
},
|
|
306
466
|
}
|
|
307
467
|
: undefined
|
|
308
468
|
}
|
|
309
469
|
sort={sort}
|
|
310
|
-
onSortChange={
|
|
470
|
+
onSortChange={(next) => {
|
|
471
|
+
listView.setSort(next)
|
|
472
|
+
}}
|
|
473
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
311
474
|
/>
|
|
312
475
|
)}
|
|
313
476
|
{updateDeal.error !== null && (
|
|
@@ -315,17 +478,8 @@ export function DealsPage(): React.JSX.Element {
|
|
|
315
478
|
<ErrorPanel error={updateDeal.error} />
|
|
316
479
|
</div>
|
|
317
480
|
)}
|
|
318
|
-
{
|
|
319
|
-
|
|
320
|
-
type="button"
|
|
321
|
-
onClick={deals.loadMore}
|
|
322
|
-
disabled={deals.isLoadingMore}
|
|
323
|
-
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"
|
|
324
|
-
>
|
|
325
|
-
{deals.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
326
|
-
</button>
|
|
327
|
-
)}
|
|
328
|
-
{companies.hasMore && (
|
|
481
|
+
{view === 'list' && <Paginator list={deals} />}
|
|
482
|
+
{companies.hasNext && (
|
|
329
483
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
330
484
|
More companies exist than one page returns, so some company names may show as “—”.
|
|
331
485
|
</p>
|
|
@@ -11,11 +11,15 @@ import { useOpportunities } from '../api/resources/opportunities.ts'
|
|
|
11
11
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
12
12
|
import { usePeople } from '../api/resources/people.ts'
|
|
13
13
|
import { useRaises } from '../api/resources/raises.ts'
|
|
14
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
14
15
|
import { DataTable } from '../components/DataTable.tsx'
|
|
15
16
|
import type { Column } from '../components/DataTable.tsx'
|
|
16
17
|
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
18
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
17
19
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
18
20
|
import { formatDate } from '../lib/dates.ts'
|
|
21
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
22
|
+
import { useListView } from '../lib/listView.ts'
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Every decision in the workspace.
|
|
@@ -40,6 +44,7 @@ const TARGET_LABELS: Readonly<Record<RecordTargetType, string>> = {
|
|
|
40
44
|
opportunity: 'Opportunity',
|
|
41
45
|
partnership: 'Partnership',
|
|
42
46
|
raise: 'Raise',
|
|
47
|
+
enquiry: 'Enquiry',
|
|
43
48
|
candidate: 'Candidate',
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -50,6 +55,7 @@ const TARGET_ROUTES: Readonly<Partial<Record<RecordTargetType, string>>> = {
|
|
|
50
55
|
opportunity: '/opportunities',
|
|
51
56
|
raise: '/fundraising',
|
|
52
57
|
partnership: '/partnerships',
|
|
58
|
+
enquiry: '/enquiries',
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
interface TargetDirectory {
|
|
@@ -90,12 +96,12 @@ function useTargetDirectory(): TargetDirectory {
|
|
|
90
96
|
return {
|
|
91
97
|
nameFor: (decision) => nameById.get(decision.targetId),
|
|
92
98
|
isComplete:
|
|
93
|
-
!people.
|
|
94
|
-
!companies.
|
|
95
|
-
!deals.
|
|
96
|
-
!opportunities.
|
|
97
|
-
!raises.
|
|
98
|
-
!partnerships.
|
|
99
|
+
!people.hasNext &&
|
|
100
|
+
!companies.hasNext &&
|
|
101
|
+
!deals.hasNext &&
|
|
102
|
+
!opportunities.hasNext &&
|
|
103
|
+
!raises.hasNext &&
|
|
104
|
+
!partnerships.hasNext,
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -123,9 +129,17 @@ function LinkedTo({
|
|
|
123
129
|
)
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['decision', 'target', 'decided', 'due', 'owner']
|
|
133
|
+
|
|
134
|
+
const SERVER_SORT_KEYS: readonly string[] = ['decided_at', 'created_at', 'updated_at']
|
|
135
|
+
|
|
126
136
|
export function DecisionsPage(): React.JSX.Element {
|
|
127
137
|
const [term, setTerm] = useState('')
|
|
128
|
-
const
|
|
138
|
+
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
139
|
+
const decisions = useDecisions({
|
|
140
|
+
term: term.trim().length > 0 ? term.trim() : undefined,
|
|
141
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
142
|
+
})
|
|
129
143
|
const directory = useTargetDirectory()
|
|
130
144
|
const members = useMembers()
|
|
131
145
|
const timezone = useTimezone()
|
|
@@ -134,6 +148,7 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
134
148
|
{
|
|
135
149
|
key: 'decision',
|
|
136
150
|
header: 'Decision',
|
|
151
|
+
getSortValue: (decision) => decision.body,
|
|
137
152
|
render: (decision) => (
|
|
138
153
|
<>
|
|
139
154
|
<div className="font-medium text-ink">{decision.body}</div>
|
|
@@ -143,14 +158,33 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
143
158
|
</>
|
|
144
159
|
),
|
|
145
160
|
},
|
|
161
|
+
{
|
|
162
|
+
key: 'rationale',
|
|
163
|
+
header: 'Rationale',
|
|
164
|
+
getSortValue: (decision) => decision.rationale,
|
|
165
|
+
render: (decision) =>
|
|
166
|
+
decision.rationale === null ? (
|
|
167
|
+
'—'
|
|
168
|
+
) : (
|
|
169
|
+
<span className="text-ink-muted">{decision.rationale}</span>
|
|
170
|
+
),
|
|
171
|
+
},
|
|
146
172
|
{
|
|
147
173
|
key: 'target',
|
|
148
174
|
header: 'Linked to',
|
|
175
|
+
getSortValue: (decision) => directory.nameFor(decision) ?? TARGET_LABELS[decision.targetType],
|
|
149
176
|
render: (decision) => <LinkedTo decision={decision} name={directory.nameFor(decision)} />,
|
|
150
177
|
},
|
|
178
|
+
{
|
|
179
|
+
key: 'targetType',
|
|
180
|
+
header: 'Type',
|
|
181
|
+
getSortValue: (decision) => TARGET_LABELS[decision.targetType],
|
|
182
|
+
render: (decision) => TARGET_LABELS[decision.targetType],
|
|
183
|
+
},
|
|
151
184
|
{
|
|
152
185
|
key: 'decided',
|
|
153
186
|
header: 'Decided',
|
|
187
|
+
sortKey: 'decided_at',
|
|
154
188
|
render: (decision) => (
|
|
155
189
|
<span className="text-ink-muted">{formatDate(decision.decidedAt, timezone)}</span>
|
|
156
190
|
),
|
|
@@ -158,6 +192,7 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
158
192
|
{
|
|
159
193
|
key: 'due',
|
|
160
194
|
header: 'By',
|
|
195
|
+
getSortValue: (decision) => decision.dueAt,
|
|
161
196
|
render: (decision) => (
|
|
162
197
|
<span className="text-ink-muted">
|
|
163
198
|
{decision.dueAt === null ? '—' : formatDate(decision.dueAt, timezone)}
|
|
@@ -167,17 +202,44 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
167
202
|
{
|
|
168
203
|
key: 'owner',
|
|
169
204
|
header: 'Owner',
|
|
205
|
+
getSortValue: (decision) =>
|
|
206
|
+
decision.ownerId === null ? null : (members.nameById.get(decision.ownerId) ?? null),
|
|
170
207
|
render: (decision) => (
|
|
171
208
|
<span className="text-ink-muted">
|
|
172
209
|
{decision.ownerId === null ? '—' : (members.nameById.get(decision.ownerId) ?? '—')}
|
|
173
210
|
</span>
|
|
174
211
|
),
|
|
175
212
|
},
|
|
213
|
+
{
|
|
214
|
+
key: 'createdAt',
|
|
215
|
+
header: 'Created',
|
|
216
|
+
sortKey: 'created_at',
|
|
217
|
+
render: (decision) => formatDate(decision.createdAt, timezone),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
key: 'updatedAt',
|
|
221
|
+
header: 'Updated',
|
|
222
|
+
sortKey: 'updated_at',
|
|
223
|
+
render: (decision) => formatDate(decision.updatedAt, timezone),
|
|
224
|
+
},
|
|
176
225
|
]
|
|
177
226
|
|
|
227
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
228
|
+
const listView = useListView('decisions', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
229
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
230
|
+
|
|
178
231
|
return (
|
|
179
232
|
<div className="animate-fade-in">
|
|
180
|
-
<PageHeader
|
|
233
|
+
<PageHeader
|
|
234
|
+
title="Decisions"
|
|
235
|
+
actions={
|
|
236
|
+
<ColumnPicker
|
|
237
|
+
options={pickerOptions}
|
|
238
|
+
visibleKeys={listView.visibleKeys}
|
|
239
|
+
onChange={listView.setVisibleKeys}
|
|
240
|
+
/>
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
181
243
|
<FilterBar value={term} onChange={setTerm} placeholder="Filter decisions…" />
|
|
182
244
|
|
|
183
245
|
{decisions.error !== null ? (
|
|
@@ -186,11 +248,15 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
186
248
|
<LoadingPanel label="Loading decisions…" />
|
|
187
249
|
) : (
|
|
188
250
|
<>
|
|
251
|
+
<Paginator list={decisions} placement="top" />
|
|
189
252
|
<DataTable
|
|
190
253
|
columns={columns}
|
|
191
254
|
rows={decisions.records}
|
|
192
255
|
getRowId={(decision) => decision.id}
|
|
193
256
|
emptyMessage="No decisions match"
|
|
257
|
+
sort={sort}
|
|
258
|
+
onSortChange={setSort}
|
|
259
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
194
260
|
/>
|
|
195
261
|
{!directory.isComplete && (
|
|
196
262
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
@@ -198,16 +264,7 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
198
264
|
record type.
|
|
199
265
|
</p>
|
|
200
266
|
)}
|
|
201
|
-
{decisions
|
|
202
|
-
<button
|
|
203
|
-
type="button"
|
|
204
|
-
onClick={decisions.loadMore}
|
|
205
|
-
disabled={decisions.isLoadingMore}
|
|
206
|
-
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"
|
|
207
|
-
>
|
|
208
|
-
{decisions.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
209
|
-
</button>
|
|
210
|
-
)}
|
|
267
|
+
<Paginator list={decisions} />
|
|
211
268
|
</>
|
|
212
269
|
)}
|
|
213
270
|
</div>
|