@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
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { Opportunity, OpportunityInput, PipelineStage } from '@kelpie/schemas'
|
|
2
2
|
import { useState } from 'react'
|
|
3
|
+
import type { FormEvent } from 'react'
|
|
3
4
|
import { Link, useNavigate, useParams } from 'react-router'
|
|
4
5
|
|
|
5
6
|
import { usePatch } from '../api/resource.ts'
|
|
6
7
|
import type { PatchResult } from '../api/resource.ts'
|
|
7
8
|
import { useCompanies, useCompany } from '../api/resources/companies.ts'
|
|
9
|
+
import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
|
|
8
10
|
import { useMembers } from '../api/resources/members.ts'
|
|
9
11
|
import {
|
|
10
12
|
useDeleteOpportunity,
|
|
11
13
|
useOpportunity,
|
|
12
14
|
useUpdateOpportunity,
|
|
13
15
|
} from '../api/resources/opportunities.ts'
|
|
16
|
+
import { usePeople } from '../api/resources/people.ts'
|
|
14
17
|
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
15
18
|
import { useRecordPlanItems } from '../api/resources/planItems.ts'
|
|
16
19
|
import { ActivitiesPanel, LatestActivity } from '../components/ActivitiesPanel.tsx'
|
|
@@ -20,13 +23,18 @@ import type { ChipTone } from '../components/Chip.tsx'
|
|
|
20
23
|
import { DecisionsPanel } from '../components/DecisionsPanel.tsx'
|
|
21
24
|
import { DeleteRecord } from '../components/DeleteRecord.tsx'
|
|
22
25
|
import { EntitySearch } from '../components/EntitySearch.tsx'
|
|
26
|
+
import { FormsPanel } from '../components/FormsPanel.tsx'
|
|
27
|
+
import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
28
|
+
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
23
29
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
30
|
+
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
24
31
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
25
32
|
import { PlanAttention } from '../components/PlanAttention.tsx'
|
|
26
33
|
import { PlanPanel } from '../components/PlanPanel.tsx'
|
|
27
34
|
import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
|
|
28
35
|
import { RecordTabs } from '../components/RecordTabs.tsx'
|
|
29
36
|
import type { RecordTabDescriptor } from '../components/RecordTabs.tsx'
|
|
37
|
+
import { SectionHeader } from '../components/SectionHeader.tsx'
|
|
30
38
|
import { SidebarField } from '../components/SidebarField.tsx'
|
|
31
39
|
import { SummaryBlock } from '../components/SummaryBlock.tsx'
|
|
32
40
|
import { formatDay } from '../lib/dates.ts'
|
|
@@ -38,8 +46,10 @@ import { toTags } from './fields.ts'
|
|
|
38
46
|
* One opportunity.
|
|
39
47
|
*
|
|
40
48
|
* The same shape as `DealDetail` minus what an opportunity does not have: no
|
|
41
|
-
* value,
|
|
42
|
-
*
|
|
49
|
+
* value, and a company that may be absent. People attach through `person_links`
|
|
50
|
+
* the same way they do on a deal, and they are shown in the same contacts
|
|
51
|
+
* section in the aside. A UI module can add its own tab through the
|
|
52
|
+
* `opportunity` record-tab slot.
|
|
43
53
|
*/
|
|
44
54
|
|
|
45
55
|
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
@@ -53,7 +63,9 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
53
63
|
const { record, isLoading, isNotFound, error } = useOpportunity(id)
|
|
54
64
|
const deleteOpportunity = useDeleteOpportunity()
|
|
55
65
|
const moduleTabs = inSlotOrder(useRecordTabs('opportunity'))
|
|
66
|
+
const hasCustomFields = useHasCustomFields('opportunity')
|
|
56
67
|
const [activeTab, setActiveTab] = useState('overview')
|
|
68
|
+
const formSubmissions = useFormSubmissionsForRecord('opportunity', id)
|
|
57
69
|
|
|
58
70
|
if (isNotFound) {
|
|
59
71
|
return <NotFoundPanel label="Opportunity" backTo="/opportunities" />
|
|
@@ -69,10 +81,15 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
69
81
|
|
|
70
82
|
const tabs: readonly RecordTabDescriptor<string>[] = [
|
|
71
83
|
{ id: 'overview', label: 'Overview' },
|
|
84
|
+
...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
|
|
72
85
|
{ id: 'plan', label: 'Plan' },
|
|
73
86
|
{ id: 'activity', label: 'Activity' },
|
|
74
87
|
{ id: 'notes', label: 'Notes' },
|
|
75
88
|
{ id: 'decisions', label: 'Decisions' },
|
|
89
|
+
{ id: 'lists', label: 'Lists' },
|
|
90
|
+
...(formSubmissions.records.length === 0
|
|
91
|
+
? []
|
|
92
|
+
: [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
|
|
76
93
|
...moduleTabs.map((tab) => ({ id: tab.id, label: tab.label })),
|
|
77
94
|
]
|
|
78
95
|
const active = tabs.some((tab) => tab.id === activeTab) ? activeTab : 'overview'
|
|
@@ -114,6 +131,7 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
114
131
|
ariaLabel="Opportunity sections"
|
|
115
132
|
>
|
|
116
133
|
{active === 'overview' && <OpportunityOverview opportunity={record} />}
|
|
134
|
+
{active === 'fields' && <OpportunityFields opportunity={record} />}
|
|
117
135
|
{active === 'plan' && <PlanPanel targetType="opportunity" targetId={record.id} />}
|
|
118
136
|
{active === 'activity' && (
|
|
119
137
|
<ActivitiesPanel targetType="opportunity" targetId={record.id} />
|
|
@@ -122,12 +140,15 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
122
140
|
{active === 'decisions' && (
|
|
123
141
|
<DecisionsPanel targetType="opportunity" targetId={record.id} />
|
|
124
142
|
)}
|
|
143
|
+
{active === 'lists' && <ListsPanel targetType="opportunity" targetId={record.id} />}
|
|
144
|
+
{active === 'forms' && <FormsPanel targetType="opportunity" targetId={record.id} />}
|
|
125
145
|
{moduleTab?.render({ objectType: 'opportunity', recordId: record.id })}
|
|
126
146
|
</RecordTabs>
|
|
127
147
|
</div>
|
|
128
148
|
|
|
129
149
|
<aside className="space-y-4 text-[12px] lg:sticky lg:top-6">
|
|
130
150
|
<OpportunitySidebar opportunity={record} />
|
|
151
|
+
<OpportunityContacts opportunity={record} />
|
|
131
152
|
</aside>
|
|
132
153
|
</div>
|
|
133
154
|
</div>
|
|
@@ -335,3 +356,148 @@ function OpportunitySidebar({
|
|
|
335
356
|
</section>
|
|
336
357
|
)
|
|
337
358
|
}
|
|
359
|
+
|
|
360
|
+
function OpportunityFields({
|
|
361
|
+
opportunity,
|
|
362
|
+
}: {
|
|
363
|
+
readonly opportunity: Opportunity
|
|
364
|
+
}): React.JSX.Element {
|
|
365
|
+
const { patch, error } = useOpportunityPatch(opportunity)
|
|
366
|
+
return (
|
|
367
|
+
<div className="space-y-4">
|
|
368
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
369
|
+
<CustomFieldsPanel
|
|
370
|
+
objectType="opportunity"
|
|
371
|
+
values={opportunity.customFields}
|
|
372
|
+
onPatch={(customFields) => {
|
|
373
|
+
patch({ customFields })
|
|
374
|
+
}}
|
|
375
|
+
/>
|
|
376
|
+
</div>
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** The people on the opportunity, linked and unlinked by replacing `person_ids`. */
|
|
381
|
+
function OpportunityContacts({
|
|
382
|
+
opportunity,
|
|
383
|
+
}: {
|
|
384
|
+
readonly opportunity: Opportunity
|
|
385
|
+
}): React.JSX.Element {
|
|
386
|
+
const { patch, error } = useOpportunityPatch(opportunity)
|
|
387
|
+
|
|
388
|
+
const [adding, setAdding] = useState(false)
|
|
389
|
+
const [personId, setPersonId] = useState('')
|
|
390
|
+
const [search, setSearch] = useState('')
|
|
391
|
+
|
|
392
|
+
// No `?id=` filter exists on people, so names come from the directory's first
|
|
393
|
+
// page. Past it, a contact renders by id rather than silently vanishing.
|
|
394
|
+
const directory = usePeople({ limit: 200 })
|
|
395
|
+
const searchable = usePeople({ term: search.trim().length > 0 ? search.trim() : undefined })
|
|
396
|
+
const nameById = new Map(directory.records.map((person) => [person.id, person.name]))
|
|
397
|
+
const linked = new Set(opportunity.personIds)
|
|
398
|
+
|
|
399
|
+
function reset(): void {
|
|
400
|
+
setAdding(false)
|
|
401
|
+
setPersonId('')
|
|
402
|
+
setSearch('')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function submit(event: FormEvent): void {
|
|
406
|
+
event.preventDefault()
|
|
407
|
+
|
|
408
|
+
if (personId.length === 0 || linked.has(personId)) {
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
patch({ personIds: [...opportunity.personIds, personId] })
|
|
413
|
+
reset()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<section className="rounded-md border border-border">
|
|
418
|
+
<div className="border-b border-border px-3.5 py-2.5">
|
|
419
|
+
<SectionHeader
|
|
420
|
+
title="Contacts"
|
|
421
|
+
onAdd={() => {
|
|
422
|
+
setAdding((current) => !current)
|
|
423
|
+
}}
|
|
424
|
+
addLabel="Add contact"
|
|
425
|
+
compact
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
{error !== null && (
|
|
430
|
+
<div className="px-3.5 py-2">
|
|
431
|
+
<ErrorPanel error={error} />
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
|
|
435
|
+
<ul className="divide-y divide-border">
|
|
436
|
+
{opportunity.personIds.length === 0 && !adding && (
|
|
437
|
+
<li className="px-3.5 py-4 text-[12px] text-ink-faint">No contacts yet.</li>
|
|
438
|
+
)}
|
|
439
|
+
{opportunity.personIds.map((linkedPersonId) => (
|
|
440
|
+
<li
|
|
441
|
+
key={linkedPersonId}
|
|
442
|
+
className="flex items-start justify-between gap-2 px-3.5 py-2.5"
|
|
443
|
+
>
|
|
444
|
+
<Link
|
|
445
|
+
to={`/people/${linkedPersonId}`}
|
|
446
|
+
className="text-[13px] font-medium text-ink hover:text-accent"
|
|
447
|
+
>
|
|
448
|
+
{nameById.get(linkedPersonId) ?? linkedPersonId}
|
|
449
|
+
</Link>
|
|
450
|
+
<button
|
|
451
|
+
type="button"
|
|
452
|
+
onClick={() => {
|
|
453
|
+
patch({
|
|
454
|
+
personIds: opportunity.personIds.filter(
|
|
455
|
+
(candidate) => candidate !== linkedPersonId,
|
|
456
|
+
),
|
|
457
|
+
})
|
|
458
|
+
}}
|
|
459
|
+
className="text-[11px] font-medium text-danger hover:underline"
|
|
460
|
+
>
|
|
461
|
+
Remove
|
|
462
|
+
</button>
|
|
463
|
+
</li>
|
|
464
|
+
))}
|
|
465
|
+
</ul>
|
|
466
|
+
|
|
467
|
+
{adding && (
|
|
468
|
+
<form onSubmit={submit} className="space-y-2 border-t border-border bg-surface px-3.5 py-3">
|
|
469
|
+
<EntitySearch
|
|
470
|
+
options={searchable.records
|
|
471
|
+
.filter((person) => !linked.has(person.id))
|
|
472
|
+
.map((person) => ({
|
|
473
|
+
id: person.id,
|
|
474
|
+
label: person.name,
|
|
475
|
+
meta: person.email ?? undefined,
|
|
476
|
+
}))}
|
|
477
|
+
value={personId}
|
|
478
|
+
onChange={setPersonId}
|
|
479
|
+
onQueryChange={setSearch}
|
|
480
|
+
placeholder="Search people…"
|
|
481
|
+
emptyMessage="No people match"
|
|
482
|
+
required
|
|
483
|
+
/>
|
|
484
|
+
<div className="flex justify-end gap-2">
|
|
485
|
+
<button
|
|
486
|
+
type="button"
|
|
487
|
+
onClick={reset}
|
|
488
|
+
className="rounded-md px-2.5 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
489
|
+
>
|
|
490
|
+
Cancel
|
|
491
|
+
</button>
|
|
492
|
+
<button
|
|
493
|
+
type="submit"
|
|
494
|
+
className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover"
|
|
495
|
+
>
|
|
496
|
+
Add
|
|
497
|
+
</button>
|
|
498
|
+
</div>
|
|
499
|
+
</form>
|
|
500
|
+
)}
|
|
501
|
+
</section>
|
|
502
|
+
)
|
|
503
|
+
}
|
|
@@ -6,6 +6,7 @@ import { Link, useNavigate, useParams } from 'react-router'
|
|
|
6
6
|
import { usePatch } from '../api/resource.ts'
|
|
7
7
|
import type { PatchResult } from '../api/resource.ts'
|
|
8
8
|
import { useCompanies, useCompany } from '../api/resources/companies.ts'
|
|
9
|
+
import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
|
|
9
10
|
import { useMembers } from '../api/resources/members.ts'
|
|
10
11
|
import {
|
|
11
12
|
useDeletePartnership,
|
|
@@ -22,7 +23,11 @@ import type { ChipTone } from '../components/Chip.tsx'
|
|
|
22
23
|
import { DecisionsPanel } from '../components/DecisionsPanel.tsx'
|
|
23
24
|
import { DeleteRecord } from '../components/DeleteRecord.tsx'
|
|
24
25
|
import { EntitySearch } from '../components/EntitySearch.tsx'
|
|
26
|
+
import { FormsPanel } from '../components/FormsPanel.tsx'
|
|
27
|
+
import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
28
|
+
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
25
29
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
30
|
+
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
26
31
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
27
32
|
import { PlanAttention } from '../components/PlanAttention.tsx'
|
|
28
33
|
import { PlanPanel } from '../components/PlanPanel.tsx'
|
|
@@ -58,7 +63,9 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
58
63
|
const { record, isLoading, isNotFound, error } = usePartnership(id)
|
|
59
64
|
const deletePartnership = useDeletePartnership()
|
|
60
65
|
const moduleTabs = inSlotOrder(useRecordTabs('partnership'))
|
|
66
|
+
const hasCustomFields = useHasCustomFields('partnership')
|
|
61
67
|
const [activeTab, setActiveTab] = useState('overview')
|
|
68
|
+
const formSubmissions = useFormSubmissionsForRecord('partnership', id)
|
|
62
69
|
|
|
63
70
|
if (isNotFound) {
|
|
64
71
|
return <NotFoundPanel label="Partnership" backTo="/partnerships" />
|
|
@@ -74,10 +81,15 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
74
81
|
|
|
75
82
|
const tabs: readonly RecordTabDescriptor<string>[] = [
|
|
76
83
|
{ id: 'overview', label: 'Overview' },
|
|
84
|
+
...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
|
|
77
85
|
{ id: 'plan', label: 'Plan' },
|
|
78
86
|
{ id: 'activity', label: 'Activity' },
|
|
79
87
|
{ id: 'notes', label: 'Notes' },
|
|
80
88
|
{ id: 'decisions', label: 'Decisions' },
|
|
89
|
+
{ id: 'lists', label: 'Lists' },
|
|
90
|
+
...(formSubmissions.records.length === 0
|
|
91
|
+
? []
|
|
92
|
+
: [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
|
|
81
93
|
...moduleTabs.map((tab) => ({ id: tab.id, label: tab.label })),
|
|
82
94
|
]
|
|
83
95
|
const active = tabs.some((tab) => tab.id === activeTab) ? activeTab : 'overview'
|
|
@@ -119,6 +131,7 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
119
131
|
ariaLabel="Partnership sections"
|
|
120
132
|
>
|
|
121
133
|
{active === 'overview' && <PartnershipOverview partnership={record} />}
|
|
134
|
+
{active === 'fields' && <PartnershipFields partnership={record} />}
|
|
122
135
|
{active === 'plan' && <PlanPanel targetType="partnership" targetId={record.id} />}
|
|
123
136
|
{active === 'activity' && (
|
|
124
137
|
<ActivitiesPanel targetType="partnership" targetId={record.id} />
|
|
@@ -127,6 +140,8 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
127
140
|
{active === 'decisions' && (
|
|
128
141
|
<DecisionsPanel targetType="partnership" targetId={record.id} />
|
|
129
142
|
)}
|
|
143
|
+
{active === 'lists' && <ListsPanel targetType="partnership" targetId={record.id} />}
|
|
144
|
+
{active === 'forms' && <FormsPanel targetType="partnership" targetId={record.id} />}
|
|
130
145
|
{moduleTab?.render({ objectType: 'partnership', recordId: record.id })}
|
|
131
146
|
</RecordTabs>
|
|
132
147
|
</div>
|
|
@@ -364,6 +379,26 @@ function PartnershipSidebar({
|
|
|
364
379
|
)
|
|
365
380
|
}
|
|
366
381
|
|
|
382
|
+
function PartnershipFields({
|
|
383
|
+
partnership,
|
|
384
|
+
}: {
|
|
385
|
+
readonly partnership: Partnership
|
|
386
|
+
}): React.JSX.Element {
|
|
387
|
+
const { patch, error } = usePartnershipPatch(partnership)
|
|
388
|
+
return (
|
|
389
|
+
<div className="space-y-4">
|
|
390
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
391
|
+
<CustomFieldsPanel
|
|
392
|
+
objectType="partnership"
|
|
393
|
+
values={partnership.customFields}
|
|
394
|
+
onPatch={(customFields) => {
|
|
395
|
+
patch({ customFields })
|
|
396
|
+
}}
|
|
397
|
+
/>
|
|
398
|
+
</div>
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
|
|
367
402
|
/** The key people on the partnership, linked and unlinked by replacing `person_ids`. */
|
|
368
403
|
function PartnershipKeyPeople({
|
|
369
404
|
partnership,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Partnership, 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/partnerships.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 Partnerships board: ongoing two-way relationships, grouped by status. The
|
|
@@ -32,6 +36,33 @@ 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
|
+
'company',
|
|
53
|
+
'stage',
|
|
54
|
+
'kind',
|
|
55
|
+
'nextPlan',
|
|
56
|
+
'owner',
|
|
57
|
+
'next',
|
|
58
|
+
'goals',
|
|
59
|
+
'successLooksLike',
|
|
60
|
+
'tags',
|
|
61
|
+
'summary',
|
|
62
|
+
'createdAt',
|
|
63
|
+
'updatedAt',
|
|
64
|
+
]
|
|
65
|
+
|
|
35
66
|
/** Tones keyed by the seeded slugs. A renamed stage keeps its slug and its tone. */
|
|
36
67
|
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
37
68
|
active: 'success',
|
|
@@ -43,19 +74,33 @@ function stageTone(stage: PipelineStage): ChipTone {
|
|
|
43
74
|
return STAGE_TONES[stage.slug] ?? 'neutral'
|
|
44
75
|
}
|
|
45
76
|
|
|
77
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = [
|
|
78
|
+
'name',
|
|
79
|
+
'company',
|
|
80
|
+
'kind',
|
|
81
|
+
'nextPlan',
|
|
82
|
+
'owner',
|
|
83
|
+
'next',
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
87
|
+
|
|
46
88
|
export function PartnershipsPage(): React.JSX.Element {
|
|
47
89
|
const navigate = useNavigate()
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
90
|
+
|
|
91
|
+
const listView = useListView('partnerships', COLUMN_KEYS, DEFAULT_VISIBLE_KEYS)
|
|
92
|
+
const view: BoardView = listView.mode ?? 'columns'
|
|
93
|
+
const scope = readScope(listView.scope)
|
|
94
|
+
const grouping = readGrouping(listView.grouping)
|
|
95
|
+
const sort = listView.sort
|
|
52
96
|
|
|
53
97
|
const stages = usePipelineStages('partnership')
|
|
54
|
-
const partnerships = usePartnerships({ sort })
|
|
98
|
+
const partnerships = usePartnerships({ sort: serverSortOnly(sort, SERVER_SORT_KEYS) })
|
|
55
99
|
const companies = useCompanies({ limit: 200 })
|
|
56
100
|
const members = useMembers()
|
|
57
101
|
const createPartnership = useCreatePartnership()
|
|
58
102
|
const updatePartnership = useUpdatePartnership()
|
|
103
|
+
const timezone = useTimezone()
|
|
59
104
|
|
|
60
105
|
const allStages = [...stages.records].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
61
106
|
const visibleStages = scope === 'open' ? allStages.filter((stage) => stage.open) : allStages
|
|
@@ -78,7 +123,7 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
78
123
|
{ enabled: askedIds.length > 0 },
|
|
79
124
|
)
|
|
80
125
|
const nextPlanByPartnership = nextOpenByTarget(planItems.records)
|
|
81
|
-
const plansTruncated = visiblePartnerships.length > MAX_PAGE_SIZE || planItems.
|
|
126
|
+
const plansTruncated = visiblePartnerships.length > MAX_PAGE_SIZE || planItems.hasNext
|
|
82
127
|
|
|
83
128
|
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
84
129
|
|
|
@@ -97,6 +142,8 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
97
142
|
await navigate(`/partnerships/${partnership.id}`)
|
|
98
143
|
}
|
|
99
144
|
|
|
145
|
+
const stageLabelById = new Map(allStages.map((stage) => [stage.id, stage.label]))
|
|
146
|
+
|
|
100
147
|
const columns: readonly Column<Partnership>[] = [
|
|
101
148
|
{
|
|
102
149
|
key: 'name',
|
|
@@ -107,16 +154,25 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
107
154
|
{
|
|
108
155
|
key: 'company',
|
|
109
156
|
header: 'Company',
|
|
157
|
+
getSortValue: (partnership) => companyNameById.get(partnership.companyId) ?? null,
|
|
110
158
|
render: (partnership) => companyNameById.get(partnership.companyId) ?? '—',
|
|
111
159
|
},
|
|
160
|
+
{
|
|
161
|
+
key: 'stage',
|
|
162
|
+
header: 'Stage',
|
|
163
|
+
getSortValue: (partnership) => stageLabelById.get(partnership.stageId) ?? null,
|
|
164
|
+
render: (partnership) => stageLabelById.get(partnership.stageId) ?? '—',
|
|
165
|
+
},
|
|
112
166
|
{
|
|
113
167
|
key: 'kind',
|
|
114
168
|
header: 'Kind',
|
|
169
|
+
getSortValue: (partnership) => partnership.kind || null,
|
|
115
170
|
render: (partnership) => (partnership.kind.length > 0 ? partnership.kind : '—'),
|
|
116
171
|
},
|
|
117
172
|
{
|
|
118
173
|
key: 'nextPlan',
|
|
119
174
|
header: 'Next plan',
|
|
175
|
+
getSortValue: (partnership) => nextPlanByPartnership.get(partnership.id)?.date ?? null,
|
|
120
176
|
render: (partnership) => {
|
|
121
177
|
const next = nextPlanByPartnership.get(partnership.id)
|
|
122
178
|
|
|
@@ -137,6 +193,10 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
137
193
|
{
|
|
138
194
|
key: 'owner',
|
|
139
195
|
header: 'Owner',
|
|
196
|
+
getSortValue: (partnership) =>
|
|
197
|
+
partnership.ownerId === null
|
|
198
|
+
? null
|
|
199
|
+
: (members.nameById.get(partnership.ownerId) ?? 'Unknown'),
|
|
140
200
|
render: (partnership) =>
|
|
141
201
|
partnership.ownerId === null
|
|
142
202
|
? '—'
|
|
@@ -146,14 +206,71 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
146
206
|
key: 'next',
|
|
147
207
|
header: 'Next touch',
|
|
148
208
|
className: 'w-28',
|
|
209
|
+
getSortValue: (partnership) => partnership.nextTouchpoint,
|
|
149
210
|
render: (partnership) => (
|
|
150
211
|
<span className="font-mono text-[12px] text-ink-muted">
|
|
151
212
|
{partnership.nextTouchpoint === null ? '—' : formatDay(partnership.nextTouchpoint)}
|
|
152
213
|
</span>
|
|
153
214
|
),
|
|
154
215
|
},
|
|
216
|
+
{
|
|
217
|
+
key: 'goals',
|
|
218
|
+
header: 'Goals',
|
|
219
|
+
getSortValue: (partnership) => partnership.goals || null,
|
|
220
|
+
render: (partnership) =>
|
|
221
|
+
partnership.goals.length === 0 ? '—' : (
|
|
222
|
+
<span className="text-ink-muted">{partnership.goals}</span>
|
|
223
|
+
),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
key: 'successLooksLike',
|
|
227
|
+
header: 'Success looks like',
|
|
228
|
+
getSortValue: (partnership) => partnership.successLooksLike || null,
|
|
229
|
+
render: (partnership) =>
|
|
230
|
+
partnership.successLooksLike.length === 0 ? '—' : (
|
|
231
|
+
<span className="text-ink-muted">{partnership.successLooksLike}</span>
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
key: 'tags',
|
|
236
|
+
header: 'Tags',
|
|
237
|
+
getSortValue: (partnership) => partnership.tags.join(', ') || null,
|
|
238
|
+
render: (partnership) =>
|
|
239
|
+
partnership.tags.length === 0 ? '—' : (
|
|
240
|
+
<span className="flex flex-wrap gap-1">
|
|
241
|
+
{partnership.tags.map((tag) => (
|
|
242
|
+
<Chip key={tag}>
|
|
243
|
+
<span className="text-[10px]">{tag}</span>
|
|
244
|
+
</Chip>
|
|
245
|
+
))}
|
|
246
|
+
</span>
|
|
247
|
+
),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
key: 'summary',
|
|
251
|
+
header: 'Summary',
|
|
252
|
+
getSortValue: (partnership) => partnership.summary || null,
|
|
253
|
+
render: (partnership) =>
|
|
254
|
+
partnership.summary.length === 0 ? '—' : (
|
|
255
|
+
<span className="text-ink-muted">{partnership.summary}</span>
|
|
256
|
+
),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
key: 'createdAt',
|
|
260
|
+
header: 'Created',
|
|
261
|
+
sortKey: 'created_at',
|
|
262
|
+
render: (partnership) => formatDate(partnership.createdAt, timezone),
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
key: 'updatedAt',
|
|
266
|
+
header: 'Updated',
|
|
267
|
+
sortKey: 'updated_at',
|
|
268
|
+
render: (partnership) => formatDate(partnership.updatedAt, timezone),
|
|
269
|
+
},
|
|
155
270
|
]
|
|
156
271
|
|
|
272
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
273
|
+
|
|
157
274
|
const stageGroups: readonly DataTableGroup<Partnership>[] = visibleStages.map((stage) => ({
|
|
158
275
|
id: stage.id,
|
|
159
276
|
label: <Chip tone={stageTone(stage)}>{stage.label}</Chip>,
|
|
@@ -217,7 +334,9 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
217
334
|
<SegmentedControl
|
|
218
335
|
ariaLabel="Status scope"
|
|
219
336
|
value={scope}
|
|
220
|
-
onChange={
|
|
337
|
+
onChange={(next) => {
|
|
338
|
+
listView.setScope(next)
|
|
339
|
+
}}
|
|
221
340
|
options={[
|
|
222
341
|
{ id: 'open', label: 'Open' },
|
|
223
342
|
{ id: 'all', label: 'All' },
|
|
@@ -227,7 +346,9 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
227
346
|
<SegmentedControl
|
|
228
347
|
ariaLabel="Group the list"
|
|
229
348
|
value={grouping}
|
|
230
|
-
onChange={
|
|
349
|
+
onChange={(next) => {
|
|
350
|
+
listView.setGrouping(next)
|
|
351
|
+
}}
|
|
231
352
|
options={[
|
|
232
353
|
{ id: 'stage', label: 'By status' },
|
|
233
354
|
{ id: 'due', label: 'By due' },
|
|
@@ -237,12 +358,21 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
237
358
|
<SegmentedControl
|
|
238
359
|
ariaLabel="Board or list"
|
|
239
360
|
value={view}
|
|
240
|
-
onChange={
|
|
361
|
+
onChange={(next) => {
|
|
362
|
+
listView.setMode(next)
|
|
363
|
+
}}
|
|
241
364
|
options={[
|
|
242
365
|
{ id: 'list', label: 'List' },
|
|
243
366
|
{ id: 'columns', label: 'Board' },
|
|
244
367
|
]}
|
|
245
368
|
/>
|
|
369
|
+
{view === 'list' && (
|
|
370
|
+
<ColumnPicker
|
|
371
|
+
options={pickerOptions}
|
|
372
|
+
visibleKeys={listView.visibleKeys}
|
|
373
|
+
onChange={listView.setVisibleKeys}
|
|
374
|
+
/>
|
|
375
|
+
)}
|
|
246
376
|
</>
|
|
247
377
|
}
|
|
248
378
|
/>
|
|
@@ -265,6 +395,7 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
265
395
|
<LoadingPanel label="Loading partnerships…" />
|
|
266
396
|
) : (
|
|
267
397
|
<>
|
|
398
|
+
{view === 'list' && <Paginator list={partnerships} placement="top" />}
|
|
268
399
|
{view === 'columns' ? (
|
|
269
400
|
<KanbanBoard
|
|
270
401
|
stages={visibleStages.map((stage) => ({ id: stage.id, label: stage.label }))}
|
|
@@ -311,13 +442,16 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
311
442
|
? {
|
|
312
443
|
label: 'Show all partnerships',
|
|
313
444
|
onClick: () => {
|
|
314
|
-
setScope('all')
|
|
445
|
+
listView.setScope('all')
|
|
315
446
|
},
|
|
316
447
|
}
|
|
317
448
|
: undefined
|
|
318
449
|
}
|
|
319
450
|
sort={sort}
|
|
320
|
-
onSortChange={
|
|
451
|
+
onSortChange={(next) => {
|
|
452
|
+
listView.setSort(next)
|
|
453
|
+
}}
|
|
454
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
321
455
|
/>
|
|
322
456
|
)}
|
|
323
457
|
{updatePartnership.error !== null && (
|
|
@@ -325,17 +459,8 @@ export function PartnershipsPage(): React.JSX.Element {
|
|
|
325
459
|
<ErrorPanel error={updatePartnership.error} />
|
|
326
460
|
</div>
|
|
327
461
|
)}
|
|
328
|
-
{
|
|
329
|
-
|
|
330
|
-
type="button"
|
|
331
|
-
onClick={partnerships.loadMore}
|
|
332
|
-
disabled={partnerships.isLoadingMore}
|
|
333
|
-
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"
|
|
334
|
-
>
|
|
335
|
-
{partnerships.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
336
|
-
</button>
|
|
337
|
-
)}
|
|
338
|
-
{companies.hasMore && (
|
|
462
|
+
{view === 'list' && <Paginator list={partnerships} />}
|
|
463
|
+
{companies.hasNext && (
|
|
339
464
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
340
465
|
More companies exist than one page returns, so some company names may show as “—”.
|
|
341
466
|
</p>
|