@kelpie/ui 0.7.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/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/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 +3 -1
- 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/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 +110 -12
- 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/listMembers.ts +173 -0
- package/src/api/resources/lists.ts +61 -0
- package/src/api/resources/people.ts +4 -1
- 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 +4 -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/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 +276 -13
- 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
|
@@ -11,11 +11,15 @@ import { useOpportunities } from '../api/resources/opportunities.ts'
|
|
|
11
11
|
import { usePartnerships } from '../api/resources/partnerships.ts'
|
|
12
12
|
import { usePeople } from '../api/resources/people.ts'
|
|
13
13
|
import { useRaises } from '../api/resources/raises.ts'
|
|
14
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
14
15
|
import { DataTable } from '../components/DataTable.tsx'
|
|
15
16
|
import type { Column } from '../components/DataTable.tsx'
|
|
16
17
|
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
18
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
17
19
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
18
20
|
import { formatDate } from '../lib/dates.ts'
|
|
21
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
22
|
+
import { useListView } from '../lib/listView.ts'
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Every decision in the workspace.
|
|
@@ -90,12 +94,12 @@ function useTargetDirectory(): TargetDirectory {
|
|
|
90
94
|
return {
|
|
91
95
|
nameFor: (decision) => nameById.get(decision.targetId),
|
|
92
96
|
isComplete:
|
|
93
|
-
!people.
|
|
94
|
-
!companies.
|
|
95
|
-
!deals.
|
|
96
|
-
!opportunities.
|
|
97
|
-
!raises.
|
|
98
|
-
!partnerships.
|
|
97
|
+
!people.hasNext &&
|
|
98
|
+
!companies.hasNext &&
|
|
99
|
+
!deals.hasNext &&
|
|
100
|
+
!opportunities.hasNext &&
|
|
101
|
+
!raises.hasNext &&
|
|
102
|
+
!partnerships.hasNext,
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
|
|
@@ -123,9 +127,17 @@ function LinkedTo({
|
|
|
123
127
|
)
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['decision', 'target', 'decided', 'due', 'owner']
|
|
131
|
+
|
|
132
|
+
const SERVER_SORT_KEYS: readonly string[] = ['decided_at', 'created_at', 'updated_at']
|
|
133
|
+
|
|
126
134
|
export function DecisionsPage(): React.JSX.Element {
|
|
127
135
|
const [term, setTerm] = useState('')
|
|
128
|
-
const
|
|
136
|
+
const [sort, setSort] = useState<string | undefined>(undefined)
|
|
137
|
+
const decisions = useDecisions({
|
|
138
|
+
term: term.trim().length > 0 ? term.trim() : undefined,
|
|
139
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
140
|
+
})
|
|
129
141
|
const directory = useTargetDirectory()
|
|
130
142
|
const members = useMembers()
|
|
131
143
|
const timezone = useTimezone()
|
|
@@ -134,6 +146,7 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
134
146
|
{
|
|
135
147
|
key: 'decision',
|
|
136
148
|
header: 'Decision',
|
|
149
|
+
getSortValue: (decision) => decision.body,
|
|
137
150
|
render: (decision) => (
|
|
138
151
|
<>
|
|
139
152
|
<div className="font-medium text-ink">{decision.body}</div>
|
|
@@ -143,14 +156,33 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
143
156
|
</>
|
|
144
157
|
),
|
|
145
158
|
},
|
|
159
|
+
{
|
|
160
|
+
key: 'rationale',
|
|
161
|
+
header: 'Rationale',
|
|
162
|
+
getSortValue: (decision) => decision.rationale,
|
|
163
|
+
render: (decision) =>
|
|
164
|
+
decision.rationale === null ? (
|
|
165
|
+
'—'
|
|
166
|
+
) : (
|
|
167
|
+
<span className="text-ink-muted">{decision.rationale}</span>
|
|
168
|
+
),
|
|
169
|
+
},
|
|
146
170
|
{
|
|
147
171
|
key: 'target',
|
|
148
172
|
header: 'Linked to',
|
|
173
|
+
getSortValue: (decision) => directory.nameFor(decision) ?? TARGET_LABELS[decision.targetType],
|
|
149
174
|
render: (decision) => <LinkedTo decision={decision} name={directory.nameFor(decision)} />,
|
|
150
175
|
},
|
|
176
|
+
{
|
|
177
|
+
key: 'targetType',
|
|
178
|
+
header: 'Type',
|
|
179
|
+
getSortValue: (decision) => TARGET_LABELS[decision.targetType],
|
|
180
|
+
render: (decision) => TARGET_LABELS[decision.targetType],
|
|
181
|
+
},
|
|
151
182
|
{
|
|
152
183
|
key: 'decided',
|
|
153
184
|
header: 'Decided',
|
|
185
|
+
sortKey: 'decided_at',
|
|
154
186
|
render: (decision) => (
|
|
155
187
|
<span className="text-ink-muted">{formatDate(decision.decidedAt, timezone)}</span>
|
|
156
188
|
),
|
|
@@ -158,6 +190,7 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
158
190
|
{
|
|
159
191
|
key: 'due',
|
|
160
192
|
header: 'By',
|
|
193
|
+
getSortValue: (decision) => decision.dueAt,
|
|
161
194
|
render: (decision) => (
|
|
162
195
|
<span className="text-ink-muted">
|
|
163
196
|
{decision.dueAt === null ? '—' : formatDate(decision.dueAt, timezone)}
|
|
@@ -167,17 +200,44 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
167
200
|
{
|
|
168
201
|
key: 'owner',
|
|
169
202
|
header: 'Owner',
|
|
203
|
+
getSortValue: (decision) =>
|
|
204
|
+
decision.ownerId === null ? null : (members.nameById.get(decision.ownerId) ?? null),
|
|
170
205
|
render: (decision) => (
|
|
171
206
|
<span className="text-ink-muted">
|
|
172
207
|
{decision.ownerId === null ? '—' : (members.nameById.get(decision.ownerId) ?? '—')}
|
|
173
208
|
</span>
|
|
174
209
|
),
|
|
175
210
|
},
|
|
211
|
+
{
|
|
212
|
+
key: 'createdAt',
|
|
213
|
+
header: 'Created',
|
|
214
|
+
sortKey: 'created_at',
|
|
215
|
+
render: (decision) => formatDate(decision.createdAt, timezone),
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: 'updatedAt',
|
|
219
|
+
header: 'Updated',
|
|
220
|
+
sortKey: 'updated_at',
|
|
221
|
+
render: (decision) => formatDate(decision.updatedAt, timezone),
|
|
222
|
+
},
|
|
176
223
|
]
|
|
177
224
|
|
|
225
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
226
|
+
const listView = useListView('decisions', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
227
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
228
|
+
|
|
178
229
|
return (
|
|
179
230
|
<div className="animate-fade-in">
|
|
180
|
-
<PageHeader
|
|
231
|
+
<PageHeader
|
|
232
|
+
title="Decisions"
|
|
233
|
+
actions={
|
|
234
|
+
<ColumnPicker
|
|
235
|
+
options={pickerOptions}
|
|
236
|
+
visibleKeys={listView.visibleKeys}
|
|
237
|
+
onChange={listView.setVisibleKeys}
|
|
238
|
+
/>
|
|
239
|
+
}
|
|
240
|
+
/>
|
|
181
241
|
<FilterBar value={term} onChange={setTerm} placeholder="Filter decisions…" />
|
|
182
242
|
|
|
183
243
|
{decisions.error !== null ? (
|
|
@@ -186,11 +246,15 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
186
246
|
<LoadingPanel label="Loading decisions…" />
|
|
187
247
|
) : (
|
|
188
248
|
<>
|
|
249
|
+
<Paginator list={decisions} placement="top" />
|
|
189
250
|
<DataTable
|
|
190
251
|
columns={columns}
|
|
191
252
|
rows={decisions.records}
|
|
192
253
|
getRowId={(decision) => decision.id}
|
|
193
254
|
emptyMessage="No decisions match"
|
|
255
|
+
sort={sort}
|
|
256
|
+
onSortChange={setSort}
|
|
257
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
194
258
|
/>
|
|
195
259
|
{!directory.isComplete && (
|
|
196
260
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
@@ -198,16 +262,7 @@ export function DecisionsPage(): React.JSX.Element {
|
|
|
198
262
|
record type.
|
|
199
263
|
</p>
|
|
200
264
|
)}
|
|
201
|
-
{decisions
|
|
202
|
-
<button
|
|
203
|
-
type="button"
|
|
204
|
-
onClick={decisions.loadMore}
|
|
205
|
-
disabled={decisions.isLoadingMore}
|
|
206
|
-
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"
|
|
207
|
-
>
|
|
208
|
-
{decisions.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
209
|
-
</button>
|
|
210
|
-
)}
|
|
265
|
+
<Paginator list={decisions} />
|
|
211
266
|
</>
|
|
212
267
|
)}
|
|
213
268
|
</div>
|
package/src/pages/FormsPage.tsx
CHANGED
|
@@ -2,13 +2,19 @@ import type { Form } from '@kelpie/schemas'
|
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { useNavigate } from 'react-router'
|
|
4
4
|
|
|
5
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
5
6
|
import { useCreateForm, useForms } from '../api/resources/forms.ts'
|
|
6
7
|
import { Chip } from '../components/Chip.tsx'
|
|
8
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
7
9
|
import { DataTable } from '../components/DataTable.tsx'
|
|
8
10
|
import type { Column } from '../components/DataTable.tsx'
|
|
9
11
|
import { FilterBar, PageHeader } from '../components/PageHeader.tsx'
|
|
12
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
10
13
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
11
14
|
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
15
|
+
import { formatDate } from '../lib/dates.ts'
|
|
16
|
+
import { useListView } from '../lib/listView.ts'
|
|
17
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
12
18
|
import { CONTACT_FORM_FIELDS } from './forms/template.ts'
|
|
13
19
|
|
|
14
20
|
/**
|
|
@@ -31,6 +37,10 @@ const STATUS_OPTIONS = [
|
|
|
31
37
|
{ id: 'paused' as const, label: 'Paused' },
|
|
32
38
|
]
|
|
33
39
|
|
|
40
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = ['name', 'status', 'fields', 'deal']
|
|
41
|
+
|
|
42
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
43
|
+
|
|
34
44
|
export function FormsPage(): React.JSX.Element {
|
|
35
45
|
const [term, setTerm] = useState('')
|
|
36
46
|
const [status, setStatus] = useState<StatusFilter>('all')
|
|
@@ -38,10 +48,11 @@ export function FormsPage(): React.JSX.Element {
|
|
|
38
48
|
const navigate = useNavigate()
|
|
39
49
|
const hasFilter = term.trim().length > 0 || status !== 'all'
|
|
40
50
|
const createForm = useCreateForm()
|
|
51
|
+
const timezone = useTimezone()
|
|
41
52
|
const forms = useForms({
|
|
42
53
|
term: term.trim().length > 0 ? term.trim() : undefined,
|
|
43
54
|
...(status === 'all' ? {} : { status }),
|
|
44
|
-
sort,
|
|
55
|
+
sort: serverSortOnly(sort, SERVER_SORT_KEYS),
|
|
45
56
|
})
|
|
46
57
|
|
|
47
58
|
/**
|
|
@@ -70,10 +81,17 @@ export function FormsPage(): React.JSX.Element {
|
|
|
70
81
|
</>
|
|
71
82
|
),
|
|
72
83
|
},
|
|
84
|
+
{
|
|
85
|
+
key: 'description',
|
|
86
|
+
header: 'Description',
|
|
87
|
+
getSortValue: (form) => form.description,
|
|
88
|
+
render: (form) => form.description ?? '—',
|
|
89
|
+
},
|
|
73
90
|
{
|
|
74
91
|
key: 'status',
|
|
75
92
|
header: 'Status',
|
|
76
93
|
className: 'w-28',
|
|
94
|
+
getSortValue: (form) => form.status,
|
|
77
95
|
render: (form) => (
|
|
78
96
|
<Chip tone={form.status === 'active' ? 'success' : 'neutral'}>{form.status}</Chip>
|
|
79
97
|
),
|
|
@@ -82,12 +100,14 @@ export function FormsPage(): React.JSX.Element {
|
|
|
82
100
|
key: 'fields',
|
|
83
101
|
header: 'Fields',
|
|
84
102
|
className: 'w-20',
|
|
103
|
+
getSortValue: (form) => form.fields.length,
|
|
85
104
|
render: (form) => <span className="font-mono text-[12px]">{form.fields.length}</span>,
|
|
86
105
|
},
|
|
87
106
|
{
|
|
88
107
|
key: 'deal',
|
|
89
108
|
header: 'Deal',
|
|
90
109
|
className: 'w-32',
|
|
110
|
+
getSortValue: (form) => form.createDeal,
|
|
91
111
|
render: (form) =>
|
|
92
112
|
form.createDeal ? (
|
|
93
113
|
<Chip tone="accent">Creates deal</Chip>
|
|
@@ -95,8 +115,52 @@ export function FormsPage(): React.JSX.Element {
|
|
|
95
115
|
<span className="text-ink-faint">—</span>
|
|
96
116
|
),
|
|
97
117
|
},
|
|
118
|
+
{
|
|
119
|
+
key: 'dealNameTemplate',
|
|
120
|
+
header: 'Deal name template',
|
|
121
|
+
getSortValue: (form) => form.dealNameTemplate,
|
|
122
|
+
render: (form) =>
|
|
123
|
+
form.dealNameTemplate === null ? (
|
|
124
|
+
'—'
|
|
125
|
+
) : (
|
|
126
|
+
<span className="font-mono text-[12px] text-ink-muted">{form.dealNameTemplate}</span>
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: 'thankYouMessage',
|
|
131
|
+
header: 'Thank-you',
|
|
132
|
+
getSortValue: (form) => form.thankYouMessage || null,
|
|
133
|
+
render: (form) =>
|
|
134
|
+
form.thankYouMessage.length === 0 ? '—' : (
|
|
135
|
+
<span className="text-ink-muted">{form.thankYouMessage}</span>
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
key: 'publicKey',
|
|
140
|
+
header: 'Public key',
|
|
141
|
+
getSortValue: (form) => form.publicKey,
|
|
142
|
+
render: (form) => (
|
|
143
|
+
<span className="font-mono text-[12px] text-ink-muted">{form.publicKey}</span>
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: 'createdAt',
|
|
148
|
+
header: 'Created',
|
|
149
|
+
sortKey: 'created_at',
|
|
150
|
+
render: (form) => formatDate(form.createdAt, timezone),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: 'updatedAt',
|
|
154
|
+
header: 'Updated',
|
|
155
|
+
sortKey: 'updated_at',
|
|
156
|
+
render: (form) => formatDate(form.updatedAt, timezone),
|
|
157
|
+
},
|
|
98
158
|
]
|
|
99
159
|
|
|
160
|
+
const supportedKeys = columns.map((column) => column.key)
|
|
161
|
+
const listView = useListView('forms', supportedKeys, DEFAULT_VISIBLE_KEYS)
|
|
162
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
163
|
+
|
|
100
164
|
return (
|
|
101
165
|
<div className="animate-fade-in">
|
|
102
166
|
<PageHeader
|
|
@@ -105,12 +169,19 @@ export function FormsPage(): React.JSX.Element {
|
|
|
105
169
|
onAdd={addForm}
|
|
106
170
|
addLabel="New form"
|
|
107
171
|
actions={
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
<>
|
|
173
|
+
<SegmentedControl
|
|
174
|
+
value={status}
|
|
175
|
+
onChange={setStatus}
|
|
176
|
+
options={STATUS_OPTIONS}
|
|
177
|
+
ariaLabel="Filter by status"
|
|
178
|
+
/>
|
|
179
|
+
<ColumnPicker
|
|
180
|
+
options={pickerOptions}
|
|
181
|
+
visibleKeys={listView.visibleKeys}
|
|
182
|
+
onChange={listView.setVisibleKeys}
|
|
183
|
+
/>
|
|
184
|
+
</>
|
|
114
185
|
}
|
|
115
186
|
/>
|
|
116
187
|
<FilterBar value={term} onChange={setTerm} placeholder="Filter forms…" />
|
|
@@ -123,6 +194,7 @@ export function FormsPage(): React.JSX.Element {
|
|
|
123
194
|
<LoadingPanel label="Loading forms…" />
|
|
124
195
|
) : (
|
|
125
196
|
<>
|
|
197
|
+
<Paginator list={forms} placement="top" />
|
|
126
198
|
<DataTable
|
|
127
199
|
columns={columns}
|
|
128
200
|
rows={forms.records}
|
|
@@ -133,17 +205,9 @@ export function FormsPage(): React.JSX.Element {
|
|
|
133
205
|
emptyAction={hasFilter ? undefined : { label: 'New form', onClick: addForm }}
|
|
134
206
|
sort={sort}
|
|
135
207
|
onSortChange={setSort}
|
|
208
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
136
209
|
/>
|
|
137
|
-
{forms
|
|
138
|
-
<button
|
|
139
|
-
type="button"
|
|
140
|
-
onClick={forms.loadMore}
|
|
141
|
-
disabled={forms.isLoadingMore}
|
|
142
|
-
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"
|
|
143
|
-
>
|
|
144
|
-
{forms.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
145
|
-
</button>
|
|
146
|
-
)}
|
|
210
|
+
<Paginator list={forms} />
|
|
147
211
|
</>
|
|
148
212
|
)}
|
|
149
213
|
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { PipelineStage, Raise } from '@kelpie/schemas'
|
|
2
|
-
import { useState } from 'react'
|
|
3
2
|
import { Link, useNavigate } from 'react-router'
|
|
4
3
|
|
|
5
4
|
import { useCompanies } from '../api/resources/companies.ts'
|
|
@@ -7,17 +6,22 @@ import { useMembers } from '../api/resources/members.ts'
|
|
|
7
6
|
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
8
7
|
import { MAX_PAGE_SIZE, usePlanItems } from '../api/resources/planItems.ts'
|
|
9
8
|
import { useCreateRaise, useRaises, useUpdateRaise } from '../api/resources/raises.ts'
|
|
9
|
+
import { useTimezone } from '../api/resources/account.ts'
|
|
10
10
|
import { Chip } from '../components/Chip.tsx'
|
|
11
11
|
import type { ChipTone } from '../components/Chip.tsx'
|
|
12
|
+
import { ColumnPicker } from '../components/ColumnPicker.tsx'
|
|
12
13
|
import { DataTable } from '../components/DataTable.tsx'
|
|
13
14
|
import type { Column, DataTableGroup } from '../components/DataTable.tsx'
|
|
14
15
|
import { KanbanBoard } from '../components/KanbanBoard.tsx'
|
|
15
16
|
import { PageHeader } from '../components/PageHeader.tsx'
|
|
17
|
+
import { Paginator } from '../components/Paginator.tsx'
|
|
16
18
|
import { ErrorPanel, LoadingPanel } from '../components/QueryState.tsx'
|
|
17
19
|
import { SegmentedControl } from '../components/SegmentedControl.tsx'
|
|
18
|
-
import { formatDay } from '../lib/dates.ts'
|
|
20
|
+
import { formatDate, formatDay } from '../lib/dates.ts'
|
|
21
|
+
import { useListView } from '../lib/listView.ts'
|
|
19
22
|
import { formatMoney } from '../lib/money.ts'
|
|
20
23
|
import { DUE_BUCKETS, byDateThenTitle, dueBucketFor, nextOpenByTarget } from '../lib/plan.ts'
|
|
24
|
+
import { serverSortOnly } from '../lib/sort.ts'
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* The Fundraising board: one raise per firm per round, grouped by stage. The
|
|
@@ -29,6 +33,34 @@ type BoardView = 'list' | 'columns'
|
|
|
29
33
|
type PipelineScope = 'open' | 'all'
|
|
30
34
|
type ListGrouping = 'stage' | 'due'
|
|
31
35
|
|
|
36
|
+
const SCOPES: readonly PipelineScope[] = ['open', 'all']
|
|
37
|
+
const GROUPINGS: readonly ListGrouping[] = ['stage', 'due']
|
|
38
|
+
|
|
39
|
+
function readScope(stored: string | undefined): PipelineScope {
|
|
40
|
+
return SCOPES.includes(stored as PipelineScope) ? (stored as PipelineScope) : 'open'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readGrouping(stored: string | undefined): ListGrouping {
|
|
44
|
+
return GROUPINGS.includes(stored as ListGrouping) ? (stored as ListGrouping) : 'stage'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const COLUMN_KEYS: readonly string[] = [
|
|
48
|
+
'name',
|
|
49
|
+
'company',
|
|
50
|
+
'stage',
|
|
51
|
+
'check',
|
|
52
|
+
'currency',
|
|
53
|
+
'thesisFit',
|
|
54
|
+
'passReason',
|
|
55
|
+
'nextPlan',
|
|
56
|
+
'owner',
|
|
57
|
+
'close',
|
|
58
|
+
'tags',
|
|
59
|
+
'summary',
|
|
60
|
+
'createdAt',
|
|
61
|
+
'updatedAt',
|
|
62
|
+
]
|
|
63
|
+
|
|
32
64
|
/** Tones keyed by the seeded slugs. A renamed stage keeps its slug and its tone. */
|
|
33
65
|
const STAGE_TONES: Readonly<Record<string, ChipTone>> = {
|
|
34
66
|
closed: 'success',
|
|
@@ -43,19 +75,33 @@ function stageTone(stage: PipelineStage): ChipTone {
|
|
|
43
75
|
return STAGE_TONES[stage.slug] ?? 'neutral'
|
|
44
76
|
}
|
|
45
77
|
|
|
78
|
+
const DEFAULT_VISIBLE_KEYS: readonly string[] = [
|
|
79
|
+
'name',
|
|
80
|
+
'company',
|
|
81
|
+
'check',
|
|
82
|
+
'nextPlan',
|
|
83
|
+
'owner',
|
|
84
|
+
'close',
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
const SERVER_SORT_KEYS: readonly string[] = ['name', 'created_at', 'updated_at']
|
|
88
|
+
|
|
46
89
|
export function FundraisingPage(): React.JSX.Element {
|
|
47
90
|
const navigate = useNavigate()
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
91
|
+
|
|
92
|
+
const listView = useListView('raises', COLUMN_KEYS, DEFAULT_VISIBLE_KEYS)
|
|
93
|
+
const view: BoardView = listView.mode ?? 'columns'
|
|
94
|
+
const scope = readScope(listView.scope)
|
|
95
|
+
const grouping = readGrouping(listView.grouping)
|
|
96
|
+
const sort = listView.sort
|
|
52
97
|
|
|
53
98
|
const stages = usePipelineStages('raise')
|
|
54
|
-
const raises = useRaises({ sort })
|
|
99
|
+
const raises = useRaises({ sort: serverSortOnly(sort, SERVER_SORT_KEYS) })
|
|
55
100
|
const companies = useCompanies({ limit: 200 })
|
|
56
101
|
const members = useMembers()
|
|
57
102
|
const createRaise = useCreateRaise()
|
|
58
103
|
const updateRaise = useUpdateRaise()
|
|
104
|
+
const timezone = useTimezone()
|
|
59
105
|
|
|
60
106
|
const allStages = [...stages.records].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
61
107
|
const visibleStages = scope === 'open' ? allStages.filter((stage) => stage.open) : allStages
|
|
@@ -76,7 +122,7 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
76
122
|
{ enabled: askedIds.length > 0 },
|
|
77
123
|
)
|
|
78
124
|
const nextPlanByRaise = nextOpenByTarget(planItems.records)
|
|
79
|
-
const plansTruncated = visibleRaises.length > MAX_PAGE_SIZE || planItems.
|
|
125
|
+
const plansTruncated = visibleRaises.length > MAX_PAGE_SIZE || planItems.hasNext
|
|
80
126
|
|
|
81
127
|
const companyNameById = new Map(companies.records.map((company) => [company.id, company.name]))
|
|
82
128
|
|
|
@@ -95,6 +141,8 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
95
141
|
await navigate(`/fundraising/${raise.id}`)
|
|
96
142
|
}
|
|
97
143
|
|
|
144
|
+
const stageLabelById = new Map(allStages.map((stage) => [stage.id, stage.label]))
|
|
145
|
+
|
|
98
146
|
const columns: readonly Column<Raise>[] = [
|
|
99
147
|
{
|
|
100
148
|
key: 'name',
|
|
@@ -105,12 +153,20 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
105
153
|
{
|
|
106
154
|
key: 'company',
|
|
107
155
|
header: 'Firm',
|
|
156
|
+
getSortValue: (raise) => companyNameById.get(raise.companyId) ?? null,
|
|
108
157
|
render: (raise) => companyNameById.get(raise.companyId) ?? '—',
|
|
109
158
|
},
|
|
159
|
+
{
|
|
160
|
+
key: 'stage',
|
|
161
|
+
header: 'Stage',
|
|
162
|
+
getSortValue: (raise) => stageLabelById.get(raise.stageId) ?? null,
|
|
163
|
+
render: (raise) => stageLabelById.get(raise.stageId) ?? '—',
|
|
164
|
+
},
|
|
110
165
|
{
|
|
111
166
|
key: 'check',
|
|
112
167
|
header: 'Check',
|
|
113
168
|
className: 'w-28',
|
|
169
|
+
getSortValue: (raise) => raise.checkSizeCents,
|
|
114
170
|
render: (raise) =>
|
|
115
171
|
raise.checkSizeCents === null ? (
|
|
116
172
|
'—'
|
|
@@ -120,9 +176,34 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
120
176
|
</span>
|
|
121
177
|
),
|
|
122
178
|
},
|
|
179
|
+
{
|
|
180
|
+
key: 'currency',
|
|
181
|
+
header: 'Currency',
|
|
182
|
+
getSortValue: (raise) => raise.currency,
|
|
183
|
+
render: (raise) => raise.currency ?? '—',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
key: 'thesisFit',
|
|
187
|
+
header: 'Thesis fit',
|
|
188
|
+
getSortValue: (raise) => raise.thesisFit || null,
|
|
189
|
+
render: (raise) =>
|
|
190
|
+
raise.thesisFit.length === 0 ? '—' : (
|
|
191
|
+
<span className="text-ink-muted">{raise.thesisFit}</span>
|
|
192
|
+
),
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
key: 'passReason',
|
|
196
|
+
header: 'Pass reason',
|
|
197
|
+
getSortValue: (raise) => raise.passReason,
|
|
198
|
+
render: (raise) =>
|
|
199
|
+
raise.passReason === null ? '—' : (
|
|
200
|
+
<span className="text-ink-muted">{raise.passReason}</span>
|
|
201
|
+
),
|
|
202
|
+
},
|
|
123
203
|
{
|
|
124
204
|
key: 'nextPlan',
|
|
125
205
|
header: 'Next plan',
|
|
206
|
+
getSortValue: (raise) => nextPlanByRaise.get(raise.id)?.date ?? null,
|
|
126
207
|
render: (raise) => {
|
|
127
208
|
const next = nextPlanByRaise.get(raise.id)
|
|
128
209
|
|
|
@@ -143,6 +224,8 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
143
224
|
{
|
|
144
225
|
key: 'owner',
|
|
145
226
|
header: 'Owner',
|
|
227
|
+
getSortValue: (raise) =>
|
|
228
|
+
raise.ownerId === null ? null : (members.nameById.get(raise.ownerId) ?? 'Unknown'),
|
|
146
229
|
render: (raise) =>
|
|
147
230
|
raise.ownerId === null ? '—' : (members.nameById.get(raise.ownerId) ?? 'Unknown'),
|
|
148
231
|
},
|
|
@@ -150,14 +233,51 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
150
233
|
key: 'close',
|
|
151
234
|
header: 'Close',
|
|
152
235
|
className: 'w-28',
|
|
236
|
+
getSortValue: (raise) => raise.expectedClose,
|
|
153
237
|
render: (raise) => (
|
|
154
238
|
<span className="font-mono text-[12px] text-ink-muted">
|
|
155
239
|
{raise.expectedClose === null ? '—' : formatDay(raise.expectedClose)}
|
|
156
240
|
</span>
|
|
157
241
|
),
|
|
158
242
|
},
|
|
243
|
+
{
|
|
244
|
+
key: 'tags',
|
|
245
|
+
header: 'Tags',
|
|
246
|
+
getSortValue: (raise) => raise.tags.join(', ') || null,
|
|
247
|
+
render: (raise) =>
|
|
248
|
+
raise.tags.length === 0 ? '—' : (
|
|
249
|
+
<span className="flex flex-wrap gap-1">
|
|
250
|
+
{raise.tags.map((tag) => (
|
|
251
|
+
<Chip key={tag}>
|
|
252
|
+
<span className="text-[10px]">{tag}</span>
|
|
253
|
+
</Chip>
|
|
254
|
+
))}
|
|
255
|
+
</span>
|
|
256
|
+
),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
key: 'summary',
|
|
260
|
+
header: 'Summary',
|
|
261
|
+
getSortValue: (raise) => raise.summary || null,
|
|
262
|
+
render: (raise) =>
|
|
263
|
+
raise.summary.length === 0 ? '—' : <span className="text-ink-muted">{raise.summary}</span>,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
key: 'createdAt',
|
|
267
|
+
header: 'Created',
|
|
268
|
+
sortKey: 'created_at',
|
|
269
|
+
render: (raise) => formatDate(raise.createdAt, timezone),
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: 'updatedAt',
|
|
273
|
+
header: 'Updated',
|
|
274
|
+
sortKey: 'updated_at',
|
|
275
|
+
render: (raise) => formatDate(raise.updatedAt, timezone),
|
|
276
|
+
},
|
|
159
277
|
]
|
|
160
278
|
|
|
279
|
+
const pickerOptions = columns.map((column) => ({ key: column.key, label: column.header }))
|
|
280
|
+
|
|
161
281
|
const stageGroups: readonly DataTableGroup<Raise>[] = visibleStages.map((stage) => ({
|
|
162
282
|
id: stage.id,
|
|
163
283
|
label: <Chip tone={stageTone(stage)}>{stage.label}</Chip>,
|
|
@@ -221,7 +341,9 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
221
341
|
<SegmentedControl
|
|
222
342
|
ariaLabel="Stage scope"
|
|
223
343
|
value={scope}
|
|
224
|
-
onChange={
|
|
344
|
+
onChange={(next) => {
|
|
345
|
+
listView.setScope(next)
|
|
346
|
+
}}
|
|
225
347
|
options={[
|
|
226
348
|
{ id: 'open', label: 'Open' },
|
|
227
349
|
{ id: 'all', label: 'All' },
|
|
@@ -231,7 +353,9 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
231
353
|
<SegmentedControl
|
|
232
354
|
ariaLabel="Group the list"
|
|
233
355
|
value={grouping}
|
|
234
|
-
onChange={
|
|
356
|
+
onChange={(next) => {
|
|
357
|
+
listView.setGrouping(next)
|
|
358
|
+
}}
|
|
235
359
|
options={[
|
|
236
360
|
{ id: 'stage', label: 'By stage' },
|
|
237
361
|
{ id: 'due', label: 'By due' },
|
|
@@ -241,12 +365,21 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
241
365
|
<SegmentedControl
|
|
242
366
|
ariaLabel="Board or list"
|
|
243
367
|
value={view}
|
|
244
|
-
onChange={
|
|
368
|
+
onChange={(next) => {
|
|
369
|
+
listView.setMode(next)
|
|
370
|
+
}}
|
|
245
371
|
options={[
|
|
246
372
|
{ id: 'list', label: 'List' },
|
|
247
373
|
{ id: 'columns', label: 'Board' },
|
|
248
374
|
]}
|
|
249
375
|
/>
|
|
376
|
+
{view === 'list' && (
|
|
377
|
+
<ColumnPicker
|
|
378
|
+
options={pickerOptions}
|
|
379
|
+
visibleKeys={listView.visibleKeys}
|
|
380
|
+
onChange={listView.setVisibleKeys}
|
|
381
|
+
/>
|
|
382
|
+
)}
|
|
250
383
|
</>
|
|
251
384
|
}
|
|
252
385
|
/>
|
|
@@ -269,6 +402,7 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
269
402
|
<LoadingPanel label="Loading raises…" />
|
|
270
403
|
) : (
|
|
271
404
|
<>
|
|
405
|
+
{view === 'list' && <Paginator list={raises} placement="top" />}
|
|
272
406
|
{view === 'columns' ? (
|
|
273
407
|
<KanbanBoard
|
|
274
408
|
stages={visibleStages.map((stage) => ({ id: stage.id, label: stage.label }))}
|
|
@@ -313,13 +447,16 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
313
447
|
? {
|
|
314
448
|
label: 'Show all raises',
|
|
315
449
|
onClick: () => {
|
|
316
|
-
setScope('all')
|
|
450
|
+
listView.setScope('all')
|
|
317
451
|
},
|
|
318
452
|
}
|
|
319
453
|
: undefined
|
|
320
454
|
}
|
|
321
455
|
sort={sort}
|
|
322
|
-
onSortChange={
|
|
456
|
+
onSortChange={(next) => {
|
|
457
|
+
listView.setSort(next)
|
|
458
|
+
}}
|
|
459
|
+
visibleColumnKeys={listView.visibleKeys}
|
|
323
460
|
/>
|
|
324
461
|
)}
|
|
325
462
|
{updateRaise.error !== null && (
|
|
@@ -327,17 +464,8 @@ export function FundraisingPage(): React.JSX.Element {
|
|
|
327
464
|
<ErrorPanel error={updateRaise.error} />
|
|
328
465
|
</div>
|
|
329
466
|
)}
|
|
330
|
-
{
|
|
331
|
-
|
|
332
|
-
type="button"
|
|
333
|
-
onClick={raises.loadMore}
|
|
334
|
-
disabled={raises.isLoadingMore}
|
|
335
|
-
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"
|
|
336
|
-
>
|
|
337
|
-
{raises.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
338
|
-
</button>
|
|
339
|
-
)}
|
|
340
|
-
{companies.hasMore && (
|
|
467
|
+
{view === 'list' && <Paginator list={raises} />}
|
|
468
|
+
{companies.hasNext && (
|
|
341
469
|
<p className="mt-2 text-[11px] text-ink-faint">
|
|
342
470
|
More companies exist than one page returns, so some firm names may show as “—”.
|
|
343
471
|
</p>
|