@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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RECORD_TARGET_TYPE_LABELS,
|
|
3
|
+
RECORD_TARGET_TYPES,
|
|
4
|
+
} from '@kelpie/schemas'
|
|
5
|
+
import type { List, RecordTargetType } from '@kelpie/schemas'
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
import type { FormEvent } from 'react'
|
|
8
|
+
import { useNavigate } from 'react-router'
|
|
9
|
+
|
|
10
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
11
|
+
import { useCreateList, useLists } from '../api/resources/lists.ts'
|
|
12
|
+
import { Chip } from '../components/Chip.tsx'
|
|
13
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
14
|
+
import { DataTable } from '../components/DataTable.tsx'
|
|
15
|
+
import type { Column } from '../components/DataTable.tsx'
|
|
16
|
+
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
17
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
18
|
+
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
19
|
+
import { formatDate } from '../lib/dates.ts'
|
|
20
|
+
import { useListView } from '../lib/listView.ts'
|
|
21
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
22
|
+
|
|
23
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'type', 'members', 'description']
|
|
24
|
+
|
|
25
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The Lists index.
|
|
29
|
+
*
|
|
30
|
+
* A list holds records of one type, chosen at creation and never changed. The
|
|
31
|
+
* "Add list" button opens a small inline form that captures name and type; on
|
|
32
|
+
* create, the user is taken straight to the list's detail page.
|
|
33
|
+
*/
|
|
34
|
+
export function ListsPage(): React.JSX.Element {
|
|
35
|
+
const [term, setTerm] = useState('')
|
|
36
|
+
const [creating, setCreating] = useState(false)
|
|
37
|
+
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
38
|
+
const navigate = useNavigate()
|
|
39
|
+
const hasFilter = term.trim().length > 0
|
|
40
|
+
const lists = useLists({
|
|
41
|
+
term: hasFilter ? term.trim() : undefined,
|
|
42
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
43
|
+
})
|
|
44
|
+
const createList = useCreateList()
|
|
45
|
+
const timezone = useTimezone()
|
|
46
|
+
|
|
47
|
+
const columns: readonly Column<List>[] = [
|
|
48
|
+
{
|
|
49
|
+
key: 'name',
|
|
50
|
+
header: 'Name',
|
|
51
|
+
sortKey: 'name',
|
|
52
|
+
render: (list) => <span className="font-medium text-ink">{list.name}</span>,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: 'type',
|
|
56
|
+
header: 'Type',
|
|
57
|
+
getSortValue: (list) => RECORD_TARGET_TYPE_LABELS[list.targetType],
|
|
58
|
+
render: (list) => <Chip>{RECORD_TARGET_TYPE_LABELS[list.targetType]}</Chip>,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: 'members',
|
|
62
|
+
header: 'Members',
|
|
63
|
+
getSortValue: (list) => list.memberCount,
|
|
64
|
+
render: (list) => String(list.memberCount),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: 'description',
|
|
68
|
+
header: 'Description',
|
|
69
|
+
getSortValue: (list) => list.description,
|
|
70
|
+
render: (list) => list.description ?? '—',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'createdAt',
|
|
74
|
+
header: 'Created',
|
|
75
|
+
sortKey: 'created_at',
|
|
76
|
+
render: (list) => formatDate(list.createdAt, timezone),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: 'updatedAt',
|
|
80
|
+
header: 'Updated',
|
|
81
|
+
sortKey: 'updated_at',
|
|
82
|
+
render: (list) => formatDate(list.updatedAt, timezone),
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
87
|
+
const listView = useListView('lists', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
88
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="animate-fade-in">
|
|
92
|
+
<PageHeader
|
|
93
|
+
title="Lists"
|
|
94
|
+
onAdd={() => {
|
|
95
|
+
setCreating((current) => !current)
|
|
96
|
+
}}
|
|
97
|
+
addLabel="Add list"
|
|
98
|
+
actions={
|
|
99
|
+
<ColumnPicker
|
|
100
|
+
options={pickerOptions}
|
|
101
|
+
visibleKeys={listView.visibleKeys}
|
|
102
|
+
onChange={listView.setVisibleKeys}
|
|
103
|
+
/>
|
|
104
|
+
}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
{creating && (
|
|
108
|
+
<CreateListForm
|
|
109
|
+
isPending={createList.isPending}
|
|
110
|
+
error={createList.error}
|
|
111
|
+
onCancel={() => {
|
|
112
|
+
setCreating(false)
|
|
113
|
+
}}
|
|
114
|
+
onSubmit={(input) => {
|
|
115
|
+
createList
|
|
116
|
+
.runAsync(input)
|
|
117
|
+
.then((list) => {
|
|
118
|
+
setCreating(false)
|
|
119
|
+
return navigate(`/lists/${list.id}`)
|
|
120
|
+
})
|
|
121
|
+
.catch(() => undefined)
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
<FilterBar value={term} onChange={setTerm} placeholder="Filter by name…" />
|
|
127
|
+
|
|
128
|
+
{lists.error !== null ? (
|
|
129
|
+
<ErrorPanel error={lists.error} />
|
|
130
|
+
) : lists.isLoading ? (
|
|
131
|
+
<LoadingPanel label="Loading lists…" />
|
|
132
|
+
) : (
|
|
133
|
+
<>
|
|
134
|
+
<Paginator list={lists} placement="top" />
|
|
135
|
+
<DataTable
|
|
136
|
+
columns={columns}
|
|
137
|
+
rows={lists.records}
|
|
138
|
+
getRowId={(list) => list.id}
|
|
139
|
+
onRowClick={(list) => {
|
|
140
|
+
void navigate(`/lists/${list.id}`)
|
|
141
|
+
}}
|
|
142
|
+
emptyMessage={hasFilter ? 'No lists match this filter' : 'No lists yet'}
|
|
143
|
+
emptyDescription={
|
|
144
|
+
hasFilter ? 'Try a different search term.' : 'Add a list to group records together.'
|
|
145
|
+
}
|
|
146
|
+
emptyAction={
|
|
147
|
+
hasFilter
|
|
148
|
+
? undefined
|
|
149
|
+
: {
|
|
150
|
+
label: 'Add list',
|
|
151
|
+
onClick: () => {
|
|
152
|
+
setCreating(true)
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
sort={sort}
|
|
157
|
+
onSortChange={setSort}
|
|
158
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
159
|
+
/>
|
|
160
|
+
<Paginator list={lists} />
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface CreateListFormProps {
|
|
168
|
+
readonly isPending: boolean
|
|
169
|
+
readonly error: Error | null
|
|
170
|
+
readonly onCancel: () => void
|
|
171
|
+
readonly onSubmit: (input: {
|
|
172
|
+
readonly name: string
|
|
173
|
+
readonly targetType: RecordTargetType
|
|
174
|
+
readonly description: string | null
|
|
175
|
+
}) => void
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function CreateListForm({
|
|
179
|
+
isPending,
|
|
180
|
+
error,
|
|
181
|
+
onCancel,
|
|
182
|
+
onSubmit,
|
|
183
|
+
}: CreateListFormProps): React.JSX.Element {
|
|
184
|
+
const [name, setName] = useState('')
|
|
185
|
+
const [targetType, setTargetType] = useState<RecordTargetType>('person')
|
|
186
|
+
const [description, setDescription] = useState('')
|
|
187
|
+
|
|
188
|
+
function submit(event: FormEvent<HTMLFormElement>): void {
|
|
189
|
+
event.preventDefault()
|
|
190
|
+
const trimmed = name.trim()
|
|
191
|
+
|
|
192
|
+
if (trimmed.length === 0) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
onSubmit({
|
|
197
|
+
name: trimmed,
|
|
198
|
+
targetType,
|
|
199
|
+
description: description.trim().length > 0 ? description.trim() : null,
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<form
|
|
205
|
+
onSubmit={submit}
|
|
206
|
+
className="mb-4 rounded-md border border-border bg-surface-raised p-4"
|
|
207
|
+
>
|
|
208
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
209
|
+
<label className="flex flex-col gap-1">
|
|
210
|
+
<span className="text-[11px] font-semibold tracking-wide text-ink-faint uppercase">
|
|
211
|
+
Name
|
|
212
|
+
</span>
|
|
213
|
+
<input
|
|
214
|
+
value={name}
|
|
215
|
+
onChange={(event) => {
|
|
216
|
+
setName(event.target.value)
|
|
217
|
+
}}
|
|
218
|
+
required
|
|
219
|
+
autoFocus
|
|
220
|
+
placeholder="Priority people"
|
|
221
|
+
className="rounded-md border border-border bg-surface-raised px-2.5 py-1.5 text-[13px] outline-none focus:border-accent focus:ring-2 focus:ring-accent/20"
|
|
222
|
+
/>
|
|
223
|
+
</label>
|
|
224
|
+
<label className="flex flex-col gap-1">
|
|
225
|
+
<span className="text-[11px] font-semibold tracking-wide text-ink-faint uppercase">
|
|
226
|
+
Type
|
|
227
|
+
</span>
|
|
228
|
+
<select
|
|
229
|
+
value={targetType}
|
|
230
|
+
onChange={(event) => {
|
|
231
|
+
setTargetType(event.target.value as RecordTargetType)
|
|
232
|
+
}}
|
|
233
|
+
className="rounded-md border border-border bg-surface-raised px-2.5 py-1.5 text-[13px] outline-none focus:border-accent focus:ring-2 focus:ring-accent/20"
|
|
234
|
+
>
|
|
235
|
+
{RECORD_TARGET_TYPES.map((type) => (
|
|
236
|
+
<option key={type} value={type}>
|
|
237
|
+
{RECORD_TARGET_TYPE_LABELS[type]}
|
|
238
|
+
</option>
|
|
239
|
+
))}
|
|
240
|
+
</select>
|
|
241
|
+
</label>
|
|
242
|
+
<label className="flex flex-col gap-1 sm:col-span-2">
|
|
243
|
+
<span className="text-[11px] font-semibold tracking-wide text-ink-faint uppercase">
|
|
244
|
+
Description
|
|
245
|
+
</span>
|
|
246
|
+
<input
|
|
247
|
+
value={description}
|
|
248
|
+
onChange={(event) => {
|
|
249
|
+
setDescription(event.target.value)
|
|
250
|
+
}}
|
|
251
|
+
placeholder="Optional"
|
|
252
|
+
className="rounded-md border border-border bg-surface-raised px-2.5 py-1.5 text-[13px] outline-none focus:border-accent focus:ring-2 focus:ring-accent/20"
|
|
253
|
+
/>
|
|
254
|
+
</label>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
{error !== null && (
|
|
258
|
+
<div className="mt-3">
|
|
259
|
+
<ErrorPanel error={error} />
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
|
|
263
|
+
<div className="mt-3 flex gap-2">
|
|
264
|
+
<button
|
|
265
|
+
type="submit"
|
|
266
|
+
disabled={isPending || name.trim().length === 0}
|
|
267
|
+
className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-medium text-accent-ink transition hover:bg-accent-strong disabled:opacity-50"
|
|
268
|
+
>
|
|
269
|
+
{isPending ? 'Creating…' : 'Create list'}
|
|
270
|
+
</button>
|
|
271
|
+
<button
|
|
272
|
+
type="button"
|
|
273
|
+
onClick={onCancel}
|
|
274
|
+
className="rounded-md px-2.5 py-1.5 text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
275
|
+
>
|
|
276
|
+
Cancel
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
</form>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Opportunity, 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'
|
|
@@ -11,16 +10,21 @@ import {
|
|
|
11
10
|
} from '../api/resources/opportunities.ts'
|
|
12
11
|
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
13
12
|
import { MAX_PAGE_SIZE, usePlanItems } from '../api/resources/planItems.ts'
|
|
13
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
14
14
|
import { Chip } from '../components/Chip.tsx'
|
|
15
15
|
import type { ChipTone } from '../components/Chip.tsx'
|
|
16
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
16
17
|
import { DataTable } from '../components/DataTable.tsx'
|
|
17
18
|
import type { Column, DataTableGroup } from '../components/DataTable.tsx'
|
|
18
19
|
import { KanbanBoard } from '../components/KanbanBoard.tsx'
|
|
19
20
|
import { PageHeader } from '../components/PageHeader.tsx'
|
|
21
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
20
22
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
21
23
|
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
22
|
-
import { formatDay } from '../lib/dates.ts'
|
|
24
|
+
import { formatDate, formatDay } from '../lib/dates.ts'
|
|
25
|
+
import { useListView } from '../lib/listView.ts'
|
|
23
26
|
import { DUE_BUCKETS, byDateThenTitle, dueBucketFor, nextOpenByTarget } from '../lib/plan.ts'
|
|
27
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* The Opportunities pipeline: grants, accelerators, tenders, press, speaking.
|
|
@@ -32,6 +36,31 @@ type BoardView = 'list' | 'columns'
|
|
|
32
36
|
type PipelineScope = 'open' | 'all'
|
|
33
37
|
type ListGrouping = 'stage' | 'due'
|
|
34
38
|
|
|
39
|
+
const SCOPES: readonly PipelineScope[] = ['open', 'all']
|
|
40
|
+
const GROUPINGS: readonly ListGrouping[] = ['stage', 'due']
|
|
41
|
+
|
|
42
|
+
function readScope(stored: string | undefined): PipelineScope {
|
|
43
|
+
return SCOPES.includes(stored as PipelineScope) ? (stored as PipelineScope) : 'open'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readGrouping(stored: string | undefined): ListGrouping {
|
|
47
|
+
return GROUPINGS.includes(stored as ListGrouping) ? (stored as ListGrouping) : 'stage'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const COLUMN_KEYS: readonly string[] = [
|
|
51
|
+
'name',
|
|
52
|
+
'kind',
|
|
53
|
+
'company',
|
|
54
|
+
'stage',
|
|
55
|
+
'nextPlan',
|
|
56
|
+
'owner',
|
|
57
|
+
'close',
|
|
58
|
+
'tags',
|
|
59
|
+
'summary',
|
|
60
|
+
'createdAt',
|
|
61
|
+
'updatedAt',
|
|
62
|
+
]
|
|
63
|
+
|
|
35
64
|
/** Tones keyed by the seeded slugs. A renamed stage keeps its slug and its tone. */
|
|
36
65
|
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
37
66
|
won: 'success',
|
|
@@ -44,19 +73,33 @@ function stageTone(stage: PipelineStage): ChipTone {
|
|
|
44
73
|
return STAGE_TONES[stage.slug] ?? 'neutral'
|
|
45
74
|
}
|
|
46
75
|
|
|
76
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = [
|
|
77
|
+
'name',
|
|
78
|
+
'kind',
|
|
79
|
+
'company',
|
|
80
|
+
'nextPlan',
|
|
81
|
+
'owner',
|
|
82
|
+
'close',
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
86
|
+
|
|
47
87
|
export function OpportunitiesPage(): React.JSX.Element {
|
|
48
88
|
const navigate = useNavigate()
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
89
|
+
|
|
90
|
+
const listView = useListView('opportunities', COLUMN_KEYS, DEFAULT_VISIBLE_KEYS)
|
|
91
|
+
const view: BoardView = listView.mode ?? 'columns'
|
|
92
|
+
const scope = readScope(listView.scope)
|
|
93
|
+
const grouping = readGrouping(listView.grouping)
|
|
94
|
+
const sort = listView.sort
|
|
53
95
|
|
|
54
96
|
const stages = usePipelineStages('opportunity')
|
|
55
|
-
const opportunities = useOpportunities({ sort })
|
|
97
|
+
const opportunities = useOpportunities({ sort: serverSortOnly(sort, SERVER_SORT_KEYS) })
|
|
56
98
|
const companies = useCompanies({ limit: 200 })
|
|
57
99
|
const members = useMembers()
|
|
58
100
|
const createOpportunity = useCreateOpportunity()
|
|
59
101
|
const updateOpportunity = useUpdateOpportunity()
|
|
102
|
+
const timezone = useTimezone()
|
|
60
103
|
|
|
61
104
|
const allStages = [...stages.records].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
62
105
|
const visibleStages = scope === 'open' ? allStages.filter((stage) => stage.open) : allStages
|
|
@@ -79,7 +122,7 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
79
122
|
{ enabled: askedIds.length > 0 },
|
|
80
123
|
)
|
|
81
124
|
const nextPlanByOpportunity = nextOpenByTarget(planItems.records)
|
|
82
|
-
const plansTruncated = visibleOpportunities.length > MAX_PAGE_SIZE || planItems.
|
|
125
|
+
const plansTruncated = visibleOpportunities.length > MAX_PAGE_SIZE || planItems.hasNext
|
|
83
126
|
|
|
84
127
|
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
85
128
|
|
|
@@ -89,6 +132,8 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
89
132
|
await navigate(`/opportunities/${opportunity.id}`)
|
|
90
133
|
}
|
|
91
134
|
|
|
135
|
+
const stageLabelById = new Map(allStages.map((stage) => [stage.id, stage.label]))
|
|
136
|
+
|
|
92
137
|
const columns: readonly Column<Opportunity>[] = [
|
|
93
138
|
{
|
|
94
139
|
key: 'name',
|
|
@@ -99,19 +144,29 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
99
144
|
{
|
|
100
145
|
key: 'kind',
|
|
101
146
|
header: 'Kind',
|
|
147
|
+
getSortValue: (opportunity) => opportunity.kind || null,
|
|
102
148
|
render: (opportunity) => (opportunity.kind.length > 0 ? opportunity.kind : '—'),
|
|
103
149
|
},
|
|
104
150
|
{
|
|
105
151
|
key: 'company',
|
|
106
152
|
header: 'Company',
|
|
153
|
+
getSortValue: (opportunity) =>
|
|
154
|
+
opportunity.companyId === null ? null : (companyNameById.get(opportunity.companyId) ?? null),
|
|
107
155
|
render: (opportunity) =>
|
|
108
156
|
opportunity.companyId === null
|
|
109
157
|
? '—'
|
|
110
158
|
: (companyNameById.get(opportunity.companyId) ?? '—'),
|
|
111
159
|
},
|
|
160
|
+
{
|
|
161
|
+
key: 'stage',
|
|
162
|
+
header: 'Stage',
|
|
163
|
+
getSortValue: (opportunity) => stageLabelById.get(opportunity.stageId) ?? null,
|
|
164
|
+
render: (opportunity) => stageLabelById.get(opportunity.stageId) ?? '—',
|
|
165
|
+
},
|
|
112
166
|
{
|
|
113
167
|
key: 'nextPlan',
|
|
114
168
|
header: 'Next plan',
|
|
169
|
+
getSortValue: (opportunity) => nextPlanByOpportunity.get(opportunity.id)?.date ?? null,
|
|
115
170
|
render: (opportunity) => {
|
|
116
171
|
const next = nextPlanByOpportunity.get(opportunity.id)
|
|
117
172
|
|
|
@@ -132,6 +187,10 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
132
187
|
{
|
|
133
188
|
key: 'owner',
|
|
134
189
|
header: 'Owner',
|
|
190
|
+
getSortValue: (opportunity) =>
|
|
191
|
+
opportunity.ownerId === null
|
|
192
|
+
? null
|
|
193
|
+
: (members.nameById.get(opportunity.ownerId) ?? 'Unknown'),
|
|
135
194
|
render: (opportunity) =>
|
|
136
195
|
opportunity.ownerId === null
|
|
137
196
|
? '—'
|
|
@@ -141,14 +200,53 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
141
200
|
key: 'close',
|
|
142
201
|
header: 'Close',
|
|
143
202
|
className: 'w-28',
|
|
203
|
+
getSortValue: (opportunity) => opportunity.expectedClose,
|
|
144
204
|
render: (opportunity) => (
|
|
145
205
|
<span className="font-mono text-[12px] text-ink-muted">
|
|
146
206
|
{opportunity.expectedClose === null ? '—' : formatDay(opportunity.expectedClose)}
|
|
147
207
|
</span>
|
|
148
208
|
),
|
|
149
209
|
},
|
|
210
|
+
{
|
|
211
|
+
key: 'tags',
|
|
212
|
+
header: 'Tags',
|
|
213
|
+
getSortValue: (opportunity) => opportunity.tags.join(', ') || null,
|
|
214
|
+
render: (opportunity) =>
|
|
215
|
+
opportunity.tags.length === 0 ? '—' : (
|
|
216
|
+
<span className="flex flex-wrap gap-1">
|
|
217
|
+
{opportunity.tags.map((tag) => (
|
|
218
|
+
<Chip key={tag}>
|
|
219
|
+
<span className="text-[10px]">{tag}</span>
|
|
220
|
+
</Chip>
|
|
221
|
+
))}
|
|
222
|
+
</span>
|
|
223
|
+
),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
key: 'summary',
|
|
227
|
+
header: 'Summary',
|
|
228
|
+
getSortValue: (opportunity) => opportunity.summary || null,
|
|
229
|
+
render: (opportunity) =>
|
|
230
|
+
opportunity.summary.length === 0 ? '—' : (
|
|
231
|
+
<span className="text-ink-muted">{opportunity.summary}</span>
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
key: 'createdAt',
|
|
236
|
+
header: 'Created',
|
|
237
|
+
sortKey: 'created_at',
|
|
238
|
+
render: (opportunity) => formatDate(opportunity.createdAt, timezone),
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
key: 'updatedAt',
|
|
242
|
+
header: 'Updated',
|
|
243
|
+
sortKey: 'updated_at',
|
|
244
|
+
render: (opportunity) => formatDate(opportunity.updatedAt, timezone),
|
|
245
|
+
},
|
|
150
246
|
]
|
|
151
247
|
|
|
248
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
249
|
+
|
|
152
250
|
const stageGroups: readonly DataTableGroup<Opportunity>[] = visibleStages.map((stage) => ({
|
|
153
251
|
id: stage.id,
|
|
154
252
|
label: <Chip tone={stageTone(stage)}>{stage.label}</Chip>,
|
|
@@ -219,7 +317,9 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
219
317
|
<SegmentedControl
|
|
220
318
|
ariaLabel="Stage scope"
|
|
221
319
|
value={scope}
|
|
222
|
-
onChange={
|
|
320
|
+
onChange={(next) => {
|
|
321
|
+
listView.setScope(next)
|
|
322
|
+
}}
|
|
223
323
|
options={[
|
|
224
324
|
{ id: 'open', label: 'Open' },
|
|
225
325
|
{ id: 'all', label: 'All' },
|
|
@@ -229,7 +329,9 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
229
329
|
<SegmentedControl
|
|
230
330
|
ariaLabel="Group the list"
|
|
231
331
|
value={grouping}
|
|
232
|
-
onChange={
|
|
332
|
+
onChange={(next) => {
|
|
333
|
+
listView.setGrouping(next)
|
|
334
|
+
}}
|
|
233
335
|
options={[
|
|
234
336
|
{ id: 'stage', label: 'By stage' },
|
|
235
337
|
{ id: 'due', label: 'By due' },
|
|
@@ -239,12 +341,21 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
239
341
|
<SegmentedControl
|
|
240
342
|
ariaLabel="Board or list"
|
|
241
343
|
value={view}
|
|
242
|
-
onChange={
|
|
344
|
+
onChange={(next) => {
|
|
345
|
+
listView.setMode(next)
|
|
346
|
+
}}
|
|
243
347
|
options={[
|
|
244
348
|
{ id: 'list', label: 'List' },
|
|
245
349
|
{ id: 'columns', label: 'Board' },
|
|
246
350
|
]}
|
|
247
351
|
/>
|
|
352
|
+
{view === 'list' && (
|
|
353
|
+
<ColumnPicker
|
|
354
|
+
options={pickerOptions}
|
|
355
|
+
visibleKeys={listView.visibleKeys}
|
|
356
|
+
onChange={listView.setVisibleKeys}
|
|
357
|
+
/>
|
|
358
|
+
)}
|
|
248
359
|
</>
|
|
249
360
|
}
|
|
250
361
|
/>
|
|
@@ -261,6 +372,7 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
261
372
|
<LoadingPanel label="Loading opportunities…" />
|
|
262
373
|
) : (
|
|
263
374
|
<>
|
|
375
|
+
{view === 'list' && <Paginator list={opportunities} placement="top" />}
|
|
264
376
|
{view === 'columns' ? (
|
|
265
377
|
<KanbanBoard
|
|
266
378
|
stages={visibleStages.map((stage) => ({ id: stage.id, label: stage.label }))}
|
|
@@ -307,13 +419,16 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
307
419
|
? {
|
|
308
420
|
label: 'Show all opportunities',
|
|
309
421
|
onClick: () => {
|
|
310
|
-
setScope('all')
|
|
422
|
+
listView.setScope('all')
|
|
311
423
|
},
|
|
312
424
|
}
|
|
313
425
|
: undefined
|
|
314
426
|
}
|
|
315
427
|
sort={sort}
|
|
316
|
-
onSortChange={
|
|
428
|
+
onSortChange={(next) => {
|
|
429
|
+
listView.setSort(next)
|
|
430
|
+
}}
|
|
431
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
317
432
|
/>
|
|
318
433
|
)}
|
|
319
434
|
{updateOpportunity.error !== null && (
|
|
@@ -321,17 +436,8 @@ export function OpportunitiesPage(): React.JSX.Element {
|
|
|
321
436
|
<ErrorPanel error={updateOpportunity.error} />
|
|
322
437
|
</div>
|
|
323
438
|
)}
|
|
324
|
-
{
|
|
325
|
-
|
|
326
|
-
type="button"
|
|
327
|
-
onClick={opportunities.loadMore}
|
|
328
|
-
disabled={opportunities.isLoadingMore}
|
|
329
|
-
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"
|
|
330
|
-
>
|
|
331
|
-
{opportunities.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
332
|
-
</button>
|
|
333
|
-
)}
|
|
334
|
-
{companies.hasMore && (
|
|
439
|
+
{view === 'list' && <Paginator list={opportunities} />}
|
|
440
|
+
{companies.hasNext && (
|
|
335
441
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
336
442
|
More companies exist than one page returns, so some company names may show as “—”.
|
|
337
443
|
</p>
|