@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,434 @@
|
|
|
1
|
+
import type { Enquiry, PipelineStage } from '@kelpie/schemas'
|
|
2
|
+
import { Link, useNavigate } from 'react-router'
|
|
3
|
+
|
|
4
|
+
import { useCompanies } from '../api/resources/companies.ts'
|
|
5
|
+
import {
|
|
6
|
+
useCreateEnquiry,
|
|
7
|
+
useEnquiries,
|
|
8
|
+
useUpdateEnquiry,
|
|
9
|
+
} from '../api/resources/enquiries.ts'
|
|
10
|
+
import { useMembers } from '../api/resources/members.ts'
|
|
11
|
+
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
12
|
+
import { MAX_PAGE_SIZE, usePlanItems } from '../api/resources/planItems.ts'
|
|
13
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
14
|
+
import { Chip } from '../components/Chip.tsx'
|
|
15
|
+
import type { ChipTone } from '../components/Chip.tsx'
|
|
16
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
17
|
+
import { DataTable } from '../components/DataTable.tsx'
|
|
18
|
+
import type { Column, DataTableGroup } from '../components/DataTable.tsx'
|
|
19
|
+
import { KanbanBoard } from '../components/KanbanBoard.tsx'
|
|
20
|
+
import { PageHeader } from '../components/PageHeader.tsx'
|
|
21
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
22
|
+
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
23
|
+
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
24
|
+
import { formatDate, formatDay } from '../lib/dates.ts'
|
|
25
|
+
import { useListView } from '../lib/listView.ts'
|
|
26
|
+
import { DUE_BUCKETS, byDateThenTitle, dueBucketFor, nextOpenByTarget } from '../lib/plan.ts'
|
|
27
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The Enquiries pipeline: inbound requests that may become a Deal. The same
|
|
31
|
+
* board-and-list pair as Opportunities over the shared kanban, with `source`
|
|
32
|
+
* in place of `kind` and a company that may be absent.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
type BoardView = 'list' | 'columns'
|
|
36
|
+
type PipelineScope = 'open' | 'all'
|
|
37
|
+
type ListGrouping = 'stage' | 'due'
|
|
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
|
+
'source',
|
|
53
|
+
'company',
|
|
54
|
+
'stage',
|
|
55
|
+
'nextPlan',
|
|
56
|
+
'owner',
|
|
57
|
+
'tags',
|
|
58
|
+
'summary',
|
|
59
|
+
'createdAt',
|
|
60
|
+
'updatedAt',
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
/** Tones keyed by the seeded slugs. A renamed stage keeps its slug and its tone. */
|
|
64
|
+
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
65
|
+
new: 'accent',
|
|
66
|
+
in_progress: 'warning',
|
|
67
|
+
closed: 'neutral',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stageTone(stage: PipelineStage): ChipTone {
|
|
71
|
+
return STAGE_TONES[stage.slug] ?? 'neutral'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = [
|
|
75
|
+
'name',
|
|
76
|
+
'source',
|
|
77
|
+
'company',
|
|
78
|
+
'nextPlan',
|
|
79
|
+
'owner',
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
83
|
+
|
|
84
|
+
export function EnquiriesPage(): React.JSX.Element {
|
|
85
|
+
const navigate = useNavigate()
|
|
86
|
+
|
|
87
|
+
const listView = useListView('enquiries', COLUMN_KEYS, DEFAULT_VISIBLE_KEYS)
|
|
88
|
+
const view: BoardView = listView.mode ?? 'columns'
|
|
89
|
+
const scope = readScope(listView.scope)
|
|
90
|
+
const grouping = readGrouping(listView.grouping)
|
|
91
|
+
const sort = listView.sort
|
|
92
|
+
|
|
93
|
+
const stages = usePipelineStages('enquiry')
|
|
94
|
+
const enquiries = useEnquiries({ sort: serverSortOnly(sort, SERVER_SORT_KEYS) })
|
|
95
|
+
const companies = useCompanies({ limit: 200 })
|
|
96
|
+
const members = useMembers()
|
|
97
|
+
const createEnquiry = useCreateEnquiry()
|
|
98
|
+
const updateEnquiry = useUpdateEnquiry()
|
|
99
|
+
const timezone = useTimezone()
|
|
100
|
+
|
|
101
|
+
const allStages = [...stages.records].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
102
|
+
const visibleStages = scope === 'open' ? allStages.filter((stage) => stage.open) : allStages
|
|
103
|
+
const visibleStageIds = new Set(visibleStages.map((stage) => stage.id))
|
|
104
|
+
const visibleEnquiries = enquiries.records.filter((enquiry) =>
|
|
105
|
+
visibleStageIds.has(enquiry.stageId),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const askedIds = visibleEnquiries.slice(0, MAX_PAGE_SIZE).map((enquiry) => enquiry.id)
|
|
109
|
+
const planItems = usePlanItems(
|
|
110
|
+
{
|
|
111
|
+
targetType: 'enquiry',
|
|
112
|
+
targetIds: askedIds,
|
|
113
|
+
statuses: ['todo', 'in_progress'],
|
|
114
|
+
limit: MAX_PAGE_SIZE,
|
|
115
|
+
},
|
|
116
|
+
{ enabled: askedIds.length > 0 },
|
|
117
|
+
)
|
|
118
|
+
const nextPlanByEnquiry = nextOpenByTarget(planItems.records)
|
|
119
|
+
const plansTruncated = visibleEnquiries.length > MAX_PAGE_SIZE || planItems.hasNext
|
|
120
|
+
|
|
121
|
+
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
122
|
+
|
|
123
|
+
async function addEnquiry(): Promise<void> {
|
|
124
|
+
const enquiry = await createEnquiry.runAsync({ name: 'New enquiry' })
|
|
125
|
+
|
|
126
|
+
await navigate(`/enquiries/${enquiry.id}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const stageLabelById = new Map(allStages.map((stage) => [stage.id, stage.label]))
|
|
130
|
+
|
|
131
|
+
const columns: readonly Column<Enquiry>[] = [
|
|
132
|
+
{
|
|
133
|
+
key: 'name',
|
|
134
|
+
header: 'Enquiry',
|
|
135
|
+
sortKey: 'name',
|
|
136
|
+
render: (enquiry) => <span className="font-medium text-ink">{enquiry.name}</span>,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
key: 'source',
|
|
140
|
+
header: 'Source',
|
|
141
|
+
getSortValue: (enquiry) => enquiry.source || null,
|
|
142
|
+
render: (enquiry) => (enquiry.source.length > 0 ? enquiry.source : '—'),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: 'company',
|
|
146
|
+
header: 'Company',
|
|
147
|
+
getSortValue: (enquiry) =>
|
|
148
|
+
enquiry.companyId === null ? null : (companyNameById.get(enquiry.companyId) ?? null),
|
|
149
|
+
render: (enquiry) =>
|
|
150
|
+
enquiry.companyId === null
|
|
151
|
+
? '—'
|
|
152
|
+
: (companyNameById.get(enquiry.companyId) ?? '—'),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
key: 'stage',
|
|
156
|
+
header: 'Stage',
|
|
157
|
+
getSortValue: (enquiry) => stageLabelById.get(enquiry.stageId) ?? null,
|
|
158
|
+
render: (enquiry) => stageLabelById.get(enquiry.stageId) ?? '—',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
key: 'nextPlan',
|
|
162
|
+
header: 'Next plan',
|
|
163
|
+
getSortValue: (enquiry) => nextPlanByEnquiry.get(enquiry.id)?.date ?? null,
|
|
164
|
+
render: (enquiry) => {
|
|
165
|
+
const next = nextPlanByEnquiry.get(enquiry.id)
|
|
166
|
+
|
|
167
|
+
if (next === undefined) {
|
|
168
|
+
return '—'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<span className="text-ink-muted">
|
|
173
|
+
{next.title}
|
|
174
|
+
<span className="ml-1.5 font-mono text-[11px] text-ink-faint">
|
|
175
|
+
{formatDay(next.date)}
|
|
176
|
+
</span>
|
|
177
|
+
</span>
|
|
178
|
+
)
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
key: 'owner',
|
|
183
|
+
header: 'Owner',
|
|
184
|
+
getSortValue: (enquiry) =>
|
|
185
|
+
enquiry.ownerId === null ? null : (members.nameById.get(enquiry.ownerId) ?? 'Unknown'),
|
|
186
|
+
render: (enquiry) =>
|
|
187
|
+
enquiry.ownerId === null
|
|
188
|
+
? '—'
|
|
189
|
+
: (members.nameById.get(enquiry.ownerId) ?? 'Unknown'),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: 'tags',
|
|
193
|
+
header: 'Tags',
|
|
194
|
+
getSortValue: (enquiry) => enquiry.tags.join(', ') || null,
|
|
195
|
+
render: (enquiry) =>
|
|
196
|
+
enquiry.tags.length === 0 ? '—' : (
|
|
197
|
+
<span className="flex flex-wrap gap-1">
|
|
198
|
+
{enquiry.tags.map((tag) => (
|
|
199
|
+
<Chip key={tag}>
|
|
200
|
+
<span className="text-[10px]">{tag}</span>
|
|
201
|
+
</Chip>
|
|
202
|
+
))}
|
|
203
|
+
</span>
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: 'summary',
|
|
208
|
+
header: 'Summary',
|
|
209
|
+
getSortValue: (enquiry) => enquiry.summary || null,
|
|
210
|
+
render: (enquiry) =>
|
|
211
|
+
enquiry.summary.length === 0 ? '—' : (
|
|
212
|
+
<span className="text-ink-muted">{enquiry.summary}</span>
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
key: 'createdAt',
|
|
217
|
+
header: 'Created',
|
|
218
|
+
sortKey: 'created_at',
|
|
219
|
+
render: (enquiry) => formatDate(enquiry.createdAt, timezone),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
key: 'updatedAt',
|
|
223
|
+
header: 'Updated',
|
|
224
|
+
sortKey: 'updated_at',
|
|
225
|
+
render: (enquiry) => formatDate(enquiry.updatedAt, timezone),
|
|
226
|
+
},
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
230
|
+
|
|
231
|
+
const stageGroups: readonly DataTableGroup<Enquiry>[] = visibleStages.map((stage) => ({
|
|
232
|
+
id: stage.id,
|
|
233
|
+
label: <Chip tone={stageTone(stage)}>{stage.label}</Chip>,
|
|
234
|
+
rows: visibleEnquiries.filter((enquiry) => enquiry.stageId === stage.id),
|
|
235
|
+
}))
|
|
236
|
+
|
|
237
|
+
const dueGroups: readonly DataTableGroup<Enquiry>[] = DUE_BUCKETS.map((bucket) => ({
|
|
238
|
+
id: bucket.id,
|
|
239
|
+
label: bucket.label,
|
|
240
|
+
rows: visibleEnquiries
|
|
241
|
+
.filter(
|
|
242
|
+
(enquiry) => dueBucketFor(nextPlanByEnquiry.get(enquiry.id)?.date) === bucket.id,
|
|
243
|
+
)
|
|
244
|
+
.sort((left, right) => {
|
|
245
|
+
const leftNext = nextPlanByEnquiry.get(left.id)
|
|
246
|
+
const rightNext = nextPlanByEnquiry.get(right.id)
|
|
247
|
+
|
|
248
|
+
if (leftNext === undefined || rightNext === undefined) {
|
|
249
|
+
return left.name.localeCompare(right.name)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return byDateThenTitle(leftNext, rightNext) || left.name.localeCompare(right.name)
|
|
253
|
+
}),
|
|
254
|
+
}))
|
|
255
|
+
|
|
256
|
+
const cards = visibleEnquiries.map((enquiry) => {
|
|
257
|
+
const next = nextPlanByEnquiry.get(enquiry.id)
|
|
258
|
+
const companyName =
|
|
259
|
+
enquiry.companyId === null ? undefined : companyNameById.get(enquiry.companyId)
|
|
260
|
+
const meta = [
|
|
261
|
+
enquiry.source.length > 0 ? enquiry.source : undefined,
|
|
262
|
+
companyName,
|
|
263
|
+
next?.title,
|
|
264
|
+
]
|
|
265
|
+
.filter((part) => part !== undefined)
|
|
266
|
+
.join(' · ')
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
id: enquiry.id,
|
|
270
|
+
stage: enquiry.stageId,
|
|
271
|
+
title: enquiry.name,
|
|
272
|
+
meta,
|
|
273
|
+
href: `/enquiries/${enquiry.id}`,
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
const isLoading = enquiries.isLoading || stages.isLoading
|
|
278
|
+
const loadError = enquiries.error ?? stages.error
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div className="animate-fade-in">
|
|
282
|
+
<PageHeader
|
|
283
|
+
title="Enquiries"
|
|
284
|
+
onAdd={() => {
|
|
285
|
+
void addEnquiry()
|
|
286
|
+
}}
|
|
287
|
+
addLabel="Add enquiry"
|
|
288
|
+
actions={
|
|
289
|
+
<>
|
|
290
|
+
<Link
|
|
291
|
+
to="/enquiries/settings"
|
|
292
|
+
className="rounded-md border border-border bg-surface-raised px-2.5 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
293
|
+
>
|
|
294
|
+
Settings
|
|
295
|
+
</Link>
|
|
296
|
+
<SegmentedControl
|
|
297
|
+
ariaLabel="Stage scope"
|
|
298
|
+
value={scope}
|
|
299
|
+
onChange={(next) => {
|
|
300
|
+
listView.setScope(next)
|
|
301
|
+
}}
|
|
302
|
+
options={[
|
|
303
|
+
{ id: 'open', label: 'Open' },
|
|
304
|
+
{ id: 'all', label: 'All' },
|
|
305
|
+
]}
|
|
306
|
+
/>
|
|
307
|
+
{view === 'list' && (
|
|
308
|
+
<SegmentedControl
|
|
309
|
+
ariaLabel="Group the list"
|
|
310
|
+
value={grouping}
|
|
311
|
+
onChange={(next) => {
|
|
312
|
+
listView.setGrouping(next)
|
|
313
|
+
}}
|
|
314
|
+
options={[
|
|
315
|
+
{ id: 'stage', label: 'By stage' },
|
|
316
|
+
{ id: 'due', label: 'By due' },
|
|
317
|
+
]}
|
|
318
|
+
/>
|
|
319
|
+
)}
|
|
320
|
+
<SegmentedControl
|
|
321
|
+
ariaLabel="Board or list"
|
|
322
|
+
value={view}
|
|
323
|
+
onChange={(next) => {
|
|
324
|
+
listView.setMode(next)
|
|
325
|
+
}}
|
|
326
|
+
options={[
|
|
327
|
+
{ id: 'list', label: 'List' },
|
|
328
|
+
{ id: 'columns', label: 'Board' },
|
|
329
|
+
]}
|
|
330
|
+
/>
|
|
331
|
+
{view === 'list' && (
|
|
332
|
+
<ColumnPicker
|
|
333
|
+
options={pickerOptions}
|
|
334
|
+
visibleKeys={listView.visibleKeys}
|
|
335
|
+
onChange={listView.setVisibleKeys}
|
|
336
|
+
/>
|
|
337
|
+
)}
|
|
338
|
+
</>
|
|
339
|
+
}
|
|
340
|
+
/>
|
|
341
|
+
|
|
342
|
+
{createEnquiry.error !== null && (
|
|
343
|
+
<div className="mb-3">
|
|
344
|
+
<ErrorPanel error={createEnquiry.error} />
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
|
|
348
|
+
{loadError !== null ? (
|
|
349
|
+
<ErrorPanel error={loadError} />
|
|
350
|
+
) : isLoading ? (
|
|
351
|
+
<LoadingPanel label="Loading enquiries…" />
|
|
352
|
+
) : (
|
|
353
|
+
<>
|
|
354
|
+
{view === 'list' && <Paginator list={enquiries} placement="top" />}
|
|
355
|
+
{view === 'columns' ? (
|
|
356
|
+
<KanbanBoard
|
|
357
|
+
stages={visibleStages.map((stage) => ({ id: stage.id, label: stage.label }))}
|
|
358
|
+
cards={cards}
|
|
359
|
+
onMove={(enquiryId, stageId) => {
|
|
360
|
+
const enquiry = enquiries.records.find(
|
|
361
|
+
(candidate) => candidate.id === enquiryId,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
if (enquiry !== undefined && enquiry.stageId !== stageId) {
|
|
365
|
+
updateEnquiry.run({ id: enquiryId, changes: { stageId } })
|
|
366
|
+
}
|
|
367
|
+
}}
|
|
368
|
+
/>
|
|
369
|
+
) : (
|
|
370
|
+
<DataTable
|
|
371
|
+
columns={columns}
|
|
372
|
+
groups={grouping === 'stage' ? stageGroups : dueGroups}
|
|
373
|
+
getRowId={(enquiry) => enquiry.id}
|
|
374
|
+
onRowClick={(enquiry) => {
|
|
375
|
+
void navigate(`/enquiries/${enquiry.id}`)
|
|
376
|
+
}}
|
|
377
|
+
emptyMessage={
|
|
378
|
+
enquiries.records.length === 0
|
|
379
|
+
? 'No enquiries yet'
|
|
380
|
+
: scope === 'open'
|
|
381
|
+
? 'No open enquiries'
|
|
382
|
+
: 'No enquiries in this view'
|
|
383
|
+
}
|
|
384
|
+
emptyDescription={
|
|
385
|
+
enquiries.records.length > 0 && scope === 'open'
|
|
386
|
+
? 'You have enquiries, but none are open right now.'
|
|
387
|
+
: undefined
|
|
388
|
+
}
|
|
389
|
+
emptyAction={
|
|
390
|
+
enquiries.records.length === 0
|
|
391
|
+
? {
|
|
392
|
+
label: 'Add enquiry',
|
|
393
|
+
onClick: () => {
|
|
394
|
+
void addEnquiry()
|
|
395
|
+
},
|
|
396
|
+
}
|
|
397
|
+
: scope === 'open'
|
|
398
|
+
? {
|
|
399
|
+
label: 'Show all enquiries',
|
|
400
|
+
onClick: () => {
|
|
401
|
+
listView.setScope('all')
|
|
402
|
+
},
|
|
403
|
+
}
|
|
404
|
+
: undefined
|
|
405
|
+
}
|
|
406
|
+
sort={sort}
|
|
407
|
+
onSortChange={(next) => {
|
|
408
|
+
listView.setSort(next)
|
|
409
|
+
}}
|
|
410
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
411
|
+
/>
|
|
412
|
+
)}
|
|
413
|
+
{updateEnquiry.error !== null && (
|
|
414
|
+
<div className="mt-3">
|
|
415
|
+
<ErrorPanel error={updateEnquiry.error} />
|
|
416
|
+
</div>
|
|
417
|
+
)}
|
|
418
|
+
{view === 'list' && <Paginator list={enquiries} />}
|
|
419
|
+
{companies.hasNext && (
|
|
420
|
+
<p className="mt-2 text-[11px] text-ink-faint">
|
|
421
|
+
More companies exist than one page returns, so some company names may show as “—”.
|
|
422
|
+
</p>
|
|
423
|
+
)}
|
|
424
|
+
{plansTruncated && (
|
|
425
|
+
<p className="mt-2 text-[11px] text-ink-faint">
|
|
426
|
+
More plan items exist than one page returns, so some enquiries may show no next
|
|
427
|
+
plan.
|
|
428
|
+
</p>
|
|
429
|
+
)}
|
|
430
|
+
</>
|
|
431
|
+
)}
|
|
432
|
+
</div>
|
|
433
|
+
)
|
|
434
|
+
}
|