@kelpie/ui 0.11.0 → 0.13.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 +25 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -3
- 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/account/SecurityPage.js +2 -2
- package/dist/pages/account/SecurityPage.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/AuthForm.d.ts +18 -0
- package/dist/pages/auth/AuthForm.d.ts.map +1 -1
- package/dist/pages/auth/AuthForm.js +17 -0
- package/dist/pages/auth/AuthForm.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/AuthMethods.d.ts +12 -0
- package/dist/pages/auth/AuthMethods.d.ts.map +1 -0
- package/dist/pages/auth/AuthMethods.js +20 -0
- package/dist/pages/auth/AuthMethods.js.map +1 -0
- 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 +7 -0
- package/dist/pages/auth/SignInPage.d.ts.map +1 -1
- package/dist/pages/auth/SignInPage.js +5 -8
- 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 +9 -1
- package/dist/pages/auth/SignUpPage.js.map +1 -1
- package/dist/pages/auth/nextPath.d.ts +15 -0
- package/dist/pages/auth/nextPath.d.ts.map +1 -0
- package/dist/pages/auth/nextPath.js +17 -0
- package/dist/pages/auth/nextPath.js.map +1 -0
- 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/dist/registry/context.d.ts +3 -1
- package/dist/registry/context.d.ts.map +1 -1
- package/dist/registry/context.js +4 -0
- package/dist/registry/context.js.map +1 -1
- package/dist/registry/contributions.d.ts +24 -0
- package/dist/registry/contributions.d.ts.map +1 -1
- package/dist/registry/contributions.js.map +1 -1
- package/dist/registry/registry.d.ts +4 -1
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +6 -0
- package/dist/registry/registry.js.map +1 -1
- 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 +49 -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/account/SecurityPage.tsx +6 -1
- 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/AuthForm.tsx +38 -0
- package/src/pages/auth/AuthLayout.tsx +8 -4
- package/src/pages/auth/AuthMethods.tsx +29 -0
- package/src/pages/auth/RegionSwitcher.tsx +56 -0
- package/src/pages/auth/SignInPage.tsx +13 -14
- package/src/pages/auth/SignUpPage.tsx +32 -0
- package/src/pages/auth/nextPath.ts +17 -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/registry/context.ts +6 -0
- package/src/registry/contributions.ts +26 -0
- package/src/registry/registry.ts +12 -0
- package/src/styles.css +30 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVENT_FORMAT_LABELS,
|
|
3
|
+
EVENT_STATUS_LABELS,
|
|
4
|
+
} from '@kelpie/schemas'
|
|
5
|
+
import type { Event as CrmEvent, EventFormat, EventStatus } from '@kelpie/schemas'
|
|
6
|
+
import { useMemo, useState } from 'react'
|
|
7
|
+
import type { FormEvent } from 'react'
|
|
8
|
+
import { Link, useNavigate } from 'react-router'
|
|
9
|
+
|
|
10
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
11
|
+
import { useCreateEvent, useEvents } from '../api/resources/events.ts'
|
|
12
|
+
import { Chip } from '../components/Chip.tsx'
|
|
13
|
+
import type { ChipTone } from '../components/Chip.tsx'
|
|
14
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
15
|
+
import { DataTable } from '../components/DataTable.tsx'
|
|
16
|
+
import type { Column } from '../components/DataTable.tsx'
|
|
17
|
+
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
18
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
19
|
+
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
20
|
+
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
21
|
+
import { formatDateTime } from '../lib/dates.ts'
|
|
22
|
+
import { useListView } from '../lib/listView.ts'
|
|
23
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Events list and month calendar. Upcoming is the default. Not a kanban.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
type Scope = 'upcoming' | 'past' | 'all'
|
|
30
|
+
type BoardView = 'list' | 'calendar'
|
|
31
|
+
|
|
32
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'starts', 'format', 'status', 'location']
|
|
33
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'starts_at', 'created_at', 'updated_at']
|
|
34
|
+
const MONTH_AND_YEAR = new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' })
|
|
35
|
+
|
|
36
|
+
const STATUS_TONES: Readonly<Record<EventStatus, ChipTone>> = {
|
|
37
|
+
draft: 'neutral',
|
|
38
|
+
scheduled: 'accent',
|
|
39
|
+
cancelled: 'danger',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function dayKey(value: Date, timezone: string): string {
|
|
43
|
+
return new Intl.DateTimeFormat('en-CA', {
|
|
44
|
+
timeZone: timezone,
|
|
45
|
+
year: 'numeric',
|
|
46
|
+
month: '2-digit',
|
|
47
|
+
day: '2-digit',
|
|
48
|
+
}).format(value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function EventsPage(): React.JSX.Element {
|
|
52
|
+
const [term, setTerm] = useState('')
|
|
53
|
+
const [adding, setAdding] = useState(false)
|
|
54
|
+
const [name, setName] = useState('')
|
|
55
|
+
const [scope, setScope] = useState<Scope>('upcoming')
|
|
56
|
+
const [board, setBoard] = useState<BoardView>('list')
|
|
57
|
+
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
58
|
+
const navigate = useNavigate()
|
|
59
|
+
const timezone = useTimezone()
|
|
60
|
+
const now = new Date()
|
|
61
|
+
const [month, setMonth] = useState({ year: now.getFullYear(), month: now.getMonth() })
|
|
62
|
+
const createEvent = useCreateEvent()
|
|
63
|
+
|
|
64
|
+
const fromTo =
|
|
65
|
+
board === 'calendar'
|
|
66
|
+
? monthBounds(month.year, month.month)
|
|
67
|
+
: scope === 'upcoming'
|
|
68
|
+
? { from: now.toISOString(), to: undefined }
|
|
69
|
+
: scope === 'past'
|
|
70
|
+
? { from: undefined, to: now.toISOString() }
|
|
71
|
+
: { from: undefined, to: undefined }
|
|
72
|
+
|
|
73
|
+
const events = useEvents({
|
|
74
|
+
term: term.trim().length > 0 ? term.trim() : undefined,
|
|
75
|
+
from: fromTo.from,
|
|
76
|
+
to: fromTo.to,
|
|
77
|
+
sort: serverSortOnly(
|
|
78
|
+
sort ?? (scope === 'past' ? '-starts_at' : 'starts_at'),
|
|
79
|
+
SERVER_SORT_KEYS,
|
|
80
|
+
),
|
|
81
|
+
limit: board === 'calendar' ? 200 : undefined,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
async function addEvent(event: FormEvent): Promise<void> {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
|
|
87
|
+
const trimmed = name.trim()
|
|
88
|
+
|
|
89
|
+
if (trimmed.length === 0) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const startsAt = new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
94
|
+
const endsAt = new Date(startsAt.getTime() + 60 * 60 * 1000)
|
|
95
|
+
const created = await createEvent.runAsync({
|
|
96
|
+
name: trimmed,
|
|
97
|
+
startsAt,
|
|
98
|
+
endsAt,
|
|
99
|
+
format: 'virtual',
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
setAdding(false)
|
|
103
|
+
setName('')
|
|
104
|
+
await navigate(`/events/${created.id}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const columns: readonly Column<CrmEvent>[] = [
|
|
108
|
+
{
|
|
109
|
+
key: 'name',
|
|
110
|
+
header: 'Event',
|
|
111
|
+
sortKey: 'name',
|
|
112
|
+
render: (record) => <span className="font-medium text-ink">{record.name}</span>,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: 'starts',
|
|
116
|
+
header: 'Starts',
|
|
117
|
+
sortKey: 'starts_at',
|
|
118
|
+
render: (record) => (
|
|
119
|
+
<span className="tabular-nums text-ink-muted">
|
|
120
|
+
{formatDateTime(record.startsAt, timezone)}
|
|
121
|
+
</span>
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: 'format',
|
|
126
|
+
header: 'Format',
|
|
127
|
+
getSortValue: (record) => record.format,
|
|
128
|
+
render: (record) => EVENT_FORMAT_LABELS[record.format as EventFormat] ?? record.format,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: 'status',
|
|
132
|
+
header: 'Status',
|
|
133
|
+
getSortValue: (record) => record.status,
|
|
134
|
+
render: (record) => (
|
|
135
|
+
<Chip tone={STATUS_TONES[record.status] ?? 'neutral'}>
|
|
136
|
+
{EVENT_STATUS_LABELS[record.status] ?? record.status}
|
|
137
|
+
</Chip>
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: 'location',
|
|
142
|
+
header: 'Location',
|
|
143
|
+
getSortValue: (record) => record.location,
|
|
144
|
+
render: (record) => (
|
|
145
|
+
<span className="text-ink-muted">{record.location.length > 0 ? record.location : '—'}</span>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
151
|
+
const listView = useListView('events', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
152
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div className="animate-fade-in">
|
|
156
|
+
<PageHeader
|
|
157
|
+
title="Events"
|
|
158
|
+
onAdd={() => {
|
|
159
|
+
setAdding(true)
|
|
160
|
+
}}
|
|
161
|
+
addLabel="Add event"
|
|
162
|
+
actions={
|
|
163
|
+
<>
|
|
164
|
+
<SegmentedControl
|
|
165
|
+
ariaLabel="Upcoming or past"
|
|
166
|
+
value={scope}
|
|
167
|
+
onChange={setScope}
|
|
168
|
+
options={[
|
|
169
|
+
{ id: 'upcoming', label: 'Upcoming' },
|
|
170
|
+
{ id: 'past', label: 'Past' },
|
|
171
|
+
{ id: 'all', label: 'All' },
|
|
172
|
+
]}
|
|
173
|
+
/>
|
|
174
|
+
<SegmentedControl
|
|
175
|
+
ariaLabel="List or calendar"
|
|
176
|
+
value={board}
|
|
177
|
+
onChange={setBoard}
|
|
178
|
+
options={[
|
|
179
|
+
{ id: 'list', label: 'List' },
|
|
180
|
+
{ id: 'calendar', label: 'Calendar' },
|
|
181
|
+
]}
|
|
182
|
+
/>
|
|
183
|
+
<ColumnPicker
|
|
184
|
+
options={pickerOptions}
|
|
185
|
+
visibleKeys={listView.visibleKeys}
|
|
186
|
+
onChange={listView.setVisibleKeys}
|
|
187
|
+
/>
|
|
188
|
+
</>
|
|
189
|
+
}
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
<FilterBar value={term} onChange={setTerm} placeholder="Search events…" />
|
|
193
|
+
|
|
194
|
+
{adding && (
|
|
195
|
+
<form
|
|
196
|
+
onSubmit={(event) => {
|
|
197
|
+
void addEvent(event)
|
|
198
|
+
}}
|
|
199
|
+
className="mb-4 flex flex-wrap items-end gap-2 rounded-md border border-border p-3"
|
|
200
|
+
>
|
|
201
|
+
<label className="min-w-[220px] flex-1 text-[12px] font-medium text-ink-muted">
|
|
202
|
+
Name
|
|
203
|
+
<input
|
|
204
|
+
value={name}
|
|
205
|
+
onChange={(event) => {
|
|
206
|
+
setName(event.target.value)
|
|
207
|
+
}}
|
|
208
|
+
className="mt-1 w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] text-ink outline-none focus:border-accent"
|
|
209
|
+
autoFocus
|
|
210
|
+
/>
|
|
211
|
+
</label>
|
|
212
|
+
<button
|
|
213
|
+
type="submit"
|
|
214
|
+
disabled={createEvent.isPending || name.trim().length === 0}
|
|
215
|
+
className="rounded-md bg-accent px-3 py-2 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
|
|
216
|
+
>
|
|
217
|
+
Create
|
|
218
|
+
</button>
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
onClick={() => {
|
|
222
|
+
setAdding(false)
|
|
223
|
+
setName('')
|
|
224
|
+
}}
|
|
225
|
+
className="text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
226
|
+
>
|
|
227
|
+
Cancel
|
|
228
|
+
</button>
|
|
229
|
+
{createEvent.error !== null && <ErrorPanel error={createEvent.error} />}
|
|
230
|
+
</form>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{events.error !== null ? (
|
|
234
|
+
<ErrorPanel error={events.error} />
|
|
235
|
+
) : events.isLoading ? (
|
|
236
|
+
<LoadingPanel label="Loading events…" />
|
|
237
|
+
) : board === 'calendar' ? (
|
|
238
|
+
<EventCalendar
|
|
239
|
+
year={month.year}
|
|
240
|
+
month={month.month}
|
|
241
|
+
timezone={timezone}
|
|
242
|
+
events={events.records}
|
|
243
|
+
onShift={(delta) => {
|
|
244
|
+
setMonth((current) => {
|
|
245
|
+
const next = new Date(current.year, current.month + delta, 1)
|
|
246
|
+
|
|
247
|
+
return { year: next.getFullYear(), month: next.getMonth() }
|
|
248
|
+
})
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
) : (
|
|
252
|
+
<>
|
|
253
|
+
<Paginator list={events} placement="top" />
|
|
254
|
+
<DataTable
|
|
255
|
+
columns={columns}
|
|
256
|
+
rows={events.records}
|
|
257
|
+
getRowId={(record) => record.id}
|
|
258
|
+
onRowClick={(record) => {
|
|
259
|
+
void navigate(`/events/${record.id}`)
|
|
260
|
+
}}
|
|
261
|
+
emptyMessage={term.trim().length > 0 ? 'No events match this filter' : 'No events yet'}
|
|
262
|
+
sort={sort}
|
|
263
|
+
onSortChange={setSort}
|
|
264
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
265
|
+
/>
|
|
266
|
+
<Paginator list={events} />
|
|
267
|
+
</>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function monthBounds(year: number, month: number): { from: string; to: string } {
|
|
274
|
+
const from = new Date(Date.UTC(year, month, 1))
|
|
275
|
+
const to = new Date(Date.UTC(year, month + 1, 1))
|
|
276
|
+
|
|
277
|
+
return { from: from.toISOString(), to: to.toISOString() }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function calendarCells(year: number, month: number): readonly { key: string; date: string | null; day: number | null }[] {
|
|
281
|
+
const firstWeekday = (new Date(year, month, 1).getDay() + 6) % 7
|
|
282
|
+
const dayCount = new Date(year, month + 1, 0).getDate()
|
|
283
|
+
const cells: { key: string; date: string | null; day: number | null }[] = []
|
|
284
|
+
|
|
285
|
+
for (let index = 0; index < firstWeekday; index++) {
|
|
286
|
+
cells.push({ key: `lead-${String(index)}`, date: null, day: null })
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (let day = 1; day <= dayCount; day++) {
|
|
290
|
+
const date = `${String(year)}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
|
291
|
+
|
|
292
|
+
cells.push({ key: date, date, day })
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
while (cells.length % 7 !== 0) {
|
|
296
|
+
cells.push({ key: `trail-${String(cells.length)}`, date: null, day: null })
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return cells
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function EventCalendar({
|
|
303
|
+
year,
|
|
304
|
+
month,
|
|
305
|
+
timezone,
|
|
306
|
+
events,
|
|
307
|
+
onShift,
|
|
308
|
+
}: {
|
|
309
|
+
readonly year: number
|
|
310
|
+
readonly month: number
|
|
311
|
+
readonly timezone: string
|
|
312
|
+
readonly events: readonly CrmEvent[]
|
|
313
|
+
readonly onShift: (delta: number) => void
|
|
314
|
+
}): React.JSX.Element {
|
|
315
|
+
const byDay = useMemo(() => {
|
|
316
|
+
const grouped = new Map<string, CrmEvent[]>()
|
|
317
|
+
|
|
318
|
+
for (const record of events) {
|
|
319
|
+
const key = dayKey(record.startsAt, timezone)
|
|
320
|
+
const held = grouped.get(key) ?? []
|
|
321
|
+
|
|
322
|
+
held.push(record)
|
|
323
|
+
grouped.set(key, held)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return grouped
|
|
327
|
+
}, [events, timezone])
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<div>
|
|
331
|
+
<div className="mb-4 flex items-center gap-2">
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
onClick={() => {
|
|
335
|
+
onShift(-1)
|
|
336
|
+
}}
|
|
337
|
+
aria-label="Previous month"
|
|
338
|
+
className="rounded-md border border-border px-2 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
339
|
+
>
|
|
340
|
+
←
|
|
341
|
+
</button>
|
|
342
|
+
<span className="min-w-[140px] text-center text-[13px] font-semibold text-ink">
|
|
343
|
+
{MONTH_AND_YEAR.format(new Date(year, month, 1))}
|
|
344
|
+
</span>
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
onClick={() => {
|
|
348
|
+
onShift(1)
|
|
349
|
+
}}
|
|
350
|
+
aria-label="Next month"
|
|
351
|
+
className="rounded-md border border-border px-2 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
|
|
352
|
+
>
|
|
353
|
+
→
|
|
354
|
+
</button>
|
|
355
|
+
</div>
|
|
356
|
+
<div className="grid grid-cols-7 gap-px overflow-hidden rounded-md border border-border bg-border">
|
|
357
|
+
{['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((label) => (
|
|
358
|
+
<div key={label} className="bg-surface px-2 py-1 text-[11px] font-medium text-ink-muted">
|
|
359
|
+
{label}
|
|
360
|
+
</div>
|
|
361
|
+
))}
|
|
362
|
+
{calendarCells(year, month).map((cell) => (
|
|
363
|
+
<div key={cell.key} className="min-h-[88px] bg-surface p-1.5">
|
|
364
|
+
{cell.day !== null && (
|
|
365
|
+
<div className="text-[11px] font-medium text-ink-muted">{cell.day}</div>
|
|
366
|
+
)}
|
|
367
|
+
{cell.date !== null &&
|
|
368
|
+
(byDay.get(cell.date) ?? []).map((record) => (
|
|
369
|
+
<Link
|
|
370
|
+
key={record.id}
|
|
371
|
+
to={`/events/${record.id}`}
|
|
372
|
+
className="mt-1 block truncate text-[11px] font-medium text-accent hover:underline"
|
|
373
|
+
>
|
|
374
|
+
{record.name}
|
|
375
|
+
</Link>
|
|
376
|
+
))}
|
|
377
|
+
</div>
|
|
378
|
+
))}
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
)
|
|
382
|
+
}
|
package/src/pages/ListDetail.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
useRemoveListMember,
|
|
15
15
|
} from '../api/resources/listMembers.ts'
|
|
16
16
|
import { useEnquiries } from '../api/resources/enquiries.ts'
|
|
17
|
+
import { useEvents } from '../api/resources/events.ts'
|
|
17
18
|
import { useOpportunities } from '../api/resources/opportunities.ts'
|
|
18
19
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
19
20
|
import { usePeople } from '../api/resources/people.ts'
|
|
@@ -274,9 +275,13 @@ function detailPathFor(targetType: RecordTargetType, targetId: string): string {
|
|
|
274
275
|
return `/fundraising/${targetId}`
|
|
275
276
|
case 'enquiry':
|
|
276
277
|
return `/enquiries/${targetId}`
|
|
278
|
+
case 'event':
|
|
279
|
+
return `/events/${targetId}`
|
|
277
280
|
case 'candidate':
|
|
278
281
|
// A candidate has no page of its own; the person's does.
|
|
279
282
|
return `/hiring`
|
|
283
|
+
case 'attendance':
|
|
284
|
+
return `/events`
|
|
280
285
|
}
|
|
281
286
|
}
|
|
282
287
|
|
|
@@ -309,6 +314,8 @@ function MemberPicker({
|
|
|
309
314
|
return <RaisesPicker attached={attached} onPick={onPick} />
|
|
310
315
|
case 'enquiry':
|
|
311
316
|
return <EnquiriesPicker attached={attached} onPick={onPick} />
|
|
317
|
+
case 'event':
|
|
318
|
+
return <EventsPicker attached={attached} onPick={onPick} />
|
|
312
319
|
case 'candidate':
|
|
313
320
|
return (
|
|
314
321
|
<p className="text-[12px] text-ink-faint">
|
|
@@ -316,6 +323,13 @@ function MemberPicker({
|
|
|
316
323
|
page, then attach them here through the API.
|
|
317
324
|
</p>
|
|
318
325
|
)
|
|
326
|
+
case 'attendance':
|
|
327
|
+
return (
|
|
328
|
+
<p className="text-[12px] text-ink-faint">
|
|
329
|
+
Attendance lists have no inline picker in this release. Register people on an Event,
|
|
330
|
+
then attach them here through the API.
|
|
331
|
+
</p>
|
|
332
|
+
)
|
|
319
333
|
}
|
|
320
334
|
}
|
|
321
335
|
|
|
@@ -394,6 +408,16 @@ function EnquiriesPicker({ attached, onPick }: PickerProps): React.JSX.Element {
|
|
|
394
408
|
return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
|
|
395
409
|
}
|
|
396
410
|
|
|
411
|
+
function EventsPicker({ attached, onPick }: PickerProps): React.JSX.Element {
|
|
412
|
+
const [search, setSearch] = useState('')
|
|
413
|
+
const records = useEvents({ term: trimmedOrUndefined(search) })
|
|
414
|
+
const options: readonly SearchOption[] = records.records
|
|
415
|
+
.filter((row) => !attached.has(row.id))
|
|
416
|
+
.map((row) => ({ id: row.id, label: row.name }))
|
|
417
|
+
|
|
418
|
+
return <PickerBox onQueryChange={setSearch} onPick={onPick} options={options} />
|
|
419
|
+
}
|
|
420
|
+
|
|
397
421
|
function PickerBox({
|
|
398
422
|
onQueryChange,
|
|
399
423
|
onPick,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../api/resources/conversions.ts'
|
|
13
13
|
import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
|
|
14
14
|
import { useMembers } from '../api/resources/members.ts'
|
|
15
|
+
import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
|
|
15
16
|
import {
|
|
16
17
|
useDeleteOpportunity,
|
|
17
18
|
useOpportunity,
|
|
@@ -33,6 +34,7 @@ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
|
33
34
|
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
34
35
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
35
36
|
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
37
|
+
import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
|
|
36
38
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
37
39
|
import { PlanAttention } from '../components/PlanAttention.tsx'
|
|
38
40
|
import { PlanPanel } from '../components/PlanPanel.tsx'
|
|
@@ -70,6 +72,7 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
70
72
|
const deleteOpportunity = useDeleteOpportunity()
|
|
71
73
|
const moduleTabs = inSlotOrder(useRecordTabs('opportunity'))
|
|
72
74
|
const hasCustomFields = useHasCustomFields('opportunity')
|
|
75
|
+
const eventsEnabled = useModuleEnabled('events')
|
|
73
76
|
const [activeTab, setActiveTab] = useState('overview')
|
|
74
77
|
const [showConvert, setShowConvert] = useState(false)
|
|
75
78
|
const formSubmissions = useFormSubmissionsForRecord('opportunity', id)
|
|
@@ -94,6 +97,7 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
94
97
|
{ id: 'notes', label: 'Notes' },
|
|
95
98
|
{ id: 'decisions', label: 'Decisions' },
|
|
96
99
|
{ id: 'lists', label: 'Lists' },
|
|
100
|
+
...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
|
|
97
101
|
...(formSubmissions.records.length === 0
|
|
98
102
|
? []
|
|
99
103
|
: [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
|
|
@@ -176,6 +180,9 @@ export function OpportunityDetail(): React.JSX.Element {
|
|
|
176
180
|
<DecisionsPanel targetType="opportunity" targetId={record.id} />
|
|
177
181
|
)}
|
|
178
182
|
{active === 'lists' && <ListsPanel targetType="opportunity" targetId={record.id} />}
|
|
183
|
+
{active === 'events' && (
|
|
184
|
+
<LinkedEventsPanel targetType="opportunity" targetId={record.id} />
|
|
185
|
+
)}
|
|
179
186
|
{active === 'forms' && <FormsPanel targetType="opportunity" targetId={record.id} />}
|
|
180
187
|
{moduleTab?.render({ objectType: 'opportunity', recordId: record.id })}
|
|
181
188
|
</RecordTabs>
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../api/resources/conversions.ts'
|
|
13
13
|
import { useFormSubmissionsForRecord } from '../api/resources/forms.ts'
|
|
14
14
|
import { useMembers } from '../api/resources/members.ts'
|
|
15
|
+
import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
|
|
15
16
|
import {
|
|
16
17
|
useDeletePartnership,
|
|
17
18
|
usePartnership,
|
|
@@ -33,6 +34,7 @@ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
|
33
34
|
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
34
35
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
35
36
|
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
37
|
+
import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
|
|
36
38
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
37
39
|
import { PlanAttention } from '../components/PlanAttention.tsx'
|
|
38
40
|
import { PlanPanel } from '../components/PlanPanel.tsx'
|
|
@@ -70,6 +72,7 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
70
72
|
const deletePartnership = useDeletePartnership()
|
|
71
73
|
const moduleTabs = inSlotOrder(useRecordTabs('partnership'))
|
|
72
74
|
const hasCustomFields = useHasCustomFields('partnership')
|
|
75
|
+
const eventsEnabled = useModuleEnabled('events')
|
|
73
76
|
const [activeTab, setActiveTab] = useState('overview')
|
|
74
77
|
const [showConvert, setShowConvert] = useState(false)
|
|
75
78
|
const formSubmissions = useFormSubmissionsForRecord('partnership', id)
|
|
@@ -94,6 +97,7 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
94
97
|
{ id: 'notes', label: 'Notes' },
|
|
95
98
|
{ id: 'decisions', label: 'Decisions' },
|
|
96
99
|
{ id: 'lists', label: 'Lists' },
|
|
100
|
+
...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
|
|
97
101
|
...(formSubmissions.records.length === 0
|
|
98
102
|
? []
|
|
99
103
|
: [{ id: 'forms', label: 'Forms', count: formSubmissions.records.length }]),
|
|
@@ -176,6 +180,9 @@ export function PartnershipDetail(): React.JSX.Element {
|
|
|
176
180
|
<DecisionsPanel targetType="partnership" targetId={record.id} />
|
|
177
181
|
)}
|
|
178
182
|
{active === 'lists' && <ListsPanel targetType="partnership" targetId={record.id} />}
|
|
183
|
+
{active === 'events' && (
|
|
184
|
+
<LinkedEventsPanel targetType="partnership" targetId={record.id} />
|
|
185
|
+
)}
|
|
179
186
|
{active === 'forms' && <FormsPanel targetType="partnership" targetId={record.id} />}
|
|
180
187
|
{moduleTab?.render({ objectType: 'partnership', recordId: record.id })}
|
|
181
188
|
</RecordTabs>
|
package/src/pages/PeoplePage.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SOCIAL_NETWORK_LABELS, formatAddress, primaryAddress } from '@kelpie/schemas'
|
|
1
2
|
import type { Person } from '@kelpie/schemas'
|
|
2
3
|
import { useState } from 'react'
|
|
3
4
|
import { useNavigate } from 'react-router'
|
|
@@ -5,6 +6,7 @@ import { useNavigate } from 'react-router'
|
|
|
5
6
|
import { useTimezone } from '../api/resources/account.ts'
|
|
6
7
|
import { useCreatePerson, usePeople } from '../api/resources/people.ts'
|
|
7
8
|
import { Chip } from '../components/Chip.tsx'
|
|
9
|
+
import { SocialNetworkIcon } from '../components/SocialNetworkIcon.tsx'
|
|
8
10
|
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
9
11
|
import { DataTable } from '../components/DataTable.tsx'
|
|
10
12
|
import type { Column } from '../components/DataTable.tsx'
|
|
@@ -130,17 +132,28 @@ export function PeoplePage(): React.JSX.Element {
|
|
|
130
132
|
<span className="flex flex-wrap gap-1">
|
|
131
133
|
{person.socialProfiles.map((entry) => (
|
|
132
134
|
<Chip key={entry.network}>
|
|
133
|
-
<span className="
|
|
135
|
+
<span className="inline-flex items-center gap-1">
|
|
136
|
+
<SocialNetworkIcon network={entry.network} className="size-3" />
|
|
137
|
+
<span className="text-[10px]">{SOCIAL_NETWORK_LABELS[entry.network]}</span>
|
|
138
|
+
</span>
|
|
134
139
|
</Chip>
|
|
135
140
|
))}
|
|
136
141
|
</span>
|
|
137
142
|
),
|
|
138
143
|
},
|
|
139
144
|
{
|
|
140
|
-
key: '
|
|
141
|
-
header: '
|
|
142
|
-
getSortValue: (person) =>
|
|
143
|
-
|
|
145
|
+
key: 'addresses',
|
|
146
|
+
header: 'Address',
|
|
147
|
+
getSortValue: (person) => {
|
|
148
|
+
const address = primaryAddress(person.addresses)
|
|
149
|
+
|
|
150
|
+
return address === undefined ? null : formatAddress(address)
|
|
151
|
+
},
|
|
152
|
+
render: (person) => {
|
|
153
|
+
const address = primaryAddress(person.addresses)
|
|
154
|
+
|
|
155
|
+
return address === undefined ? '—' : formatAddress(address)
|
|
156
|
+
},
|
|
144
157
|
},
|
|
145
158
|
{
|
|
146
159
|
key: 'timezone',
|