@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
|
@@ -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',
|