@burdenoff/microfe-planmagnet 2026.928.6 → 2026.928.8

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.
Files changed (24) hide show
  1. package/dist/generated/wspace-operations.d.ts +5 -0
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +15 -0
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -1
  6. package/dist/planmagnet/automation/AutomationRuleFields.js +13 -12
  7. package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -1
  8. package/dist/planmagnet/controls/Pickers.d.ts +7 -0
  9. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  10. package/dist/planmagnet/controls/Pickers.js +45 -8
  11. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  12. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
  13. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +74 -59
  14. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
  15. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -1
  16. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +153 -63
  17. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -1
  18. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
  19. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +138 -112
  20. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
  21. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -1
  22. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +77 -70
  23. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -1
  24. package/package.json +1 -1
@@ -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, Card as g, CardContent as _, CardHeader as v, CardTitle as y } from "@burdenoff/fe-libs/ui";
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 b = d`
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
- `, x = d`
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 S() {
59
- let d = e(), S = u().id ?? "", { data: C, loading: w, error: T } = f(b, {
60
- variables: { id: S },
61
- skip: !S
62
- }), { data: E } = f(x, {
63
- variables: { portfolioId: S },
64
- skip: !S
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 (w && !C) return /* @__PURE__ */ p("div", {
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 (T) return /* @__PURE__ */ m("div", {
70
+ if (E) return /* @__PURE__ */ m("div", {
71
71
  className: "p-6 text-destructive",
72
- children: ["Failed to load portfolio: ", T.message]
72
+ children: ["Failed to load portfolio: ", E.message]
73
73
  });
74
- let D = C?.planmagnetPortfolio;
75
- if (!D) return /* @__PURE__ */ p("div", {
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 O = E?.planmagnetPortfolioOverview ?? null;
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: D.name
91
+ children: O.name
92
92
  }),
93
93
  /* @__PURE__ */ p(h, {
94
94
  variant: "outline",
95
95
  className: "text-xs",
96
- children: n(D.status)
96
+ children: n(O.status)
97
97
  })
98
98
  ]
99
- }), D.alignment && /* @__PURE__ */ p("p", {
99
+ }), O.alignment && /* @__PURE__ */ p("p", {
100
100
  className: "text-sm text-muted-foreground mt-1",
101
- children: D.alignment === "CUSTOM" ? D.alignmentLabel ?? "Custom alignment" : n(D.alignment)
101
+ children: O.alignment === "CUSTOM" ? O.alignmentLabel ?? "Custom alignment" : n(O.alignment)
102
102
  })] }), /* @__PURE__ */ p(i, {
103
- portfolioId: D.id,
104
- name: D.name,
105
- status: D.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(g, { children: /* @__PURE__ */ m(_, {
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: O?.totalPrograms ?? D.programs.length
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(g, { children: /* @__PURE__ */ m(_, {
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: O?.totalProjects ?? D.projects.length
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(g, { children: /* @__PURE__ */ m(_, {
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: O?.atRiskCount ?? 0
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(g, { children: /* @__PURE__ */ m(_, {
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: O?.overdueMilestones ?? 0
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(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Overview" }) }), /* @__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: D.description ?? "No description provided."
179
- }), O ? /* @__PURE__ */ m("div", {
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: O.budgetUtilization == null ? "No budget data" : `${O.budgetUtilization.toFixed(0)}%`
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: O.healthScore == null ? "No projects yet" : O.healthScore.toFixed(0)
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: O.totalSpent == null ? "N/A" : O.totalSpent.toLocaleString()
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(O.projectsByStatus).length > 0 ? Object.entries(O.projectsByStatus).map(([e, t]) => `${n(e)}: ${t}`).join(", ") : "No projects yet"
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(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
228
+ /* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ m(b, { children: [
229
229
  "Programs (",
230
- D.programs.length,
230
+ O.programs.length,
231
231
  ")"
232
- ] }) }), /* @__PURE__ */ m(_, {
232
+ ] }) }), /* @__PURE__ */ m(v, {
233
233
  className: "space-y-2",
234
- children: [D.programs.length === 0 && /* @__PURE__ */ p("p", {
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
- }), D.programs.map((e) => /* @__PURE__ */ m("button", {
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(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
251
+ /* @__PURE__ */ m(_, { children: [/* @__PURE__ */ p(y, { children: /* @__PURE__ */ m(b, { children: [
252
252
  "Projects (",
253
- D.projects.length,
253
+ O.projects.length,
254
254
  ")"
255
- ] }) }), /* @__PURE__ */ m(_, {
255
+ ] }) }), /* @__PURE__ */ m(v, {
256
256
  className: "space-y-2",
257
- children: [D.projects.length === 0 && /* @__PURE__ */ p("p", {
258
- className: "text-sm text-muted-foreground",
259
- children: "No projects are linked to this portfolio yet."
260
- }), D.projects.map((e) => /* @__PURE__ */ m("button", {
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(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Details" }) }), /* @__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: D.ownerId })]
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: D.budgetTotal == null ? "Not set" : `${D.currency ?? ""} ${D.budgetTotal.toLocaleString()}`.trim()
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: D.fiscalPeriod ?? "Not set" })]
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(D.createdAt).toLocaleDateString() })]
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(D.updatedAt).toLocaleDateString() })]
331
+ }), /* @__PURE__ */ p("span", { children: new Date(O.updatedAt).toLocaleDateString() })]
317
332
  })
318
333
  ]
319
- })] }), D.tags.length > 0 && /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Tags" }) }), /* @__PURE__ */ p(_, {
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: D.tags.map((e) => /* @__PURE__ */ p(h, {
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 { S as PortfoliosPortfolioDetailPagePage };
348
+ export { C as PortfoliosPortfolioDetailPagePage };
334
349
 
335
350
  //# sourceMappingURL=PortfoliosPortfolioDetailPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PortfoliosPortfolioDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Briefcase, Target, AlertTriangle, Clock, Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PortfolioHeaderActions } from '../controls/PortfolioControls';\n\nconst GET_PORTFOLIO = gql`\n query GetPortfolio($id: ID!) {\n planmagnetPortfolio(id: $id) {\n id\n workspaceId\n name\n description\n status\n alignment\n alignmentLabel\n ownerId\n budgetTotal\n currency\n fiscalPeriod\n tags\n createdAt\n updatedAt\n programs {\n id\n name\n status\n }\n projects {\n id\n name\n status\n }\n }\n }\n`;\n\nconst GET_PORTFOLIO_OVERVIEW = gql`\n query GetPortfolioOverview($portfolioId: ID!) {\n planmagnetPortfolioOverview(portfolioId: $portfolioId) {\n portfolioId\n totalPrograms\n totalProjects\n projectsByStatus\n totalBudget\n totalSpent\n budgetUtilization\n healthScore\n atRiskCount\n overdueMilestones\n }\n }\n`;\n\ninterface RelatedNode {\n id: string;\n name: string;\n status: string;\n}\n\ninterface OverviewNode {\n totalPrograms: number;\n totalProjects: number;\n projectsByStatus: Record<string, number>;\n totalBudget: number | null;\n totalSpent: number | null;\n budgetUtilization: number | null;\n healthScore: number | null;\n atRiskCount: number;\n overdueMilestones: number;\n}\n\nexport function PortfoliosPortfolioDetailPagePage() {\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const portfolioId = params.id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetPortfolio: {\n id: string;\n name: string;\n description: string | null;\n status: string;\n alignment: string | null;\n alignmentLabel: string | null;\n ownerId: string;\n budgetTotal: number | null;\n currency: string | null;\n fiscalPeriod: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n programs: RelatedNode[];\n projects: RelatedNode[];\n } | null;\n }>(GET_PORTFOLIO, { variables: { id: portfolioId }, skip: !portfolioId });\n\n const { data: overviewData } = useQuery<{ planmagnetPortfolioOverview: OverviewNode }>(\n GET_PORTFOLIO_OVERVIEW,\n { variables: { portfolioId }, skip: !portfolioId }\n );\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load portfolio: {error.message}</div>;\n }\n\n const portfolio = data?.planmagnetPortfolio;\n if (!portfolio) return <div className=\"p-6 text-muted-foreground\">Portfolio not found.</div>;\n\n const overview = overviewData?.planmagnetPortfolioOverview ?? null;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Briefcase className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{portfolio.name}</h1>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(portfolio.status)}\n </Badge>\n </div>\n {portfolio.alignment && (\n <p className=\"text-sm text-muted-foreground mt-1\">\n {portfolio.alignment === 'CUSTOM'\n ? (portfolio.alignmentLabel ?? 'Custom alignment')\n : enumLabel(portfolio.alignment)}\n </p>\n )}\n </div>\n <PortfolioHeaderActions\n portfolioId={portfolio.id}\n name={portfolio.name}\n status={portfolio.status}\n />\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Briefcase className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalPrograms ?? portfolio.programs.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Programs</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Target className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalProjects ?? portfolio.projects.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Projects</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <AlertTriangle className=\"h-5 w-5 mx-auto text-destructive\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.atRiskCount ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">At Risk</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-status-warning-text\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.overdueMilestones ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">Overdue Milestones</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm\">{portfolio.description ?? 'No description provided.'}</p>\n {overview ? (\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Budget Utilization</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.budgetUtilization != null\n ? `${overview.budgetUtilization.toFixed(0)}%`\n : 'No budget data'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Health Score</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.healthScore != null\n ? overview.healthScore.toFixed(0)\n : 'No projects yet'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Total Spent</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.totalSpent != null ? overview.totalSpent.toLocaleString() : 'N/A'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Projects by Status</p>\n <p className=\"text-sm mt-1\">\n {Object.keys(overview.projectsByStatus).length > 0\n ? Object.entries(overview.projectsByStatus)\n .map(([status, count]) => `${enumLabel(status)}: ${count}`)\n .join(', ')\n : 'No projects yet'}\n </p>\n </div>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">Loading overview…</p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Programs ({portfolio.programs.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.programs.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No programs are linked to this portfolio yet.\n </p>\n )}\n {portfolio.programs.map((program) => (\n <button\n key={program.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/products/program-detail-page', program.id))}\n >\n <span className=\"text-sm font-medium\">{program.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(program.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Projects ({portfolio.projects.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.projects.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No projects are linked to this portfolio yet.\n </p>\n )}\n {portfolio.projects.map((project) => (\n <button\n key={project.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/projects/project-detail-page', project.id))}\n >\n <span className=\"text-sm font-medium\">{project.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(project.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <PersonName id={portfolio.ownerId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Budget</span>\n <span className=\"font-medium\">\n {portfolio.budgetTotal != null\n ? `${portfolio.currency ?? ''} ${portfolio.budgetTotal.toLocaleString()}`.trim()\n : 'Not set'}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Fiscal Period</span>\n <span>{portfolio.fiscalPeriod ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(portfolio.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(portfolio.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {portfolio.tags.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {portfolio.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAM,IAAgB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BnB,IAAyB,CAAG;;;;;;;;;;;;;;;;AAmClC,SAAgB,IAAoC;CAClD,IAAM,IAAW,GAAuB,EAElC,IADS,GAA2B,CACf,MAAM,IAE3B,EAAE,SAAM,YAAS,aAAU,EAkB9B,GAAe;EAAE,WAAW,EAAE,IAAI,GAAa;EAAE,MAAM,CAAC;EAAa,CAAC,EAEnE,EAAE,MAAM,MAAiB,EAC7B,GACA;EAAE,WAAW,EAAE,gBAAa;EAAE,MAAM,CAAC;EAAa,CACnD;AAID,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAIV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAsC,8BAA2B,EAAM,QAAc;;CAG9F,IAAM,IAAY,GAAM;AACxB,KAAI,CAAC,EAAW,QAAO,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAA0B,CAAA;CAE5F,IAAM,IAAW,GAAc,+BAA+B;AAE9D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA;MAC9C,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAU;OAAU,CAAA;MAC5D,kBAAC,GAAD;OAAO,SAAQ;OAAU,WAAU;iBAChC,EAAU,EAAU,OAAO;OACtB,CAAA;MACJ;QACL,EAAU,aACT,kBAAC,KAAD;KAAG,WAAU;eACV,EAAU,cAAc,WACpB,EAAU,kBAAkB,qBAC7B,EAAU,EAAU,UAAU;KAChC,CAAA,CAEF,EAAA,CAAA,EACN,kBAAC,GAAD;KACE,aAAa,EAAU;KACvB,MAAM,EAAU;KAChB,QAAQ,EAAU;KAClB,CAAA,CACE;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAW,WAAU,gCAAiC,CAAA;OACtD,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAU,iBAAiB,EAAU,SAAS;QAC7C,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAY,CAAA;OAC7C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA;OACnD,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAU,iBAAiB,EAAU,SAAS;QAC7C,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAY,CAAA;OAC7C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAe,WAAU,oCAAqC,CAAA;OAC9D,kBAAC,KAAD;QAAG,WAAU;kBAA2B,GAAU,eAAe;QAAM,CAAA;OACvE,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAW,CAAA;OAC5C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAO,WAAU,4CAA6C,CAAA;OAC9D,kBAAC,KAAD;QAAG,WAAU;kBAA2B,GAAU,qBAAqB;QAAM,CAAA;OAC7E,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAsB,CAAA;OACvD;SACT,CAAA;KACH;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAW,EAAU,eAAe;QAA+B,CAAA,EAC/E,IACC,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAsB,CAAA,EACnE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAS,qBAAqB,OAE3B,mBADA,GAAG,EAAS,kBAAkB,QAAQ,EAAE,CAAC;WAE3C,CAAA,CACA;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAgB,CAAA,EAC7D,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAS,eAAe,OAErB,oBADA,EAAS,YAAY,QAAQ,EAAE;WAEjC,CAAA,CACA;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAe,CAAA,EAC5D,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAS,cAAc,OAA8C,QAAvC,EAAS,WAAW,gBAAgB;WACjE,CAAA,CACA;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAsB,CAAA,EACnE,kBAAC,KAAD;WAAG,WAAU;qBACV,OAAO,KAAK,EAAS,iBAAiB,CAAC,SAAS,IAC7C,OAAO,QAAQ,EAAS,iBAAiB,CACtC,KAAK,CAAC,GAAQ,OAAW,GAAG,EAAU,EAAO,CAAC,IAAI,IAAQ,CAC1D,KAAK,KAAK,GACb;WACF,CAAA,CACA;;SACF;YAEN,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAqB,CAAA,CAExD;SACT,EAAA,CAAA;MAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;OAAW;OAAW,EAAU,SAAS;OAAO;OAAa,EAAA,CAAA,EAClD,CAAA,EACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACG,EAAU,SAAS,WAAW,KAC7B,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAEzC,CAAA,EAEL,EAAU,SAAS,KAAK,MACvB,kBAAC,UAAD;QAEE,MAAK;QACL,WAAU;QACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;kBAJlF,CAME,kBAAC,QAAD;SAAM,WAAU;mBAAuB,EAAQ;SAAY,CAAA,EAC3D,kBAAC,GAAD;SAAO,SAAQ;SAAU,WAAU;mBAChC,EAAU,EAAQ,OAAO;SACpB,CAAA,CACD;UATF,EAAQ,GASN,CACT,CACU;SACT,EAAA,CAAA;MAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;OAAW;OAAW,EAAU,SAAS;OAAO;OAAa,EAAA,CAAA,EAClD,CAAA,EACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACG,EAAU,SAAS,WAAW,KAC7B,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAEzC,CAAA,EAEL,EAAU,SAAS,KAAK,MACvB,kBAAC,UAAD;QAEE,MAAK;QACL,WAAU;QACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;kBAJlF,CAME,kBAAC,QAAD;SAAM,WAAU;mBAAuB,EAAQ;SAAY,CAAA,EAC3D,kBAAC,GAAD;SAAO,SAAQ;SAAU,WAAU;mBAChC,EAAU,EAAQ,OAAO;SACpB,CAAA,CACD;UATF,EAAQ,GASN,CACT,CACU;SACT,EAAA,CAAA;MACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA,EACnB,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAY,CAAA,EACpD,kBAAC,GAAD,EAAY,IAAI,EAAU,SAAW,CAAA,CACjC;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAa,CAAA,EACrD,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAU,eAAe,OAEtB,YADA,GAAG,EAAU,YAAY,GAAG,GAAG,EAAU,YAAY,gBAAgB,GAAG,MAAM;SAE7E,CAAA,CACH;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAoB,CAAA,EAC5D,kBAAC,QAAD,EAAA,UAAO,EAAU,gBAAgB,WAAiB,CAAA,CAC9C;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB,EAAQ,CAAA,CAC7D;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB,EAAQ,CAAA,CAC7D;;OACM;QACT,EAAA,CAAA,EAEN,EAAU,KAAK,SAAS,KACvB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,QAAgB,CAAA,EAChB,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBACpB,EAAU,KAAK,KAAK,MACnB,kBAAC,GAAD;OAAiB,SAAQ;OAAU,WAAU;iBAC1C;OACK,EAFI,EAEJ,CACR;MACU,CAAA,CACT,EAAA,CAAA,CAEL;OACF;;GACF"}
1
+ {"version":3,"file":"PortfoliosPortfolioDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge, Button } from '@burdenoff/fe-libs/ui';\nimport { Briefcase, Target, AlertTriangle, Clock, Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PortfolioHeaderActions } from '../controls/PortfolioControls';\n\nconst GET_PORTFOLIO = gql`\n query GetPortfolio($id: ID!) {\n planmagnetPortfolio(id: $id) {\n id\n workspaceId\n name\n description\n status\n alignment\n alignmentLabel\n ownerId\n budgetTotal\n currency\n fiscalPeriod\n tags\n createdAt\n updatedAt\n programs {\n id\n name\n status\n }\n projects {\n id\n name\n status\n }\n }\n }\n`;\n\nconst GET_PORTFOLIO_OVERVIEW = gql`\n query GetPortfolioOverview($portfolioId: ID!) {\n planmagnetPortfolioOverview(portfolioId: $portfolioId) {\n portfolioId\n totalPrograms\n totalProjects\n projectsByStatus\n totalBudget\n totalSpent\n budgetUtilization\n healthScore\n atRiskCount\n overdueMilestones\n }\n }\n`;\n\ninterface RelatedNode {\n id: string;\n name: string;\n status: string;\n}\n\ninterface OverviewNode {\n totalPrograms: number;\n totalProjects: number;\n projectsByStatus: Record<string, number>;\n totalBudget: number | null;\n totalSpent: number | null;\n budgetUtilization: number | null;\n healthScore: number | null;\n atRiskCount: number;\n overdueMilestones: number;\n}\n\nexport function PortfoliosPortfolioDetailPagePage() {\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const portfolioId = params.id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetPortfolio: {\n id: string;\n name: string;\n description: string | null;\n status: string;\n alignment: string | null;\n alignmentLabel: string | null;\n ownerId: string;\n budgetTotal: number | null;\n currency: string | null;\n fiscalPeriod: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n programs: RelatedNode[];\n projects: RelatedNode[];\n } | null;\n }>(GET_PORTFOLIO, { variables: { id: portfolioId }, skip: !portfolioId });\n\n const { data: overviewData } = useQuery<{ planmagnetPortfolioOverview: OverviewNode }>(\n GET_PORTFOLIO_OVERVIEW,\n { variables: { portfolioId }, skip: !portfolioId }\n );\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load portfolio: {error.message}</div>;\n }\n\n const portfolio = data?.planmagnetPortfolio;\n if (!portfolio) return <div className=\"p-6 text-muted-foreground\">Portfolio not found.</div>;\n\n const overview = overviewData?.planmagnetPortfolioOverview ?? null;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Briefcase className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{portfolio.name}</h1>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(portfolio.status)}\n </Badge>\n </div>\n {portfolio.alignment && (\n <p className=\"text-sm text-muted-foreground mt-1\">\n {portfolio.alignment === 'CUSTOM'\n ? (portfolio.alignmentLabel ?? 'Custom alignment')\n : enumLabel(portfolio.alignment)}\n </p>\n )}\n </div>\n <PortfolioHeaderActions\n portfolioId={portfolio.id}\n name={portfolio.name}\n status={portfolio.status}\n />\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Briefcase className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalPrograms ?? portfolio.programs.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Programs</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Target className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalProjects ?? portfolio.projects.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Projects</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <AlertTriangle className=\"h-5 w-5 mx-auto text-destructive\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.atRiskCount ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">At Risk</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-status-warning-text\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.overdueMilestones ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">Overdue Milestones</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm\">{portfolio.description ?? 'No description provided.'}</p>\n {overview ? (\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Budget Utilization</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.budgetUtilization != null\n ? `${overview.budgetUtilization.toFixed(0)}%`\n : 'No budget data'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Health Score</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.healthScore != null\n ? overview.healthScore.toFixed(0)\n : 'No projects yet'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Total Spent</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.totalSpent != null ? overview.totalSpent.toLocaleString() : 'N/A'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Projects by Status</p>\n <p className=\"text-sm mt-1\">\n {Object.keys(overview.projectsByStatus).length > 0\n ? Object.entries(overview.projectsByStatus)\n .map(([status, count]) => `${enumLabel(status)}: ${count}`)\n .join(', ')\n : 'No projects yet'}\n </p>\n </div>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">Loading overview…</p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Programs ({portfolio.programs.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.programs.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No programs are linked to this portfolio yet.\n </p>\n )}\n {portfolio.programs.map((program) => (\n <button\n key={program.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/products/program-detail-page', program.id))}\n >\n <span className=\"text-sm font-medium\">{program.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(program.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Projects ({portfolio.projects.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.projects.length === 0 && (\n <div className=\"space-y-1\">\n <p className=\"text-sm text-muted-foreground\">\n No projects are linked to this portfolio yet.\n </p>\n <p className=\"text-sm text-muted-foreground\">\n Open a project and set its Portfolio field to add it here.{' '}\n <Button\n variant=\"link\"\n className=\"h-auto p-0 text-sm\"\n onClick={() => navigate('/projects/projects-page')}\n >\n Go to Projects\n </Button>\n </p>\n </div>\n )}\n {portfolio.projects.map((project) => (\n <button\n key={project.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/projects/project-detail-page', project.id))}\n >\n <span className=\"text-sm font-medium\">{project.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(project.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <PersonName id={portfolio.ownerId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Budget</span>\n <span className=\"font-medium\">\n {portfolio.budgetTotal != null\n ? `${portfolio.currency ?? ''} ${portfolio.budgetTotal.toLocaleString()}`.trim()\n : 'Not set'}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Fiscal Period</span>\n <span>{portfolio.fiscalPeriod ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(portfolio.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(portfolio.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {portfolio.tags.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {portfolio.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsCriticalPathPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"names":[],"mappings":"AAwDA,wBAAgB,4BAA4B,4CAgL3C"}
1
+ {"version":3,"file":"ProjectsCriticalPathPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"names":[],"mappings":"AAkFA,wBAAgB,4BAA4B,4CAmR3C"}