@burdenoff/microfe-planmagnet 2026.928.6 → 2026.928.7
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/generated/wspace-operations.d.ts +5 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +15 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +7 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +45 -8
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +74 -59
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +138 -112
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,6 +38,13 @@ export declare function ProgramSelect({ id, value, onChange, noneLabel, }: {
|
|
|
38
38
|
onChange: (programId: string) => void;
|
|
39
39
|
noneLabel?: string;
|
|
40
40
|
}): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
/** A portfolio select (BOFF-8047). '' means "no portfolio" when `noneLabel` is given. */
|
|
42
|
+
export declare function PortfolioSelect({ id, value, onChange, noneLabel, }: {
|
|
43
|
+
id?: string;
|
|
44
|
+
value: string;
|
|
45
|
+
onChange: (portfolioId: string) => void;
|
|
46
|
+
noneLabel?: string;
|
|
47
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
41
48
|
/** Search, then pick one competitor. `exclude` hides competitors already linked. */
|
|
42
49
|
export declare function CompetitorSearchSelect({ id, value, onChange, exclude, }: {
|
|
43
50
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pickers.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/Pickers.tsx"],"names":[],"mappings":"AA2BA,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAY,GACb,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8BA;AAcD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EACzB,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA+BA;AAeD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,EAAE,EACF,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAqDA;AAcD,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8CA;AAcD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAcD,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,EACrC,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAkDA"}
|
|
1
|
+
{"version":3,"file":"Pickers.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/Pickers.tsx"],"names":[],"mappings":"AA2BA,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAY,GACb,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8BA;AAcD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EACzB,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA+BA;AAeD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,EAAE,EACF,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAqDA;AAcD,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8CA;AAcD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAcD,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAcD,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,EACrC,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAkDA"}
|
|
@@ -241,6 +241,43 @@ function v({ id: t, value: n, onChange: r, noneLabel: s }) {
|
|
|
241
241
|
});
|
|
242
242
|
}
|
|
243
243
|
var y = r`
|
|
244
|
+
query PickerPortfolios($workspaceId: ID!) {
|
|
245
|
+
planmagnetPortfolios(workspaceId: $workspaceId, pagination: { first: 100 }) {
|
|
246
|
+
items {
|
|
247
|
+
id
|
|
248
|
+
name
|
|
249
|
+
}
|
|
250
|
+
totalCount
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
function b({ id: t, value: n, onChange: r, noneLabel: s }) {
|
|
255
|
+
let { workspaceId: l } = e(), { data: u, loading: d, error: f } = i(y, {
|
|
256
|
+
variables: { workspaceId: l ?? "" },
|
|
257
|
+
skip: !l
|
|
258
|
+
}), p = u?.planmagnetPortfolios.items ?? [];
|
|
259
|
+
return /* @__PURE__ */ o("select", {
|
|
260
|
+
id: t,
|
|
261
|
+
className: c,
|
|
262
|
+
value: n,
|
|
263
|
+
onChange: (e) => r(e.target.value),
|
|
264
|
+
children: [
|
|
265
|
+
s !== void 0 && /* @__PURE__ */ a("option", {
|
|
266
|
+
value: "",
|
|
267
|
+
children: s
|
|
268
|
+
}),
|
|
269
|
+
p.length === 0 && s === void 0 && /* @__PURE__ */ a("option", {
|
|
270
|
+
value: "",
|
|
271
|
+
children: d && !u ? "Loading portfolios…" : f ? "Portfolios could not be loaded" : "No portfolios yet"
|
|
272
|
+
}),
|
|
273
|
+
p.map((e) => /* @__PURE__ */ a("option", {
|
|
274
|
+
value: e.id,
|
|
275
|
+
children: e.name
|
|
276
|
+
}, e.id))
|
|
277
|
+
]
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
var x = r`
|
|
244
281
|
query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {
|
|
245
282
|
planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {
|
|
246
283
|
items {
|
|
@@ -251,14 +288,14 @@ var y = r`
|
|
|
251
288
|
}
|
|
252
289
|
}
|
|
253
290
|
`;
|
|
254
|
-
function
|
|
255
|
-
let { workspaceId: f } = e(), [p, m] = n(""), h = t(p.trim()), { data: g, loading: _ } = i(
|
|
291
|
+
function S({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
292
|
+
let { workspaceId: f } = e(), [p, m] = n(""), h = t(p.trim()), { data: g, loading: _ } = i(x, {
|
|
256
293
|
variables: {
|
|
257
294
|
workspaceId: f ?? "",
|
|
258
295
|
filter: h ? { search: h } : void 0
|
|
259
296
|
},
|
|
260
297
|
skip: !f
|
|
261
|
-
}), v = g?.planmagnetCompetitors.items ?? [],
|
|
298
|
+
}), v = g?.planmagnetCompetitors.items ?? [], y = v.filter((e) => !d.includes(e.id)), b = g?.planmagnetCompetitors.totalCount ?? 0;
|
|
262
299
|
return /* @__PURE__ */ o("div", {
|
|
263
300
|
className: "space-y-1",
|
|
264
301
|
children: [
|
|
@@ -275,19 +312,19 @@ function b({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
|
275
312
|
onChange: (e) => u(e.target.value),
|
|
276
313
|
children: [/* @__PURE__ */ a("option", {
|
|
277
314
|
value: "",
|
|
278
|
-
children: _ && !g ? "Searching…" :
|
|
279
|
-
}),
|
|
315
|
+
children: _ && !g ? "Searching…" : y.length ? "Choose a competitor" : "No match"
|
|
316
|
+
}), y.map((e) => /* @__PURE__ */ a("option", {
|
|
280
317
|
value: e.id,
|
|
281
318
|
children: e.name
|
|
282
319
|
}, e.id))]
|
|
283
320
|
}),
|
|
284
|
-
|
|
321
|
+
b > v.length && /* @__PURE__ */ o("p", {
|
|
285
322
|
className: "text-xs text-muted-foreground",
|
|
286
323
|
children: [
|
|
287
324
|
"Showing ",
|
|
288
325
|
v.length,
|
|
289
326
|
" of ",
|
|
290
|
-
|
|
327
|
+
b,
|
|
291
328
|
" — type to narrow the list."
|
|
292
329
|
]
|
|
293
330
|
})
|
|
@@ -295,6 +332,6 @@ function b({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
|
295
332
|
});
|
|
296
333
|
}
|
|
297
334
|
//#endregion
|
|
298
|
-
export {
|
|
335
|
+
export { S as CompetitorSearchSelect, g as DocSearchSelect, b as PortfolioSelect, v as ProgramSelect, u as ProjectSelect, f as TeamSelect, m as WorkItemSearchSelect };
|
|
299
336
|
|
|
300
337
|
//# sourceMappingURL=Pickers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. `exclude` hides ids already spoken for. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n exclude = [],\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProjects: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROJECTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const projects = (data?.planmagnetProjects.items ?? []).filter((p) => !exclude.includes(p.id));\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_TEAMS = gql`\n query PickerTeams($workspaceId: ID!) {\n planmagnetTeams(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A team select (BOFF-8044). '' means \"no team\" when `noneLabel` is given. */\nexport function TeamSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (teamId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetTeams: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_TEAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const teams = data?.planmagnetTeams.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Team'}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {teams.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading teams…'\n : error\n ? 'Teams could not be loaded'\n : 'No teams yet'}\n </option>\n )}\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PROGRAMS = gql`\n query PickerPrograms($workspaceId: ID!) {\n planmagnetPrograms(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A program select. '' means \"no program\" when `noneLabel` is given. */\nexport function ProgramSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (programId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPrograms: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROGRAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const programs = data?.planmagnetPrograms.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {programs.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading programs…'\n : error\n ? 'Programs could not be loaded'\n : 'No programs yet'}\n </option>\n )}\n {programs.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_COMPETITORS = gql`\n query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one competitor. `exclude` hides competitors already linked. */\nexport function CompetitorSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (competitorId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetCompetitors: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_COMPETITORS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetCompetitors.items ?? [];\n const competitors = found.filter((c) => !exclude.includes(c.id));\n const total = data?.planmagnetCompetitors.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search competitors by name\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data\n ? 'Searching…'\n : competitors.length\n ? 'Choose a competitor'\n : 'No match'}\n </option>\n {competitors.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAYA,IAAM,IACJ,6FAEI,IAAkB,CAAG;;;;;;;;;;;AAa3B,SAAgB,EAAc,EAC5B,OACA,UACA,aACA,cACA,aAAU,EAAE,IAOX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAiB;EAAE,WAAW,EAAE,aAAa,KAAe,IAAI;EAAE,MAAM,CAAC;EAAa,CAAC,EACpF,KAAY,GAAM,mBAAmB,SAAS,EAAE,EAAE,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC;AAC9F,QACE,kBAAC,UAAD;EACM;EACJ,WAAW;EACJ;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;YAJ3C;GAMG,MAAc,KAAA,KAAa,kBAAC,UAAD;IAAQ,OAAM;cAAI;IAAmB,CAAA;GAChE,EAAS,WAAW,KAAK,MAAc,KAAA,KACtC,kBAAC,UAAD;IAAQ,OAAM;cACX,KAAW,CAAC,IACT,sBACA,IACE,iCACA;IACC,CAAA;GAEV,EAAS,KAAK,MACb,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;IACI,EAFI,EAAE,GAEN,CACT;GACK;;;AAIb,IAAM,IAAe,CAAG;;;;;;;;;;;AAaxB,SAAgB,EAAW,EACzB,OACA,UACA,aACA,gBAMC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAc;EAAE,WAAW,EAAE,aAAa,KAAe,IAAI;EAAE,MAAM,CAAC;EAAa,CAAC,EACjF,IAAQ,GAAM,gBAAgB,SAAS,EAAE;AAC/C,QACE,kBAAC,UAAD;EACM;EACJ,cAAY,IAAK,KAAA,IAAY;EAC7B,WAAW;EACJ;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;YAL3C;GAOG,MAAc,KAAA,KAAa,kBAAC,UAAD;IAAQ,OAAM;cAAI;IAAmB,CAAA;GAChE,EAAM,WAAW,KAAK,MAAc,KAAA,KACnC,kBAAC,UAAD;IAAQ,OAAM;cACX,KAAW,CAAC,IACT,mBACA,IACE,8BACA;IACC,CAAA;GAEV,EAAM,KAAK,MACV,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;IACI,EAFI,EAAE,GAEN,CACT;GACK;;;AAIb,IAAM,IAAoB,CAAG;;;;;;;;;;;;AAiB7B,SAAgB,EAAqB,EACnC,OACA,cACA,UACA,aACA,aAAU,EAAE,IAOX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAC1C,EAAE,SAAM,eAAY,EAKvB,GAAmB;EACpB,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ;IAAE,GAAI,KAAY,EAAE,QAAQ,GAAU;IAAG,GAAI,KAAa,EAAE,cAAW;IAAG;GACnF;EACD,MAAM,CAAC;EACR,CAAC,EACI,IAAQ,GAAM,oBAAoB,SAAS,EAAE,EAC7C,IAAQ,EAAM,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC,EACpD,IAAQ,GAAM,oBAAoB,cAAc;AACtD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,CAAA;GACF,kBAAC,UAAD;IACM;IACJ,WAAW;IACJ;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;cAJ3C;KAME,kBAAC,UAAD;MAAQ,OAAM;gBACX,KAAW,CAAC,IAAO,eAAe,EAAM,SAAS,uBAAuB;MAClE,CAAA;KAER,KAAS,CAAC,EAAM,MAAM,MAAM,EAAE,OAAO,EAAM,IAC1C,kBAAC,UAAD;MAAe;gBAAO;MAAmC,CAAA;KAE1D,EAAM,KAAK,MACV,kBAAC,UAAD;MAAmB,OAAO,EAAE;gBAA5B;OACG,EAAE;OAAI;OAAI,EAAE;OACN;QAFI,EAAE,GAEN,CACT;KACK;;GACR,IAAQ,EAAM,UACb,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA6C;KAClC,EAAM;KAAO;KAAK;KAAM;KAC/B;;GAEF;;;AAIV,IAAM,IAAc,CAAG;;;;;;;;;;;AAavB,SAAgB,EAAgB,EAC9B,OACA,UACA,aACA,aAAU,EAAE,IAMX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAC1C,EAAE,SAAM,eAAY,EAEvB,GAAa;EACd,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ,IAAW,EAAE,QAAQ,GAAU,GAAG,KAAA;GAC3C;EACD,MAAM,CAAC;EACR,CAAC,EACI,IAAQ,GAAM,eAAe,SAAS,EAAE,EACxC,IAAO,EAAM,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC,EACnD,IAAQ,GAAM,eAAe,cAAc;AACjD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,CAAA;GACF,kBAAC,UAAD;IACM;IACJ,WAAW;IACJ;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;cAJ3C,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,KAAW,CAAC,IAAO,eAAe,EAAK,SAAS,iBAAiB;KAC3D,CAAA,EACR,EAAK,KAAK,MACT,kBAAC,UAAD;KAAmB,OAAO,EAAE;eACzB,EAAE;KACI,EAFI,EAAE,GAEN,CACT,CACK;;GACR,IAAQ,EAAM,UACb,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA6C;KAClC,EAAM;KAAO;KAAK;KAAM;KAC/B;;GAEF;;;AAIV,IAAM,IAAkB,CAAG;;;;;;;;;;;AAa3B,SAAgB,EAAc,EAC5B,OACA,UACA,aACA,gBAMC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAiB;EAAE,WAAW,EAAE,aAAa,KAAe,IAAI;EAAE,MAAM,CAAC;EAAa,CAAC,EACpF,IAAW,GAAM,mBAAmB,SAAS,EAAE;AACrD,QACE,kBAAC,UAAD;EACM;EACJ,WAAW;EACJ;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;YAJ3C;GAMG,MAAc,KAAA,KAAa,kBAAC,UAAD;IAAQ,OAAM;cAAI;IAAmB,CAAA;GAChE,EAAS,WAAW,KAAK,MAAc,KAAA,KACtC,kBAAC,UAAD;IAAQ,OAAM;cACX,KAAW,CAAC,IACT,sBACA,IACE,iCACA;IACC,CAAA;GAEV,EAAS,KAAK,MACb,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;IACI,EAFI,EAAE,GAEN,CACT;GACK;;;AAIb,IAAM,IAAqB,CAAG;;;;;;;;;;;AAa9B,SAAgB,EAAuB,EACrC,OACA,UACA,aACA,aAAU,EAAE,IAMX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAC1C,EAAE,SAAM,eAAY,EAEvB,GAAoB;EACrB,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ,IAAW,EAAE,QAAQ,GAAU,GAAG,KAAA;GAC3C;EACD,MAAM,CAAC;EACR,CAAC,EACI,IAAQ,GAAM,sBAAsB,SAAS,EAAE,EAC/C,IAAc,EAAM,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC,EAC1D,IAAQ,GAAM,sBAAsB,cAAc;AACxD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,CAAA;GACF,kBAAC,UAAD;IACM;IACJ,WAAW;IACJ;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;cAJ3C,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,KAAW,CAAC,IACT,eACA,EAAY,SACV,wBACA;KACC,CAAA,EACR,EAAY,KAAK,MAChB,kBAAC,UAAD;KAAmB,OAAO,EAAE;eACzB,EAAE;KACI,EAFI,EAAE,GAEN,CACT,CACK;;GACR,IAAQ,EAAM,UACb,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA6C;KAClC,EAAM;KAAO;KAAK;KAAM;KAC/B;;GAEF"}
|
|
1
|
+
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. `exclude` hides ids already spoken for. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n exclude = [],\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProjects: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROJECTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const projects = (data?.planmagnetProjects.items ?? []).filter((p) => !exclude.includes(p.id));\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_TEAMS = gql`\n query PickerTeams($workspaceId: ID!) {\n planmagnetTeams(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A team select (BOFF-8044). '' means \"no team\" when `noneLabel` is given. */\nexport function TeamSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (teamId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetTeams: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_TEAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const teams = data?.planmagnetTeams.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Team'}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {teams.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading teams…'\n : error\n ? 'Teams could not be loaded'\n : 'No teams yet'}\n </option>\n )}\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PROGRAMS = gql`\n query PickerPrograms($workspaceId: ID!) {\n planmagnetPrograms(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A program select. '' means \"no program\" when `noneLabel` is given. */\nexport function ProgramSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (programId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPrograms: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROGRAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const programs = data?.planmagnetPrograms.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {programs.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading programs…'\n : error\n ? 'Programs could not be loaded'\n : 'No programs yet'}\n </option>\n )}\n {programs.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_PORTFOLIOS = gql`\n query PickerPortfolios($workspaceId: ID!) {\n planmagnetPortfolios(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A portfolio select (BOFF-8047). '' means \"no portfolio\" when `noneLabel` is given. */\nexport function PortfolioSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (portfolioId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPortfolios: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PORTFOLIOS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const portfolios = data?.planmagnetPortfolios.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {portfolios.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading portfolios…'\n : error\n ? 'Portfolios could not be loaded'\n : 'No portfolios yet'}\n </option>\n )}\n {portfolios.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_COMPETITORS = gql`\n query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one competitor. `exclude` hides competitors already linked. */\nexport function CompetitorSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (competitorId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetCompetitors: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_COMPETITORS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetCompetitors.items ?? [];\n const competitors = found.filter((c) => !exclude.includes(c.id));\n const total = data?.planmagnetCompetitors.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search competitors by name\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data\n ? 'Searching…'\n : competitors.length\n ? 'Choose a competitor'\n : 'No match'}\n </option>\n {competitors.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PortfoliosPortfolioDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,iCAAiC,
|
|
1
|
+
{"version":3,"file":"PortfoliosPortfolioDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,iCAAiC,4CAmRhD"}
|
|
@@ -8,9 +8,9 @@ import { useParams as u } from "react-router";
|
|
|
8
8
|
import { gql as d } from "@apollo/client";
|
|
9
9
|
import { useQuery as f } from "@apollo/client/react";
|
|
10
10
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
11
|
-
import { Badge as h,
|
|
11
|
+
import { Badge as h, Button as g, Card as _, CardContent as v, CardHeader as y, CardTitle as b } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx
|
|
13
|
-
var
|
|
13
|
+
var x = d`
|
|
14
14
|
query GetPortfolio($id: ID!) {
|
|
15
15
|
planmagnetPortfolio(id: $id) {
|
|
16
16
|
id
|
|
@@ -39,7 +39,7 @@ var b = d`
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
`,
|
|
42
|
+
`, S = d`
|
|
43
43
|
query GetPortfolioOverview($portfolioId: ID!) {
|
|
44
44
|
planmagnetPortfolioOverview(portfolioId: $portfolioId) {
|
|
45
45
|
portfolioId
|
|
@@ -55,28 +55,28 @@ var b = d`
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
`;
|
|
58
|
-
function
|
|
59
|
-
let d = e(),
|
|
60
|
-
variables: { id:
|
|
61
|
-
skip: !
|
|
62
|
-
}), { data:
|
|
63
|
-
variables: { portfolioId:
|
|
64
|
-
skip: !
|
|
58
|
+
function C() {
|
|
59
|
+
let d = e(), C = u().id ?? "", { data: w, loading: T, error: E } = f(x, {
|
|
60
|
+
variables: { id: C },
|
|
61
|
+
skip: !C
|
|
62
|
+
}), { data: D } = f(S, {
|
|
63
|
+
variables: { portfolioId: C },
|
|
64
|
+
skip: !C
|
|
65
65
|
});
|
|
66
|
-
if (
|
|
66
|
+
if (T && !w) return /* @__PURE__ */ p("div", {
|
|
67
67
|
className: "flex items-center justify-center p-12",
|
|
68
68
|
children: /* @__PURE__ */ p(c, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
69
69
|
});
|
|
70
|
-
if (
|
|
70
|
+
if (E) return /* @__PURE__ */ m("div", {
|
|
71
71
|
className: "p-6 text-destructive",
|
|
72
|
-
children: ["Failed to load portfolio: ",
|
|
72
|
+
children: ["Failed to load portfolio: ", E.message]
|
|
73
73
|
});
|
|
74
|
-
let
|
|
75
|
-
if (!
|
|
74
|
+
let O = w?.planmagnetPortfolio;
|
|
75
|
+
if (!O) return /* @__PURE__ */ p("div", {
|
|
76
76
|
className: "p-6 text-muted-foreground",
|
|
77
77
|
children: "Portfolio not found."
|
|
78
78
|
});
|
|
79
|
-
let
|
|
79
|
+
let k = D?.planmagnetPortfolioOverview ?? null;
|
|
80
80
|
return /* @__PURE__ */ m("div", {
|
|
81
81
|
className: "space-y-6 p-6",
|
|
82
82
|
children: [
|
|
@@ -88,33 +88,33 @@ function S() {
|
|
|
88
88
|
/* @__PURE__ */ p(o, { className: "h-5 w-5 text-primary" }),
|
|
89
89
|
/* @__PURE__ */ p("h1", {
|
|
90
90
|
className: "text-2xl font-semibold",
|
|
91
|
-
children:
|
|
91
|
+
children: O.name
|
|
92
92
|
}),
|
|
93
93
|
/* @__PURE__ */ p(h, {
|
|
94
94
|
variant: "outline",
|
|
95
95
|
className: "text-xs",
|
|
96
|
-
children: n(
|
|
96
|
+
children: n(O.status)
|
|
97
97
|
})
|
|
98
98
|
]
|
|
99
|
-
}),
|
|
99
|
+
}), O.alignment && /* @__PURE__ */ p("p", {
|
|
100
100
|
className: "text-sm text-muted-foreground mt-1",
|
|
101
|
-
children:
|
|
101
|
+
children: O.alignment === "CUSTOM" ? O.alignmentLabel ?? "Custom alignment" : n(O.alignment)
|
|
102
102
|
})] }), /* @__PURE__ */ p(i, {
|
|
103
|
-
portfolioId:
|
|
104
|
-
name:
|
|
105
|
-
status:
|
|
103
|
+
portfolioId: O.id,
|
|
104
|
+
name: O.name,
|
|
105
|
+
status: O.status
|
|
106
106
|
})]
|
|
107
107
|
}),
|
|
108
108
|
/* @__PURE__ */ m("div", {
|
|
109
109
|
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
110
110
|
children: [
|
|
111
|
-
/* @__PURE__ */ p(
|
|
111
|
+
/* @__PURE__ */ p(_, { children: /* @__PURE__ */ m(v, {
|
|
112
112
|
className: "pt-4 text-center",
|
|
113
113
|
children: [
|
|
114
114
|
/* @__PURE__ */ p(o, { className: "h-5 w-5 mx-auto text-primary" }),
|
|
115
115
|
/* @__PURE__ */ p("p", {
|
|
116
116
|
className: "text-2xl font-bold mt-1",
|
|
117
|
-
children:
|
|
117
|
+
children: k?.totalPrograms ?? O.programs.length
|
|
118
118
|
}),
|
|
119
119
|
/* @__PURE__ */ p("p", {
|
|
120
120
|
className: "text-xs text-muted-foreground",
|
|
@@ -122,13 +122,13 @@ function S() {
|
|
|
122
122
|
})
|
|
123
123
|
]
|
|
124
124
|
}) }),
|
|
125
|
-
/* @__PURE__ */ p(
|
|
125
|
+
/* @__PURE__ */ p(_, { children: /* @__PURE__ */ m(v, {
|
|
126
126
|
className: "pt-4 text-center",
|
|
127
127
|
children: [
|
|
128
128
|
/* @__PURE__ */ p(l, { className: "h-5 w-5 mx-auto text-primary" }),
|
|
129
129
|
/* @__PURE__ */ p("p", {
|
|
130
130
|
className: "text-2xl font-bold mt-1",
|
|
131
|
-
children:
|
|
131
|
+
children: k?.totalProjects ?? O.projects.length
|
|
132
132
|
}),
|
|
133
133
|
/* @__PURE__ */ p("p", {
|
|
134
134
|
className: "text-xs text-muted-foreground",
|
|
@@ -136,13 +136,13 @@ function S() {
|
|
|
136
136
|
})
|
|
137
137
|
]
|
|
138
138
|
}) }),
|
|
139
|
-
/* @__PURE__ */ p(
|
|
139
|
+
/* @__PURE__ */ p(_, { children: /* @__PURE__ */ m(v, {
|
|
140
140
|
className: "pt-4 text-center",
|
|
141
141
|
children: [
|
|
142
142
|
/* @__PURE__ */ p(a, { className: "h-5 w-5 mx-auto text-destructive" }),
|
|
143
143
|
/* @__PURE__ */ p("p", {
|
|
144
144
|
className: "text-2xl font-bold mt-1",
|
|
145
|
-
children:
|
|
145
|
+
children: k?.atRiskCount ?? 0
|
|
146
146
|
}),
|
|
147
147
|
/* @__PURE__ */ p("p", {
|
|
148
148
|
className: "text-xs text-muted-foreground",
|
|
@@ -150,13 +150,13 @@ function S() {
|
|
|
150
150
|
})
|
|
151
151
|
]
|
|
152
152
|
}) }),
|
|
153
|
-
/* @__PURE__ */ p(
|
|
153
|
+
/* @__PURE__ */ p(_, { children: /* @__PURE__ */ m(v, {
|
|
154
154
|
className: "pt-4 text-center",
|
|
155
155
|
children: [
|
|
156
156
|
/* @__PURE__ */ p(s, { className: "h-5 w-5 mx-auto text-status-warning-text" }),
|
|
157
157
|
/* @__PURE__ */ p("p", {
|
|
158
158
|
className: "text-2xl font-bold mt-1",
|
|
159
|
-
children:
|
|
159
|
+
children: k?.overdueMilestones ?? 0
|
|
160
160
|
}),
|
|
161
161
|
/* @__PURE__ */ p("p", {
|
|
162
162
|
className: "text-xs text-muted-foreground",
|
|
@@ -171,12 +171,12 @@ function S() {
|
|
|
171
171
|
children: [/* @__PURE__ */ m("div", {
|
|
172
172
|
className: "lg:col-span-2 space-y-6",
|
|
173
173
|
children: [
|
|
174
|
-
/* @__PURE__ */ m(
|
|
174
|
+
/* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ p(b, { children: "Overview" }) }), /* @__PURE__ */ m(v, {
|
|
175
175
|
className: "space-y-4",
|
|
176
176
|
children: [/* @__PURE__ */ p("p", {
|
|
177
177
|
className: "text-sm",
|
|
178
|
-
children:
|
|
179
|
-
}),
|
|
178
|
+
children: O.description ?? "No description provided."
|
|
179
|
+
}), k ? /* @__PURE__ */ m("div", {
|
|
180
180
|
className: "grid grid-cols-2 gap-4",
|
|
181
181
|
children: [
|
|
182
182
|
/* @__PURE__ */ m("div", {
|
|
@@ -186,7 +186,7 @@ function S() {
|
|
|
186
186
|
children: "Budget Utilization"
|
|
187
187
|
}), /* @__PURE__ */ p("p", {
|
|
188
188
|
className: "text-lg font-semibold mt-1",
|
|
189
|
-
children:
|
|
189
|
+
children: k.budgetUtilization == null ? "No budget data" : `${k.budgetUtilization.toFixed(0)}%`
|
|
190
190
|
})]
|
|
191
191
|
}),
|
|
192
192
|
/* @__PURE__ */ m("div", {
|
|
@@ -196,7 +196,7 @@ function S() {
|
|
|
196
196
|
children: "Health Score"
|
|
197
197
|
}), /* @__PURE__ */ p("p", {
|
|
198
198
|
className: "text-lg font-semibold mt-1",
|
|
199
|
-
children:
|
|
199
|
+
children: k.healthScore == null ? "No projects yet" : k.healthScore.toFixed(0)
|
|
200
200
|
})]
|
|
201
201
|
}),
|
|
202
202
|
/* @__PURE__ */ m("div", {
|
|
@@ -206,7 +206,7 @@ function S() {
|
|
|
206
206
|
children: "Total Spent"
|
|
207
207
|
}), /* @__PURE__ */ p("p", {
|
|
208
208
|
className: "text-lg font-semibold mt-1",
|
|
209
|
-
children:
|
|
209
|
+
children: k.totalSpent == null ? "N/A" : k.totalSpent.toLocaleString()
|
|
210
210
|
})]
|
|
211
211
|
}),
|
|
212
212
|
/* @__PURE__ */ m("div", {
|
|
@@ -216,7 +216,7 @@ function S() {
|
|
|
216
216
|
children: "Projects by Status"
|
|
217
217
|
}), /* @__PURE__ */ p("p", {
|
|
218
218
|
className: "text-sm mt-1",
|
|
219
|
-
children: Object.keys(
|
|
219
|
+
children: Object.keys(k.projectsByStatus).length > 0 ? Object.entries(k.projectsByStatus).map(([e, t]) => `${n(e)}: ${t}`).join(", ") : "No projects yet"
|
|
220
220
|
})]
|
|
221
221
|
})
|
|
222
222
|
]
|
|
@@ -225,16 +225,16 @@ function S() {
|
|
|
225
225
|
children: "Loading overview…"
|
|
226
226
|
})]
|
|
227
227
|
})] }),
|
|
228
|
-
/* @__PURE__ */ m(
|
|
228
|
+
/* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ m(b, { children: [
|
|
229
229
|
"Programs (",
|
|
230
|
-
|
|
230
|
+
O.programs.length,
|
|
231
231
|
")"
|
|
232
|
-
] }) }), /* @__PURE__ */ m(
|
|
232
|
+
] }) }), /* @__PURE__ */ m(v, {
|
|
233
233
|
className: "space-y-2",
|
|
234
|
-
children: [
|
|
234
|
+
children: [O.programs.length === 0 && /* @__PURE__ */ p("p", {
|
|
235
235
|
className: "text-sm text-muted-foreground",
|
|
236
236
|
children: "No programs are linked to this portfolio yet."
|
|
237
|
-
}),
|
|
237
|
+
}), O.programs.map((e) => /* @__PURE__ */ m("button", {
|
|
238
238
|
type: "button",
|
|
239
239
|
className: "flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted",
|
|
240
240
|
onClick: () => d(r("/products/program-detail-page", e.id)),
|
|
@@ -248,16 +248,31 @@ function S() {
|
|
|
248
248
|
})]
|
|
249
249
|
}, e.id))]
|
|
250
250
|
})] }),
|
|
251
|
-
/* @__PURE__ */ m(
|
|
251
|
+
/* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ m(b, { children: [
|
|
252
252
|
"Projects (",
|
|
253
|
-
|
|
253
|
+
O.projects.length,
|
|
254
254
|
")"
|
|
255
|
-
] }) }), /* @__PURE__ */ m(
|
|
255
|
+
] }) }), /* @__PURE__ */ m(v, {
|
|
256
256
|
className: "space-y-2",
|
|
257
|
-
children: [
|
|
258
|
-
className: "
|
|
259
|
-
children:
|
|
260
|
-
|
|
257
|
+
children: [O.projects.length === 0 && /* @__PURE__ */ m("div", {
|
|
258
|
+
className: "space-y-1",
|
|
259
|
+
children: [/* @__PURE__ */ p("p", {
|
|
260
|
+
className: "text-sm text-muted-foreground",
|
|
261
|
+
children: "No projects are linked to this portfolio yet."
|
|
262
|
+
}), /* @__PURE__ */ m("p", {
|
|
263
|
+
className: "text-sm text-muted-foreground",
|
|
264
|
+
children: [
|
|
265
|
+
"Open a project and set its Portfolio field to add it here.",
|
|
266
|
+
" ",
|
|
267
|
+
/* @__PURE__ */ p(g, {
|
|
268
|
+
variant: "link",
|
|
269
|
+
className: "h-auto p-0 text-sm",
|
|
270
|
+
onClick: () => d("/projects/projects-page"),
|
|
271
|
+
children: "Go to Projects"
|
|
272
|
+
})
|
|
273
|
+
]
|
|
274
|
+
})]
|
|
275
|
+
}), O.projects.map((e) => /* @__PURE__ */ m("button", {
|
|
261
276
|
type: "button",
|
|
262
277
|
className: "flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted",
|
|
263
278
|
onClick: () => d(r("/projects/project-detail-page", e.id)),
|
|
@@ -274,7 +289,7 @@ function S() {
|
|
|
274
289
|
]
|
|
275
290
|
}), /* @__PURE__ */ m("div", {
|
|
276
291
|
className: "space-y-6",
|
|
277
|
-
children: [/* @__PURE__ */ m(
|
|
292
|
+
children: [/* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ p(b, { children: "Details" }) }), /* @__PURE__ */ m(v, {
|
|
278
293
|
className: "space-y-2 text-sm",
|
|
279
294
|
children: [
|
|
280
295
|
/* @__PURE__ */ m("div", {
|
|
@@ -282,7 +297,7 @@ function S() {
|
|
|
282
297
|
children: [/* @__PURE__ */ p("span", {
|
|
283
298
|
className: "text-muted-foreground",
|
|
284
299
|
children: "Owner"
|
|
285
|
-
}), /* @__PURE__ */ p(t, { id:
|
|
300
|
+
}), /* @__PURE__ */ p(t, { id: O.ownerId })]
|
|
286
301
|
}),
|
|
287
302
|
/* @__PURE__ */ m("div", {
|
|
288
303
|
className: "flex justify-between",
|
|
@@ -291,7 +306,7 @@ function S() {
|
|
|
291
306
|
children: "Budget"
|
|
292
307
|
}), /* @__PURE__ */ p("span", {
|
|
293
308
|
className: "font-medium",
|
|
294
|
-
children:
|
|
309
|
+
children: O.budgetTotal == null ? "Not set" : `${O.currency ?? ""} ${O.budgetTotal.toLocaleString()}`.trim()
|
|
295
310
|
})]
|
|
296
311
|
}),
|
|
297
312
|
/* @__PURE__ */ m("div", {
|
|
@@ -299,26 +314,26 @@ function S() {
|
|
|
299
314
|
children: [/* @__PURE__ */ p("span", {
|
|
300
315
|
className: "text-muted-foreground",
|
|
301
316
|
children: "Fiscal Period"
|
|
302
|
-
}), /* @__PURE__ */ p("span", { children:
|
|
317
|
+
}), /* @__PURE__ */ p("span", { children: O.fiscalPeriod ?? "Not set" })]
|
|
303
318
|
}),
|
|
304
319
|
/* @__PURE__ */ m("div", {
|
|
305
320
|
className: "flex justify-between",
|
|
306
321
|
children: [/* @__PURE__ */ p("span", {
|
|
307
322
|
className: "text-muted-foreground",
|
|
308
323
|
children: "Created"
|
|
309
|
-
}), /* @__PURE__ */ p("span", { children: new Date(
|
|
324
|
+
}), /* @__PURE__ */ p("span", { children: new Date(O.createdAt).toLocaleDateString() })]
|
|
310
325
|
}),
|
|
311
326
|
/* @__PURE__ */ m("div", {
|
|
312
327
|
className: "flex justify-between",
|
|
313
328
|
children: [/* @__PURE__ */ p("span", {
|
|
314
329
|
className: "text-muted-foreground",
|
|
315
330
|
children: "Updated"
|
|
316
|
-
}), /* @__PURE__ */ p("span", { children: new Date(
|
|
331
|
+
}), /* @__PURE__ */ p("span", { children: new Date(O.updatedAt).toLocaleDateString() })]
|
|
317
332
|
})
|
|
318
333
|
]
|
|
319
|
-
})] }),
|
|
334
|
+
})] }), O.tags.length > 0 && /* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ p(b, { children: "Tags" }) }), /* @__PURE__ */ p(v, {
|
|
320
335
|
className: "flex flex-wrap gap-1",
|
|
321
|
-
children:
|
|
336
|
+
children: O.tags.map((e) => /* @__PURE__ */ p(h, {
|
|
322
337
|
variant: "outline",
|
|
323
338
|
className: "text-xs",
|
|
324
339
|
children: e
|
|
@@ -330,6 +345,6 @@ function S() {
|
|
|
330
345
|
});
|
|
331
346
|
}
|
|
332
347
|
//#endregion
|
|
333
|
-
export {
|
|
348
|
+
export { C as PortfoliosPortfolioDetailPagePage };
|
|
334
349
|
|
|
335
350
|
//# sourceMappingURL=PortfoliosPortfolioDetailPagePage.js.map
|