@kelpie/ui 0.12.0 → 0.14.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/resources/attendances.d.ts +13 -0
- package/dist/api/resources/attendances.d.ts.map +1 -0
- package/dist/api/resources/attendances.js +34 -0
- package/dist/api/resources/attendances.js.map +1 -0
- package/dist/api/resources/eventAssociations.d.ts +12 -0
- package/dist/api/resources/eventAssociations.d.ts.map +1 -0
- package/dist/api/resources/eventAssociations.js +25 -0
- package/dist/api/resources/eventAssociations.js.map +1 -0
- package/dist/api/resources/events.d.ts +17 -0
- package/dist/api/resources/events.d.ts.map +1 -0
- package/dist/api/resources/events.js +40 -0
- package/dist/api/resources/events.js.map +1 -0
- package/dist/api/resources/handbookSeed.d.ts +11 -0
- package/dist/api/resources/handbookSeed.d.ts.map +1 -0
- package/dist/api/resources/handbookSeed.js +43 -0
- package/dist/api/resources/handbookSeed.js.map +1 -0
- package/dist/api/resources/moduleSettings.d.ts +10 -0
- package/dist/api/resources/moduleSettings.d.ts.map +1 -1
- package/dist/api/resources/moduleSettings.js +21 -0
- package/dist/api/resources/moduleSettings.js.map +1 -1
- package/dist/api/resources/planItems.d.ts +4 -4
- package/dist/api/resources/planItems.d.ts.map +1 -1
- package/dist/api/resources/planItems.js +1 -1
- package/dist/api/resources/planItems.js.map +1 -1
- package/dist/api/resources/publicConfig.js +3 -3
- package/dist/api/resources/sampleData.d.ts.map +1 -1
- package/dist/api/resources/sampleData.js +2 -0
- package/dist/api/resources/sampleData.js.map +1 -1
- package/dist/api/resources/session.d.ts +22 -1
- package/dist/api/resources/session.d.ts.map +1 -1
- package/dist/api/resources/session.js +43 -1
- package/dist/api/resources/session.js.map +1 -1
- package/dist/api/resources/workspace.d.ts.map +1 -1
- package/dist/api/resources/workspace.js +2 -3
- package/dist/api/resources/workspace.js.map +1 -1
- package/dist/app/KelpieApp.d.ts.map +1 -1
- package/dist/app/KelpieApp.js +5 -1
- package/dist/app/KelpieApp.js.map +1 -1
- package/dist/components/ActivitiesPanel.d.ts.map +1 -1
- package/dist/components/ActivitiesPanel.js +2 -0
- package/dist/components/ActivitiesPanel.js.map +1 -1
- package/dist/components/AddressesField.d.ts +23 -0
- package/dist/components/AddressesField.d.ts.map +1 -0
- package/dist/components/AddressesField.js +215 -0
- package/dist/components/AddressesField.js.map +1 -0
- package/dist/components/CountrySearch.d.ts +16 -0
- package/dist/components/CountrySearch.d.ts.map +1 -0
- package/dist/components/CountrySearch.js +30 -0
- package/dist/components/CountrySearch.js.map +1 -0
- package/dist/components/EnvironmentBanner.d.ts +0 -12
- package/dist/components/EnvironmentBanner.d.ts.map +1 -1
- package/dist/components/EnvironmentBanner.js +21 -6
- package/dist/components/EnvironmentBanner.js.map +1 -1
- package/dist/components/InlineEdit.d.ts +7 -1
- package/dist/components/InlineEdit.d.ts.map +1 -1
- package/dist/components/InlineEdit.js +8 -1
- package/dist/components/InlineEdit.js.map +1 -1
- package/dist/components/LinkedEventsPanel.d.ts +11 -0
- package/dist/components/LinkedEventsPanel.d.ts.map +1 -0
- package/dist/components/LinkedEventsPanel.js +18 -0
- package/dist/components/LinkedEventsPanel.js.map +1 -0
- package/dist/components/NotesPanel.d.ts +4 -6
- package/dist/components/NotesPanel.d.ts.map +1 -1
- package/dist/components/NotesPanel.js +39 -3
- package/dist/components/NotesPanel.js.map +1 -1
- package/dist/components/PlanAttention.d.ts.map +1 -1
- package/dist/components/PlanAttention.js +3 -2
- package/dist/components/PlanAttention.js.map +1 -1
- package/dist/components/PlanPanel.d.ts +3 -3
- package/dist/components/PlanPanel.d.ts.map +1 -1
- package/dist/components/Shell.d.ts.map +1 -1
- package/dist/components/Shell.js +8 -2
- package/dist/components/Shell.js.map +1 -1
- package/dist/components/SocialNetworkIcon.d.ts +17 -0
- package/dist/components/SocialNetworkIcon.d.ts.map +1 -0
- package/dist/components/SocialNetworkIcon.js +39 -0
- package/dist/components/SocialNetworkIcon.js.map +1 -0
- package/dist/components/SocialProfilesField.d.ts +8 -0
- package/dist/components/SocialProfilesField.d.ts.map +1 -1
- package/dist/components/SocialProfilesField.js +19 -5
- package/dist/components/SocialProfilesField.js.map +1 -1
- package/dist/components/TimezoneSearch.d.ts +21 -0
- package/dist/components/TimezoneSearch.d.ts.map +1 -0
- package/dist/components/TimezoneSearch.js +27 -0
- package/dist/components/TimezoneSearch.js.map +1 -0
- package/dist/components/WorkspaceSwitcher.d.ts +8 -0
- package/dist/components/WorkspaceSwitcher.d.ts.map +1 -0
- package/dist/components/WorkspaceSwitcher.js +67 -0
- package/dist/components/WorkspaceSwitcher.js.map +1 -0
- package/dist/components/environmentTone.d.ts +10 -0
- package/dist/components/environmentTone.d.ts.map +1 -0
- package/dist/components/environmentTone.js +35 -0
- package/dist/components/environmentTone.js.map +1 -0
- package/dist/index.d.ts +21 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/timezones.d.ts +17 -0
- package/dist/lib/timezones.d.ts.map +1 -0
- package/dist/lib/timezones.js +39 -0
- package/dist/lib/timezones.js.map +1 -0
- package/dist/pages/CompaniesPage.d.ts.map +1 -1
- package/dist/pages/CompaniesPage.js +12 -11
- package/dist/pages/CompaniesPage.js.map +1 -1
- package/dist/pages/CompanyDetail.d.ts.map +1 -1
- package/dist/pages/CompanyDetail.js +23 -12
- package/dist/pages/CompanyDetail.js.map +1 -1
- package/dist/pages/DealDetail.d.ts.map +1 -1
- package/dist/pages/DealDetail.js +5 -1
- package/dist/pages/DealDetail.js.map +1 -1
- package/dist/pages/DecisionsPage.d.ts.map +1 -1
- package/dist/pages/DecisionsPage.js +7 -0
- package/dist/pages/DecisionsPage.js.map +1 -1
- package/dist/pages/EnquiryDetail.d.ts.map +1 -1
- package/dist/pages/EnquiryDetail.js +5 -1
- package/dist/pages/EnquiryDetail.js.map +1 -1
- package/dist/pages/EventDetail.d.ts +2 -0
- package/dist/pages/EventDetail.d.ts.map +1 -0
- package/dist/pages/EventDetail.js +332 -0
- package/dist/pages/EventDetail.js.map +1 -0
- package/dist/pages/EventsPage.d.ts +2 -0
- package/dist/pages/EventsPage.d.ts.map +1 -0
- package/dist/pages/EventsPage.js +176 -0
- package/dist/pages/EventsPage.js.map +1 -0
- package/dist/pages/ListDetail.d.ts.map +1 -1
- package/dist/pages/ListDetail.js +17 -0
- package/dist/pages/ListDetail.js.map +1 -1
- package/dist/pages/OpportunityDetail.d.ts.map +1 -1
- package/dist/pages/OpportunityDetail.js +5 -1
- package/dist/pages/OpportunityDetail.js.map +1 -1
- package/dist/pages/PartnershipDetail.d.ts.map +1 -1
- package/dist/pages/PartnershipDetail.js +5 -1
- package/dist/pages/PartnershipDetail.js.map +1 -1
- package/dist/pages/PeoplePage.d.ts.map +1 -1
- package/dist/pages/PeoplePage.js +13 -5
- package/dist/pages/PeoplePage.js.map +1 -1
- package/dist/pages/PersonDetail.d.ts.map +1 -1
- package/dist/pages/PersonDetail.js +94 -28
- package/dist/pages/PersonDetail.js.map +1 -1
- package/dist/pages/PlanningPage.d.ts.map +1 -1
- package/dist/pages/PlanningPage.js +6 -3
- package/dist/pages/PlanningPage.js.map +1 -1
- package/dist/pages/RaiseDetail.d.ts.map +1 -1
- package/dist/pages/RaiseDetail.js +5 -1
- package/dist/pages/RaiseDetail.js.map +1 -1
- package/dist/pages/RoleDetail.d.ts.map +1 -1
- package/dist/pages/RoleDetail.js +4 -1
- package/dist/pages/RoleDetail.js.map +1 -1
- package/dist/pages/SearchPage.d.ts.map +1 -1
- package/dist/pages/SearchPage.js +2 -1
- package/dist/pages/SearchPage.js.map +1 -1
- package/dist/pages/account/PreferencesPage.d.ts.map +1 -1
- package/dist/pages/account/PreferencesPage.js +2 -28
- package/dist/pages/account/PreferencesPage.js.map +1 -1
- package/dist/pages/admin/DataPage.js +10 -9
- package/dist/pages/admin/DataPage.js.map +1 -1
- package/dist/pages/admin/FieldsPage.js +1 -1
- package/dist/pages/admin/FieldsPage.js.map +1 -1
- package/dist/pages/admin/ModulesPage.d.ts.map +1 -1
- package/dist/pages/admin/ModulesPage.js +1 -0
- package/dist/pages/admin/ModulesPage.js.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.d.ts +12 -0
- package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.js +2 -21
- package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/auth/AuthLayout.d.ts +5 -4
- package/dist/pages/auth/AuthLayout.d.ts.map +1 -1
- package/dist/pages/auth/AuthLayout.js +7 -5
- package/dist/pages/auth/AuthLayout.js.map +1 -1
- package/dist/pages/auth/RegionSwitcher.d.ts +10 -0
- package/dist/pages/auth/RegionSwitcher.d.ts.map +1 -0
- package/dist/pages/auth/RegionSwitcher.js +28 -0
- package/dist/pages/auth/RegionSwitcher.js.map +1 -0
- package/dist/pages/auth/SignInPage.d.ts.map +1 -1
- package/dist/pages/auth/SignInPage.js +3 -1
- package/dist/pages/auth/SignInPage.js.map +1 -1
- package/dist/pages/auth/SignUpPage.d.ts.map +1 -1
- package/dist/pages/auth/SignUpPage.js +7 -0
- package/dist/pages/auth/SignUpPage.js.map +1 -1
- package/dist/pages/auth/regionHref.d.ts +6 -0
- package/dist/pages/auth/regionHref.d.ts.map +1 -0
- package/dist/pages/auth/regionHref.js +8 -0
- package/dist/pages/auth/regionHref.js.map +1 -0
- package/dist/pages/dashboard/DashboardPage.d.ts +8 -0
- package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -1
- package/dist/pages/dashboard/DashboardPage.js +5 -13
- package/dist/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/pages/dashboard/attention.d.ts +1 -8
- package/dist/pages/dashboard/attention.d.ts.map +1 -1
- package/dist/pages/dashboard/attention.js +21 -54
- package/dist/pages/dashboard/attention.js.map +1 -1
- package/dist/pages/dashboard/sections.d.ts.map +1 -1
- package/dist/pages/dashboard/sections.js +3 -2
- package/dist/pages/dashboard/sections.js.map +1 -1
- package/dist/pages/forms/FormActions.d.ts.map +1 -1
- package/dist/pages/forms/FormActions.js +7 -3
- package/dist/pages/forms/FormActions.js.map +1 -1
- package/dist/pages/onboarding/HandbookStepPage.d.ts +4 -8
- package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/HandbookStepPage.js +13 -13
- package/dist/pages/onboarding/HandbookStepPage.js.map +1 -1
- package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/InvitesStepPage.js +13 -7
- package/dist/pages/onboarding/InvitesStepPage.js.map +1 -1
- package/dist/pages/onboarding/ModulesStepPage.d.ts +11 -0
- package/dist/pages/onboarding/ModulesStepPage.d.ts.map +1 -0
- package/dist/pages/onboarding/ModulesStepPage.js +80 -0
- package/dist/pages/onboarding/ModulesStepPage.js.map +1 -0
- package/dist/pages/onboarding/OnboardingNav.d.ts +17 -0
- package/dist/pages/onboarding/OnboardingNav.d.ts.map +1 -0
- package/dist/pages/onboarding/OnboardingNav.js +37 -0
- package/dist/pages/onboarding/OnboardingNav.js.map +1 -0
- package/dist/pages/onboarding/OrganisationStepPage.d.ts +13 -0
- package/dist/pages/onboarding/OrganisationStepPage.d.ts.map +1 -0
- package/dist/pages/onboarding/OrganisationStepPage.js +59 -0
- package/dist/pages/onboarding/OrganisationStepPage.js.map +1 -0
- package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/WorkspaceStepPage.js +25 -20
- package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
- package/dist/pages/onboarding/handbookTemplates.d.ts +19 -0
- package/dist/pages/onboarding/handbookTemplates.d.ts.map +1 -0
- package/dist/pages/onboarding/handbookTemplates.js +57 -0
- package/dist/pages/onboarding/handbookTemplates.js.map +1 -0
- package/dist/pages/onboarding/onboardingModules.d.ts +23 -0
- package/dist/pages/onboarding/onboardingModules.d.ts.map +1 -0
- package/dist/pages/onboarding/onboardingModules.js +153 -0
- package/dist/pages/onboarding/onboardingModules.js.map +1 -0
- package/dist/pages/onboarding/onboardingRerun.d.ts +21 -0
- package/dist/pages/onboarding/onboardingRerun.d.ts.map +1 -0
- package/dist/pages/onboarding/onboardingRerun.js +33 -0
- package/dist/pages/onboarding/onboardingRerun.js.map +1 -0
- package/package.json +4 -3
- package/src/api/resources/attendances.ts +54 -0
- package/src/api/resources/eventAssociations.ts +43 -0
- package/src/api/resources/events.ts +70 -0
- package/src/api/resources/handbookSeed.ts +63 -0
- package/src/api/resources/moduleSettings.ts +25 -0
- package/src/api/resources/planItems.ts +5 -5
- package/src/api/resources/publicConfig.ts +3 -3
- package/src/api/resources/sampleData.ts +2 -0
- package/src/api/resources/session.ts +59 -0
- package/src/api/resources/workspace.ts +2 -3
- package/src/app/KelpieApp.tsx +11 -3
- package/src/components/ActivitiesPanel.tsx +2 -0
- package/src/components/AddressesField.tsx +502 -0
- package/src/components/CountrySearch.tsx +71 -0
- package/src/components/EnvironmentBanner.tsx +23 -6
- package/src/components/InlineEdit.tsx +35 -1
- package/src/components/LinkedEventsPanel.tsx +50 -0
- package/src/components/NotesPanel.tsx +132 -15
- package/src/components/PlanAttention.tsx +5 -4
- package/src/components/PlanPanel.tsx +3 -3
- package/src/components/Shell.tsx +16 -1
- package/src/components/SocialNetworkIcon.tsx +88 -0
- package/src/components/SocialProfilesField.tsx +57 -10
- package/src/components/TimezoneSearch.tsx +67 -0
- package/src/components/WorkspaceSwitcher.tsx +136 -0
- package/src/components/environmentTone.ts +40 -0
- package/src/index.ts +42 -3
- package/src/lib/timezones.ts +43 -0
- package/src/pages/CompaniesPage.tsx +14 -13
- package/src/pages/CompanyDetail.tsx +59 -27
- package/src/pages/DealDetail.tsx +5 -0
- package/src/pages/DecisionsPage.tsx +7 -0
- package/src/pages/EnquiryDetail.tsx +7 -0
- package/src/pages/EventDetail.tsx +794 -0
- package/src/pages/EventsPage.tsx +382 -0
- package/src/pages/ListDetail.tsx +24 -0
- package/src/pages/OpportunityDetail.tsx +7 -0
- package/src/pages/PartnershipDetail.tsx +7 -0
- package/src/pages/PeoplePage.tsx +18 -5
- package/src/pages/PersonDetail.tsx +300 -100
- package/src/pages/PlanningPage.tsx +17 -14
- package/src/pages/RaiseDetail.tsx +5 -0
- package/src/pages/RoleDetail.tsx +8 -0
- package/src/pages/SearchPage.tsx +2 -1
- package/src/pages/account/PreferencesPage.tsx +2 -37
- package/src/pages/admin/DataPage.tsx +63 -21
- package/src/pages/admin/FieldsPage.tsx +1 -1
- package/src/pages/admin/ModulesPage.tsx +1 -0
- package/src/pages/admin/WorkspaceSettingsPage.tsx +10 -33
- package/src/pages/auth/AuthLayout.tsx +8 -4
- package/src/pages/auth/RegionSwitcher.tsx +56 -0
- package/src/pages/auth/SignInPage.tsx +10 -6
- package/src/pages/auth/SignUpPage.tsx +25 -0
- package/src/pages/auth/regionHref.ts +10 -0
- package/src/pages/dashboard/DashboardPage.tsx +16 -48
- package/src/pages/dashboard/attention.ts +24 -78
- package/src/pages/dashboard/sections.tsx +8 -2
- package/src/pages/forms/FormActions.tsx +14 -8
- package/src/pages/onboarding/HandbookStepPage.tsx +21 -24
- package/src/pages/onboarding/InvitesStepPage.tsx +27 -19
- package/src/pages/onboarding/ModulesStepPage.tsx +145 -0
- package/src/pages/onboarding/OnboardingNav.tsx +97 -0
- package/src/pages/onboarding/OrganisationStepPage.tsx +130 -0
- package/src/pages/onboarding/WorkspaceStepPage.tsx +53 -38
- package/src/pages/onboarding/handbookTemplates.ts +77 -0
- package/src/pages/onboarding/onboardingModules.ts +202 -0
- package/src/pages/onboarding/onboardingRerun.ts +41 -0
- package/src/styles.css +30 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ATTENDANCE_STATUS_LABELS, PERSON_ADDRESS_KIND_LABELS, PERSON_ADDRESS_KINDS } from '@kelpie/schemas'
|
|
1
2
|
import { CONSENT_PURPOSE_STATUS_LABELS, IN_PROCESS, PREFERRED_CHANNELS } from '@kelpie/schemas'
|
|
2
3
|
import type {
|
|
3
4
|
Candidate,
|
|
@@ -13,11 +14,14 @@ import { Link, useNavigate, useParams } from 'react-router'
|
|
|
13
14
|
import { usePatch } from '../api/resource.ts'
|
|
14
15
|
import type { PatchResult } from '../api/resource.ts'
|
|
15
16
|
import { useCandidates } from '../api/resources/candidates.ts'
|
|
17
|
+
import { useAttendances, useCreateAttendance } from '../api/resources/attendances.ts'
|
|
18
|
+
import { useEvents } from '../api/resources/events.ts'
|
|
16
19
|
import { useCompanies } from '../api/resources/companies.ts'
|
|
17
20
|
import { useDeals } from '../api/resources/deals.ts'
|
|
18
21
|
import { useEnquiries } from '../api/resources/enquiries.ts'
|
|
19
22
|
import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
|
|
20
23
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
24
|
+
import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
|
|
21
25
|
import { useDeletePerson, usePerson, useUpdatePerson } from '../api/resources/people.ts'
|
|
22
26
|
import {
|
|
23
27
|
useCreatePosition,
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
usePositions,
|
|
26
30
|
useUpdatePositionTitle,
|
|
27
31
|
} from '../api/resources/positions.ts'
|
|
32
|
+
import { AddressSidebarLink, AddressesField } from '../components/AddressesField.tsx'
|
|
28
33
|
import { ActivitiesPanel, LatestActivity } from '../components/ActivitiesPanel.tsx'
|
|
29
34
|
import { AgentTasks } from '../components/AgentTasks.tsx'
|
|
30
35
|
import { Chip } from '../components/Chip.tsx'
|
|
@@ -39,11 +44,13 @@ import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
|
39
44
|
import { RelatedPlanAttention } from '../components/PlanAttention.tsx'
|
|
40
45
|
import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
|
|
41
46
|
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
47
|
+
import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
|
|
42
48
|
import { RecordTabs } from '../components/RecordTabs.tsx'
|
|
43
49
|
import type { RecordTabDescriptor } from '../components/RecordTabs.tsx'
|
|
44
50
|
import { SectionHeader } from '../components/SectionHeader.tsx'
|
|
45
51
|
import { SidebarField } from '../components/SidebarField.tsx'
|
|
46
|
-
import { SocialProfilesField } from '../components/SocialProfilesField.tsx'
|
|
52
|
+
import { SocialProfileIcons, SocialProfilesField } from '../components/SocialProfilesField.tsx'
|
|
53
|
+
import { TimezoneSearch } from '../components/TimezoneSearch.tsx'
|
|
47
54
|
import { PhonesField } from '../components/PhonesField.tsx'
|
|
48
55
|
import { SummaryBlock } from '../components/SummaryBlock.tsx'
|
|
49
56
|
import { useRecordTabs } from '../registry/context.ts'
|
|
@@ -59,14 +66,15 @@ import { usePersonNames, useRoleTitles } from './hiringDirectory.ts'
|
|
|
59
66
|
/**
|
|
60
67
|
* One person.
|
|
61
68
|
*
|
|
62
|
-
* The mockup carried eight tabs. Overview,
|
|
63
|
-
* are here; the remaining three read
|
|
64
|
-
* return with their pages. A UI
|
|
65
|
-
* record-tab slot.
|
|
69
|
+
* The mockup carried eight tabs. Overview, Addresses, Social, Positions,
|
|
70
|
+
* Activity, Notes, Decisions and Hiring are here; the remaining three read
|
|
71
|
+
* Deals, Opportunities or Partnerships, and return with their pages. A UI
|
|
72
|
+
* module can add its own through the `person` record-tab slot.
|
|
66
73
|
*
|
|
67
74
|
* Hiring appears only when this person is up for a role, which is the mockup's
|
|
68
75
|
* rule: a Person carries no hiring fields, so with no candidacy there is nothing
|
|
69
|
-
* for the tab to hold.
|
|
76
|
+
* for the tab to hold. Events appears only when the workspace has that module
|
|
77
|
+
* on: the attendances list answers 403 otherwise.
|
|
70
78
|
*
|
|
71
79
|
* Influence and relationship warmth are not on this page. They are Person
|
|
72
80
|
* columns in the API and fields on the mockup's own `Person` type, but the
|
|
@@ -82,11 +90,20 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
82
90
|
const deletePerson = useDeletePerson()
|
|
83
91
|
const moduleTabs = inSlotOrder(useRecordTabs('person'))
|
|
84
92
|
const hasCustomFields = useHasCustomFields('person')
|
|
93
|
+
const eventsEnabled = useModuleEnabled('events')
|
|
85
94
|
const [activeTab, setActiveTab] = useState('overview')
|
|
86
95
|
const candidacies = useCandidates({ personIds: id === undefined ? [] : [id] }, {
|
|
87
96
|
enabled: id !== undefined,
|
|
88
97
|
})
|
|
98
|
+
const positions = usePositions(
|
|
99
|
+
{ personIds: id === undefined ? [] : [id] },
|
|
100
|
+
{ enabled: id !== undefined },
|
|
101
|
+
)
|
|
89
102
|
const formSubmissions = useFormSubmissionsForRecord('person', id)
|
|
103
|
+
const attendances = useAttendances(
|
|
104
|
+
{ personIds: id === undefined ? [] : [id] },
|
|
105
|
+
{ enabled: id !== undefined && eventsEnabled },
|
|
106
|
+
)
|
|
90
107
|
|
|
91
108
|
if (isNotFound) {
|
|
92
109
|
return <NotFoundPanel label="Person" backTo="/people" />
|
|
@@ -102,11 +119,17 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
102
119
|
|
|
103
120
|
const tabs: readonly RecordTabDescriptor<string>[] = [
|
|
104
121
|
{ id: 'overview', label: 'Overview' },
|
|
122
|
+
{ id: 'addresses', label: 'Addresses', count: record.addresses.length },
|
|
123
|
+
{ id: 'social', label: 'Social', count: record.socialProfiles.length },
|
|
124
|
+
{ id: 'positions', label: 'Positions', count: positions.records.length },
|
|
105
125
|
...(hasCustomFields ? [{ id: 'fields', label: 'Fields' }] : []),
|
|
106
126
|
{ id: 'activity', label: 'Activity' },
|
|
107
127
|
...(candidacies.records.length === 0
|
|
108
128
|
? []
|
|
109
129
|
: [{ id: 'hiring', label: 'Hiring', count: candidacies.records.length }]),
|
|
130
|
+
...(eventsEnabled
|
|
131
|
+
? [{ id: 'events', label: 'Events', count: attendances.records.length }]
|
|
132
|
+
: []),
|
|
110
133
|
{ id: 'notes', label: 'Notes' },
|
|
111
134
|
{ id: 'decisions', label: 'Decisions' },
|
|
112
135
|
{ id: 'lists', label: 'Lists' },
|
|
@@ -129,7 +152,12 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
129
152
|
|
|
130
153
|
<div className="grid items-start gap-8 lg:grid-cols-[minmax(0,1fr)_280px]">
|
|
131
154
|
<div className="min-w-0 space-y-8">
|
|
132
|
-
<PersonHeading
|
|
155
|
+
<PersonHeading
|
|
156
|
+
person={record}
|
|
157
|
+
onOpenPositions={() => {
|
|
158
|
+
setActiveTab('positions')
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
133
161
|
|
|
134
162
|
<div className="flex justify-end gap-2">
|
|
135
163
|
<AgentTasks targetType="person" targetId={record.id} targetLabel={record.name} />
|
|
@@ -149,11 +177,15 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
149
177
|
|
|
150
178
|
<RecordTabs tabs={tabs} active={active} onChange={setActiveTab} ariaLabel="Person sections">
|
|
151
179
|
{active === 'overview' && <PersonOverview person={record} />}
|
|
180
|
+
{active === 'addresses' && <PersonAddresses person={record} />}
|
|
181
|
+
{active === 'social' && <PersonSocial person={record} />}
|
|
182
|
+
{active === 'positions' && <PersonPositions person={record} />}
|
|
152
183
|
{active === 'fields' && <PersonFields person={record} />}
|
|
153
184
|
{active === 'activity' && <ActivitiesPanel targetType="person" targetId={record.id} />}
|
|
154
185
|
{active === 'hiring' && (
|
|
155
186
|
<PersonHiring candidacies={candidacies.records} personName={record.name} />
|
|
156
187
|
)}
|
|
188
|
+
{active === 'events' && <PersonEvents personId={record.id} />}
|
|
157
189
|
{active === 'notes' && <NotesPanel targetType="person" targetId={record.id} />}
|
|
158
190
|
{active === 'decisions' && <DecisionsPanel targetType="person" targetId={record.id} />}
|
|
159
191
|
{active === 'lists' && <ListsPanel targetType="person" targetId={record.id} />}
|
|
@@ -164,9 +196,13 @@ export function PersonDetail(): React.JSX.Element {
|
|
|
164
196
|
|
|
165
197
|
<aside className="space-y-4 text-[12px] lg:sticky lg:top-6">
|
|
166
198
|
<PersonNameParts person={record} />
|
|
167
|
-
<PersonSidebar
|
|
199
|
+
<PersonSidebar
|
|
200
|
+
person={record}
|
|
201
|
+
onOpenAddresses={() => {
|
|
202
|
+
setActiveTab('addresses')
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
168
205
|
<PersonConsents person={record} />
|
|
169
|
-
<PersonPositions person={record} />
|
|
170
206
|
</aside>
|
|
171
207
|
</div>
|
|
172
208
|
</div>
|
|
@@ -178,8 +214,17 @@ function usePersonPatch(person: Person): PatchResult<PersonInput> {
|
|
|
178
214
|
return usePatch(useUpdatePerson, person)
|
|
179
215
|
}
|
|
180
216
|
|
|
181
|
-
function PersonHeading({
|
|
217
|
+
function PersonHeading({
|
|
218
|
+
person,
|
|
219
|
+
onOpenPositions,
|
|
220
|
+
}: {
|
|
221
|
+
readonly person: Person
|
|
222
|
+
readonly onOpenPositions: () => void
|
|
223
|
+
}): React.JSX.Element {
|
|
182
224
|
const { patch, error } = usePersonPatch(person)
|
|
225
|
+
const positions = usePositions({ personIds: [person.id] })
|
|
226
|
+
const companies = useCompanies({ personIds: [person.id] })
|
|
227
|
+
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
183
228
|
|
|
184
229
|
return (
|
|
185
230
|
<div className="min-w-0 flex-1">
|
|
@@ -188,14 +233,50 @@ function PersonHeading({ person }: { readonly person: Person }): React.JSX.Eleme
|
|
|
188
233
|
<ErrorPanel error={error} />
|
|
189
234
|
</div>
|
|
190
235
|
)}
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
236
|
+
<div className="flex items-center gap-3">
|
|
237
|
+
<div className="min-w-0 flex-1">
|
|
238
|
+
<InlineEdit
|
|
239
|
+
value={person.name}
|
|
240
|
+
onChange={(name) => {
|
|
241
|
+
patch({ name })
|
|
242
|
+
}}
|
|
243
|
+
displayClassName="text-[22px] font-semibold tracking-tight text-ink not-italic"
|
|
244
|
+
emptyLabel="Untitled"
|
|
245
|
+
/>
|
|
246
|
+
</div>
|
|
247
|
+
<SocialProfileIcons profiles={person.socialProfiles} />
|
|
248
|
+
</div>
|
|
249
|
+
{positions.records.length > 0 && (
|
|
250
|
+
<ul className="mt-1 space-y-0.5">
|
|
251
|
+
{positions.records.map((position) => {
|
|
252
|
+
const companyName = companyNameById.get(position.companyId) ?? 'Unknown'
|
|
253
|
+
const title = position.title.trim()
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<li key={position.id} className="text-[13px] text-ink-muted">
|
|
257
|
+
{title.length > 0 && (
|
|
258
|
+
<>
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
onClick={onOpenPositions}
|
|
262
|
+
className="font-medium text-accent transition hover:text-accent-hover hover:underline"
|
|
263
|
+
>
|
|
264
|
+
{title}
|
|
265
|
+
</button>
|
|
266
|
+
<span> at </span>
|
|
267
|
+
</>
|
|
268
|
+
)}
|
|
269
|
+
<Link
|
|
270
|
+
to={`/companies/${position.companyId}`}
|
|
271
|
+
className="font-medium text-accent transition hover:text-accent-hover hover:underline"
|
|
272
|
+
>
|
|
273
|
+
{companyName}
|
|
274
|
+
</Link>
|
|
275
|
+
</li>
|
|
276
|
+
)
|
|
277
|
+
})}
|
|
278
|
+
</ul>
|
|
279
|
+
)}
|
|
199
280
|
<div className="mt-1">
|
|
200
281
|
<InlineEdit
|
|
201
282
|
value={person.email ?? ''}
|
|
@@ -207,6 +288,15 @@ function PersonHeading({ person }: { readonly person: Person }): React.JSX.Eleme
|
|
|
207
288
|
emptyLabel="Add email…"
|
|
208
289
|
/>
|
|
209
290
|
</div>
|
|
291
|
+
{person.phones.length > 0 && (
|
|
292
|
+
<ul className="mt-1 space-y-0.5" aria-label="Phone numbers">
|
|
293
|
+
{person.phones.map((phone, index) => (
|
|
294
|
+
<li key={`${phone}-${String(index)}`} className="text-[13px] text-ink-muted">
|
|
295
|
+
{phone}
|
|
296
|
+
</li>
|
|
297
|
+
))}
|
|
298
|
+
</ul>
|
|
299
|
+
)}
|
|
210
300
|
</div>
|
|
211
301
|
)
|
|
212
302
|
}
|
|
@@ -243,6 +333,95 @@ function PersonOverview({ person }: { readonly person: Person }): React.JSX.Elem
|
|
|
243
333
|
)
|
|
244
334
|
}
|
|
245
335
|
|
|
336
|
+
function PersonEvents({ personId }: { readonly personId: string }): React.JSX.Element {
|
|
337
|
+
const attendances = useAttendances({ personIds: [personId], limit: 200 })
|
|
338
|
+
const createAttendance = useCreateAttendance()
|
|
339
|
+
const [eventId, setEventId] = useState('')
|
|
340
|
+
const [search, setSearch] = useState('')
|
|
341
|
+
const searchable = useEvents({
|
|
342
|
+
term: search.trim().length > 0 ? search.trim() : undefined,
|
|
343
|
+
statuses: ['draft', 'scheduled'],
|
|
344
|
+
limit: 50,
|
|
345
|
+
})
|
|
346
|
+
const directory = useEvents({ limit: 200 })
|
|
347
|
+
const nameById = new Map(directory.records.map((record) => [record.id, record.name]))
|
|
348
|
+
const registered = new Set(attendances.records.map((row) => row.eventId))
|
|
349
|
+
|
|
350
|
+
function add(submitEvent: FormEvent): void {
|
|
351
|
+
submitEvent.preventDefault()
|
|
352
|
+
|
|
353
|
+
if (eventId.length === 0 || registered.has(eventId)) {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
createAttendance.run({ eventId, personId, source: 'manual' })
|
|
358
|
+
setEventId('')
|
|
359
|
+
setSearch('')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<div className="space-y-8">
|
|
364
|
+
<section>
|
|
365
|
+
<SectionHeader title="Attendance" />
|
|
366
|
+
{createAttendance.error !== null && (
|
|
367
|
+
<div className="mb-3">
|
|
368
|
+
<ErrorPanel error={createAttendance.error} />
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
{attendances.error !== null && <ErrorPanel error={attendances.error} />}
|
|
372
|
+
{attendances.isLoading ? (
|
|
373
|
+
<p className="text-[13px] text-ink-faint">Loading events…</p>
|
|
374
|
+
) : attendances.records.length === 0 ? (
|
|
375
|
+
<p className="text-[13px] text-ink-faint">Not registered for any events yet.</p>
|
|
376
|
+
) : (
|
|
377
|
+
<ul className="divide-y divide-border overflow-hidden rounded-md border border-border">
|
|
378
|
+
{attendances.records.map((row) => (
|
|
379
|
+
<li key={row.id} className="flex flex-wrap items-center justify-between gap-2 px-3 py-2">
|
|
380
|
+
<Link
|
|
381
|
+
to={`/events/${row.eventId}`}
|
|
382
|
+
className="text-[13px] font-medium text-accent hover:underline"
|
|
383
|
+
>
|
|
384
|
+
{nameById.get(row.eventId) ?? row.eventId}
|
|
385
|
+
</Link>
|
|
386
|
+
<span className="text-[12px] text-ink-muted">
|
|
387
|
+
{ATTENDANCE_STATUS_LABELS[row.status]}
|
|
388
|
+
</span>
|
|
389
|
+
</li>
|
|
390
|
+
))}
|
|
391
|
+
</ul>
|
|
392
|
+
)}
|
|
393
|
+
<form onSubmit={add} className="mt-3 flex flex-wrap items-end gap-2">
|
|
394
|
+
<div className="min-w-[220px] flex-1">
|
|
395
|
+
<EntitySearch
|
|
396
|
+
options={searchable.records
|
|
397
|
+
.filter((record) => !registered.has(record.id))
|
|
398
|
+
.map((record) => ({
|
|
399
|
+
id: record.id,
|
|
400
|
+
label: record.name,
|
|
401
|
+
meta: record.kind.length > 0 ? record.kind : undefined,
|
|
402
|
+
}))}
|
|
403
|
+
value={eventId}
|
|
404
|
+
onChange={setEventId}
|
|
405
|
+
onQueryChange={setSearch}
|
|
406
|
+
placeholder="Register for an event…"
|
|
407
|
+
emptyMessage="No events match"
|
|
408
|
+
size="sm"
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
411
|
+
<button
|
|
412
|
+
type="submit"
|
|
413
|
+
disabled={createAttendance.isPending || eventId.length === 0}
|
|
414
|
+
className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
|
|
415
|
+
>
|
|
416
|
+
Register
|
|
417
|
+
</button>
|
|
418
|
+
</form>
|
|
419
|
+
</section>
|
|
420
|
+
<LinkedEventsPanel targetType="person" targetId={personId} />
|
|
421
|
+
</div>
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
246
425
|
/**
|
|
247
426
|
* Every role this person is up for, one card each.
|
|
248
427
|
*
|
|
@@ -363,7 +542,13 @@ function PersonNameParts({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
363
542
|
)
|
|
364
543
|
}
|
|
365
544
|
|
|
366
|
-
function PersonSidebar({
|
|
545
|
+
function PersonSidebar({
|
|
546
|
+
person,
|
|
547
|
+
onOpenAddresses,
|
|
548
|
+
}: {
|
|
549
|
+
readonly person: Person
|
|
550
|
+
readonly onOpenAddresses: () => void
|
|
551
|
+
}): React.JSX.Element {
|
|
367
552
|
const { patch, error } = usePersonPatch(person)
|
|
368
553
|
|
|
369
554
|
return (
|
|
@@ -384,23 +569,14 @@ function PersonSidebar({ person }: { readonly person: Person }): React.JSX.Eleme
|
|
|
384
569
|
/>
|
|
385
570
|
</SidebarField>
|
|
386
571
|
<SidebarField label="Timezone">
|
|
387
|
-
<
|
|
572
|
+
<TimezoneSearch
|
|
388
573
|
value={person.timezone ?? ''}
|
|
389
574
|
onChange={(timezone) => {
|
|
390
575
|
patch({ timezone: timezone.length > 0 ? timezone : null })
|
|
391
576
|
}}
|
|
392
|
-
displayClassName="not-italic normal-case text-[12px]"
|
|
393
|
-
/>
|
|
394
|
-
</SidebarField>
|
|
395
|
-
<SidebarField label="Location">
|
|
396
|
-
<InlineEdit
|
|
397
|
-
value={person.location ?? ''}
|
|
398
|
-
onChange={(location) => {
|
|
399
|
-
patch({ location: location.length > 0 ? location : null })
|
|
400
|
-
}}
|
|
401
|
-
displayClassName="not-italic normal-case text-[12px]"
|
|
402
577
|
/>
|
|
403
578
|
</SidebarField>
|
|
579
|
+
<AddressSidebarLink addresses={person.addresses} onOpen={onOpenAddresses} />
|
|
404
580
|
<SidebarField label="Phone">
|
|
405
581
|
<PhonesField
|
|
406
582
|
value={person.phones}
|
|
@@ -409,14 +585,6 @@ function PersonSidebar({ person }: { readonly person: Person }): React.JSX.Eleme
|
|
|
409
585
|
}}
|
|
410
586
|
/>
|
|
411
587
|
</SidebarField>
|
|
412
|
-
<SidebarField label="Social profiles">
|
|
413
|
-
<SocialProfilesField
|
|
414
|
-
value={person.socialProfiles}
|
|
415
|
-
onChange={(socialProfiles) => {
|
|
416
|
-
patch({ socialProfiles })
|
|
417
|
-
}}
|
|
418
|
-
/>
|
|
419
|
-
</SidebarField>
|
|
420
588
|
<SidebarField label="Tags">
|
|
421
589
|
<InlineEdit
|
|
422
590
|
value={person.tags.join(', ')}
|
|
@@ -442,6 +610,48 @@ function PersonSidebar({ person }: { readonly person: Person }): React.JSX.Eleme
|
|
|
442
610
|
)
|
|
443
611
|
}
|
|
444
612
|
|
|
613
|
+
function PersonAddresses({ person }: { readonly person: Person }): React.JSX.Element {
|
|
614
|
+
const { patch, error } = usePersonPatch(person)
|
|
615
|
+
|
|
616
|
+
return (
|
|
617
|
+
<div className="max-w-lg space-y-4">
|
|
618
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
619
|
+
<SectionHeader
|
|
620
|
+
title="Addresses"
|
|
621
|
+
description="One address per kind. Mark one as primary for lists and the sidebar."
|
|
622
|
+
/>
|
|
623
|
+
<AddressesField
|
|
624
|
+
value={person.addresses}
|
|
625
|
+
kinds={PERSON_ADDRESS_KINDS}
|
|
626
|
+
kindLabels={PERSON_ADDRESS_KIND_LABELS}
|
|
627
|
+
onChange={(addresses) => {
|
|
628
|
+
patch({ addresses })
|
|
629
|
+
}}
|
|
630
|
+
/>
|
|
631
|
+
</div>
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function PersonSocial({ person }: { readonly person: Person }): React.JSX.Element {
|
|
636
|
+
const { patch, error } = usePersonPatch(person)
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<div className="max-w-lg space-y-4">
|
|
640
|
+
{error !== null && <ErrorPanel error={error} />}
|
|
641
|
+
<SectionHeader
|
|
642
|
+
title="Social profiles"
|
|
643
|
+
description="One profile per network. Icons beside the name open each profile."
|
|
644
|
+
/>
|
|
645
|
+
<SocialProfilesField
|
|
646
|
+
value={person.socialProfiles}
|
|
647
|
+
onChange={(socialProfiles) => {
|
|
648
|
+
patch({ socialProfiles })
|
|
649
|
+
}}
|
|
650
|
+
/>
|
|
651
|
+
</div>
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
|
|
445
655
|
function PersonFields({ person }: { readonly person: Person }): React.JSX.Element {
|
|
446
656
|
const { patch, error } = usePersonPatch(person)
|
|
447
657
|
return (
|
|
@@ -499,69 +709,21 @@ function PersonPositions({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
499
709
|
}
|
|
500
710
|
|
|
501
711
|
return (
|
|
502
|
-
<
|
|
503
|
-
<
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
/>
|
|
512
|
-
</div>
|
|
513
|
-
|
|
514
|
-
{createPosition.error !== null && (
|
|
515
|
-
<div className="px-3.5 py-2">
|
|
516
|
-
<ErrorPanel error={createPosition.error} />
|
|
517
|
-
</div>
|
|
518
|
-
)}
|
|
519
|
-
{deletePosition.error !== null && (
|
|
520
|
-
<div className="px-3.5 py-2">
|
|
521
|
-
<ErrorPanel error={deletePosition.error} />
|
|
522
|
-
</div>
|
|
523
|
-
)}
|
|
712
|
+
<div className="space-y-4">
|
|
713
|
+
<SectionHeader
|
|
714
|
+
title="Positions"
|
|
715
|
+
description="Titles this person holds at companies."
|
|
716
|
+
onAdd={() => {
|
|
717
|
+
setAdding((current) => !current)
|
|
718
|
+
}}
|
|
719
|
+
addLabel="Add position"
|
|
720
|
+
/>
|
|
524
721
|
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
<li className="px-3.5 py-4 text-[12px] text-ink-faint">Loading positions…</li>
|
|
528
|
-
)}
|
|
529
|
-
{!positions.isLoading && positions.records.length === 0 && !adding && (
|
|
530
|
-
<li className="px-3.5 py-4 text-[12px] text-ink-faint">No positions yet.</li>
|
|
531
|
-
)}
|
|
532
|
-
{positions.records.map((position) => (
|
|
533
|
-
<li key={position.id} className="space-y-1 px-3.5 py-2.5">
|
|
534
|
-
<div className="flex items-start justify-between gap-2">
|
|
535
|
-
<Link
|
|
536
|
-
to={`/companies/${position.companyId}`}
|
|
537
|
-
className="text-[13px] font-medium text-ink hover:text-accent"
|
|
538
|
-
>
|
|
539
|
-
{companyNameById.get(position.companyId) ?? 'Unknown'}
|
|
540
|
-
</Link>
|
|
541
|
-
<button
|
|
542
|
-
type="button"
|
|
543
|
-
onClick={() => {
|
|
544
|
-
deletePosition.run(position.id)
|
|
545
|
-
}}
|
|
546
|
-
className="text-[11px] font-medium text-danger hover:underline"
|
|
547
|
-
>
|
|
548
|
-
Remove
|
|
549
|
-
</button>
|
|
550
|
-
</div>
|
|
551
|
-
<InlineEdit
|
|
552
|
-
value={position.title}
|
|
553
|
-
onChange={(next) => {
|
|
554
|
-
updateTitle.run({ id: position.id, changes: { title: next } })
|
|
555
|
-
}}
|
|
556
|
-
displayClassName="not-italic text-[12px]"
|
|
557
|
-
emptyLabel="Add position title…"
|
|
558
|
-
/>
|
|
559
|
-
</li>
|
|
560
|
-
))}
|
|
561
|
-
</ul>
|
|
722
|
+
{createPosition.error !== null && <ErrorPanel error={createPosition.error} />}
|
|
723
|
+
{deletePosition.error !== null && <ErrorPanel error={deletePosition.error} />}
|
|
562
724
|
|
|
563
725
|
{adding && (
|
|
564
|
-
<form onSubmit={submit} className="space-y-2
|
|
726
|
+
<form onSubmit={submit} className="space-y-2 rounded-md border border-border bg-surface p-3">
|
|
565
727
|
<EntitySearch
|
|
566
728
|
options={searchable.records
|
|
567
729
|
.filter((company) => !held.has(company.id))
|
|
@@ -583,7 +745,7 @@ function PersonPositions({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
583
745
|
setTitle(event.target.value)
|
|
584
746
|
}}
|
|
585
747
|
placeholder="Position title (optional)"
|
|
586
|
-
className="w-full rounded-md border border-border bg-surface-raised px-2 py-1.5 text-[
|
|
748
|
+
className="w-full rounded-md border border-border bg-surface-raised px-2 py-1.5 text-[13px] outline-none focus:border-accent"
|
|
587
749
|
/>
|
|
588
750
|
<div className="flex justify-end gap-2">
|
|
589
751
|
<button
|
|
@@ -602,7 +764,45 @@ function PersonPositions({ person }: { readonly person: Person }): React.JSX.Ele
|
|
|
602
764
|
</div>
|
|
603
765
|
</form>
|
|
604
766
|
)}
|
|
605
|
-
|
|
767
|
+
|
|
768
|
+
{positions.isLoading && <p className="text-[13px] text-ink-faint">Loading positions…</p>}
|
|
769
|
+
|
|
770
|
+
{!positions.isLoading && positions.records.length === 0 && !adding ? (
|
|
771
|
+
<p className="text-[13px] text-ink-faint">No positions yet.</p>
|
|
772
|
+
) : (
|
|
773
|
+
<ul className="divide-y divide-border rounded-md border border-border">
|
|
774
|
+
{positions.records.map((position) => (
|
|
775
|
+
<li key={position.id} className="space-y-1 px-3.5 py-3">
|
|
776
|
+
<div className="flex items-start justify-between gap-2">
|
|
777
|
+
<Link
|
|
778
|
+
to={`/companies/${position.companyId}`}
|
|
779
|
+
className="text-[13px] font-medium text-ink hover:text-accent"
|
|
780
|
+
>
|
|
781
|
+
{companyNameById.get(position.companyId) ?? 'Unknown'}
|
|
782
|
+
</Link>
|
|
783
|
+
<button
|
|
784
|
+
type="button"
|
|
785
|
+
onClick={() => {
|
|
786
|
+
deletePosition.run(position.id)
|
|
787
|
+
}}
|
|
788
|
+
className="text-[11px] font-medium text-danger hover:underline"
|
|
789
|
+
>
|
|
790
|
+
Remove
|
|
791
|
+
</button>
|
|
792
|
+
</div>
|
|
793
|
+
<InlineEdit
|
|
794
|
+
value={position.title}
|
|
795
|
+
onChange={(next) => {
|
|
796
|
+
updateTitle.run({ id: position.id, changes: { title: next } })
|
|
797
|
+
}}
|
|
798
|
+
displayClassName="not-italic text-[12px] text-ink-muted"
|
|
799
|
+
emptyLabel="Add position title…"
|
|
800
|
+
/>
|
|
801
|
+
</li>
|
|
802
|
+
))}
|
|
803
|
+
</ul>
|
|
804
|
+
)}
|
|
805
|
+
</div>
|
|
606
806
|
)
|
|
607
807
|
}
|
|
608
808
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
PIPELINE_KIND_LABELS,
|
|
3
|
-
PIPELINE_KINDS,
|
|
4
2
|
PLAN_ITEM_STATUS_LABELS,
|
|
5
3
|
PLAN_ITEM_STATUSES,
|
|
4
|
+
PLAN_ITEM_TARGET_TYPE_LABELS,
|
|
5
|
+
PLAN_ITEM_TARGET_TYPES,
|
|
6
6
|
} from '@kelpie/schemas'
|
|
7
|
-
import type {
|
|
7
|
+
import type { PlanItem, PlanItemStatus, PlanItemTargetType } from '@kelpie/schemas'
|
|
8
8
|
import { useState } from 'react'
|
|
9
9
|
import type { FormEvent } from 'react'
|
|
10
10
|
|
|
11
11
|
import { useDeals } from '../api/resources/deals.ts'
|
|
12
12
|
import { useEnquiries } from '../api/resources/enquiries.ts'
|
|
13
|
+
import { useEvents } from '../api/resources/events.ts'
|
|
13
14
|
import { useMembers } from '../api/resources/members.ts'
|
|
14
15
|
import { useOpportunities } from '../api/resources/opportunities.ts'
|
|
15
16
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
@@ -41,7 +42,7 @@ import { monthBounds, planStatusTone, todayIso } from '../lib/plan.ts'
|
|
|
41
42
|
*/
|
|
42
43
|
|
|
43
44
|
type ViewMode = 'list' | 'calendar'
|
|
44
|
-
type TypeFilter =
|
|
45
|
+
type TypeFilter = PlanItemTargetType | 'all'
|
|
45
46
|
|
|
46
47
|
const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const
|
|
47
48
|
|
|
@@ -110,9 +111,9 @@ export function PlanningPage(): React.JSX.Element {
|
|
|
110
111
|
className="rounded-md border border-border bg-surface-raised px-2.5 py-1 text-[12px] outline-none focus:border-accent"
|
|
111
112
|
>
|
|
112
113
|
<option value="all">All types</option>
|
|
113
|
-
{
|
|
114
|
+
{PLAN_ITEM_TARGET_TYPES.map((kind) => (
|
|
114
115
|
<option key={kind} value={kind}>
|
|
115
|
-
{
|
|
116
|
+
{PLAN_ITEM_TARGET_TYPE_LABELS[kind]}
|
|
116
117
|
</option>
|
|
117
118
|
))}
|
|
118
119
|
</select>
|
|
@@ -207,7 +208,7 @@ export function PlanningPage(): React.JSX.Element {
|
|
|
207
208
|
* for PlanTargetLink. One page per kind, same ceiling as the rest of Planning.
|
|
208
209
|
*/
|
|
209
210
|
function usePipelineTargets(): {
|
|
210
|
-
readonly byKind: Readonly<Record<
|
|
211
|
+
readonly byKind: Readonly<Record<PlanItemTargetType, readonly PipelineTarget[]>>
|
|
211
212
|
readonly nameById: ReadonlyMap<string, string>
|
|
212
213
|
} {
|
|
213
214
|
const deals = useDeals({ limit: MAX_PAGE_SIZE })
|
|
@@ -215,13 +216,15 @@ function usePipelineTargets(): {
|
|
|
215
216
|
const raises = useRaises({ limit: MAX_PAGE_SIZE })
|
|
216
217
|
const partnerships = usePartnerships({ limit: MAX_PAGE_SIZE })
|
|
217
218
|
const enquiries = useEnquiries({ limit: MAX_PAGE_SIZE })
|
|
219
|
+
const events = useEvents({ limit: MAX_PAGE_SIZE })
|
|
218
220
|
|
|
219
|
-
const byKind: Readonly<Record<
|
|
221
|
+
const byKind: Readonly<Record<PlanItemTargetType, readonly PipelineTarget[]>> = {
|
|
220
222
|
enquiry: enquiries.records.map((record) => ({ id: record.id, name: record.name })),
|
|
221
223
|
deal: deals.records.map((record) => ({ id: record.id, name: record.name })),
|
|
222
224
|
opportunity: opportunities.records.map((record) => ({ id: record.id, name: record.name })),
|
|
223
225
|
raise: raises.records.map((record) => ({ id: record.id, name: record.name })),
|
|
224
226
|
partnership: partnerships.records.map((record) => ({ id: record.id, name: record.name })),
|
|
227
|
+
event: events.records.map((record) => ({ id: record.id, name: record.name })),
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
return {
|
|
@@ -235,7 +238,7 @@ function usePipelineTargets(): {
|
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
interface AddPlanItemFields {
|
|
238
|
-
readonly targetType:
|
|
241
|
+
readonly targetType: PlanItemTargetType
|
|
239
242
|
readonly targetId: string
|
|
240
243
|
readonly date: string
|
|
241
244
|
readonly title: string
|
|
@@ -251,8 +254,8 @@ function AddPlanItemForm({
|
|
|
251
254
|
onSubmit,
|
|
252
255
|
onCancel,
|
|
253
256
|
}: {
|
|
254
|
-
readonly initialTargetType:
|
|
255
|
-
readonly targetsByKind: Readonly<Record<
|
|
257
|
+
readonly initialTargetType: PlanItemTargetType
|
|
258
|
+
readonly targetsByKind: Readonly<Record<PlanItemTargetType, readonly PipelineTarget[]>>
|
|
256
259
|
readonly isPending: boolean
|
|
257
260
|
readonly error: Error | null
|
|
258
261
|
readonly onSubmit: (fields: AddPlanItemFields) => void
|
|
@@ -288,16 +291,16 @@ function AddPlanItemForm({
|
|
|
288
291
|
<select
|
|
289
292
|
value={draft.targetType}
|
|
290
293
|
onChange={(event) => {
|
|
291
|
-
const targetType = event.target.value as
|
|
294
|
+
const targetType = event.target.value as PlanItemTargetType
|
|
292
295
|
|
|
293
296
|
setDraft((current) => ({ ...current, targetType, targetId: '' }))
|
|
294
297
|
}}
|
|
295
298
|
aria-label="Record type"
|
|
296
299
|
className={inputClass}
|
|
297
300
|
>
|
|
298
|
-
{
|
|
301
|
+
{PLAN_ITEM_TARGET_TYPES.map((kind) => (
|
|
299
302
|
<option key={kind} value={kind}>
|
|
300
|
-
{
|
|
303
|
+
{PLAN_ITEM_TARGET_TYPE_LABELS[kind]}
|
|
301
304
|
</option>
|
|
302
305
|
))}
|
|
303
306
|
</select>
|