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