@burdenoff/microfe-planmagnet 2026.928.7 → 2026.928.9

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 (25) hide show
  1. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoutes.js +18 -14
  3. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  4. package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -1
  5. package/dist/planmagnet/automation/AutomationRuleFields.js +13 -12
  6. package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -1
  7. package/dist/planmagnet/controls/WorkItemControls.d.ts +21 -0
  8. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  9. package/dist/planmagnet/controls/WorkItemControls.js +34 -15
  10. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  11. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -1
  12. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +153 -63
  13. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -1
  14. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +0 -7
  15. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  16. package/dist/planmagnet/pages/ReportsReportsPagePage.js +517 -39
  17. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  18. package/dist/planmagnet/pages/TasksMyWorkPagePage.d.ts +2 -0
  19. package/dist/planmagnet/pages/TasksMyWorkPagePage.d.ts.map +1 -0
  20. package/dist/planmagnet/pages/TasksMyWorkPagePage.js +257 -0
  21. package/dist/planmagnet/pages/TasksMyWorkPagePage.js.map +1 -0
  22. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -1
  23. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +77 -70
  24. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -1
  25. package/package.json +1 -1
@@ -47,13 +47,14 @@ var S = p`
47
47
  "GetWorkItems",
48
48
  "GetWorkItemCounts",
49
49
  "ProjectBoard",
50
- "GetDashboardData"
50
+ "GetDashboardData",
51
+ "GetMyWorkItems"
51
52
  ], T = [
52
53
  "GetWorkItemDetail",
53
54
  "WorkItemActivity",
54
55
  ...w
55
- ], E = "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]";
56
- function D(e) {
56
+ ], E = "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]", D = "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60";
57
+ function O(e) {
57
58
  let [t, n] = m(S, {
58
59
  refetchQueries: T,
59
60
  awaitRefetchQueries: !0
@@ -74,8 +75,8 @@ function D(e) {
74
75
  error: r
75
76
  };
76
77
  }
77
- function O({ workItemId: e, field: t, value: n, options: r, label: i }) {
78
- let { save: a, saving: s, error: c } = D(e), l = r.includes(n) ? r : [n, ...r];
78
+ function k({ workItemId: e, field: t, value: n, options: r, label: i }) {
79
+ let { save: a, saving: s, error: c } = O(e), l = r.includes(n) ? r : [n, ...r];
79
80
  return /* @__PURE__ */ g("div", {
80
81
  className: "flex flex-col items-end gap-1",
81
82
  children: [/* @__PURE__ */ h("select", {
@@ -94,8 +95,8 @@ function O({ workItemId: e, field: t, value: n, options: r, label: i }) {
94
95
  }), c && /* @__PURE__ */ h(o, { message: c })]
95
96
  });
96
97
  }
97
- function k({ workItemId: e, projectId: t, value: n }) {
98
- let r = c(t), { save: i, saving: a, error: u } = D(e);
98
+ function A({ workItemId: e, projectId: t, value: n }) {
99
+ let r = c(t), { save: i, saving: a, error: u } = O(e);
99
100
  return /* @__PURE__ */ g("div", {
100
101
  className: "flex flex-col items-end gap-1",
101
102
  children: [
@@ -121,7 +122,7 @@ function k({ workItemId: e, projectId: t, value: n }) {
121
122
  ]
122
123
  });
123
124
  }
124
- function A(t) {
125
+ function j(t) {
125
126
  let { workspaceId: r, authToken: i, apiGatewayUrl: a } = e(), o = v({
126
127
  workspaceId: r ?? "",
127
128
  authToken: i ?? void 0,
@@ -140,8 +141,8 @@ function A(t) {
140
141
  fetchMore: o.fetchMore
141
142
  };
142
143
  }
143
- function j({ id: e, value: t, onChange: n, disabled: r = !1, noneLabel: i = "Unassigned", ariaLabel: a = "Assignee" }) {
144
- let { options: o, loading: s, hasMore: c, fetchMore: l } = A(t || null);
144
+ function M({ id: e, value: t, onChange: n, disabled: r = !1, noneLabel: i = "Unassigned", ariaLabel: a = "Assignee" }) {
145
+ let { options: o, loading: s, hasMore: c, fetchMore: l } = j(t || null);
145
146
  return /* @__PURE__ */ g("div", {
146
147
  className: "flex flex-col gap-1",
147
148
  children: [
@@ -174,11 +175,11 @@ function j({ id: e, value: t, onChange: n, disabled: r = !1, noneLabel: i = "Una
174
175
  ]
175
176
  });
176
177
  }
177
- function M({ workItemId: e, assigneeId: t }) {
178
- let { save: n, saving: r, error: i } = D(e);
178
+ function N({ workItemId: e, assigneeId: t }) {
179
+ let { save: n, saving: r, error: i } = O(e);
179
180
  return /* @__PURE__ */ g("div", {
180
181
  className: "flex flex-col items-end gap-1",
181
- children: [/* @__PURE__ */ h(j, {
182
+ children: [/* @__PURE__ */ h(M, {
182
183
  value: t ?? "",
183
184
  disabled: r,
184
185
  onChange: (e) => {
@@ -187,7 +188,25 @@ function M({ workItemId: e, assigneeId: t }) {
187
188
  }), i && /* @__PURE__ */ h(o, { message: i })]
188
189
  });
189
190
  }
190
- function N({ workItemId: e, title: n }) {
191
+ function P({ workItemId: e, dueDate: t }) {
192
+ let { save: n, saving: r, error: i } = O(e), a = t ? t.slice(0, 10) : "";
193
+ return /* @__PURE__ */ g("div", {
194
+ className: "flex flex-col items-end gap-1",
195
+ children: [/* @__PURE__ */ h("input", {
196
+ type: "date",
197
+ "aria-label": "Due date",
198
+ "data-testid": "task-due-date",
199
+ className: D,
200
+ value: a,
201
+ disabled: r,
202
+ onChange: (e) => {
203
+ let t = e.target.value;
204
+ t !== a && n({ dueDate: t ? (/* @__PURE__ */ new Date(`${t}T00:00:00.000Z`)).toISOString() : null });
205
+ }
206
+ }), i && /* @__PURE__ */ h(o, { message: i })]
207
+ });
208
+ }
209
+ function F({ workItemId: e, title: n }) {
191
210
  let s = t(), [c, l] = m(C, {
192
211
  refetchQueries: w,
193
212
  context: r
@@ -224,6 +243,6 @@ function N({ workItemId: e, title: n }) {
224
243
  });
225
244
  }
226
245
  //#endregion
227
- export { j as AssigneeSelect, b as WORK_ITEM_PRIORITIES, y as WORK_ITEM_STATUSES, M as WorkItemAssigneeSelect, O as WorkItemEnumSelect, N as WorkItemHeaderActions, k as WorkItemStatusSelect, x as enumLabel, A as useAssigneeOptions };
246
+ export { M as AssigneeSelect, b as WORK_ITEM_PRIORITIES, y as WORK_ITEM_STATUSES, N as WorkItemAssigneeSelect, P as WorkItemDueDateInput, k as WorkItemEnumSelect, F as WorkItemHeaderActions, A as WorkItemStatusSelect, x as enumLabel, j as useAssigneeOptions };
228
247
 
229
248
  //# sourceMappingURL=WorkItemControls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkItemControls.js","names":[],"sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { useWorkspaceDirectory } from '@burdenoff/fe-libs/sharing';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { allowedTargets, useProjectWorkflow, workflowLabel } from '../hooks/useProjectWorkflow';\nimport { usePersonProfile } from '../people/People';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\n\n/**\n * Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or\n * assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status\n * could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team\n * page, sprint \"Team Members\" list and board card read \"Unassigned\".\n *\n * Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records\n * its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.\n */\n\nexport const WORK_ITEM_STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nexport const WORK_ITEM_PRIORITIES = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const;\n\nexport function enumLabel(value: string): string {\n const words = value.toLowerCase().replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst UPDATE_WORK_ITEM = gql`\n mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $id, input: $input) {\n id\n status\n priority\n assigneeId\n }\n }\n`;\nconst DELETE_WORK_ITEM = gql`\n mutation WorkItemControlsDelete($id: ID!) {\n planmagnetDeleteWorkItem(id: $id)\n }\n`;\n\n/** Every query that shows a task's status, priority or assignee. */\nconst LIST_QUERIES = ['GetWorkItems', 'GetWorkItemCounts', 'ProjectBoard', 'GetDashboardData'];\nconst DETAIL_QUERIES = ['GetWorkItemDetail', 'WorkItemActivity', ...LIST_QUERIES];\n\nconst selectClass =\n 'rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]';\n\nfunction useWorkItemUpdate(workItemId: string) {\n const [update, state] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: DETAIL_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const save = async (input: Record<string, unknown>) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: workItemId, input } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be updated.');\n }\n };\n return { save, saving: state.loading, error };\n}\n\n/** A status or priority select that saves on change. */\nexport function WorkItemEnumSelect({\n workItemId,\n field,\n value,\n options,\n label,\n}: {\n workItemId: string;\n field: 'status' | 'priority';\n value: string;\n options: readonly string[];\n label: string;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n // A stored value the list does not know (older data) stays selectable rather than silently showing another one.\n const all = options.includes(value) ? options : [value, ...options];\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label={label}\n data-testid={`task-${field}`}\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ [field]: e.target.value });\n }}\n >\n {all.map((option) => (\n <option key={option} value={option}>\n {enumLabel(option)}\n </option>\n ))}\n </select>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * The status select (BOFF-8043): the project's names for its statuses, and only the moves its workflow allows — the\n * server refuses anything else anyway, with the reason shown below the select.\n */\nexport function WorkItemStatusSelect({\n workItemId,\n projectId,\n value,\n}: {\n workItemId: string;\n projectId: string;\n value: string;\n}) {\n const workflow = useProjectWorkflow(projectId);\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n const options = allowedTargets(workflow, value, WORK_ITEM_STATUSES);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label=\"Status\"\n data-testid=\"task-status\"\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ status: e.target.value });\n }}\n >\n {options.map((option) => (\n <option key={option} value={option}>\n {workflowLabel(workflow, option)}\n </option>\n ))}\n </select>\n {workflow?.enforceTransitions && (\n <span className=\"text-[11px] text-muted-foreground\">\n Only the moves this project allows\n </span>\n )}\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any\n * member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet\n * admin rights, so an ordinary member would get an empty picker.\n */\nexport function useAssigneeOptions(currentId: string | null | undefined) {\n const { workspaceId, authToken, apiGatewayUrl } = usePlanMagnet();\n const directory = useWorkspaceDirectory({\n workspaceId: workspaceId ?? '',\n authToken: authToken ?? undefined,\n apiGatewayUrl: apiGatewayUrl ?? undefined,\n });\n const current = usePersonProfile(currentId);\n const options = directory.members.map((m) => ({ id: m.id, name: m.name }));\n // The current assignee may be outside the loaded page (or no longer a member): keep them selectable by name.\n if (currentId && current && !options.some((o) => o.id === currentId)) {\n options.unshift({ id: currentId, name: current.displayName });\n }\n return {\n options,\n loading: directory.loading,\n hasMore: directory.hasMore,\n fetchMore: directory.fetchMore,\n };\n}\n\n/** A plain workspace-member select (for forms). '' means `noneLabel` (\"Unassigned\" by default). */\nexport function AssigneeSelect({\n id,\n value,\n onChange,\n disabled = false,\n noneLabel = 'Unassigned',\n ariaLabel = 'Assignee',\n}: {\n id?: string;\n value: string;\n onChange: (userId: string) => void;\n disabled?: boolean;\n /** BOFF-8044: e.g. \"No lead\" when reused for a Team's leadId, which isn't an \"assignee\". */\n noneLabel?: string;\n /** The control's accessible name when it has no visible `<label>` (i.e. no `id`). */\n ariaLabel?: string;\n}) {\n const { options, loading, hasMore, fetchMore } = useAssigneeOptions(value || null);\n return (\n <div className=\"flex flex-col gap-1\">\n <select\n id={id}\n aria-label={id ? undefined : ariaLabel}\n data-testid=\"assignee-select\"\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">{noneLabel}</option>\n {options.map((o) => (\n <option key={o.id} value={o.id}>\n {o.name}\n </option>\n ))}\n </select>\n {loading && options.length === 0 && (\n <span className=\"text-xs text-muted-foreground\">Loading members…</span>\n )}\n {hasMore && (\n <button\n type=\"button\"\n className=\"self-start text-xs text-primary hover:underline\"\n onClick={fetchMore}\n >\n Show more members\n </button>\n )}\n </div>\n );\n}\n\n/** The task detail's assignee control: saves on change. */\nexport function WorkItemAssigneeSelect({\n workItemId,\n assigneeId,\n}: {\n workItemId: string;\n assigneeId: string | null;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <AssigneeSelect\n value={assigneeId ?? ''}\n disabled={saving}\n onChange={(next) => {\n if (next !== (assigneeId ?? '')) void save({ assigneeId: next || null });\n }}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit and Delete for the task detail header. */\nexport function WorkItemHeaderActions({\n workItemId,\n title,\n}: {\n workItemId: string;\n title: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [remove, removeState] = useMutation(DELETE_WORK_ITEM, {\n refetchQueries: LIST_QUERIES,\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: workItemId } }));\n navigate('/tasks/tasks-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"task-edit\"\n onClick={() => navigate(`/tasks/edit-task-page?taskId=${encodeURIComponent(workItemId)}`)}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${title}\"?`}\n description=\"The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.\"\n confirmLabel=\"Delete task\"\n destructive\n disabled={removeState.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkItemControls.js","names":[],"sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { useWorkspaceDirectory } from '@burdenoff/fe-libs/sharing';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { allowedTargets, useProjectWorkflow, workflowLabel } from '../hooks/useProjectWorkflow';\nimport { usePersonProfile } from '../people/People';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\n\n/**\n * Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or\n * assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status\n * could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team\n * page, sprint \"Team Members\" list and board card read \"Unassigned\".\n *\n * Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records\n * its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.\n */\n\nexport const WORK_ITEM_STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nexport const WORK_ITEM_PRIORITIES = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const;\n\nexport function enumLabel(value: string): string {\n const words = value.toLowerCase().replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst UPDATE_WORK_ITEM = gql`\n mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $id, input: $input) {\n id\n status\n priority\n assigneeId\n }\n }\n`;\nconst DELETE_WORK_ITEM = gql`\n mutation WorkItemControlsDelete($id: ID!) {\n planmagnetDeleteWorkItem(id: $id)\n }\n`;\n\n/** Every query that shows a task's status, priority or assignee. */\nconst LIST_QUERIES = [\n 'GetWorkItems',\n 'GetWorkItemCounts',\n 'ProjectBoard',\n 'GetDashboardData',\n // BOFF-8048: My Work reads the same underlying rows by assigneeId — an inline edit made elsewhere (the board, the\n // detail page) must not leave a stale row sitting in a workspace member's My Work list.\n 'GetMyWorkItems',\n];\nconst DETAIL_QUERIES = ['GetWorkItemDetail', 'WorkItemActivity', ...LIST_QUERIES];\n\nconst selectClass =\n 'rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]';\nconst dateInputClass =\n 'rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60';\n\n/**\n * Shared by every inline-edit control on this page and on My Work (BOFF-8048): saves through\n * planmagnetUpdateWorkItem and surfaces a refusal (workflow, validation) as `error` rather than swallowing it —\n * see utils/mutationResult.ts. Exported so a page composing several of these controls in one row (My Work) can also\n * drive its own due-date input through the identical save path without re-deriving it.\n */\nexport function useWorkItemUpdate(workItemId: string) {\n const [update, state] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: DETAIL_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const save = async (input: Record<string, unknown>) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: workItemId, input } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be updated.');\n }\n };\n return { save, saving: state.loading, error };\n}\n\n/** A status or priority select that saves on change. */\nexport function WorkItemEnumSelect({\n workItemId,\n field,\n value,\n options,\n label,\n}: {\n workItemId: string;\n field: 'status' | 'priority';\n value: string;\n options: readonly string[];\n label: string;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n // A stored value the list does not know (older data) stays selectable rather than silently showing another one.\n const all = options.includes(value) ? options : [value, ...options];\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label={label}\n data-testid={`task-${field}`}\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ [field]: e.target.value });\n }}\n >\n {all.map((option) => (\n <option key={option} value={option}>\n {enumLabel(option)}\n </option>\n ))}\n </select>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * The status select (BOFF-8043): the project's names for its statuses, and only the moves its workflow allows — the\n * server refuses anything else anyway, with the reason shown below the select.\n */\nexport function WorkItemStatusSelect({\n workItemId,\n projectId,\n value,\n}: {\n workItemId: string;\n projectId: string;\n value: string;\n}) {\n const workflow = useProjectWorkflow(projectId);\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n const options = allowedTargets(workflow, value, WORK_ITEM_STATUSES);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label=\"Status\"\n data-testid=\"task-status\"\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ status: e.target.value });\n }}\n >\n {options.map((option) => (\n <option key={option} value={option}>\n {workflowLabel(workflow, option)}\n </option>\n ))}\n </select>\n {workflow?.enforceTransitions && (\n <span className=\"text-[11px] text-muted-foreground\">\n Only the moves this project allows\n </span>\n )}\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any\n * member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet\n * admin rights, so an ordinary member would get an empty picker.\n */\nexport function useAssigneeOptions(currentId: string | null | undefined) {\n const { workspaceId, authToken, apiGatewayUrl } = usePlanMagnet();\n const directory = useWorkspaceDirectory({\n workspaceId: workspaceId ?? '',\n authToken: authToken ?? undefined,\n apiGatewayUrl: apiGatewayUrl ?? undefined,\n });\n const current = usePersonProfile(currentId);\n const options = directory.members.map((m) => ({ id: m.id, name: m.name }));\n // The current assignee may be outside the loaded page (or no longer a member): keep them selectable by name.\n if (currentId && current && !options.some((o) => o.id === currentId)) {\n options.unshift({ id: currentId, name: current.displayName });\n }\n return {\n options,\n loading: directory.loading,\n hasMore: directory.hasMore,\n fetchMore: directory.fetchMore,\n };\n}\n\n/** A plain workspace-member select (for forms). '' means `noneLabel` (\"Unassigned\" by default). */\nexport function AssigneeSelect({\n id,\n value,\n onChange,\n disabled = false,\n noneLabel = 'Unassigned',\n ariaLabel = 'Assignee',\n}: {\n id?: string;\n value: string;\n onChange: (userId: string) => void;\n disabled?: boolean;\n /** BOFF-8044: e.g. \"No lead\" when reused for a Team's leadId, which isn't an \"assignee\". */\n noneLabel?: string;\n /** The control's accessible name when it has no visible `<label>` (i.e. no `id`). */\n ariaLabel?: string;\n}) {\n const { options, loading, hasMore, fetchMore } = useAssigneeOptions(value || null);\n return (\n <div className=\"flex flex-col gap-1\">\n <select\n id={id}\n aria-label={id ? undefined : ariaLabel}\n data-testid=\"assignee-select\"\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">{noneLabel}</option>\n {options.map((o) => (\n <option key={o.id} value={o.id}>\n {o.name}\n </option>\n ))}\n </select>\n {loading && options.length === 0 && (\n <span className=\"text-xs text-muted-foreground\">Loading members…</span>\n )}\n {hasMore && (\n <button\n type=\"button\"\n className=\"self-start text-xs text-primary hover:underline\"\n onClick={fetchMore}\n >\n Show more members\n </button>\n )}\n </div>\n );\n}\n\n/** The task detail's assignee control: saves on change. */\nexport function WorkItemAssigneeSelect({\n workItemId,\n assigneeId,\n}: {\n workItemId: string;\n assigneeId: string | null;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <AssigneeSelect\n value={assigneeId ?? ''}\n disabled={saving}\n onChange={(next) => {\n if (next !== (assigneeId ?? '')) void save({ assigneeId: next || null });\n }}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * The due-date inline control (BOFF-8048): a plain date input saving straight through\n * planmagnetUpdateWorkItem's `dueDate: DateTime` scalar — an already-implemented real scalar (see\n * utils/datetime-scalar.ts), so the epoch-serialization trap that hits a GraphQL `String` date field does not\n * apply here. Clearing the field saves `null` (no due date) rather than being unclearable once set.\n */\nexport function WorkItemDueDateInput({\n workItemId,\n dueDate,\n}: {\n workItemId: string;\n dueDate: string | null;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n // <input type=\"date\"> works in yyyy-mm-dd; dueDate arrives as a full ISO DateTime.\n const value = dueDate ? dueDate.slice(0, 10) : '';\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <input\n type=\"date\"\n aria-label=\"Due date\"\n data-testid=\"task-due-date\"\n className={dateInputClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n const next = e.target.value;\n if (next === value) return;\n void save({ dueDate: next ? new Date(`${next}T00:00:00.000Z`).toISOString() : null });\n }}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit and Delete for the task detail header. */\nexport function WorkItemHeaderActions({\n workItemId,\n title,\n}: {\n workItemId: string;\n title: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [remove, removeState] = useMutation(DELETE_WORK_ITEM, {\n refetchQueries: LIST_QUERIES,\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: workItemId } }));\n navigate('/tasks/tasks-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"task-edit\"\n onClick={() => navigate(`/tasks/edit-task-page?taskId=${encodeURIComponent(workItemId)}`)}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${title}\"?`}\n description=\"The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.\"\n confirmLabel=\"Delete task\"\n destructive\n disabled={removeState.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\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"}
@@ -1,14 +1,14 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
2
  import { useEntityId as n } from "../hooks/useEntityId.js";
3
3
  import { ProjectSelect as r } from "../controls/Pickers.js";
4
- import { useState as i } from "react";
5
- import { AlertTriangle as a, Route as o } from "lucide-react";
6
- import { gql as s } from "@apollo/client";
7
- import { useQuery as c } from "@apollo/client/react";
8
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
9
- import { Badge as d, Card as f, CardContent as p, CardHeader as m, CardTitle as h } from "@burdenoff/fe-libs/ui";
4
+ import { useEffect as i, useState as a } from "react";
5
+ import { AlertTriangle as o, Route as s } from "lucide-react";
6
+ import { gql as c } from "@apollo/client";
7
+ import { useQuery as l } from "@apollo/client/react";
8
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
9
+ import { Badge as f, Card as p, CardContent as m, CardHeader as h, CardTitle as g } from "@burdenoff/fe-libs/ui";
10
10
  //#region src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx
11
- var g = s`
11
+ var _ = c`
12
12
  query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {
13
13
  planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {
14
14
  totalDurationMinutes
@@ -24,141 +24,231 @@ var g = s`
24
24
  length
25
25
  }
26
26
  }
27
- `, _ = s`
27
+ `, v = c`
28
28
  query GetCriticalPathProject($id: ID!) {
29
29
  planmagnetProject(id: $id) {
30
30
  id
31
31
  name
32
32
  }
33
33
  }
34
+ `, y = 100, b = c`
35
+ query GetNonCriticalWorkItems(
36
+ $workspaceId: ID!
37
+ $filter: PlanmagnetWorkItemFilter
38
+ $pagination: PlanmagnetPaginationInput
39
+ ) {
40
+ planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {
41
+ items {
42
+ id
43
+ title
44
+ key
45
+ }
46
+ pageInfo {
47
+ hasNextPage
48
+ endCursor
49
+ }
50
+ }
51
+ }
34
52
  `;
35
- function v(e) {
53
+ function x(e) {
36
54
  if (e <= 0) return "0 days";
37
55
  let t = Math.round(e);
38
56
  if (t < 60) return `${Math.max(1, t)}m`;
39
57
  let n = Math.round(t / 60), r = Math.floor(n / 24), i = n % 24;
40
58
  return r === 0 ? `${i}h` : i > 0 ? `${r}d ${i}h` : `${r} day${r === 1 ? "" : "s"}`;
41
59
  }
42
- function y() {
43
- let s = n(), { workspaceId: y } = e(), b = t(), [x, S] = i(""), { data: C, loading: w, error: T } = c(g, {
60
+ function S() {
61
+ let c = n(), { workspaceId: S } = e(), C = t(), [w, T] = a(""), { data: E, loading: D, error: O } = l(_, {
44
62
  variables: {
45
- workspaceId: y ?? "",
46
- projectId: s
63
+ workspaceId: S ?? "",
64
+ projectId: c
47
65
  },
48
- skip: !y || !s
49
- }), { data: E } = c(_, {
50
- variables: { id: s },
51
- skip: !s
52
- });
53
- if (!s) return /* @__PURE__ */ u("div", {
66
+ skip: !S || !c
67
+ }), { data: k } = l(v, {
68
+ variables: { id: c },
69
+ skip: !c
70
+ }), A = E?.planmagnetCriticalPath.path ?? [], j = A.length > 0, M = A.map((e) => e.id), { data: N, loading: P, error: F, fetchMore: I } = l(b, {
71
+ variables: {
72
+ workspaceId: S ?? "",
73
+ filter: {
74
+ projectId: c,
75
+ excludeIds: M
76
+ },
77
+ pagination: { first: y }
78
+ },
79
+ skip: !S || !c || !j
80
+ }), L = N?.planmagnetWorkItems.pageInfo, [R, z] = a(null);
81
+ if (i(() => {
82
+ if (R || !L?.hasNextPage || !L.endCursor) return;
83
+ let e = L.endCursor;
84
+ I({
85
+ variables: { pagination: {
86
+ first: y,
87
+ after: e
88
+ } },
89
+ updateQuery: (e, { fetchMoreResult: t }) => ({ planmagnetWorkItems: {
90
+ ...t.planmagnetWorkItems,
91
+ items: [...e.planmagnetWorkItems.items, ...t.planmagnetWorkItems.items]
92
+ } })
93
+ }).then((e) => {
94
+ let t = e?.error;
95
+ t && z(t.message ?? "Unknown error");
96
+ }).catch((e) => z(e instanceof Error ? e.message : String(e)));
97
+ }, [
98
+ R,
99
+ L?.hasNextPage,
100
+ L?.endCursor,
101
+ I
102
+ ]), !c) return /* @__PURE__ */ d("div", {
54
103
  className: "space-y-6 p-6",
55
- children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ u("h1", {
104
+ children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("h1", {
56
105
  className: "text-2xl font-semibold flex items-center gap-2",
57
- children: [/* @__PURE__ */ l(o, { className: "h-5 w-5 text-primary" }), "Critical Path"]
58
- }), /* @__PURE__ */ l("p", {
106
+ children: [/* @__PURE__ */ u(s, { className: "h-5 w-5 text-primary" }), "Critical Path"]
107
+ }), /* @__PURE__ */ u("p", {
59
108
  className: "text-sm text-muted-foreground",
60
109
  children: "Choose a project to see its critical path."
61
- })] }), /* @__PURE__ */ l(f, { children: /* @__PURE__ */ u(p, {
110
+ })] }), /* @__PURE__ */ u(p, { children: /* @__PURE__ */ d(m, {
62
111
  className: "pt-6 space-y-3",
63
112
  children: [
64
- /* @__PURE__ */ l("label", {
113
+ /* @__PURE__ */ u("label", {
65
114
  htmlFor: "critical-path-project",
66
115
  className: "text-sm font-medium",
67
116
  children: "Project"
68
117
  }),
69
- /* @__PURE__ */ l(r, {
118
+ /* @__PURE__ */ u(r, {
70
119
  id: "critical-path-project",
71
- value: x,
72
- onChange: S,
120
+ value: w,
121
+ onChange: T,
73
122
  noneLabel: "Choose a project…"
74
123
  }),
75
- /* @__PURE__ */ l("button", {
124
+ /* @__PURE__ */ u("button", {
76
125
  type: "button",
77
126
  className: "text-sm text-primary hover:underline disabled:pointer-events-none disabled:opacity-50",
78
- disabled: !x,
79
- onClick: () => b(`/projects/critical-path-page/${encodeURIComponent(x)}`),
127
+ disabled: !w,
128
+ onClick: () => C(`/projects/critical-path-page/${encodeURIComponent(w)}`),
80
129
  children: "View critical path →"
81
130
  })
82
131
  ]
83
132
  }) })]
84
133
  });
85
- if (w && !C) return /* @__PURE__ */ l("div", {
134
+ if (D && !E) return /* @__PURE__ */ u("div", {
86
135
  className: "flex items-center justify-center p-12",
87
- children: /* @__PURE__ */ l("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
136
+ children: /* @__PURE__ */ u("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
88
137
  });
89
- if (T) return /* @__PURE__ */ u("div", {
138
+ if (O) return /* @__PURE__ */ d("div", {
90
139
  className: "p-6 text-red-500",
91
- children: ["Error computing critical path: ", T.message]
140
+ children: ["Error computing critical path: ", O.message]
92
141
  });
93
- let D = C?.planmagnetCriticalPath.path ?? [], O = C?.planmagnetCriticalPath.slack ?? {}, k = C?.planmagnetCriticalPath.totalDurationMinutes ?? 0, A = C?.planmagnetDependencyCycles ?? [], j = E?.planmagnetProject?.name;
94
- return /* @__PURE__ */ u("div", {
142
+ let B = E?.planmagnetCriticalPath.slack ?? {}, V = E?.planmagnetCriticalPath.totalDurationMinutes ?? 0, H = E?.planmagnetDependencyCycles ?? [], U = k?.planmagnetProject?.name, W = [...N?.planmagnetWorkItems.items ?? []].sort((e, t) => (B[e.id] ?? 0) - (B[t.id] ?? 0));
143
+ return /* @__PURE__ */ d("div", {
95
144
  className: "space-y-6 p-6",
96
145
  children: [
97
- /* @__PURE__ */ l("div", {
146
+ /* @__PURE__ */ u("div", {
98
147
  className: "flex items-center justify-between",
99
- children: /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ u("h1", {
148
+ children: /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("h1", {
100
149
  className: "text-2xl font-semibold flex items-center gap-2",
101
- children: [/* @__PURE__ */ l(o, { className: "h-5 w-5 text-primary" }), "Critical Path"]
102
- }), /* @__PURE__ */ u("p", {
150
+ children: [/* @__PURE__ */ u(s, { className: "h-5 w-5 text-primary" }), "Critical Path"]
151
+ }), /* @__PURE__ */ d("p", {
103
152
  className: "text-sm text-muted-foreground",
104
- children: [j ?? "This project", " — the longest chain of dependent work; a delay on any node here delays the whole chain."]
153
+ children: [U ?? "This project", " — the longest chain of dependent work; a delay on any node here delays the whole chain."]
105
154
  })] })
106
155
  }),
107
- A.length > 0 && /* @__PURE__ */ u("div", {
156
+ H.length > 0 && /* @__PURE__ */ d("div", {
108
157
  role: "alert",
109
158
  className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive",
110
- children: [/* @__PURE__ */ l(a, { className: "h-4 w-4 mt-0.5 shrink-0" }), /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ u("p", {
159
+ children: [/* @__PURE__ */ u(o, { className: "h-4 w-4 mt-0.5 shrink-0" }), /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("p", {
111
160
  className: "font-medium",
112
161
  children: [
113
- A.length,
162
+ H.length,
114
163
  " dependency cycle",
115
- A.length === 1 ? "" : "s",
164
+ H.length === 1 ? "" : "s",
116
165
  " detected."
117
166
  ]
118
- }), /* @__PURE__ */ l("p", { children: "A cycle has no valid schedule, so the path below excludes the items involved. Break each cycle by removing or re-pointing one of its dependencies from the task detail page." })] })]
167
+ }), /* @__PURE__ */ u("p", { children: "A cycle has no valid schedule, so the path below excludes the items involved. Break each cycle by removing or re-pointing one of its dependencies from the task detail page." })] })]
119
168
  }),
120
- /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(m, { children: /* @__PURE__ */ l(h, { children: "Total duration" }) }), /* @__PURE__ */ u(p, { children: [/* @__PURE__ */ l("p", {
169
+ /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ u(g, { children: "Total duration" }) }), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u("p", {
121
170
  className: "text-2xl font-bold",
122
- children: v(k)
123
- }), /* @__PURE__ */ l("p", {
171
+ children: x(V)
172
+ }), /* @__PURE__ */ u("p", {
124
173
  className: "text-xs text-muted-foreground mt-1",
125
174
  children: "Work item spans plus dependency lag/lead, summed along the longest chain."
126
175
  })] })] }),
127
- /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(m, { children: /* @__PURE__ */ u(h, { children: [
176
+ /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ d(g, { children: [
128
177
  "Path (",
129
- D.length,
178
+ A.length,
130
179
  " item",
131
- D.length === 1 ? "" : "s",
180
+ A.length === 1 ? "" : "s",
132
181
  ")"
133
- ] }) }), /* @__PURE__ */ u(p, {
182
+ ] }) }), /* @__PURE__ */ d(m, {
134
183
  className: "space-y-2",
135
- children: [D.map((e, t) => /* @__PURE__ */ u("div", {
184
+ children: [A.map((e, t) => /* @__PURE__ */ d("div", {
136
185
  className: "flex items-center justify-between gap-3 rounded-md border border-input p-3",
137
- children: [/* @__PURE__ */ u("div", {
186
+ children: [/* @__PURE__ */ d("div", {
138
187
  className: "flex items-center gap-3 min-w-0",
139
- children: [/* @__PURE__ */ l("span", {
188
+ children: [/* @__PURE__ */ u("span", {
140
189
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground",
141
190
  children: t + 1
142
- }), /* @__PURE__ */ u("button", {
191
+ }), /* @__PURE__ */ d("button", {
143
192
  type: "button",
144
193
  className: "min-w-0 truncate text-left text-sm font-medium hover:underline",
145
- onClick: () => b(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
194
+ onClick: () => C(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
146
195
  children: [e.key ? `${e.key} ` : "", e.title ?? "Untitled work item"]
147
196
  })]
148
- }), /* @__PURE__ */ l(d, {
149
- variant: (O[e.id] ?? 0) === 0 ? "default" : "secondary",
197
+ }), /* @__PURE__ */ u(f, {
198
+ variant: (B[e.id] ?? 0) === 0 ? "default" : "secondary",
150
199
  className: "shrink-0",
151
- children: (O[e.id] ?? 0) === 0 ? "On critical path" : `${v(O[e.id] ?? 0)} slack`
200
+ children: (B[e.id] ?? 0) === 0 ? "On critical path" : `${x(B[e.id] ?? 0)} slack`
152
201
  })]
153
- }, e.id)), D.length === 0 && /* @__PURE__ */ l("p", {
202
+ }, e.id)), A.length === 0 && /* @__PURE__ */ u("p", {
154
203
  className: "text-center text-muted-foreground py-8",
155
204
  children: "No schedulable dependencies yet. Add dependencies from a task's detail page to compute a critical path."
156
205
  })]
206
+ })] }),
207
+ j && /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ d(g, { children: [
208
+ "Not on the critical path (",
209
+ W.length,
210
+ " item",
211
+ W.length === 1 ? "" : "s",
212
+ ")"
213
+ ] }) }), /* @__PURE__ */ d(m, {
214
+ className: "space-y-2",
215
+ children: [
216
+ W.map((e) => /* @__PURE__ */ d("div", {
217
+ className: "flex items-center justify-between gap-3 rounded-md border border-input p-3",
218
+ children: [/* @__PURE__ */ d("button", {
219
+ type: "button",
220
+ className: "min-w-0 truncate text-left text-sm font-medium hover:underline",
221
+ onClick: () => C(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
222
+ children: [e.key ? `${e.key} ` : "", e.title]
223
+ }), /* @__PURE__ */ d(f, {
224
+ variant: "secondary",
225
+ className: "shrink-0",
226
+ children: [x(B[e.id] ?? 0), " slack"]
227
+ })]
228
+ }, e.id)),
229
+ !F && R && /* @__PURE__ */ d("p", {
230
+ className: "text-center text-destructive py-4",
231
+ role: "alert",
232
+ children: ["Some work items could not be loaded, so this list may be incomplete: ", R]
233
+ }),
234
+ F && /* @__PURE__ */ d("p", {
235
+ className: "text-center text-destructive py-8",
236
+ children: ["The rest of this project's work items could not be loaded: ", F.message]
237
+ }),
238
+ !F && P && W.length === 0 && /* @__PURE__ */ u("p", {
239
+ className: "text-center text-muted-foreground py-8",
240
+ children: "Loading…"
241
+ }),
242
+ !F && !P && W.length === 0 && /* @__PURE__ */ u("p", {
243
+ className: "text-center text-muted-foreground py-8",
244
+ children: "Every scheduled work item in this project is on the critical path."
245
+ })
246
+ ]
157
247
  })] })
158
248
  ]
159
249
  });
160
250
  }
161
251
  //#endregion
162
- export { y as ProjectsCriticalPathPagePage };
252
+ export { S as ProjectsCriticalPathPagePage };
163
253
 
164
254
  //# sourceMappingURL=ProjectsCriticalPathPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsCriticalPathPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Route, AlertTriangle } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\n\n/**\n * Critical path view for a project (BOFF-8047). planmagnetCriticalPath / planmagnetDependencyCycles were\n * served and prod-verified (duration-weighted CPM over WorkItemDependency, per the P0 decision that this type\n * is CPM-only) but had no UI. Reached from the sidebar (no project preselects one) and from a project's detail\n * page (\"Critical path\" button).\n */\n\nconst GET_CRITICAL_PATH = gql`\n query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {\n planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {\n totalDurationMinutes\n slack\n path {\n id\n title\n key\n }\n }\n planmagnetDependencyCycles(workspaceId: $workspaceId, projectId: $projectId) {\n workItemIds\n length\n }\n }\n`;\n\nconst GET_CRITICAL_PATH_PROJECT = gql`\n query GetCriticalPathProject($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n }\n }\n`;\n\nfunction durationLabel(totalMinutes: number): string {\n if (totalMinutes <= 0) return '0 days';\n // Round once, then split. Rounding the day remainder separately printed \"1d 24h\" for 47.5–47.9 h, and fractional\n // minutes (durations are elapsed ms / 60000) printed decimals.\n const roundedMinutes = Math.round(totalMinutes);\n if (roundedMinutes < 60) return `${Math.max(1, roundedMinutes)}m`;\n const totalHours = Math.round(roundedMinutes / 60);\n const days = Math.floor(totalHours / 24);\n const hours = totalHours % 24;\n if (days === 0) return `${hours}h`;\n return hours > 0 ? `${days}d ${hours}h` : `${days} day${days === 1 ? '' : 's'}`;\n}\n\nexport function ProjectsCriticalPathPagePage() {\n const projectId = useEntityId();\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [pickerProjectId, setPickerProjectId] = useState('');\n\n const { data, loading, error } = useQuery<{\n planmagnetCriticalPath: {\n totalDurationMinutes: number;\n // The SDL doc comment on this field says \"days\" but the resolver\n // actually returns MINUTES (wspace-planmagnet-svc\n // src/modules/dependency/services/dependency-graph.ts:53, backed by\n // durationMinutes() in dependency.service.ts) — same unit as\n // totalDurationMinutes above. Feed values through durationLabel()\n // below, never render this number bare. SDL doc fix tracked\n // separately; do not \"fix\" this comment to match the SDL.\n slack: Record<string, number>;\n path: { id: string; title: string | null; key: string | null }[];\n };\n planmagnetDependencyCycles: { workItemIds: string[]; length: number }[];\n }>(GET_CRITICAL_PATH, {\n variables: { workspaceId: workspaceId ?? '', projectId },\n skip: !workspaceId || !projectId,\n });\n const { data: projectData } = useQuery<{\n planmagnetProject: { id: string; name: string } | null;\n }>(GET_CRITICAL_PATH_PROJECT, { variables: { id: projectId }, skip: !projectId });\n\n if (!projectId) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n Choose a project to see its critical path.\n </p>\n </div>\n <Card>\n <CardContent className=\"pt-6 space-y-3\">\n <label htmlFor=\"critical-path-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <ProjectSelect\n id=\"critical-path-project\"\n value={pickerProjectId}\n onChange={setPickerProjectId}\n noneLabel=\"Choose a project…\"\n />\n <button\n type=\"button\"\n className=\"text-sm text-primary hover:underline disabled:pointer-events-none disabled:opacity-50\"\n disabled={!pickerProjectId}\n onClick={() =>\n navigate(`/projects/critical-path-page/${encodeURIComponent(pickerProjectId)}`)\n }\n >\n View critical path →\n </button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n // Only before the first load: a refetch must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n if (error)\n return <div className=\"p-6 text-red-500\">Error computing critical path: {error.message}</div>;\n\n const path = data?.planmagnetCriticalPath.path ?? [];\n const slack = data?.planmagnetCriticalPath.slack ?? {};\n const totalMinutes = data?.planmagnetCriticalPath.totalDurationMinutes ?? 0;\n const cycles = data?.planmagnetDependencyCycles ?? [];\n const projectName = projectData?.planmagnetProject?.name;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {projectName ?? 'This project'} — the longest chain of dependent work; a delay on any\n node here delays the whole chain.\n </p>\n </div>\n </div>\n\n {cycles.length > 0 && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive\"\n >\n <AlertTriangle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>\n <p className=\"font-medium\">\n {cycles.length} dependency cycle{cycles.length === 1 ? '' : 's'} detected.\n </p>\n <p>\n A cycle has no valid schedule, so the path below excludes the items involved. Break\n each cycle by removing or re-pointing one of its dependencies from the task detail\n page.\n </p>\n </div>\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Total duration</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-2xl font-bold\">{durationLabel(totalMinutes)}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n Work item spans plus dependency lag/lead, summed along the longest chain.\n </p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>\n Path ({path.length} item{path.length === 1 ? '' : 's'})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {path.map((node, index) => (\n <div\n key={node.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground\">\n {index + 1}\n </span>\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(node.id)}`)\n }\n >\n {node.key ? `${node.key} ` : ''}\n {node.title ?? 'Untitled work item'}\n </button>\n </div>\n <Badge\n variant={(slack[node.id] ?? 0) === 0 ? 'default' : 'secondary'}\n className=\"shrink-0\"\n >\n {(slack[node.id] ?? 0) === 0\n ? 'On critical path'\n : `${durationLabel(slack[node.id] ?? 0)} slack`}\n </Badge>\n </div>\n ))}\n {path.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n No schedulable dependencies yet. Add dependencies from a task&apos;s detail page to\n compute a critical path.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectsCriticalPathPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Route, AlertTriangle } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\n\n/**\n * Critical path view for a project (BOFF-8047). planmagnetCriticalPath / planmagnetDependencyCycles were\n * served and prod-verified (duration-weighted CPM over WorkItemDependency, per the P0 decision that this type\n * is CPM-only) but had no UI. Reached from the sidebar (no project preselects one) and from a project's detail\n * page (\"Critical path\" button).\n */\n\nconst GET_CRITICAL_PATH = gql`\n query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {\n planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {\n totalDurationMinutes\n slack\n path {\n id\n title\n key\n }\n }\n planmagnetDependencyCycles(workspaceId: $workspaceId, projectId: $projectId) {\n workItemIds\n length\n }\n }\n`;\n\nconst GET_CRITICAL_PATH_PROJECT = gql`\n query GetCriticalPathProject($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n }\n }\n`;\n\nconst NON_CRITICAL_PAGE_SIZE = 100;\n\n// Every work item in the project that the critical-path chain does NOT run through (excludeIds is the\n// path's own ids), so the slack map's per-node minutes have somewhere to show up. Paged like\n// ExportWorkItemsButton: a project can have more items than one page, and stopping at the first page\n// would silently drop items with real slack from the list below.\nconst GET_NON_CRITICAL_WORK_ITEMS = gql`\n query GetNonCriticalWorkItems(\n $workspaceId: ID!\n $filter: PlanmagnetWorkItemFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n title\n key\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n`;\n\nfunction durationLabel(totalMinutes: number): string {\n if (totalMinutes <= 0) return '0 days';\n // Round once, then split. Rounding the day remainder separately printed \"1d 24h\" for 47.5–47.9 h, and fractional\n // minutes (durations are elapsed ms / 60000) printed decimals.\n const roundedMinutes = Math.round(totalMinutes);\n if (roundedMinutes < 60) return `${Math.max(1, roundedMinutes)}m`;\n const totalHours = Math.round(roundedMinutes / 60);\n const days = Math.floor(totalHours / 24);\n const hours = totalHours % 24;\n if (days === 0) return `${hours}h`;\n return hours > 0 ? `${days}d ${hours}h` : `${days} day${days === 1 ? '' : 's'}`;\n}\n\nexport function ProjectsCriticalPathPagePage() {\n const projectId = useEntityId();\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [pickerProjectId, setPickerProjectId] = useState('');\n\n const { data, loading, error } = useQuery<{\n planmagnetCriticalPath: {\n totalDurationMinutes: number;\n // The SDL doc comment on this field says \"days\" but the resolver\n // actually returns MINUTES (wspace-planmagnet-svc\n // src/modules/dependency/services/dependency-graph.ts:53, backed by\n // durationMinutes() in dependency.service.ts) — same unit as\n // totalDurationMinutes above. Feed values through durationLabel()\n // below, never render this number bare. SDL doc fix tracked\n // separately; do not \"fix\" this comment to match the SDL.\n slack: Record<string, number>;\n path: { id: string; title: string | null; key: string | null }[];\n };\n planmagnetDependencyCycles: { workItemIds: string[]; length: number }[];\n }>(GET_CRITICAL_PATH, {\n variables: { workspaceId: workspaceId ?? '', projectId },\n skip: !workspaceId || !projectId,\n });\n const { data: projectData } = useQuery<{\n planmagnetProject: { id: string; name: string } | null;\n }>(GET_CRITICAL_PATH_PROJECT, { variables: { id: projectId }, skip: !projectId });\n\n const path = data?.planmagnetCriticalPath.path ?? [];\n const hasCriticalPath = path.length > 0;\n const pathIds = path.map((node) => node.id);\n\n const {\n data: otherData,\n loading: otherLoading,\n error: otherError,\n fetchMore: fetchMoreOther,\n } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; title: string; key: string }[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n }>(GET_NON_CRITICAL_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { projectId, excludeIds: pathIds },\n pagination: { first: NON_CRITICAL_PAGE_SIZE },\n },\n skip: !workspaceId || !projectId || !hasCriticalPath,\n });\n\n const otherPageInfo = otherData?.planmagnetWorkItems.pageInfo;\n // A failed later page must not leave a short list that reads as complete: remember it and say so.\n const [moreError, setMoreError] = useState<string | null>(null);\n useEffect(() => {\n if (moreError || !otherPageInfo?.hasNextPage || !otherPageInfo.endCursor) return;\n const after = otherPageInfo.endCursor;\n fetchMoreOther({\n variables: { pagination: { first: NON_CRITICAL_PAGE_SIZE, after } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetWorkItems: {\n ...fetchMoreResult.planmagnetWorkItems,\n items: [...prev.planmagnetWorkItems.items, ...fetchMoreResult.planmagnetWorkItems.items],\n },\n }),\n })\n .then((result) => {\n // fe-libs queries resolve with { error } instead of rejecting.\n const failure = (result as { error?: { message?: string } } | undefined)?.error;\n if (failure) setMoreError(failure.message ?? 'Unknown error');\n })\n .catch((e: unknown) => setMoreError(e instanceof Error ? e.message : String(e)));\n }, [moreError, otherPageInfo?.hasNextPage, otherPageInfo?.endCursor, fetchMoreOther]);\n\n if (!projectId) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n Choose a project to see its critical path.\n </p>\n </div>\n <Card>\n <CardContent className=\"pt-6 space-y-3\">\n <label htmlFor=\"critical-path-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <ProjectSelect\n id=\"critical-path-project\"\n value={pickerProjectId}\n onChange={setPickerProjectId}\n noneLabel=\"Choose a project…\"\n />\n <button\n type=\"button\"\n className=\"text-sm text-primary hover:underline disabled:pointer-events-none disabled:opacity-50\"\n disabled={!pickerProjectId}\n onClick={() =>\n navigate(`/projects/critical-path-page/${encodeURIComponent(pickerProjectId)}`)\n }\n >\n View critical path →\n </button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n // Only before the first load: a refetch must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n if (error)\n return <div className=\"p-6 text-red-500\">Error computing critical path: {error.message}</div>;\n\n const slack = data?.planmagnetCriticalPath.slack ?? {};\n const totalMinutes = data?.planmagnetCriticalPath.totalDurationMinutes ?? 0;\n const cycles = data?.planmagnetDependencyCycles ?? [];\n const projectName = projectData?.planmagnetProject?.name;\n const otherItems = [...(otherData?.planmagnetWorkItems.items ?? [])].sort(\n (a, b) => (slack[a.id] ?? 0) - (slack[b.id] ?? 0)\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {projectName ?? 'This project'} — the longest chain of dependent work; a delay on any\n node here delays the whole chain.\n </p>\n </div>\n </div>\n\n {cycles.length > 0 && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive\"\n >\n <AlertTriangle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>\n <p className=\"font-medium\">\n {cycles.length} dependency cycle{cycles.length === 1 ? '' : 's'} detected.\n </p>\n <p>\n A cycle has no valid schedule, so the path below excludes the items involved. Break\n each cycle by removing or re-pointing one of its dependencies from the task detail\n page.\n </p>\n </div>\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Total duration</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-2xl font-bold\">{durationLabel(totalMinutes)}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n Work item spans plus dependency lag/lead, summed along the longest chain.\n </p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>\n Path ({path.length} item{path.length === 1 ? '' : 's'})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {path.map((node, index) => (\n <div\n key={node.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground\">\n {index + 1}\n </span>\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(node.id)}`)\n }\n >\n {node.key ? `${node.key} ` : ''}\n {node.title ?? 'Untitled work item'}\n </button>\n </div>\n <Badge\n variant={(slack[node.id] ?? 0) === 0 ? 'default' : 'secondary'}\n className=\"shrink-0\"\n >\n {(slack[node.id] ?? 0) === 0\n ? 'On critical path'\n : `${durationLabel(slack[node.id] ?? 0)} slack`}\n </Badge>\n </div>\n ))}\n {path.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n No schedulable dependencies yet. Add dependencies from a task&apos;s detail page to\n compute a critical path.\n </p>\n )}\n </CardContent>\n </Card>\n\n {hasCriticalPath && (\n <Card>\n <CardHeader>\n <CardTitle>\n Not on the critical path ({otherItems.length} item\n {otherItems.length === 1 ? '' : 's'})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {otherItems.map((node) => (\n <div\n key={node.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(node.id)}`)\n }\n >\n {node.key ? `${node.key} ` : ''}\n {node.title}\n </button>\n <Badge variant=\"secondary\" className=\"shrink-0\">\n {durationLabel(slack[node.id] ?? 0)} slack\n </Badge>\n </div>\n ))}\n {!otherError && moreError && (\n <p className=\"text-center text-destructive py-4\" role=\"alert\">\n Some work items could not be loaded, so this list may be incomplete: {moreError}\n </p>\n )}\n {otherError && (\n <p className=\"text-center text-destructive py-8\">\n The rest of this project&apos;s work items could not be loaded: {otherError.message}\n </p>\n )}\n {!otherError && otherLoading && otherItems.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">Loading…</p>\n )}\n {!otherError && !otherLoading && otherItems.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n Every scheduled work item in this project is on the critical path.\n </p>\n )}\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,9 +1,2 @@
1
- /**
2
- * Reports (BOFF-7967). There is no saved-report model and no report runner yet, so there are no reports to list.
3
- * This page used to invent them — one "Velocity Report" per completed sprint, one "Burndown" per active sprint, one
4
- * "Status Report" per active project — with made-up schedules, "Run Today" counts for reports that never ran, and
5
- * the viewer named as their creator. It now says so, and points to where the same analysis lives, computed on the
6
- * server from real data.
7
- */
8
1
  export declare function ReportsReportsPagePage(): import("react/jsx-runtime").JSX.Element;
9
2
  //# sourceMappingURL=ReportsReportsPagePage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReportsReportsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ReportsReportsPagePage.tsx"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,sBAAsB,4CA+CrC"}
1
+ {"version":3,"file":"ReportsReportsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ReportsReportsPagePage.tsx"],"names":[],"mappings":"AAskBA,wBAAgB,sBAAsB,4CAkGrC"}