@kelpie/ui 0.3.0 → 0.4.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/ApiProvider.d.ts +3 -3
- package/dist/api/ApiProvider.d.ts.map +1 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +4 -1
- package/dist/api/client.js.map +1 -1
- package/dist/api/resources/agentTasks.d.ts +2 -2
- package/dist/api/resources/agentTasks.d.ts.map +1 -1
- package/dist/api/resources/candidates.d.ts +4 -4
- package/dist/api/resources/candidates.d.ts.map +1 -1
- package/dist/api/resources/companies.d.ts +4 -4
- package/dist/api/resources/companies.d.ts.map +1 -1
- package/dist/api/resources/deals.d.ts +6 -6
- package/dist/api/resources/deals.d.ts.map +1 -1
- package/dist/api/resources/decisions.d.ts +3 -3
- package/dist/api/resources/decisions.d.ts.map +1 -1
- package/dist/api/resources/forms.d.ts +3 -3
- package/dist/api/resources/forms.d.ts.map +1 -1
- package/dist/api/resources/handbookPages.d.ts +3 -3
- package/dist/api/resources/handbookPages.d.ts.map +1 -1
- package/dist/api/resources/notes.d.ts +2 -2
- package/dist/api/resources/notes.d.ts.map +1 -1
- package/dist/api/resources/opportunities.d.ts +6 -6
- package/dist/api/resources/opportunities.d.ts.map +1 -1
- package/dist/api/resources/partnerships.d.ts +7 -7
- package/dist/api/resources/partnerships.d.ts.map +1 -1
- package/dist/api/resources/people.d.ts +4 -4
- package/dist/api/resources/people.d.ts.map +1 -1
- package/dist/api/resources/planItems.d.ts +6 -6
- package/dist/api/resources/planItems.d.ts.map +1 -1
- package/dist/api/resources/positions.d.ts +3 -3
- package/dist/api/resources/positions.d.ts.map +1 -1
- package/dist/api/resources/raises.d.ts +6 -6
- package/dist/api/resources/raises.d.ts.map +1 -1
- package/dist/api/resources/roles.d.ts +3 -3
- package/dist/api/resources/roles.d.ts.map +1 -1
- package/dist/api/resources/webhooks.d.ts +2 -2
- package/dist/api/resources/webhooks.d.ts.map +1 -1
- package/dist/app/KelpieApp.d.ts.map +1 -1
- package/dist/app/KelpieApp.js +16 -1
- package/dist/app/KelpieApp.js.map +1 -1
- package/dist/components/DataTable.d.ts +10 -10
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/EntitySearch.d.ts +10 -10
- package/dist/components/EntitySearch.d.ts.map +1 -1
- package/dist/components/KanbanBoard.d.ts +2 -2
- package/dist/components/KanbanBoard.d.ts.map +1 -1
- package/dist/components/Shell.d.ts.map +1 -1
- package/dist/components/Shell.js +3 -16
- package/dist/components/Shell.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/account/AccountLayout.d.ts.map +1 -1
- package/dist/pages/account/AccountLayout.js +15 -7
- package/dist/pages/account/AccountLayout.js.map +1 -1
- package/dist/registry/context.d.ts +1 -2
- package/dist/registry/context.d.ts.map +1 -1
- package/dist/registry/context.js +0 -3
- package/dist/registry/context.js.map +1 -1
- package/dist/registry/contributions.d.ts +1 -12
- package/dist/registry/contributions.d.ts.map +1 -1
- package/dist/registry/contributions.js +0 -7
- package/dist/registry/contributions.js.map +1 -1
- package/dist/registry/registry.d.ts +1 -3
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +0 -6
- package/dist/registry/registry.js.map +1 -1
- package/dist/registry/visibleNav.d.ts +3 -0
- package/dist/registry/visibleNav.d.ts.map +1 -0
- package/dist/registry/visibleNav.js +27 -0
- package/dist/registry/visibleNav.js.map +1 -0
- package/package.json +2 -2
- package/src/api/ApiProvider.tsx +3 -3
- package/src/api/client.ts +4 -1
- package/src/api/resources/agentTasks.ts +2 -2
- package/src/api/resources/candidates.ts +4 -4
- package/src/api/resources/companies.ts +4 -4
- package/src/api/resources/deals.ts +6 -6
- package/src/api/resources/decisions.ts +3 -3
- package/src/api/resources/forms.ts +3 -3
- package/src/api/resources/handbookPages.ts +3 -3
- package/src/api/resources/notes.ts +2 -2
- package/src/api/resources/opportunities.ts +6 -6
- package/src/api/resources/partnerships.ts +7 -7
- package/src/api/resources/people.ts +4 -4
- package/src/api/resources/planItems.ts +6 -6
- package/src/api/resources/positions.ts +3 -3
- package/src/api/resources/raises.ts +6 -6
- package/src/api/resources/roles.ts +3 -3
- package/src/api/resources/webhooks.ts +2 -2
- package/src/app/KelpieApp.tsx +26 -1
- package/src/components/DataTable.tsx +12 -12
- package/src/components/EntitySearch.tsx +10 -10
- package/src/components/KanbanBoard.tsx +2 -2
- package/src/components/Shell.tsx +3 -22
- package/src/index.ts +1 -4
- package/src/pages/account/AccountLayout.tsx +20 -9
- package/src/registry/context.ts +0 -5
- package/src/registry/contributions.ts +1 -20
- package/src/registry/registry.ts +0 -11
- package/src/registry/visibleNav.ts +38 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useModuleSettings } from '../api/resources/moduleSettings.js';
|
|
2
|
+
import { inSlotOrder } from './registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* Core's nav items merged with a slot's module contributions, ordered, with the
|
|
5
|
+
* entries belonging to a disabled module removed.
|
|
6
|
+
*
|
|
7
|
+
* One hook rather than the rule written out at each nav surface. The shell's
|
|
8
|
+
* sidebar and the account tab strip both render a slot, and a workspace that
|
|
9
|
+
* switched a module off should stop seeing it in both. Two copies of this
|
|
10
|
+
* filter is exactly the sort of pair that drifts, and the symptom is a tab that
|
|
11
|
+
* survives its module being disabled and leads to a page answering 403.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* A nav item whose id does not match its module id. Everything else looks itself
|
|
15
|
+
* up directly, which is what keeps a toggleable module's own nav entry working
|
|
16
|
+
* with no change here when one is added later.
|
|
17
|
+
*/
|
|
18
|
+
const NAV_ID_TO_MODULE_ID = {
|
|
19
|
+
fundraising: 'raises',
|
|
20
|
+
data: 'import-export',
|
|
21
|
+
};
|
|
22
|
+
export function useVisibleNavItems(coreItems, moduleItems) {
|
|
23
|
+
const { settings } = useModuleSettings();
|
|
24
|
+
const disabledModuleIds = new Set(settings.filter((setting) => !setting.enabled).map((setting) => setting.moduleId));
|
|
25
|
+
return inSlotOrder([...coreItems, ...moduleItems]).filter((item) => !disabledModuleIds.has(NAV_ID_TO_MODULE_ID[item.id] ?? item.id));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=visibleNav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visibleNav.js","sourceRoot":"","sources":["../../src/registry/visibleNav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAA;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,MAAM,mBAAmB,GAAqC;IAC5D,WAAW,EAAE,QAAQ;IACrB,IAAI,EAAE,eAAe;CACtB,CAAA;AAED,MAAM,UAAU,kBAAkB,CAChC,SAA6B,EAC7B,WAA+B;IAE/B,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACxC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAC/B,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAClF,CAAA;IAED,OAAO,WAAW,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CACvD,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAC1E,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kelpie/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React UI for Kelpie: components, pages, API client, and the extension registry an assembly composes modules through.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kelpie",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@dnd-kit/core": "^6.3.1",
|
|
49
49
|
"@dnd-kit/sortable": "^10.0.0",
|
|
50
50
|
"@dnd-kit/utilities": "^3.2.2",
|
|
51
|
-
"@kelpie/schemas": "^0.
|
|
51
|
+
"@kelpie/schemas": "^0.4.0",
|
|
52
52
|
"@tanstack/react-query": "^5.101.4",
|
|
53
53
|
"react-markdown": "^10.1.0",
|
|
54
54
|
"react-router": "^8.3.0",
|
package/src/api/ApiProvider.tsx
CHANGED
|
@@ -10,10 +10,10 @@ import { createQueryClient } from './queryClient.ts'
|
|
|
10
10
|
|
|
11
11
|
export interface ApiProviderProps {
|
|
12
12
|
/** Origin plus base path, e.g. `/v1` in the browser. */
|
|
13
|
-
readonly baseUrl?: string
|
|
13
|
+
readonly baseUrl?: string | undefined
|
|
14
14
|
/** A prepared client, for tests and for assemblies that construct their own. */
|
|
15
|
-
readonly client?: ApiClient
|
|
16
|
-
readonly queryClient?: QueryClient
|
|
15
|
+
readonly client?: ApiClient | undefined
|
|
16
|
+
readonly queryClient?: QueryClient | undefined
|
|
17
17
|
readonly children: ReactNode
|
|
18
18
|
}
|
|
19
19
|
|
package/src/api/client.ts
CHANGED
|
@@ -173,7 +173,10 @@ export function createApiClient(options: ApiClientOptions): ApiClient {
|
|
|
173
173
|
method,
|
|
174
174
|
credentials: 'same-origin',
|
|
175
175
|
headers: headersFor(body),
|
|
176
|
-
body:
|
|
176
|
+
// Spread rather than `body: undefined`. `RequestInit` comes from the DOM
|
|
177
|
+
// library, where `body` is optional and does not accept an explicit
|
|
178
|
+
// `undefined`, and it is not ours to widen. A GET carries no body at all.
|
|
179
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
177
180
|
})
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -106,8 +106,8 @@ const runs = createReadOnlyResourceHooks<AgentRun>({
|
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
export interface AgentRunFilters {
|
|
109
|
-
readonly agentId?: string
|
|
110
|
-
readonly status?: AgentRunStatus
|
|
109
|
+
readonly agentId?: string | undefined
|
|
110
|
+
readonly status?: AgentRunStatus | undefined
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export function useAgentRuns(
|
|
@@ -32,12 +32,12 @@ const candidates = createResourceHooks<Candidate, CreateCandidateInput, Candidat
|
|
|
32
32
|
/** The documented filters on `GET /v1/candidates`. */
|
|
33
33
|
export interface CandidateFilters {
|
|
34
34
|
/** Candidacies on any of these roles. Repeats on the wire. */
|
|
35
|
-
readonly roleIds?: readonly string[]
|
|
35
|
+
readonly roleIds?: readonly string[] | undefined
|
|
36
36
|
/** Candidacies held by any of these people. Repeats on the wire. */
|
|
37
|
-
readonly personIds?: readonly string[]
|
|
37
|
+
readonly personIds?: readonly string[] | undefined
|
|
38
38
|
/** Candidacies in any of these pipeline states. Repeats on the wire. */
|
|
39
|
-
readonly statuses?: readonly CandidateStatus[]
|
|
40
|
-
readonly limit?: number
|
|
39
|
+
readonly statuses?: readonly CandidateStatus[] | undefined
|
|
40
|
+
readonly limit?: number | undefined
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function candidateQuery(filters: CandidateFilters): QueryParameters {
|
|
@@ -31,12 +31,12 @@ const companies = createResourceHooks<Company, CreateCompanyInput, CompanyInput>
|
|
|
31
31
|
/** The documented filters on `GET /v1/companies`. */
|
|
32
32
|
export interface CompanyFilters {
|
|
33
33
|
/** Matches name, domain, industry, summary, account type, and tags. */
|
|
34
|
-
readonly term?: string
|
|
34
|
+
readonly term?: string | undefined
|
|
35
35
|
/** Companies where any of these people holds a position. Repeats on the wire. */
|
|
36
|
-
readonly personIds?: readonly string[]
|
|
37
|
-
readonly limit?: number
|
|
36
|
+
readonly personIds?: readonly string[] | undefined
|
|
37
|
+
readonly limit?: number | undefined
|
|
38
38
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
39
|
-
readonly sort?: string
|
|
39
|
+
readonly sort?: string | undefined
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function companyQuery(filters: CompanyFilters): QueryParameters {
|
|
@@ -35,16 +35,16 @@ const deals = createResourceHooks<Deal, CreateDealInput, DealInput>({
|
|
|
35
35
|
/** The documented filters on `GET /v1/deals`. */
|
|
36
36
|
export interface DealFilters {
|
|
37
37
|
/** Matches the deal's name, summary, tags, competitors, and its company's name. */
|
|
38
|
-
readonly term?: string
|
|
38
|
+
readonly term?: string | undefined
|
|
39
39
|
/** Deals at any of these companies. Repeats on the wire. */
|
|
40
|
-
readonly companyIds?: readonly string[]
|
|
40
|
+
readonly companyIds?: readonly string[] | undefined
|
|
41
41
|
/** Deals sitting in any of these stages. Repeats on the wire. */
|
|
42
|
-
readonly stageIds?: readonly string[]
|
|
42
|
+
readonly stageIds?: readonly string[] | undefined
|
|
43
43
|
/** Deals any of these people are on. Repeats on the wire. */
|
|
44
|
-
readonly personIds?: readonly string[]
|
|
45
|
-
readonly limit?: number
|
|
44
|
+
readonly personIds?: readonly string[] | undefined
|
|
45
|
+
readonly limit?: number | undefined
|
|
46
46
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
47
|
-
readonly sort?: string
|
|
47
|
+
readonly sort?: string | undefined
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function dealQuery(filters: DealFilters): QueryParameters {
|
|
@@ -29,10 +29,10 @@ const decisions = createResourceHooks<Decision, CreateDecisionInput, DecisionInp
|
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
export interface DecisionFilters {
|
|
32
|
-
readonly targetType?: RecordTargetType
|
|
33
|
-
readonly targetId?: string
|
|
32
|
+
readonly targetType?: RecordTargetType | undefined
|
|
33
|
+
readonly targetId?: string | undefined
|
|
34
34
|
/** Matches the body, the rationale, the target type, and the target's name. */
|
|
35
|
-
readonly term?: string
|
|
35
|
+
readonly term?: string | undefined
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function decisionQuery(filters: DecisionFilters): QueryParameters {
|
|
@@ -60,10 +60,10 @@ const forms = createResourceHooks<Form, CreateFormInput, FormSettingsInput>({
|
|
|
60
60
|
|
|
61
61
|
export interface FormFilters {
|
|
62
62
|
/** Matches a form's name and its description. */
|
|
63
|
-
readonly term?: string
|
|
64
|
-
readonly status?: 'active' | 'paused'
|
|
63
|
+
readonly term?: string | undefined
|
|
64
|
+
readonly status?: 'active' | 'paused' | undefined
|
|
65
65
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
66
|
-
readonly sort?: string
|
|
66
|
+
readonly sort?: string | undefined
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function formQuery(filters: FormFilters): QueryParameters {
|
|
@@ -26,10 +26,10 @@ const pages = createResourceHooks<HandbookPage, CreateHandbookPageInput, Handboo
|
|
|
26
26
|
/** The documented filters on `GET /v1/handbook_pages`. */
|
|
27
27
|
export interface HandbookPageFilters {
|
|
28
28
|
/** Matches a page's title and its body. A handbook is searched for what it says. */
|
|
29
|
-
readonly term?: string
|
|
29
|
+
readonly term?: string | undefined
|
|
30
30
|
/** Pages at these slugs. Repeats on the wire. */
|
|
31
|
-
readonly slugs?: readonly string[]
|
|
32
|
-
readonly limit?: number
|
|
31
|
+
readonly slugs?: readonly string[] | undefined
|
|
32
|
+
readonly limit?: number | undefined
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function handbookQuery(filters: HandbookPageFilters): QueryParameters {
|
|
@@ -32,8 +32,8 @@ export interface NoteFilters {
|
|
|
32
32
|
readonly targetType: RecordTargetType
|
|
33
33
|
/** Notes on any of these records. Repeats on the wire; all one target type. */
|
|
34
34
|
readonly targetIds: readonly string[]
|
|
35
|
-
readonly pinned?: boolean
|
|
36
|
-
readonly limit?: number
|
|
35
|
+
readonly pinned?: boolean | undefined
|
|
36
|
+
readonly limit?: number | undefined
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function noteQuery(filters: NoteFilters): QueryParameters {
|
|
@@ -34,16 +34,16 @@ const opportunities = createResourceHooks<Opportunity, CreateOpportunityInput, O
|
|
|
34
34
|
/** The documented filters on `GET /v1/opportunities`. */
|
|
35
35
|
export interface OpportunityFilters {
|
|
36
36
|
/** Matches the opportunity's name, kind, summary, tags, and its company's name. */
|
|
37
|
-
readonly term?: string
|
|
37
|
+
readonly term?: string | undefined
|
|
38
38
|
/** Exact kinds ("Grant", "Accelerator", …). Repeats on the wire. */
|
|
39
|
-
readonly kinds?: readonly string[]
|
|
39
|
+
readonly kinds?: readonly string[] | undefined
|
|
40
40
|
/** Opportunities at any of these companies. Repeats on the wire. */
|
|
41
|
-
readonly companyIds?: readonly string[]
|
|
41
|
+
readonly companyIds?: readonly string[] | undefined
|
|
42
42
|
/** Opportunities sitting in any of these stages. Repeats on the wire. */
|
|
43
|
-
readonly stageIds?: readonly string[]
|
|
44
|
-
readonly limit?: number
|
|
43
|
+
readonly stageIds?: readonly string[] | undefined
|
|
44
|
+
readonly limit?: number | undefined
|
|
45
45
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
46
|
-
readonly sort?: string
|
|
46
|
+
readonly sort?: string | undefined
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function opportunityQuery(filters: OpportunityFilters): QueryParameters {
|
|
@@ -36,18 +36,18 @@ const partnerships = createResourceHooks<Partnership, CreatePartnershipInput, Pa
|
|
|
36
36
|
/** The documented filters on `GET /v1/partnerships`. */
|
|
37
37
|
export interface PartnershipFilters {
|
|
38
38
|
/** Matches the partnership's name, kind, summary, tags, and its company's name. */
|
|
39
|
-
readonly term?: string
|
|
39
|
+
readonly term?: string | undefined
|
|
40
40
|
/** Exact kinds ("Integration", "Co-marketing", …). Repeats on the wire. */
|
|
41
|
-
readonly kinds?: readonly string[]
|
|
41
|
+
readonly kinds?: readonly string[] | undefined
|
|
42
42
|
/** Partnerships with any of these companies. Repeats on the wire. */
|
|
43
|
-
readonly companyIds?: readonly string[]
|
|
43
|
+
readonly companyIds?: readonly string[] | undefined
|
|
44
44
|
/** Partnerships sitting in any of these stages. Repeats on the wire. */
|
|
45
|
-
readonly stageIds?: readonly string[]
|
|
45
|
+
readonly stageIds?: readonly string[] | undefined
|
|
46
46
|
/** Partnerships any of these people are on. Repeats on the wire. */
|
|
47
|
-
readonly personIds?: readonly string[]
|
|
48
|
-
readonly limit?: number
|
|
47
|
+
readonly personIds?: readonly string[] | undefined
|
|
48
|
+
readonly limit?: number | undefined
|
|
49
49
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
50
|
-
readonly sort?: string
|
|
50
|
+
readonly sort?: string | undefined
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function partnershipQuery(filters: PartnershipFilters): QueryParameters {
|
|
@@ -31,12 +31,12 @@ const people = createResourceHooks<Person, CreatePersonInput, PersonInput>({
|
|
|
31
31
|
/** The documented filters on `GET /v1/people`. There is no generic filter DSL. */
|
|
32
32
|
export interface PeopleFilters {
|
|
33
33
|
/** Matches name, email, summary, tags, and the titles and companies they hold. */
|
|
34
|
-
readonly term?: string
|
|
34
|
+
readonly term?: string | undefined
|
|
35
35
|
/** People holding a position at any of these companies. Repeats on the wire. */
|
|
36
|
-
readonly companyIds?: readonly string[]
|
|
37
|
-
readonly limit?: number
|
|
36
|
+
readonly companyIds?: readonly string[] | undefined
|
|
37
|
+
readonly limit?: number | undefined
|
|
38
38
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
39
|
-
readonly sort?: string
|
|
39
|
+
readonly sort?: string | undefined
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function peopleQuery(filters: PeopleFilters): QueryParameters {
|
|
@@ -35,15 +35,15 @@ const planItems = createResourceHooks<PlanItem, CreatePlanItemInput, PlanItemInp
|
|
|
35
35
|
export const MAX_PAGE_SIZE = 200
|
|
36
36
|
|
|
37
37
|
export interface PlanItemFilters {
|
|
38
|
-
readonly targetType?: PipelineKind
|
|
38
|
+
readonly targetType?: PipelineKind | undefined
|
|
39
39
|
/** The records to read steps for. Repeats on the wire: `?target_id=a&target_id=b`. */
|
|
40
|
-
readonly targetIds?: readonly string[]
|
|
41
|
-
readonly statuses?: readonly PlanItemStatus[]
|
|
40
|
+
readonly targetIds?: readonly string[] | undefined
|
|
41
|
+
readonly statuses?: readonly PlanItemStatus[] | undefined
|
|
42
42
|
/** Inclusive `YYYY-MM-DD` bounds. The calendar asks for one month with the pair. */
|
|
43
|
-
readonly from?: string
|
|
44
|
-
readonly to?: string
|
|
43
|
+
readonly from?: string | undefined
|
|
44
|
+
readonly to?: string | undefined
|
|
45
45
|
/** Page size. Worth setting only when the filter already bounds the answer. */
|
|
46
|
-
readonly limit?: number
|
|
46
|
+
readonly limit?: number | undefined
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function planItemQuery(filters: PlanItemFilters): QueryParameters {
|
|
@@ -37,10 +37,10 @@ const positions = createResourceHooks<Position, CreatePositionInput, PositionTit
|
|
|
37
37
|
|
|
38
38
|
export interface PositionFilters {
|
|
39
39
|
/** Positions held by any of these people. Repeats on the wire. */
|
|
40
|
-
readonly personIds?: readonly string[]
|
|
40
|
+
readonly personIds?: readonly string[] | undefined
|
|
41
41
|
/** Positions held at any of these companies. Repeats on the wire. */
|
|
42
|
-
readonly companyIds?: readonly string[]
|
|
43
|
-
readonly limit?: number
|
|
42
|
+
readonly companyIds?: readonly string[] | undefined
|
|
43
|
+
readonly limit?: number | undefined
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function positionQuery(filters: PositionFilters): QueryParameters {
|
|
@@ -35,16 +35,16 @@ const raises = createResourceHooks<Raise, CreateRaiseInput, RaiseInput>({
|
|
|
35
35
|
/** The documented filters on `GET /v1/raises`. */
|
|
36
36
|
export interface RaiseFilters {
|
|
37
37
|
/** Matches the raise's name, summary, tags, and its firm's name. */
|
|
38
|
-
readonly term?: string
|
|
38
|
+
readonly term?: string | undefined
|
|
39
39
|
/** Raises with any of these firms. Repeats on the wire. */
|
|
40
|
-
readonly companyIds?: readonly string[]
|
|
40
|
+
readonly companyIds?: readonly string[] | undefined
|
|
41
41
|
/** Raises sitting in any of these stages. Repeats on the wire. */
|
|
42
|
-
readonly stageIds?: readonly string[]
|
|
42
|
+
readonly stageIds?: readonly string[] | undefined
|
|
43
43
|
/** Raises any of these people are key on. Repeats on the wire. */
|
|
44
|
-
readonly personIds?: readonly string[]
|
|
45
|
-
readonly limit?: number
|
|
44
|
+
readonly personIds?: readonly string[] | undefined
|
|
45
|
+
readonly limit?: number | undefined
|
|
46
46
|
/** `field` ascending, `-field` descending. Only `name`, `created_at`, `updated_at` are sortable. */
|
|
47
|
-
readonly sort?: string
|
|
47
|
+
readonly sort?: string | undefined
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function raiseQuery(filters: RaiseFilters): QueryParameters {
|
|
@@ -31,10 +31,10 @@ const roles = createResourceHooks<Role, CreateRoleInput, RoleInput>({
|
|
|
31
31
|
/** The documented filters on `GET /v1/roles`. */
|
|
32
32
|
export interface RoleFilters {
|
|
33
33
|
/** Matches the role's title, which is what the mockup's filter box matches. */
|
|
34
|
-
readonly term?: string
|
|
34
|
+
readonly term?: string | undefined
|
|
35
35
|
/** Roles in any of these statuses. Repeats on the wire. */
|
|
36
|
-
readonly statuses?: readonly RoleStatus[]
|
|
37
|
-
readonly limit?: number
|
|
36
|
+
readonly statuses?: readonly RoleStatus[] | undefined
|
|
37
|
+
readonly limit?: number | undefined
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function roleQuery(filters: RoleFilters): QueryParameters {
|
|
@@ -41,7 +41,7 @@ const webhooks = createResourceHooks<Webhook, CreateWebhookInput, WebhookInput>(
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
export interface WebhookFilters {
|
|
44
|
-
readonly status?: Webhook['status']
|
|
44
|
+
readonly status?: Webhook['status'] | undefined
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function webhookQuery(filters: WebhookFilters): QueryParameters {
|
|
@@ -61,7 +61,7 @@ export function useDeleteWebhook(): MutationResult<string, void> {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export interface WebhookDeliveryFilters {
|
|
64
|
-
readonly status?: WebhookDelivery['status']
|
|
64
|
+
readonly status?: WebhookDelivery['status'] | undefined
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
package/src/app/KelpieApp.tsx
CHANGED
|
@@ -47,6 +47,7 @@ import { HandbookStepPage } from '../pages/onboarding/HandbookStepPage.tsx'
|
|
|
47
47
|
import { InvitesStepPage } from '../pages/onboarding/InvitesStepPage.tsx'
|
|
48
48
|
import { WorkspaceStepPage } from '../pages/onboarding/WorkspaceStepPage.tsx'
|
|
49
49
|
import { UiExtensionProvider } from '../registry/UiExtensionProvider.tsx'
|
|
50
|
+
import type { RouteContribution } from '../registry/contributions.ts'
|
|
50
51
|
import { useModuleRoutes } from '../registry/context.ts'
|
|
51
52
|
import type { UiExtensions } from '../registry/registry.ts'
|
|
52
53
|
import { SessionGate } from './SessionGate.tsx'
|
|
@@ -80,8 +81,25 @@ export function KelpieApp({ extensions, baseUrl }: KelpieAppProps): React.JSX.El
|
|
|
80
81
|
)
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A module route under `account/` renders inside `AccountLayout`, everything
|
|
86
|
+
* else directly under the shell.
|
|
87
|
+
*
|
|
88
|
+
* The prefix is the whole rule, and it is what makes `nav.account` mean
|
|
89
|
+
* anything: a module contributing a tab there needs its page to keep the tab
|
|
90
|
+
* strip, and a route mounted as a sibling of `account` would lose it the moment
|
|
91
|
+
* the tab was clicked. Core's own account pages are nested for the same reason.
|
|
92
|
+
*/
|
|
93
|
+
const ACCOUNT_PREFIX = 'account/'
|
|
94
|
+
|
|
95
|
+
function isAccountRoute(route: RouteContribution): boolean {
|
|
96
|
+
return route.path.startsWith(ACCOUNT_PREFIX)
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
function AppRoutes(): React.JSX.Element {
|
|
84
100
|
const moduleRoutes = useModuleRoutes()
|
|
101
|
+
const accountRoutes = moduleRoutes.filter(isAccountRoute)
|
|
102
|
+
const shellRoutes = moduleRoutes.filter((route) => !isAccountRoute(route))
|
|
85
103
|
|
|
86
104
|
return (
|
|
87
105
|
<Routes>
|
|
@@ -146,8 +164,15 @@ function AppRoutes(): React.JSX.Element {
|
|
|
146
164
|
<Route path="profile" element={<ProfilePage />} />
|
|
147
165
|
<Route path="security" element={<SecurityPage />} />
|
|
148
166
|
<Route path="preferences" element={<PreferencesPage />} />
|
|
167
|
+
{accountRoutes.map((route) => (
|
|
168
|
+
<Route
|
|
169
|
+
key={route.path}
|
|
170
|
+
path={route.path.slice(ACCOUNT_PREFIX.length)}
|
|
171
|
+
element={route.element}
|
|
172
|
+
/>
|
|
173
|
+
))}
|
|
149
174
|
</Route>
|
|
150
|
-
{
|
|
175
|
+
{shellRoutes.map((route) => (
|
|
151
176
|
<Route key={route.path} path={route.path} element={route.element} />
|
|
152
177
|
))}
|
|
153
178
|
{/* Last so every named route above wins first. Nested here rather than
|
|
@@ -3,10 +3,10 @@ import type { ReactNode } from 'react'
|
|
|
3
3
|
export interface Column<TRow> {
|
|
4
4
|
readonly key: string
|
|
5
5
|
readonly header: string
|
|
6
|
-
readonly className?: string
|
|
6
|
+
readonly className?: string | undefined
|
|
7
7
|
readonly render: (row: TRow) => ReactNode
|
|
8
8
|
/** The `?sort=` field this column drives. Omit for a column with no server-side sort. */
|
|
9
|
-
readonly sortKey?: string
|
|
9
|
+
readonly sortKey?: string | undefined
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface DataTableGroup<TRow> {
|
|
@@ -22,19 +22,19 @@ export interface EmptyStateAction {
|
|
|
22
22
|
|
|
23
23
|
export interface DataTableProps<TRow> {
|
|
24
24
|
readonly columns: readonly Column<TRow>[]
|
|
25
|
-
readonly rows?: readonly TRow[]
|
|
26
|
-
readonly groups?: readonly DataTableGroup<TRow>[]
|
|
27
|
-
readonly onRowClick?: (row: TRow) => void
|
|
28
|
-
readonly emptyMessage?: string
|
|
25
|
+
readonly rows?: readonly TRow[] | undefined
|
|
26
|
+
readonly groups?: readonly DataTableGroup<TRow>[] | undefined
|
|
27
|
+
readonly onRowClick?: ((row: TRow) => void) | undefined
|
|
28
|
+
readonly emptyMessage?: string | undefined
|
|
29
29
|
/** Replaces the default "Add a record to get started." line under `emptyMessage`. */
|
|
30
|
-
readonly emptyDescription?: string
|
|
30
|
+
readonly emptyDescription?: string | undefined
|
|
31
31
|
/** A button rendered in the empty state. Omit when there is nothing to do about it — a filter miss, not a genuinely empty list. */
|
|
32
|
-
readonly emptyAction?: EmptyStateAction
|
|
32
|
+
readonly emptyAction?: EmptyStateAction | undefined
|
|
33
33
|
readonly getRowId: (row: TRow) => string
|
|
34
34
|
/** Current `?sort=` value: `field` ascending, `-field` descending, `undefined` for the resource's default. */
|
|
35
|
-
readonly sort?: string
|
|
35
|
+
readonly sort?: string | undefined
|
|
36
36
|
/** Fires with the next `?sort=` value when a sortable header is clicked. Required for any column to be clickable. */
|
|
37
|
-
readonly onSortChange?: (sort: string | undefined) => void
|
|
37
|
+
readonly onSortChange?: ((sort: string | undefined) => void) | undefined
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
type SortDirection = 'asc' | 'desc'
|
|
@@ -162,7 +162,7 @@ function GroupRows<TRow>({
|
|
|
162
162
|
}: {
|
|
163
163
|
readonly group: DataTableGroup<TRow>
|
|
164
164
|
readonly columns: readonly Column<TRow>[]
|
|
165
|
-
readonly onRowClick?: (row: TRow) => void
|
|
165
|
+
readonly onRowClick?: ((row: TRow) => void) | undefined
|
|
166
166
|
readonly getRowId: (row: TRow) => string
|
|
167
167
|
}): React.JSX.Element {
|
|
168
168
|
return (
|
|
@@ -189,7 +189,7 @@ function DataRow<TRow>({
|
|
|
189
189
|
}: {
|
|
190
190
|
readonly row: TRow
|
|
191
191
|
readonly columns: readonly Column<TRow>[]
|
|
192
|
-
readonly onRowClick?: (row: TRow) => void
|
|
192
|
+
readonly onRowClick?: ((row: TRow) => void) | undefined
|
|
193
193
|
}): React.JSX.Element {
|
|
194
194
|
return (
|
|
195
195
|
<tr
|
|
@@ -13,7 +13,7 @@ import { useEffect, useId, useMemo, useRef, useState } from 'react'
|
|
|
13
13
|
export interface SearchOption {
|
|
14
14
|
readonly id: string
|
|
15
15
|
readonly label: string
|
|
16
|
-
readonly meta?: string
|
|
16
|
+
readonly meta?: string | undefined
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface EntitySearchProps {
|
|
@@ -21,17 +21,17 @@ export interface EntitySearchProps {
|
|
|
21
21
|
readonly value: string
|
|
22
22
|
readonly onChange: (id: string) => void
|
|
23
23
|
/** Set to run the search server-side. Receives what has been typed, trimmed of nothing. */
|
|
24
|
-
readonly onQueryChange?: (query: string) => void
|
|
24
|
+
readonly onQueryChange?: ((query: string) => void) | undefined
|
|
25
25
|
/** When set, typing a name that is not an exact match offers a create row. */
|
|
26
|
-
readonly onCreate?: (label: string) => void
|
|
27
|
-
readonly createLabel?: (query: string) => string
|
|
28
|
-
readonly placeholder?: string
|
|
29
|
-
readonly emptyMessage?: string
|
|
26
|
+
readonly onCreate?: ((label: string) => void) | undefined
|
|
27
|
+
readonly createLabel?: ((query: string) => string) | undefined
|
|
28
|
+
readonly placeholder?: string | undefined
|
|
29
|
+
readonly emptyMessage?: string | undefined
|
|
30
30
|
/** Most rows shown at once. The list says so when there are more. */
|
|
31
|
-
readonly limit?: number
|
|
32
|
-
readonly required?: boolean
|
|
33
|
-
readonly className?: string
|
|
34
|
-
readonly size?: 'sm' | 'md'
|
|
31
|
+
readonly limit?: number | undefined
|
|
32
|
+
readonly required?: boolean | undefined
|
|
33
|
+
readonly className?: string | undefined
|
|
34
|
+
readonly size?: 'sm' | 'md' | undefined
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function EntitySearch({
|
|
@@ -28,8 +28,8 @@ export interface KanbanCard {
|
|
|
28
28
|
readonly id: string
|
|
29
29
|
readonly stage: string
|
|
30
30
|
readonly title: string
|
|
31
|
-
readonly meta?: string
|
|
32
|
-
readonly valueLabel?: string
|
|
31
|
+
readonly meta?: string | undefined
|
|
32
|
+
readonly valueLabel?: string | undefined
|
|
33
33
|
readonly href: string
|
|
34
34
|
}
|
|
35
35
|
|
package/src/components/Shell.tsx
CHANGED
|
@@ -2,13 +2,12 @@ import { useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
import { NavLink, Outlet, useLocation, useNavigate } from 'react-router'
|
|
3
3
|
|
|
4
4
|
import { useAccount, useTheme } from '../api/resources/account.ts'
|
|
5
|
-
import { useModuleSettings } from '../api/resources/moduleSettings.ts'
|
|
6
5
|
import { useLogOut } from '../api/resources/session.ts'
|
|
7
6
|
import { initialsOf } from '../lib/names.ts'
|
|
8
7
|
import type { ThemePreference } from '../lib/theme.ts'
|
|
9
8
|
import { useNavItems } from '../registry/context.ts'
|
|
10
9
|
import type { NavItem } from '../registry/contributions.ts'
|
|
11
|
-
import {
|
|
10
|
+
import { useVisibleNavItems } from '../registry/visibleNav.ts'
|
|
12
11
|
import { ErrorBoundary } from './ErrorBoundary.tsx'
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -51,16 +50,6 @@ const CORE_NAV: readonly NavItem[] = [
|
|
|
51
50
|
*/
|
|
52
51
|
const TOP_LEVEL_NAV_IDS: ReadonlySet<string> = new Set(['dashboard', 'handbook', 'planning', 'decisions'])
|
|
53
52
|
|
|
54
|
-
/**
|
|
55
|
-
* A nav item whose id does not match its module id. Everything else looks
|
|
56
|
-
* itself up directly, which is what keeps a toggleable module's own nav entry
|
|
57
|
-
* working with no change here when one is added later.
|
|
58
|
-
*/
|
|
59
|
-
const NAV_ID_TO_MODULE_ID: Readonly<Record<string, string>> = {
|
|
60
|
-
fundraising: 'raises',
|
|
61
|
-
data: 'import-export',
|
|
62
|
-
}
|
|
63
|
-
|
|
64
53
|
function linkClass({ isActive }: { isActive: boolean }): string {
|
|
65
54
|
return [
|
|
66
55
|
'block rounded-md px-2 py-1 text-[13px] transition-colors duration-100',
|
|
@@ -89,12 +78,6 @@ export function Shell(): React.JSX.Element {
|
|
|
89
78
|
const logOut = useLogOut()
|
|
90
79
|
const moduleNav = useNavItems('primary')
|
|
91
80
|
const moduleAdminNav = useNavItems('admin')
|
|
92
|
-
const { settings } = useModuleSettings()
|
|
93
|
-
const disabledModuleIds = new Set(
|
|
94
|
-
settings.filter((setting) => !setting.enabled).map((setting) => setting.moduleId),
|
|
95
|
-
)
|
|
96
|
-
const isModuleHidden = (navId: string): boolean =>
|
|
97
|
-
disabledModuleIds.has(NAV_ID_TO_MODULE_ID[navId] ?? navId)
|
|
98
81
|
const [menuOpen, setMenuOpen] = useState(false)
|
|
99
82
|
// The same preference the account page writes, so this button and that page
|
|
100
83
|
// cannot disagree about which theme the account is on.
|
|
@@ -128,12 +111,10 @@ export function Shell(): React.JSX.Element {
|
|
|
128
111
|
}
|
|
129
112
|
}, [menuOpen])
|
|
130
113
|
|
|
131
|
-
const navItems =
|
|
114
|
+
const navItems = useVisibleNavItems(CORE_NAV, moduleNav)
|
|
132
115
|
const topLevelItems = navItems.filter((item) => TOP_LEVEL_NAV_IDS.has(item.id))
|
|
133
116
|
const crmItems = navItems.filter((item) => !TOP_LEVEL_NAV_IDS.has(item.id))
|
|
134
|
-
const adminItems =
|
|
135
|
-
(item) => !isModuleHidden(item.id),
|
|
136
|
-
)
|
|
117
|
+
const adminItems = useVisibleNavItems(CORE_ADMIN_NAV, moduleAdminNav)
|
|
137
118
|
|
|
138
119
|
return (
|
|
139
120
|
<div className="flex min-h-screen bg-surface">
|
package/src/index.ts
CHANGED
|
@@ -155,12 +155,10 @@ export type { DueBucketId, PlanAttention as PlanAttentionSummary } from './lib/p
|
|
|
155
155
|
export { applyTheme, getStoredTheme, resolveTheme, setStoredTheme, watchSystemTheme } from './lib/theme.ts'
|
|
156
156
|
export type { ThemePreference } from './lib/theme.ts'
|
|
157
157
|
|
|
158
|
-
export { EXTENSIBLE_RECORD_TYPES,
|
|
158
|
+
export { EXTENSIBLE_RECORD_TYPES, NAV_SLOTS } from './registry/contributions.ts'
|
|
159
159
|
export type {
|
|
160
160
|
DashboardCard,
|
|
161
161
|
ExtensibleRecordType,
|
|
162
|
-
IntegrationCategory,
|
|
163
|
-
IntegrationProvider,
|
|
164
162
|
NavItem,
|
|
165
163
|
NavSlot,
|
|
166
164
|
RecordContext,
|
|
@@ -177,7 +175,6 @@ export type { UiExtensions, UiModule, UiModuleContext } from './registry/registr
|
|
|
177
175
|
|
|
178
176
|
export {
|
|
179
177
|
useDashboardCards,
|
|
180
|
-
useIntegrationProviders,
|
|
181
178
|
useModuleRoutes,
|
|
182
179
|
useNavItems,
|
|
183
180
|
useOverridable,
|