@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,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
PIPELINE_KIND_LABELS,
|
|
3
|
+
PIPELINE_KINDS,
|
|
4
|
+
PLAN_ITEM_STATUS_LABELS,
|
|
5
|
+
PLAN_ITEM_STATUSES,
|
|
6
|
+
} from '@kelpie/schemas'
|
|
7
|
+
import type { PipelineKind, PlanItem, PlanItemStatus } from '@kelpie/schemas'
|
|
3
8
|
import { useState } from 'react'
|
|
9
|
+
import type { FormEvent } from 'react'
|
|
4
10
|
|
|
5
11
|
import { useDeals } from '../api/resources/deals.ts'
|
|
12
|
+
import { useEnquiries } from '../api/resources/enquiries.ts'
|
|
6
13
|
import { useMembers } from '../api/resources/members.ts'
|
|
7
14
|
import { useOpportunities } from '../api/resources/opportunities.ts'
|
|
8
15
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
9
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
MAX_PAGE_SIZE,
|
|
18
|
+
useCreatePlanItem,
|
|
19
|
+
usePlanItems,
|
|
20
|
+
} from '../api/resources/planItems.ts'
|
|
10
21
|
import { useRaises } from '../api/resources/raises.ts'
|
|
11
22
|
import { Chip } from '../components/Chip.tsx'
|
|
12
23
|
import { PageHeader } from '../components/PageHeader.tsx'
|
|
13
24
|
import { PlanTargetLink } from '../components/PlanAttention.tsx'
|
|
25
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
14
26
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
15
27
|
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
16
28
|
import { formatDay } from '../lib/dates.ts'
|
|
@@ -23,6 +35,9 @@ import { monthBounds, planStatusTone, todayIso } from '../lib/plan.ts'
|
|
|
23
35
|
* upcoming and pages through it; the calendar wants one month, which is what
|
|
24
36
|
* `?from=` and `?to=` are for, so moving between months is a request rather than
|
|
25
37
|
* a filter over rows the page happens to hold.
|
|
38
|
+
*
|
|
39
|
+
* Adding a plan item lives here as well as on each record's Plan panel: the page
|
|
40
|
+
* is the workspace-wide view of the same resource, so create belongs here too.
|
|
26
41
|
*/
|
|
27
42
|
|
|
28
43
|
type ViewMode = 'list' | 'calendar'
|
|
@@ -35,15 +50,27 @@ const CELL_ITEM_LIMIT = 3
|
|
|
35
50
|
|
|
36
51
|
const MONTH_AND_YEAR = new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' })
|
|
37
52
|
|
|
53
|
+
const inputClass =
|
|
54
|
+
'h-9 w-full rounded-md border border-border bg-surface-raised px-2.5 text-[13px] outline-none focus:border-accent'
|
|
55
|
+
|
|
56
|
+
interface PipelineTarget {
|
|
57
|
+
readonly id: string
|
|
58
|
+
readonly name: string
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
export function PlanningPage(): React.JSX.Element {
|
|
39
62
|
const [view, setView] = useState<ViewMode>('list')
|
|
40
63
|
const [typeFilter, setTypeFilter] = useState<TypeFilter>('all')
|
|
64
|
+
const [adding, setAdding] = useState(false)
|
|
41
65
|
const [month, setMonth] = useState(() => {
|
|
42
66
|
const now = new Date()
|
|
43
67
|
|
|
44
68
|
return { year: now.getFullYear(), month: now.getMonth() }
|
|
45
69
|
})
|
|
46
70
|
|
|
71
|
+
const targets = usePipelineTargets()
|
|
72
|
+
const createItem = useCreatePlanItem()
|
|
73
|
+
|
|
47
74
|
const typeQuery = typeFilter === 'all' ? {} : { targetType: typeFilter }
|
|
48
75
|
// A month is a bounded question, so the calendar asks for it at the largest
|
|
49
76
|
// page size and draws the answer whole. The list pages the ordinary way.
|
|
@@ -65,6 +92,10 @@ export function PlanningPage(): React.JSX.Element {
|
|
|
65
92
|
<div className="animate-fade-in">
|
|
66
93
|
<PageHeader
|
|
67
94
|
title="Planning"
|
|
95
|
+
onAdd={() => {
|
|
96
|
+
setAdding((current) => !current)
|
|
97
|
+
}}
|
|
98
|
+
addLabel="Add plan item"
|
|
68
99
|
actions={
|
|
69
100
|
<>
|
|
70
101
|
<label className="sr-only" htmlFor="plan-type-filter">
|
|
@@ -98,6 +129,33 @@ export function PlanningPage(): React.JSX.Element {
|
|
|
98
129
|
}
|
|
99
130
|
/>
|
|
100
131
|
|
|
132
|
+
{adding && (
|
|
133
|
+
<AddPlanItemForm
|
|
134
|
+
initialTargetType={typeFilter === 'all' ? 'deal' : typeFilter}
|
|
135
|
+
targetsByKind={targets.byKind}
|
|
136
|
+
isPending={createItem.isPending}
|
|
137
|
+
error={createItem.error}
|
|
138
|
+
onCancel={() => {
|
|
139
|
+
setAdding(false)
|
|
140
|
+
}}
|
|
141
|
+
onSubmit={(fields) => {
|
|
142
|
+
createItem
|
|
143
|
+
.runAsync({
|
|
144
|
+
targetType: fields.targetType,
|
|
145
|
+
targetId: fields.targetId,
|
|
146
|
+
date: fields.date,
|
|
147
|
+
title: fields.title,
|
|
148
|
+
ownerId: fields.ownerId.length === 0 ? null : fields.ownerId,
|
|
149
|
+
status: fields.status,
|
|
150
|
+
})
|
|
151
|
+
.then(() => {
|
|
152
|
+
setAdding(false)
|
|
153
|
+
})
|
|
154
|
+
.catch(() => undefined)
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
|
|
101
159
|
{view === 'calendar' && (
|
|
102
160
|
<div className="mb-4 flex items-center gap-2">
|
|
103
161
|
<button
|
|
@@ -131,47 +189,219 @@ export function PlanningPage(): React.JSX.Element {
|
|
|
131
189
|
) : items.isLoading ? (
|
|
132
190
|
<LoadingPanel label="Loading plan…" />
|
|
133
191
|
) : view === 'list' ? (
|
|
134
|
-
|
|
192
|
+
<>
|
|
193
|
+
<Paginator list={items} placement="top" />
|
|
194
|
+
<PlanList items={items.records} targetNames={targets.nameById} />
|
|
195
|
+
</>
|
|
135
196
|
) : (
|
|
136
197
|
<PlanCalendar year={month.year} month={month.month} items={items.records} />
|
|
137
198
|
)}
|
|
138
199
|
|
|
139
|
-
{
|
|
140
|
-
<button
|
|
141
|
-
type="button"
|
|
142
|
-
onClick={items.loadMore}
|
|
143
|
-
disabled={items.isLoadingMore}
|
|
144
|
-
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"
|
|
145
|
-
>
|
|
146
|
-
{items.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
147
|
-
</button>
|
|
148
|
-
)}
|
|
200
|
+
{view === 'list' && <Paginator list={items} />}
|
|
149
201
|
</div>
|
|
150
202
|
)
|
|
151
203
|
}
|
|
152
204
|
|
|
153
205
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* The directory is one page per pipeline, matching the Deal contacts precedent:
|
|
157
|
-
* past it a row still says what kind of record it belongs to.
|
|
206
|
+
* Pipeline records the add form can attach to, and the name map the list uses
|
|
207
|
+
* for PlanTargetLink. One page per kind, same ceiling as the rest of Planning.
|
|
158
208
|
*/
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
209
|
+
function usePipelineTargets(): {
|
|
210
|
+
readonly byKind: Readonly<Record<PipelineKind, readonly PipelineTarget[]>>
|
|
211
|
+
readonly nameById: ReadonlyMap<string, string>
|
|
212
|
+
} {
|
|
213
|
+
const deals = useDeals({ limit: MAX_PAGE_SIZE })
|
|
214
|
+
const opportunities = useOpportunities({ limit: MAX_PAGE_SIZE })
|
|
215
|
+
const raises = useRaises({ limit: MAX_PAGE_SIZE })
|
|
216
|
+
const partnerships = usePartnerships({ limit: MAX_PAGE_SIZE })
|
|
217
|
+
const enquiries = useEnquiries({ limit: MAX_PAGE_SIZE })
|
|
218
|
+
|
|
219
|
+
const byKind: Readonly<Record<PipelineKind, readonly PipelineTarget[]>> = {
|
|
220
|
+
enquiry: enquiries.records.map((record) => ({ id: record.id, name: record.name })),
|
|
221
|
+
deal: deals.records.map((record) => ({ id: record.id, name: record.name })),
|
|
222
|
+
opportunity: opportunities.records.map((record) => ({ id: record.id, name: record.name })),
|
|
223
|
+
raise: raises.records.map((record) => ({ id: record.id, name: record.name })),
|
|
224
|
+
partnership: partnerships.records.map((record) => ({ id: record.id, name: record.name })),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
byKind,
|
|
229
|
+
nameById: new Map(
|
|
230
|
+
Object.values(byKind)
|
|
231
|
+
.flat()
|
|
232
|
+
.map((record) => [record.id, record.name]),
|
|
168
233
|
),
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
interface AddPlanItemFields {
|
|
238
|
+
readonly targetType: PipelineKind
|
|
239
|
+
readonly targetId: string
|
|
240
|
+
readonly date: string
|
|
241
|
+
readonly title: string
|
|
242
|
+
readonly ownerId: string
|
|
243
|
+
readonly status: PlanItemStatus
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function AddPlanItemForm({
|
|
247
|
+
initialTargetType,
|
|
248
|
+
targetsByKind,
|
|
249
|
+
isPending,
|
|
250
|
+
error,
|
|
251
|
+
onSubmit,
|
|
252
|
+
onCancel,
|
|
253
|
+
}: {
|
|
254
|
+
readonly initialTargetType: PipelineKind
|
|
255
|
+
readonly targetsByKind: Readonly<Record<PipelineKind, readonly PipelineTarget[]>>
|
|
256
|
+
readonly isPending: boolean
|
|
257
|
+
readonly error: Error | null
|
|
258
|
+
readonly onSubmit: (fields: AddPlanItemFields) => void
|
|
259
|
+
readonly onCancel: () => void
|
|
260
|
+
}): React.JSX.Element {
|
|
261
|
+
const members = useMembers()
|
|
262
|
+
const [draft, setDraft] = useState<AddPlanItemFields>({
|
|
263
|
+
targetType: initialTargetType,
|
|
264
|
+
targetId: '',
|
|
265
|
+
date: todayIso(),
|
|
266
|
+
title: '',
|
|
267
|
+
ownerId: '',
|
|
268
|
+
status: 'todo',
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
const targets = targetsByKind[draft.targetType]
|
|
272
|
+
|
|
273
|
+
function submit(event: FormEvent): void {
|
|
274
|
+
event.preventDefault()
|
|
275
|
+
|
|
276
|
+
const title = draft.title.trim()
|
|
277
|
+
|
|
278
|
+
if (title.length === 0 || draft.date.length === 0 || draft.targetId.length === 0) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
onSubmit({ ...draft, title })
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<form onSubmit={submit} className="mb-4 space-y-3 rounded-md border border-border p-3">
|
|
287
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-[11rem_minmax(0,1fr)]">
|
|
288
|
+
<select
|
|
289
|
+
value={draft.targetType}
|
|
290
|
+
onChange={(event) => {
|
|
291
|
+
const targetType = event.target.value as PipelineKind
|
|
292
|
+
|
|
293
|
+
setDraft((current) => ({ ...current, targetType, targetId: '' }))
|
|
294
|
+
}}
|
|
295
|
+
aria-label="Record type"
|
|
296
|
+
className={inputClass}
|
|
297
|
+
>
|
|
298
|
+
{PIPELINE_KINDS.map((kind) => (
|
|
299
|
+
<option key={kind} value={kind}>
|
|
300
|
+
{PIPELINE_KIND_LABELS[kind]}
|
|
301
|
+
</option>
|
|
302
|
+
))}
|
|
303
|
+
</select>
|
|
304
|
+
<select
|
|
305
|
+
value={draft.targetId}
|
|
306
|
+
onChange={(event) => {
|
|
307
|
+
setDraft((current) => ({ ...current, targetId: event.target.value }))
|
|
308
|
+
}}
|
|
309
|
+
required
|
|
310
|
+
aria-label="Record"
|
|
311
|
+
className={inputClass}
|
|
312
|
+
>
|
|
313
|
+
<option value="">{targets.length === 0 ? 'No records' : 'Select…'}</option>
|
|
314
|
+
{targets.map((target) => (
|
|
315
|
+
<option key={target.id} value={target.id}>
|
|
316
|
+
{target.name}
|
|
317
|
+
</option>
|
|
318
|
+
))}
|
|
319
|
+
</select>
|
|
320
|
+
|
|
321
|
+
<input
|
|
322
|
+
type="date"
|
|
323
|
+
value={draft.date}
|
|
324
|
+
onChange={(event) => {
|
|
325
|
+
setDraft((current) => ({ ...current, date: event.target.value }))
|
|
326
|
+
}}
|
|
327
|
+
required
|
|
328
|
+
aria-label="Date"
|
|
329
|
+
className={inputClass}
|
|
330
|
+
/>
|
|
331
|
+
<input
|
|
332
|
+
value={draft.title}
|
|
333
|
+
onChange={(event) => {
|
|
334
|
+
setDraft((current) => ({ ...current, title: event.target.value }))
|
|
335
|
+
}}
|
|
336
|
+
placeholder="What happens…"
|
|
337
|
+
required
|
|
338
|
+
autoFocus
|
|
339
|
+
aria-label="What happens"
|
|
340
|
+
className={inputClass}
|
|
341
|
+
/>
|
|
342
|
+
|
|
343
|
+
<select
|
|
344
|
+
value={draft.ownerId}
|
|
345
|
+
onChange={(event) => {
|
|
346
|
+
setDraft((current) => ({ ...current, ownerId: event.target.value }))
|
|
347
|
+
}}
|
|
348
|
+
aria-label="Owner"
|
|
349
|
+
className={inputClass}
|
|
350
|
+
>
|
|
351
|
+
<option value="">Unassigned</option>
|
|
352
|
+
{members.members.map((member) => (
|
|
353
|
+
<option key={member.id} value={member.id}>
|
|
354
|
+
{member.name}
|
|
355
|
+
</option>
|
|
356
|
+
))}
|
|
357
|
+
</select>
|
|
358
|
+
<select
|
|
359
|
+
value={draft.status}
|
|
360
|
+
onChange={(event) => {
|
|
361
|
+
setDraft((current) => ({
|
|
362
|
+
...current,
|
|
363
|
+
status: event.target.value as PlanItemStatus,
|
|
364
|
+
}))
|
|
365
|
+
}}
|
|
366
|
+
aria-label="Status"
|
|
367
|
+
className={inputClass}
|
|
368
|
+
>
|
|
369
|
+
{PLAN_ITEM_STATUSES.map((status) => (
|
|
370
|
+
<option key={status} value={status}>
|
|
371
|
+
{PLAN_ITEM_STATUS_LABELS[status]}
|
|
372
|
+
</option>
|
|
373
|
+
))}
|
|
374
|
+
</select>
|
|
375
|
+
</div>
|
|
376
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
377
|
+
<div className="flex justify-end gap-2">
|
|
378
|
+
<button
|
|
379
|
+
type="button"
|
|
380
|
+
onClick={onCancel}
|
|
381
|
+
className="rounded-md px-2.5 py-1.5 text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
382
|
+
>
|
|
383
|
+
Cancel
|
|
384
|
+
</button>
|
|
385
|
+
<button
|
|
386
|
+
type="submit"
|
|
387
|
+
disabled={isPending || targets.length === 0}
|
|
388
|
+
className="h-9 rounded-md bg-accent px-3 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
|
|
389
|
+
>
|
|
390
|
+
{isPending ? 'Adding…' : 'Add'}
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
</form>
|
|
169
394
|
)
|
|
170
395
|
}
|
|
171
396
|
|
|
172
|
-
function PlanList({
|
|
397
|
+
function PlanList({
|
|
398
|
+
items,
|
|
399
|
+
targetNames,
|
|
400
|
+
}: {
|
|
401
|
+
readonly items: readonly PlanItem[]
|
|
402
|
+
readonly targetNames: ReadonlyMap<string, string>
|
|
403
|
+
}): React.JSX.Element {
|
|
173
404
|
const members = useMembers()
|
|
174
|
-
const targetNames = useTargetNames()
|
|
175
405
|
|
|
176
406
|
if (items.length === 0) {
|
|
177
407
|
return <p className="text-[13px] text-ink-faint">No plan items match this filter.</p>
|
|
@@ -18,7 +18,10 @@ import type { ChipTone } from '../components/Chip.tsx'
|
|
|
18
18
|
import { DecisionsPanel } from '../components/DecisionsPanel.tsx'
|
|
19
19
|
import { DeleteRecord } from '../components/DeleteRecord.tsx'
|
|
20
20
|
import { EntitySearch } from '../components/EntitySearch.tsx'
|
|
21
|
+
import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
22
|
+
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
21
23
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
24
|
+
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
22
25
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
23
26
|
import { PlanAttention } from '../components/PlanAttention.tsx'
|
|
24
27
|
import { PlanPanel } from '../components/PlanPanel.tsx'
|
|
@@ -59,6 +62,7 @@ export function RaiseDetail(): React.JSX.Element {
|
|
|
59
62
|
const { record, isLoading, isNotFound, error } = useRaise(id)
|
|
60
63
|
const deleteRaise = useDeleteRaise()
|
|
61
64
|
const moduleTabs = inSlotOrder(useRecordTabs('raise'))
|
|
65
|
+
const hasCustomFields = useHasCustomFields('raise')
|
|
62
66
|
const [activeTab, setActiveTab] = useState('overview')
|
|
63
67
|
|
|
64
68
|
if (isNotFound) {
|
|
@@ -75,10 +79,12 @@ export function RaiseDetail(): React.JSX.Element {
|
|
|
75
79
|
|
|
76
80
|
const tabs: readonly RecordTabDescriptor<string>[] = [
|
|
77
81
|
{ id: 'overview', label: 'Overview' },
|
|
82
|
+
...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
|
|
78
83
|
{ id: 'plan', label: 'Plan' },
|
|
79
84
|
{ id: 'activity', label: 'Activity' },
|
|
80
85
|
{ id: 'notes', label: 'Notes' },
|
|
81
86
|
{ id: 'decisions', label: 'Decisions' },
|
|
87
|
+
{ id: 'lists', label: 'Lists' },
|
|
82
88
|
...moduleTabs.map((tab) => ({ id: tab.id, label: tab.label })),
|
|
83
89
|
]
|
|
84
90
|
const active = tabs.some((tab) => tab.id === activeTab) ? activeTab : 'overview'
|
|
@@ -115,10 +121,12 @@ export function RaiseDetail(): React.JSX.Element {
|
|
|
115
121
|
|
|
116
122
|
<RecordTabs tabs={tabs} active={active} onChange={setActiveTab} ariaLabel="Raise sections">
|
|
117
123
|
{active === 'overview' && <RaiseOverview raise={record} />}
|
|
124
|
+
{active === 'fields' && <RaiseFields raise={record} />}
|
|
118
125
|
{active === 'plan' && <PlanPanel targetType="raise" targetId={record.id} />}
|
|
119
126
|
{active === 'activity' && <ActivitiesPanel targetType="raise" targetId={record.id} />}
|
|
120
127
|
{active === 'notes' && <NotesPanel targetType="raise" targetId={record.id} />}
|
|
121
128
|
{active === 'decisions' && <DecisionsPanel targetType="raise" targetId={record.id} />}
|
|
129
|
+
{active === 'lists' && <ListsPanel targetType="raise" targetId={record.id} />}
|
|
122
130
|
{moduleTab?.render({ objectType: 'raise', recordId: record.id })}
|
|
123
131
|
</RecordTabs>
|
|
124
132
|
</div>
|
|
@@ -381,6 +389,22 @@ function RaiseSidebar({ raise }: { readonly raise: Raise }): React.JSX.Element {
|
|
|
381
389
|
)
|
|
382
390
|
}
|
|
383
391
|
|
|
392
|
+
function RaiseFields({ raise }: { readonly raise: Raise }): React.JSX.Element {
|
|
393
|
+
const { patch, error } = useRaisePatch(raise)
|
|
394
|
+
return (
|
|
395
|
+
<div className="space-y-4">
|
|
396
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
397
|
+
<CustomFieldsPanel
|
|
398
|
+
objectType="raise"
|
|
399
|
+
values={raise.customFields}
|
|
400
|
+
onPatch={(customFields) => {
|
|
401
|
+
patch({ customFields })
|
|
402
|
+
}}
|
|
403
|
+
/>
|
|
404
|
+
</div>
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
|
|
384
408
|
/** The key people on the raise, linked and unlinked by replacing `person_ids`. */
|
|
385
409
|
function RaiseKeyPeople({ raise }: { readonly raise: Raise }): React.JSX.Element {
|
|
386
410
|
const { patch, error } = useRaisePatch(raise)
|
package/src/pages/RoleDetail.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { Chip } from '../components/Chip.tsx'
|
|
|
15
15
|
import { DeleteRecord } from '../components/DeleteRecord.tsx'
|
|
16
16
|
import { EntitySearch } from '../components/EntitySearch.tsx'
|
|
17
17
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
18
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
18
19
|
import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
|
|
19
20
|
import { SectionHeader } from '../components/SectionHeader.tsx'
|
|
20
21
|
import {
|
|
@@ -240,34 +241,28 @@ function RoleCandidates({ role }: { readonly role: Role }): React.JSX.Element {
|
|
|
240
241
|
) : candidates.records.length === 0 && !adding ? (
|
|
241
242
|
<p className="text-[13px] text-ink-faint">No candidates yet.</p>
|
|
242
243
|
) : (
|
|
243
|
-
|
|
244
|
-
{candidates
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
candidate.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
244
|
+
<>
|
|
245
|
+
<Paginator list={candidates} placement="top" />
|
|
246
|
+
<ul className="rounded-md border border-border">
|
|
247
|
+
{candidates.records.map((candidate) => (
|
|
248
|
+
<CandidateRow
|
|
249
|
+
key={candidate.id}
|
|
250
|
+
candidate={candidate}
|
|
251
|
+
personName={names.nameFor(candidate.personId)}
|
|
252
|
+
referrerName={
|
|
253
|
+
candidate.referrerPersonId === null
|
|
254
|
+
? undefined
|
|
255
|
+
: names.nameFor(candidate.referrerPersonId)
|
|
256
|
+
}
|
|
257
|
+
note={notes.noteFor(candidate.id)}
|
|
258
|
+
noteResolved={notes.isComplete || notes.noteFor(candidate.id) !== undefined}
|
|
259
|
+
/>
|
|
260
|
+
))}
|
|
261
|
+
</ul>
|
|
262
|
+
</>
|
|
259
263
|
)}
|
|
260
264
|
|
|
261
|
-
{candidates
|
|
262
|
-
<button
|
|
263
|
-
type="button"
|
|
264
|
-
onClick={candidates.loadMore}
|
|
265
|
-
disabled={candidates.isLoadingMore}
|
|
266
|
-
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"
|
|
267
|
-
>
|
|
268
|
-
{candidates.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
269
|
-
</button>
|
|
270
|
-
)}
|
|
265
|
+
<Paginator list={candidates} />
|
|
271
266
|
</section>
|
|
272
267
|
)
|
|
273
268
|
}
|
package/src/pages/SearchPage.tsx
CHANGED
|
@@ -19,6 +19,7 @@ const COLLECTIONS: Readonly<Record<SearchCollection, { label: string; path: (id:
|
|
|
19
19
|
person: { label: 'People', path: (id) => `/people/${id}` },
|
|
20
20
|
role: { label: 'Roles', path: (id) => `/hiring/${id}` },
|
|
21
21
|
company: { label: 'Companies', path: (id) => `/companies/${id}` },
|
|
22
|
+
enquiry: { label: 'Enquiries', path: (id) => `/enquiries/${id}` },
|
|
22
23
|
deal: { label: 'Deals', path: (id) => `/deals/${id}` },
|
|
23
24
|
opportunity: { label: 'Opportunities', path: (id) => `/opportunities/${id}` },
|
|
24
25
|
raise: { label: 'Fundraising', path: (id) => `/fundraising/${id}` },
|