@burdenoff/microfe-planmagnet 2026.928.4 → 2026.928.6

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 (226) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +130 -97
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.js +256 -156
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts +5 -0
  8. package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts.map +1 -0
  9. package/dist/planmagnet/automation/AutomationExecutionsPanel.js +107 -0
  10. package/dist/planmagnet/automation/AutomationExecutionsPanel.js.map +1 -0
  11. package/dist/planmagnet/automation/AutomationRuleFields.d.ts +29 -0
  12. package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -0
  13. package/dist/planmagnet/automation/AutomationRuleFields.js +308 -0
  14. package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -0
  15. package/dist/planmagnet/automation/automationRuleTypes.d.ts +60 -0
  16. package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -0
  17. package/dist/planmagnet/automation/automationRuleTypes.js +113 -0
  18. package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -0
  19. package/dist/planmagnet/board/BulkActions.d.ts +11 -0
  20. package/dist/planmagnet/board/BulkActions.d.ts.map +1 -0
  21. package/dist/planmagnet/board/BulkActions.js +182 -0
  22. package/dist/planmagnet/board/BulkActions.js.map +1 -0
  23. package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
  24. package/dist/planmagnet/board/ProjectBoard.js +289 -254
  25. package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
  26. package/dist/planmagnet/budgets/BudgetControls.d.ts +30 -0
  27. package/dist/planmagnet/budgets/BudgetControls.d.ts.map +1 -0
  28. package/dist/planmagnet/budgets/BudgetControls.js +470 -0
  29. package/dist/planmagnet/budgets/BudgetControls.js.map +1 -0
  30. package/dist/planmagnet/budgets/ExpenseControls.d.ts +27 -0
  31. package/dist/planmagnet/budgets/ExpenseControls.d.ts.map +1 -0
  32. package/dist/planmagnet/budgets/ExpenseControls.js +366 -0
  33. package/dist/planmagnet/budgets/ExpenseControls.js.map +1 -0
  34. package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts +5 -0
  35. package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts.map +1 -0
  36. package/dist/planmagnet/budgets/ProjectBudgetsSummary.js +112 -0
  37. package/dist/planmagnet/budgets/ProjectBudgetsSummary.js.map +1 -0
  38. package/dist/planmagnet/budgets/budgetMath.d.ts +26 -0
  39. package/dist/planmagnet/budgets/budgetMath.d.ts.map +1 -0
  40. package/dist/planmagnet/budgets/budgetMath.js +49 -0
  41. package/dist/planmagnet/budgets/budgetMath.js.map +1 -0
  42. package/dist/planmagnet/controls/AutomationRuleControls.d.ts +23 -0
  43. package/dist/planmagnet/controls/AutomationRuleControls.d.ts.map +1 -0
  44. package/dist/planmagnet/controls/AutomationRuleControls.js +172 -0
  45. package/dist/planmagnet/controls/AutomationRuleControls.js.map +1 -0
  46. package/dist/planmagnet/controls/CompetitorControls.js +15 -15
  47. package/dist/planmagnet/controls/CustomFieldControls.d.ts +42 -0
  48. package/dist/planmagnet/controls/CustomFieldControls.d.ts.map +1 -0
  49. package/dist/planmagnet/controls/CustomFieldControls.js +84 -0
  50. package/dist/planmagnet/controls/CustomFieldControls.js.map +1 -0
  51. package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts +8 -0
  52. package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts.map +1 -0
  53. package/dist/planmagnet/controls/CustomFieldValuesPanel.js +315 -0
  54. package/dist/planmagnet/controls/CustomFieldValuesPanel.js.map +1 -0
  55. package/dist/planmagnet/controls/FeatureControls.js +54 -54
  56. package/dist/planmagnet/controls/ObjectiveControls.js +15 -15
  57. package/dist/planmagnet/controls/Pickers.d.ts +7 -0
  58. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  59. package/dist/planmagnet/controls/Pickers.js +58 -20
  60. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  61. package/dist/planmagnet/controls/PortfolioControls.js +7 -7
  62. package/dist/planmagnet/controls/ProgramControls.js +19 -19
  63. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
  64. package/dist/planmagnet/controls/ProjectControls.js +54 -47
  65. package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
  66. package/dist/planmagnet/controls/ReleaseControls.js +7 -7
  67. package/dist/planmagnet/controls/RiskControls.d.ts +27 -0
  68. package/dist/planmagnet/controls/RiskControls.d.ts.map +1 -0
  69. package/dist/planmagnet/controls/RiskControls.js +346 -0
  70. package/dist/planmagnet/controls/RiskControls.js.map +1 -0
  71. package/dist/planmagnet/controls/TemplateControls.d.ts +15 -0
  72. package/dist/planmagnet/controls/TemplateControls.d.ts.map +1 -0
  73. package/dist/planmagnet/controls/TemplateControls.js +55 -0
  74. package/dist/planmagnet/controls/TemplateControls.js.map +1 -0
  75. package/dist/planmagnet/controls/WorkItemControls.d.ts +6 -2
  76. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  77. package/dist/planmagnet/controls/WorkItemControls.js +9 -9
  78. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  79. package/dist/planmagnet/create/CreateDialogs.d.ts +20 -0
  80. package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
  81. package/dist/planmagnet/create/CreateDialogs.js +455 -116
  82. package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
  83. package/dist/planmagnet/dependency/DependencyPanel.d.ts +6 -0
  84. package/dist/planmagnet/dependency/DependencyPanel.d.ts.map +1 -0
  85. package/dist/planmagnet/dependency/DependencyPanel.js +310 -0
  86. package/dist/planmagnet/dependency/DependencyPanel.js.map +1 -0
  87. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts +2 -0
  88. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts.map +1 -0
  89. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +171 -0
  90. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js.map +1 -0
  91. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts +2 -0
  92. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts.map +1 -0
  93. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +232 -0
  94. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js.map +1 -0
  95. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts +2 -0
  96. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts.map +1 -0
  97. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +93 -0
  98. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js.map +1 -0
  99. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  100. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -3
  101. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  102. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +5 -5
  103. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +9 -9
  104. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +6 -6
  105. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +4 -4
  106. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +6 -6
  107. package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +5 -5
  108. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts +2 -0
  109. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts.map +1 -0
  110. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js +315 -0
  111. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js.map +1 -0
  112. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts +2 -0
  113. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts.map +1 -0
  114. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +186 -0
  115. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js.map +1 -0
  116. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts +2 -0
  117. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -0
  118. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +164 -0
  119. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -0
  120. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts +2 -0
  121. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts.map +1 -0
  122. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js +348 -0
  123. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js.map +1 -0
  124. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +10 -10
  125. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
  126. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  127. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +20 -10
  128. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  129. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
  130. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +13 -8
  131. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
  132. package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
  133. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +76 -56
  134. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
  135. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  136. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +372 -203
  137. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  138. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  139. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +81 -72
  140. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  141. package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts +2 -0
  142. package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts.map +1 -0
  143. package/dist/planmagnet/pages/RisksRiskDetailPagePage.js +278 -0
  144. package/dist/planmagnet/pages/RisksRiskDetailPagePage.js.map +1 -0
  145. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts +2 -0
  146. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts.map +1 -0
  147. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js +191 -0
  148. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js.map +1 -0
  149. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  150. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +168 -122
  151. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  152. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +5 -5
  153. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
  154. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  155. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +161 -137
  156. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  157. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  158. package/dist/planmagnet/pages/TasksTasksPagePage.js +124 -113
  159. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  160. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts +2 -0
  161. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts.map +1 -0
  162. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +263 -0
  163. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js.map +1 -0
  164. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts +2 -0
  165. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts.map +1 -0
  166. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js +254 -0
  167. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js.map +1 -0
  168. package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts +2 -0
  169. package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts.map +1 -0
  170. package/dist/planmagnet/pages/TasksTimesheetsPagePage.js +165 -0
  171. package/dist/planmagnet/pages/TasksTimesheetsPagePage.js.map +1 -0
  172. package/dist/planmagnet/pages/TeamCreateTeamPagePage.d.ts.map +1 -1
  173. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js +288 -91
  174. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js.map +1 -1
  175. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts +2 -0
  176. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -0
  177. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +217 -0
  178. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -0
  179. package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts +2 -0
  180. package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts.map +1 -0
  181. package/dist/planmagnet/pages/TeamTeamEditPagePage.js +211 -0
  182. package/dist/planmagnet/pages/TeamTeamEditPagePage.js.map +1 -0
  183. package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts +2 -0
  184. package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts.map +1 -0
  185. package/dist/planmagnet/pages/TeamTeamsListPagePage.js +118 -0
  186. package/dist/planmagnet/pages/TeamTeamsListPagePage.js.map +1 -0
  187. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts +2 -0
  188. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts.map +1 -0
  189. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js +316 -0
  190. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js.map +1 -0
  191. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts +2 -0
  192. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -0
  193. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +208 -0
  194. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -0
  195. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +0 -5
  196. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
  197. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +47 -89
  198. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
  199. package/dist/planmagnet/roadmaps/RoadmapControls.js +24 -24
  200. package/dist/planmagnet/sprints/SprintControls.d.ts +15 -3
  201. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  202. package/dist/planmagnet/sprints/SprintControls.js +293 -190
  203. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  204. package/dist/planmagnet/team/TeamControls.d.ts +31 -0
  205. package/dist/planmagnet/team/TeamControls.d.ts.map +1 -0
  206. package/dist/planmagnet/team/TeamControls.js +407 -0
  207. package/dist/planmagnet/team/TeamControls.js.map +1 -0
  208. package/dist/planmagnet/time/TaskTimePanel.d.ts +7 -0
  209. package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
  210. package/dist/planmagnet/time/TaskTimePanel.js +104 -0
  211. package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
  212. package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
  213. package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
  214. package/dist/planmagnet/time/TimeEntryControls.js +338 -0
  215. package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
  216. package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
  217. package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
  218. package/dist/planmagnet/time/TimesheetControls.js +244 -0
  219. package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
  220. package/dist/planmagnet/time/timeShared.d.ts +29 -0
  221. package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
  222. package/dist/planmagnet/time/timeShared.js +34 -0
  223. package/dist/planmagnet/time/timeShared.js.map +1 -0
  224. package/dist/planmagnet/utils/rowNavigation.js +5 -2
  225. package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
  226. package/package.json +1 -1
@@ -0,0 +1,164 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { useEntityId as n } from "../hooks/useEntityId.js";
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";
10
+ //#region src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx
11
+ var g = s`
12
+ query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {
13
+ planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {
14
+ totalDurationMinutes
15
+ slack
16
+ path {
17
+ id
18
+ title
19
+ key
20
+ }
21
+ }
22
+ planmagnetDependencyCycles(workspaceId: $workspaceId, projectId: $projectId) {
23
+ workItemIds
24
+ length
25
+ }
26
+ }
27
+ `, _ = s`
28
+ query GetCriticalPathProject($id: ID!) {
29
+ planmagnetProject(id: $id) {
30
+ id
31
+ name
32
+ }
33
+ }
34
+ `;
35
+ function v(e) {
36
+ if (e <= 0) return "0 days";
37
+ let t = Math.round(e);
38
+ if (t < 60) return `${Math.max(1, t)}m`;
39
+ let n = Math.round(t / 60), r = Math.floor(n / 24), i = n % 24;
40
+ return r === 0 ? `${i}h` : i > 0 ? `${r}d ${i}h` : `${r} day${r === 1 ? "" : "s"}`;
41
+ }
42
+ function y() {
43
+ let s = n(), { workspaceId: y } = e(), b = t(), [x, S] = i(""), { data: C, loading: w, error: T } = c(g, {
44
+ variables: {
45
+ workspaceId: y ?? "",
46
+ projectId: s
47
+ },
48
+ skip: !y || !s
49
+ }), { data: E } = c(_, {
50
+ variables: { id: s },
51
+ skip: !s
52
+ });
53
+ if (!s) return /* @__PURE__ */ u("div", {
54
+ className: "space-y-6 p-6",
55
+ children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ u("h1", {
56
+ 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", {
59
+ className: "text-sm text-muted-foreground",
60
+ children: "Choose a project to see its critical path."
61
+ })] }), /* @__PURE__ */ l(f, { children: /* @__PURE__ */ u(p, {
62
+ className: "pt-6 space-y-3",
63
+ children: [
64
+ /* @__PURE__ */ l("label", {
65
+ htmlFor: "critical-path-project",
66
+ className: "text-sm font-medium",
67
+ children: "Project"
68
+ }),
69
+ /* @__PURE__ */ l(r, {
70
+ id: "critical-path-project",
71
+ value: x,
72
+ onChange: S,
73
+ noneLabel: "Choose a project…"
74
+ }),
75
+ /* @__PURE__ */ l("button", {
76
+ type: "button",
77
+ 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)}`),
80
+ children: "View critical path →"
81
+ })
82
+ ]
83
+ }) })]
84
+ });
85
+ if (w && !C) return /* @__PURE__ */ l("div", {
86
+ 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" })
88
+ });
89
+ if (T) return /* @__PURE__ */ u("div", {
90
+ className: "p-6 text-red-500",
91
+ children: ["Error computing critical path: ", T.message]
92
+ });
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", {
95
+ className: "space-y-6 p-6",
96
+ children: [
97
+ /* @__PURE__ */ l("div", {
98
+ className: "flex items-center justify-between",
99
+ children: /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ u("h1", {
100
+ 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", {
103
+ 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."]
105
+ })] })
106
+ }),
107
+ A.length > 0 && /* @__PURE__ */ u("div", {
108
+ role: "alert",
109
+ 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", {
111
+ className: "font-medium",
112
+ children: [
113
+ A.length,
114
+ " dependency cycle",
115
+ A.length === 1 ? "" : "s",
116
+ " detected."
117
+ ]
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." })] })]
119
+ }),
120
+ /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(m, { children: /* @__PURE__ */ l(h, { children: "Total duration" }) }), /* @__PURE__ */ u(p, { children: [/* @__PURE__ */ l("p", {
121
+ className: "text-2xl font-bold",
122
+ children: v(k)
123
+ }), /* @__PURE__ */ l("p", {
124
+ className: "text-xs text-muted-foreground mt-1",
125
+ children: "Work item spans plus dependency lag/lead, summed along the longest chain."
126
+ })] })] }),
127
+ /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(m, { children: /* @__PURE__ */ u(h, { children: [
128
+ "Path (",
129
+ D.length,
130
+ " item",
131
+ D.length === 1 ? "" : "s",
132
+ ")"
133
+ ] }) }), /* @__PURE__ */ u(p, {
134
+ className: "space-y-2",
135
+ children: [D.map((e, t) => /* @__PURE__ */ u("div", {
136
+ className: "flex items-center justify-between gap-3 rounded-md border border-input p-3",
137
+ children: [/* @__PURE__ */ u("div", {
138
+ className: "flex items-center gap-3 min-w-0",
139
+ children: [/* @__PURE__ */ l("span", {
140
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground",
141
+ children: t + 1
142
+ }), /* @__PURE__ */ u("button", {
143
+ type: "button",
144
+ className: "min-w-0 truncate text-left text-sm font-medium hover:underline",
145
+ onClick: () => b(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
146
+ children: [e.key ? `${e.key} ` : "", e.title ?? "Untitled work item"]
147
+ })]
148
+ }), /* @__PURE__ */ l(d, {
149
+ variant: (O[e.id] ?? 0) === 0 ? "default" : "secondary",
150
+ className: "shrink-0",
151
+ children: (O[e.id] ?? 0) === 0 ? "On critical path" : `${v(O[e.id] ?? 0)} slack`
152
+ })]
153
+ }, e.id)), D.length === 0 && /* @__PURE__ */ l("p", {
154
+ className: "text-center text-muted-foreground py-8",
155
+ children: "No schedulable dependencies yet. Add dependencies from a task's detail page to compute a critical path."
156
+ })]
157
+ })] })
158
+ ]
159
+ });
160
+ }
161
+ //#endregion
162
+ export { y as ProjectsCriticalPathPagePage };
163
+
164
+ //# sourceMappingURL=ProjectsCriticalPathPagePage.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProjectsCustomFieldsPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProjectsCustomFieldsPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsCustomFieldsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsCustomFieldsPagePage.tsx"],"names":[],"mappings":"AAiFA,wBAAgB,4BAA4B,4CAiH3C"}
@@ -0,0 +1,348 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { throwOnError as n } from "../utils/mutationResult.js";
3
+ import { ConfirmButton as r, ErrorLine as i } from "../controls/ConfirmButton.js";
4
+ import { parseIntField as a } from "../utils/formValidation.js";
5
+ import { CREATE_CUSTOM_FIELD_DEFINITION as o, CUSTOM_FIELD_QUERIES as s, CUSTOM_FIELD_TYPES as c, DELETE_CUSTOM_FIELD_DEFINITION as l, OPTION_TYPES as u, PROJECT_CUSTOM_FIELD_DEFINITIONS as d, UPDATE_CUSTOM_FIELD_DEFINITION as f, customFieldTypeLabel as p, optionStrings as m, parseOptionLines as h } from "../controls/CustomFieldControls.js";
6
+ import { useState as g } from "react";
7
+ import { ArrowLeft as _, ListPlus as v, Loader2 as y, Pencil as b, Trash2 as x } from "lucide-react";
8
+ import { useParams as S } from "react-router";
9
+ import { gql as C } from "@apollo/client";
10
+ import { useMutation as w, useQuery as T } from "@apollo/client/react";
11
+ import { jsx as E, jsxs as D } from "react/jsx-runtime";
12
+ import { Button as O, Card as k, CardContent as A, CardHeader as j, CardTitle as M, Input as N, ResponsiveDialog as P, ResponsiveDialogContent as F, ResponsiveDialogDescription as I, ResponsiveDialogFooter as L, ResponsiveDialogHeader as R, ResponsiveDialogTitle as z, ResponsiveDialogTrigger as B } from "@burdenoff/fe-libs/ui";
13
+ //#region src/planmagnet/pages/ProjectsCustomFieldsPagePage.tsx
14
+ var V = C`
15
+ query CustomFieldsProjectName($id: ID!) {
16
+ planmagnetProject(id: $id) {
17
+ id
18
+ name
19
+ }
20
+ }
21
+ `, H = () => ({
22
+ name: "",
23
+ description: "",
24
+ fieldType: "TEXT",
25
+ isRequired: !1,
26
+ optionsText: "",
27
+ sortOrderText: ""
28
+ }), U = (e) => ({
29
+ name: e.name,
30
+ description: e.description ?? "",
31
+ fieldType: e.fieldType,
32
+ isRequired: e.isRequired,
33
+ optionsText: m(e.options).join("\n"),
34
+ sortOrderText: String(e.sortOrder ?? 0)
35
+ });
36
+ function W() {
37
+ let { workspaceId: n } = e(), r = t(), i = S().id ?? "", { data: a, loading: o, error: s } = T(d, {
38
+ variables: {
39
+ workspaceId: n ?? "",
40
+ projectId: i
41
+ },
42
+ skip: !n || !i
43
+ }), { data: c } = T(V, {
44
+ variables: { id: i },
45
+ skip: !i
46
+ });
47
+ if (!i) return /* @__PURE__ */ E("div", {
48
+ className: "p-6 text-muted-foreground",
49
+ children: "No project selected."
50
+ });
51
+ if (o && !a) return /* @__PURE__ */ E("div", {
52
+ className: "flex items-center justify-center p-12",
53
+ children: /* @__PURE__ */ E(y, { className: "h-8 w-8 animate-spin text-muted-foreground" })
54
+ });
55
+ if (s) return /* @__PURE__ */ D("div", {
56
+ className: "p-6 text-red-500",
57
+ children: ["Error: ", s.message]
58
+ });
59
+ let l = [...a?.planmagnetCustomFieldDefinitions.items ?? []].sort((e, t) => e.sortOrder - t.sortOrder || e.name.localeCompare(t.name));
60
+ return /* @__PURE__ */ D("div", {
61
+ className: "space-y-6 p-6",
62
+ children: [/* @__PURE__ */ D("div", {
63
+ className: "flex items-start gap-3",
64
+ children: [/* @__PURE__ */ E(O, {
65
+ variant: "ghost",
66
+ size: "icon",
67
+ "aria-label": "Back to the project",
68
+ onClick: () => r(`/projects/project-detail-page/${i}`),
69
+ children: /* @__PURE__ */ E(_, { className: "h-4 w-4" })
70
+ }), /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ D("h1", {
71
+ className: "text-2xl font-semibold",
72
+ children: ["Custom Fields", c?.planmagnetProject ? ` — ${c.planmagnetProject.name}` : ""]
73
+ }), /* @__PURE__ */ E("p", {
74
+ className: "text-sm text-muted-foreground",
75
+ children: "Extra fields this project's tasks can carry. Each task's values are set from its own page."
76
+ })] })]
77
+ }), /* @__PURE__ */ D(k, { children: [/* @__PURE__ */ D(j, {
78
+ className: "flex flex-row items-center justify-between space-y-0",
79
+ children: [/* @__PURE__ */ D(M, { children: [
80
+ "Fields (",
81
+ l.length,
82
+ ")"
83
+ ] }), /* @__PURE__ */ E(K, { projectId: i })]
84
+ }), /* @__PURE__ */ E(A, { children: l.length === 0 ? /* @__PURE__ */ E("p", {
85
+ className: "py-4 text-center text-sm text-muted-foreground",
86
+ "data-testid": "custom-fields-empty",
87
+ children: "No custom fields yet. Add one to start collecting extra data on this project's tasks."
88
+ }) : /* @__PURE__ */ D("table", {
89
+ className: "w-full text-sm",
90
+ "data-testid": "custom-field-definitions",
91
+ children: [/* @__PURE__ */ E("thead", { children: /* @__PURE__ */ D("tr", {
92
+ className: "text-left text-muted-foreground",
93
+ children: [
94
+ /* @__PURE__ */ E("th", {
95
+ className: "py-1 pr-3 font-medium",
96
+ children: "Name"
97
+ }),
98
+ /* @__PURE__ */ E("th", {
99
+ className: "py-1 pr-3 font-medium",
100
+ children: "Type"
101
+ }),
102
+ /* @__PURE__ */ E("th", {
103
+ className: "py-1 pr-3 font-medium",
104
+ children: "Required"
105
+ }),
106
+ /* @__PURE__ */ E("th", {
107
+ className: "py-1 pr-3 font-medium",
108
+ children: "Options"
109
+ }),
110
+ /* @__PURE__ */ E("th", {
111
+ className: "py-1 font-medium",
112
+ children: "Actions"
113
+ })
114
+ ]
115
+ }) }), /* @__PURE__ */ E("tbody", { children: l.map((e) => /* @__PURE__ */ D("tr", {
116
+ className: "border-t",
117
+ "data-testid": `custom-field-row-${e.id}`,
118
+ children: [
119
+ /* @__PURE__ */ D("td", {
120
+ className: "py-2 pr-3",
121
+ children: [/* @__PURE__ */ E("p", {
122
+ className: "font-medium",
123
+ children: e.name
124
+ }), e.description && /* @__PURE__ */ E("p", {
125
+ className: "text-xs text-muted-foreground",
126
+ children: e.description
127
+ })]
128
+ }),
129
+ /* @__PURE__ */ E("td", {
130
+ className: "py-2 pr-3",
131
+ children: p(e.fieldType)
132
+ }),
133
+ /* @__PURE__ */ E("td", {
134
+ className: "py-2 pr-3",
135
+ children: e.isRequired ? "Yes" : "No"
136
+ }),
137
+ /* @__PURE__ */ E("td", {
138
+ className: "py-2 pr-3 text-xs text-muted-foreground",
139
+ children: u.has(e.fieldType) && m(e.options).join(", ") || "—"
140
+ }),
141
+ /* @__PURE__ */ E("td", {
142
+ className: "py-2",
143
+ children: /* @__PURE__ */ D("div", {
144
+ className: "flex gap-2",
145
+ children: [/* @__PURE__ */ E(K, {
146
+ projectId: i,
147
+ definition: e
148
+ }), /* @__PURE__ */ E(G, { definition: e })]
149
+ })
150
+ })
151
+ ]
152
+ }, e.id)) })]
153
+ }) })] })]
154
+ });
155
+ }
156
+ function G({ definition: e }) {
157
+ let [t, a] = w(l, {
158
+ refetchQueries: s,
159
+ awaitRefetchQueries: !0
160
+ }), [o, c] = g(null), u = async () => {
161
+ c(null);
162
+ try {
163
+ await n(t({ variables: { id: e.id } }));
164
+ } catch (e) {
165
+ c(e instanceof Error ? e.message : "The field could not be deleted.");
166
+ }
167
+ };
168
+ return /* @__PURE__ */ D("div", {
169
+ className: "flex flex-col items-start gap-1",
170
+ children: [/* @__PURE__ */ E(r, {
171
+ label: "",
172
+ ariaLabel: `Delete ${e.name}`,
173
+ variant: "outline",
174
+ icon: /* @__PURE__ */ E(x, { className: "h-4 w-4" }),
175
+ title: `Delete "${e.name}"?`,
176
+ description: "Its stored values on every task are deleted too. This cannot be undone here.",
177
+ confirmLabel: "Delete field",
178
+ destructive: !0,
179
+ disabled: a.loading,
180
+ onConfirm: () => void u()
181
+ }), o && /* @__PURE__ */ E(i, { message: o })]
182
+ });
183
+ }
184
+ function K({ projectId: e, definition: t }) {
185
+ let r = t !== void 0, l = t ? U(t) : H(), [d, m] = g(!1), [_, y] = g(l), [x, S] = g(null), [C, T] = w(o, {
186
+ refetchQueries: s,
187
+ awaitRefetchQueries: !0
188
+ }), [k, A] = w(f, {
189
+ refetchQueries: s,
190
+ awaitRefetchQueries: !0
191
+ }), j = T.loading || A.loading, M = a(_.sortOrderText, { min: 0 }), V = u.has(_.fieldType), W = V ? h(_.optionsText) : [], G = _.name.trim() === "" || M.invalid || V && W.length === 0, K = async () => {
192
+ S(null);
193
+ let i = {
194
+ name: _.name.trim(),
195
+ description: _.description.trim() || null,
196
+ fieldType: _.fieldType,
197
+ isRequired: _.isRequired,
198
+ options: V ? W : null,
199
+ sortOrder: M.value ?? 0
200
+ };
201
+ try {
202
+ r ? await n(k({ variables: {
203
+ id: t.id,
204
+ input: i
205
+ } })) : await n(C({ variables: { input: {
206
+ projectId: e,
207
+ ...i
208
+ } } })), m(!1);
209
+ } catch (e) {
210
+ S(e instanceof Error ? e.message : "The field could not be saved.");
211
+ }
212
+ }, q = r ? `custom-field-${t.id}` : "new-custom-field";
213
+ return /* @__PURE__ */ D(P, {
214
+ open: d,
215
+ onOpenChange: (e) => {
216
+ m(e), e && (y(t ? U(t) : H()), S(null));
217
+ },
218
+ children: [/* @__PURE__ */ E(B, {
219
+ asChild: !0,
220
+ children: r ? /* @__PURE__ */ E(O, {
221
+ size: "icon",
222
+ variant: "ghost",
223
+ "aria-label": `Edit ${t.name}`,
224
+ children: /* @__PURE__ */ E(b, { className: "h-4 w-4" })
225
+ }) : /* @__PURE__ */ D(O, {
226
+ size: "sm",
227
+ "data-testid": "custom-field-add",
228
+ children: [/* @__PURE__ */ E(v, { className: "mr-1 h-4 w-4" }), "Add field"]
229
+ })
230
+ }), /* @__PURE__ */ D(F, {
231
+ className: "max-w-lg",
232
+ children: [
233
+ /* @__PURE__ */ D(R, { children: [/* @__PURE__ */ E(z, { children: r ? `Edit ${t.name}` : "Add field" }), /* @__PURE__ */ E(I, { children: "Tasks in this project will be able to show and edit this value." })] }),
234
+ /* @__PURE__ */ D("div", {
235
+ className: "space-y-3",
236
+ children: [
237
+ /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
238
+ htmlFor: `${q}-name`,
239
+ className: "text-sm font-medium",
240
+ children: "Name"
241
+ }), /* @__PURE__ */ E(N, {
242
+ id: `${q}-name`,
243
+ className: "mt-1",
244
+ value: _.name,
245
+ onChange: (e) => y({
246
+ ..._,
247
+ name: e.target.value
248
+ })
249
+ })] }),
250
+ /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
251
+ htmlFor: `${q}-type`,
252
+ className: "text-sm font-medium",
253
+ children: "Type"
254
+ }), /* @__PURE__ */ E("select", {
255
+ id: `${q}-type`,
256
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
257
+ value: _.fieldType,
258
+ onChange: (e) => y({
259
+ ..._,
260
+ fieldType: e.target.value
261
+ }),
262
+ children: c.map((e) => /* @__PURE__ */ E("option", {
263
+ value: e,
264
+ children: p(e)
265
+ }, e))
266
+ })] }),
267
+ V && /* @__PURE__ */ D("div", { children: [
268
+ /* @__PURE__ */ E("label", {
269
+ htmlFor: `${q}-options`,
270
+ className: "text-sm font-medium",
271
+ children: "Options (one per line)"
272
+ }),
273
+ /* @__PURE__ */ E("textarea", {
274
+ id: `${q}-options`,
275
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[88px]",
276
+ value: _.optionsText,
277
+ onChange: (e) => y({
278
+ ..._,
279
+ optionsText: e.target.value
280
+ })
281
+ }),
282
+ W.length === 0 && /* @__PURE__ */ E(i, { message: "Add at least one option." })
283
+ ] }),
284
+ /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
285
+ htmlFor: `${q}-description`,
286
+ className: "text-sm font-medium",
287
+ children: "Description (optional)"
288
+ }), /* @__PURE__ */ E("textarea", {
289
+ id: `${q}-description`,
290
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[60px]",
291
+ value: _.description,
292
+ onChange: (e) => y({
293
+ ..._,
294
+ description: e.target.value
295
+ })
296
+ })] }),
297
+ /* @__PURE__ */ D("div", {
298
+ className: "flex items-end gap-4",
299
+ children: [/* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
300
+ htmlFor: `${q}-sort`,
301
+ className: "text-sm font-medium",
302
+ children: "Order (optional)"
303
+ }), /* @__PURE__ */ E(N, {
304
+ id: `${q}-sort`,
305
+ type: "number",
306
+ min: 0,
307
+ step: 1,
308
+ className: "mt-1 w-24",
309
+ value: _.sortOrderText,
310
+ onChange: (e) => y({
311
+ ..._,
312
+ sortOrderText: e.target.value
313
+ })
314
+ })] }), /* @__PURE__ */ D("label", {
315
+ className: "flex items-center gap-2 pb-2 text-sm",
316
+ children: [/* @__PURE__ */ E("input", {
317
+ type: "checkbox",
318
+ checked: _.isRequired,
319
+ onChange: (e) => y({
320
+ ..._,
321
+ isRequired: e.target.checked
322
+ })
323
+ }), "Required"]
324
+ })]
325
+ }),
326
+ M.invalid && /* @__PURE__ */ E(i, { message: "Order must be a whole number, 0 or more." }),
327
+ _.name.trim() === "" && /* @__PURE__ */ E(i, { message: "Give the field a name." }),
328
+ x && /* @__PURE__ */ E(i, { message: x })
329
+ ]
330
+ }),
331
+ /* @__PURE__ */ D(L, { children: [/* @__PURE__ */ E(O, {
332
+ variant: "outline",
333
+ onClick: () => m(!1),
334
+ children: "Cancel"
335
+ }), /* @__PURE__ */ E(O, {
336
+ onClick: () => void K(),
337
+ disabled: G || j,
338
+ "data-testid": `${q}-save`,
339
+ children: j ? "Saving…" : r ? "Save" : "Add field"
340
+ })] })
341
+ ]
342
+ })]
343
+ });
344
+ }
345
+ //#endregion
346
+ export { W as ProjectsCustomFieldsPagePage };
347
+
348
+ //# sourceMappingURL=ProjectsCustomFieldsPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsCustomFieldsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsCustomFieldsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { useParams } from 'react-router';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, ListPlus, Loader2, Pencil, Trash2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { parseIntField } from '../utils/formValidation';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport {\n CUSTOM_FIELD_QUERIES,\n CUSTOM_FIELD_TYPES,\n CREATE_CUSTOM_FIELD_DEFINITION,\n DELETE_CUSTOM_FIELD_DEFINITION,\n OPTION_TYPES,\n PROJECT_CUSTOM_FIELD_DEFINITIONS,\n UPDATE_CUSTOM_FIELD_DEFINITION,\n customFieldTypeLabel,\n optionStrings,\n parseOptionLines,\n type CustomFieldDefinitionSummary,\n} from '../controls/CustomFieldControls';\n\n/**\n * Per-project custom field definitions (BOFF-8047, capability register: Custom fields, `api-only`). The API has\n * created/read/updated/deleted these since 2026-09-23 (BOFF-7839); nothing in the UI reached any of it. Modeled on\n * the project Workflow page: a settings surface reached from the project detail header, not the global sidebar,\n * because it configures one project.\n */\n\nconst PROJECT_NAME = gql`\n query CustomFieldsProjectName($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n }\n }\n`;\n\ninterface FormState {\n name: string;\n description: string;\n fieldType: string;\n isRequired: boolean;\n optionsText: string;\n sortOrderText: string;\n}\n\nconst emptyForm = (): FormState => ({\n name: '',\n description: '',\n fieldType: 'TEXT',\n isRequired: false,\n optionsText: '',\n sortOrderText: '',\n});\n\nconst formOf = (def: CustomFieldDefinitionSummary): FormState => ({\n name: def.name,\n description: def.description ?? '',\n fieldType: def.fieldType,\n isRequired: def.isRequired,\n optionsText: optionStrings(def.options).join('\\n'),\n sortOrderText: String(def.sortOrder ?? 0),\n});\n\nexport function ProjectsCustomFieldsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const projectId = useParams<{ id: string }>().id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetCustomFieldDefinitions: { items: CustomFieldDefinitionSummary[]; totalCount: number };\n }>(PROJECT_CUSTOM_FIELD_DEFINITIONS, {\n variables: { workspaceId: workspaceId ?? '', projectId },\n skip: !workspaceId || !projectId,\n });\n const { data: project } = useQuery<{ planmagnetProject: { id: string; name: string } | null }>(\n PROJECT_NAME,\n { variables: { id: projectId }, skip: !projectId }\n );\n\n if (!projectId) {\n return <div className=\"p-6 text-muted-foreground\">No project selected.</div>;\n }\n // Only before the first load: a refetch must not blank the page (it unmounts every dialog's own 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 if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const definitions = [...(data?.planmagnetCustomFieldDefinitions.items ?? [])].sort(\n (a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name)\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start gap-3\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Back to the project\"\n onClick={() => navigate(`/projects/project-detail-page/${projectId}`)}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">\n Custom Fields{project?.planmagnetProject ? ` — ${project.planmagnetProject.name}` : ''}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n Extra fields this project&apos;s tasks can carry. Each task&apos;s values are set from\n its own page.\n </p>\n </div>\n </div>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Fields ({definitions.length})</CardTitle>\n <DefinitionDialog projectId={projectId} />\n </CardHeader>\n <CardContent>\n {definitions.length === 0 ? (\n <p\n className=\"py-4 text-center text-sm text-muted-foreground\"\n data-testid=\"custom-fields-empty\"\n >\n No custom fields yet. Add one to start collecting extra data on this project&apos;s\n tasks.\n </p>\n ) : (\n <table className=\"w-full text-sm\" data-testid=\"custom-field-definitions\">\n <thead>\n <tr className=\"text-left text-muted-foreground\">\n <th className=\"py-1 pr-3 font-medium\">Name</th>\n <th className=\"py-1 pr-3 font-medium\">Type</th>\n <th className=\"py-1 pr-3 font-medium\">Required</th>\n <th className=\"py-1 pr-3 font-medium\">Options</th>\n <th className=\"py-1 font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {definitions.map((def) => (\n <tr key={def.id} className=\"border-t\" data-testid={`custom-field-row-${def.id}`}>\n <td className=\"py-2 pr-3\">\n <p className=\"font-medium\">{def.name}</p>\n {def.description && (\n <p className=\"text-xs text-muted-foreground\">{def.description}</p>\n )}\n </td>\n <td className=\"py-2 pr-3\">{customFieldTypeLabel(def.fieldType)}</td>\n <td className=\"py-2 pr-3\">{def.isRequired ? 'Yes' : 'No'}</td>\n <td className=\"py-2 pr-3 text-xs text-muted-foreground\">\n {OPTION_TYPES.has(def.fieldType)\n ? optionStrings(def.options).join(', ') || '—'\n : '—'}\n </td>\n <td className=\"py-2\">\n <div className=\"flex gap-2\">\n <DefinitionDialog projectId={projectId} definition={def} />\n <DeleteDefinitionButton definition={def} />\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n\nfunction DeleteDefinitionButton({ definition }: { definition: CustomFieldDefinitionSummary }) {\n const [remove, state] = useMutation(DELETE_CUSTOM_FIELD_DEFINITION, {\n refetchQueries: CUSTOM_FIELD_QUERIES,\n awaitRefetchQueries: true,\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: definition.id } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The field could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-start gap-1\">\n <ConfirmButton\n label=\"\"\n ariaLabel={`Delete ${definition.name}`}\n variant=\"outline\"\n icon={<Trash2 className=\"h-4 w-4\" />}\n title={`Delete \"${definition.name}\"?`}\n description=\"Its stored values on every task are deleted too. This cannot be undone here.\"\n confirmLabel=\"Delete field\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onDelete()}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Create when `definition` is omitted, edit otherwise. The mutations take no `workspaceId` argument — the\n * gateway derives it from the caller's own session, the same as every other write in this app. */\nfunction DefinitionDialog({\n projectId,\n definition,\n}: {\n projectId: string;\n definition?: CustomFieldDefinitionSummary;\n}) {\n const editing = definition !== undefined;\n const initial = definition ? formOf(definition) : emptyForm();\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<FormState>(initial);\n const [error, setError] = useState<string | null>(null);\n const [create, createState] = useMutation(CREATE_CUSTOM_FIELD_DEFINITION, {\n refetchQueries: CUSTOM_FIELD_QUERIES,\n awaitRefetchQueries: true,\n });\n const [update, updateState] = useMutation(UPDATE_CUSTOM_FIELD_DEFINITION, {\n refetchQueries: CUSTOM_FIELD_QUERIES,\n awaitRefetchQueries: true,\n });\n const busy = createState.loading || updateState.loading;\n\n const sortOrder = parseIntField(form.sortOrderText, { min: 0 });\n const needsOptions = OPTION_TYPES.has(form.fieldType);\n const options = needsOptions ? parseOptionLines(form.optionsText) : [];\n const invalid =\n form.name.trim() === '' || sortOrder.invalid || (needsOptions && options.length === 0);\n\n const save = async () => {\n setError(null);\n const shared = {\n name: form.name.trim(),\n description: form.description.trim() || null,\n fieldType: form.fieldType,\n isRequired: form.isRequired,\n options: needsOptions ? options : null,\n sortOrder: sortOrder.value ?? 0,\n };\n try {\n if (editing) {\n await throwOnError(update({ variables: { id: definition.id, input: shared } }));\n } else {\n await throwOnError(create({ variables: { input: { projectId, ...shared } } }));\n }\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The field could not be saved.');\n }\n };\n\n const idPrefix = editing ? `custom-field-${definition.id}` : 'new-custom-field';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) {\n setForm(definition ? formOf(definition) : emptyForm());\n setError(null);\n }\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {editing ? (\n <Button size=\"icon\" variant=\"ghost\" aria-label={`Edit ${definition.name}`}>\n <Pencil className=\"h-4 w-4\" />\n </Button>\n ) : (\n <Button size=\"sm\" data-testid=\"custom-field-add\">\n <ListPlus className=\"mr-1 h-4 w-4\" />\n Add field\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {editing ? `Edit ${definition.name}` : 'Add field'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Tasks in this project will be able to show and edit this value.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-name`} className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id={`${idPrefix}-name`}\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-type`} className=\"text-sm font-medium\">\n Type\n </label>\n <select\n id={`${idPrefix}-type`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.fieldType}\n onChange={(e) => setForm({ ...form, fieldType: e.target.value })}\n >\n {CUSTOM_FIELD_TYPES.map((t) => (\n <option key={t} value={t}>\n {customFieldTypeLabel(t)}\n </option>\n ))}\n </select>\n </div>\n {needsOptions && (\n <div>\n <label htmlFor={`${idPrefix}-options`} className=\"text-sm font-medium\">\n Options (one per line)\n </label>\n <textarea\n id={`${idPrefix}-options`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[88px]\"\n value={form.optionsText}\n onChange={(e) => setForm({ ...form, optionsText: e.target.value })}\n />\n {options.length === 0 && <ErrorLine message=\"Add at least one option.\" />}\n </div>\n )}\n <div>\n <label htmlFor={`${idPrefix}-description`} className=\"text-sm font-medium\">\n Description (optional)\n </label>\n <textarea\n id={`${idPrefix}-description`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[60px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"flex items-end gap-4\">\n <div>\n <label htmlFor={`${idPrefix}-sort`} className=\"text-sm font-medium\">\n Order (optional)\n </label>\n <Input\n id={`${idPrefix}-sort`}\n type=\"number\"\n min={0}\n step={1}\n className=\"mt-1 w-24\"\n value={form.sortOrderText}\n onChange={(e) => setForm({ ...form, sortOrderText: e.target.value })}\n />\n </div>\n <label className=\"flex items-center gap-2 pb-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={form.isRequired}\n onChange={(e) => setForm({ ...form, isRequired: e.target.checked })}\n />\n Required\n </label>\n </div>\n {sortOrder.invalid && <ErrorLine message=\"Order must be a whole number, 0 or more.\" />}\n {form.name.trim() === '' && <ErrorLine message=\"Give the field a name.\" />}\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={() => void save()}\n disabled={invalid || busy}\n data-testid={`${idPrefix}-save`}\n >\n {busy ? 'Saving…' : editing ? 'Save' : 'Add field'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6CA,IAAM,IAAe,CAAG;;;;;;;GAkBlB,WAA8B;CAClC,MAAM;CACN,aAAa;CACb,WAAW;CACX,YAAY;CACZ,aAAa;CACb,eAAe;CAChB,GAEK,KAAU,OAAkD;CAChE,MAAM,EAAI;CACV,aAAa,EAAI,eAAe;CAChC,WAAW,EAAI;CACf,YAAY,EAAI;CAChB,aAAa,EAAc,EAAI,QAAQ,CAAC,KAAK,KAAK;CAClD,eAAe,OAAO,EAAI,aAAa,EAAE;CAC1C;AAED,SAAgB,IAA+B;CAC7C,IAAM,EAAE,mBAAgB,GAAe,EACjC,IAAW,GAAuB,EAClC,IAAY,GAA2B,CAAC,MAAM,IAE9C,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAkC;EACnC,WAAW;GAAE,aAAa,KAAe;GAAI;GAAW;EACxD,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EACI,EAAE,MAAM,MAAY,EACxB,GACA;EAAE,WAAW,EAAE,IAAI,GAAW;EAAE,MAAM,CAAC;EAAW,CACnD;AAED,KAAI,CAAC,EACH,QAAO,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAA0B,CAAA;AAG9E,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAGV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAkC,WAAQ,EAAM,QAAc;;CAGvE,IAAM,IAAc,CAAC,GAAI,GAAM,iCAAiC,SAAS,EAAE,CAAE,CAAC,MAC3E,GAAG,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,CACpE;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,SAAQ;IACR,MAAK;IACL,cAAW;IACX,eAAe,EAAS,iCAAiC,IAAY;cAErE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;IAC1B,CAAA,EACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAd,CAAuC,iBACvB,GAAS,oBAAoB,MAAM,EAAQ,kBAAkB,SAAS,GACjF;OACL,kBAAC,KAAD;IAAG,WAAU;cAAgC;IAGzC,CAAA,CACA,EAAA,CAAA,CACF;MAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD,EAAA,UAAA;IAAW;IAAS,EAAY;IAAO;IAAa,EAAA,CAAA,EACpD,kBAAC,GAAD,EAA6B,cAAa,CAAA,CAC/B;MACb,kBAAC,GAAD,EAAA,UACG,EAAY,WAAW,IACtB,kBAAC,KAAD;GACE,WAAU;GACV,eAAY;aACb;GAGG,CAAA,GAEJ,kBAAC,SAAD;GAAO,WAAU;GAAiB,eAAY;aAA9C,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;IAAI,WAAU;cAAd;KACE,kBAAC,MAAD;MAAI,WAAU;gBAAwB;MAAS,CAAA;KAC/C,kBAAC,MAAD;MAAI,WAAU;gBAAwB;MAAS,CAAA;KAC/C,kBAAC,MAAD;MAAI,WAAU;gBAAwB;MAAa,CAAA;KACnD,kBAAC,MAAD;MAAI,WAAU;gBAAwB;MAAY,CAAA;KAClD,kBAAC,MAAD;MAAI,WAAU;gBAAmB;MAAY,CAAA;KAC1C;OACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAY,KAAK,MAChB,kBAAC,MAAD;IAAiB,WAAU;IAAW,eAAa,oBAAoB,EAAI;cAA3E;KACE,kBAAC,MAAD;MAAI,WAAU;gBAAd,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAe,EAAI;OAAS,CAAA,EACxC,EAAI,eACH,kBAAC,KAAD;OAAG,WAAU;iBAAiC,EAAI;OAAgB,CAAA,CAEjE;;KACL,kBAAC,MAAD;MAAI,WAAU;gBAAa,EAAqB,EAAI,UAAU;MAAM,CAAA;KACpE,kBAAC,MAAD;MAAI,WAAU;gBAAa,EAAI,aAAa,QAAQ;MAAU,CAAA;KAC9D,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAa,IAAI,EAAI,UAAU,IAC5B,EAAc,EAAI,QAAQ,CAAC,KAAK,KAAK,IACrC;MACD,CAAA;KACL,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAA6B;QAAW,YAAY;QAAO,CAAA,EAC3D,kBAAC,GAAD,EAAwB,YAAY,GAAO,CAAA,CACvC;;MACH,CAAA;KACF;MApBI,EAAI,GAoBR,CACL,EACI,CAAA,CACF;MAEE,CAAA,CACT,EAAA,CAAA,CACH;;;AAIV,SAAS,EAAuB,EAAE,iBAA4D;CAC5F,IAAM,CAAC,GAAQ,KAAS,EAAY,GAAgC;EAClE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,EAAW,IAAI,EAAE,CAAC,CAAC;WACzD,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,kCAAkC;;;AAIhF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GACE,OAAM;GACN,WAAW,UAAU,EAAW;GAChC,SAAQ;GACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;GACpC,OAAO,WAAW,EAAW,KAAK;GAClC,aAAY;GACZ,cAAa;GACb,aAAA;GACA,UAAU,EAAM;GAChB,iBAAiB,KAAK,GAAU;GAChC,CAAA,EACD,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC;;;AAMV,SAAS,EAAiB,EACxB,cACA,iBAIC;CACD,IAAM,IAAU,MAAe,KAAA,GACzB,IAAU,IAAa,EAAO,EAAW,GAAG,GAAW,EACvD,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAM,KAAW,EAAoB,EAAQ,EAC9C,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAQ,KAAe,EAAY,GAAgC;EACxE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAgC;EACxE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,IAAO,EAAY,WAAW,EAAY,SAE1C,IAAY,EAAc,EAAK,eAAe,EAAE,KAAK,GAAG,CAAC,EACzD,IAAe,EAAa,IAAI,EAAK,UAAU,EAC/C,IAAU,IAAe,EAAiB,EAAK,YAAY,GAAG,EAAE,EAChE,IACJ,EAAK,KAAK,MAAM,KAAK,MAAM,EAAU,WAAY,KAAgB,EAAQ,WAAW,GAEhF,IAAO,YAAY;AACvB,IAAS,KAAK;EACd,IAAM,IAAS;GACb,MAAM,EAAK,KAAK,MAAM;GACtB,aAAa,EAAK,YAAY,MAAM,IAAI;GACxC,WAAW,EAAK;GAChB,YAAY,EAAK;GACjB,SAAS,IAAe,IAAU;GAClC,WAAW,EAAU,SAAS;GAC/B;AACD,MAAI;AAMF,GALI,IACF,MAAM,EAAa,EAAO,EAAE,WAAW;IAAE,IAAI,EAAW;IAAI,OAAO;IAAQ,EAAE,CAAC,CAAC,GAE/E,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,OAAO;IAAE;IAAW,GAAG;IAAQ,EAAE,EAAE,CAAC,CAAC,EAEhF,EAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,gCAAgC;;IAIxE,IAAW,IAAU,gBAAgB,EAAW,OAAO;AAE7D,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACT,MACF,EAAQ,IAAa,EAAO,EAAW,GAAG,GAAW,CAAC,EACtD,EAAS,KAAK;;YANpB,CAUE,kBAAC,GAAD;GAAyB,SAAA;aACtB,IACC,kBAAC,GAAD;IAAQ,MAAK;IAAO,SAAQ;IAAQ,cAAY,QAAQ,EAAW;cACjE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;IACvB,CAAA,GAET,kBAAC,GAAD;IAAQ,MAAK;IAAK,eAAY;cAA9B,CACE,kBAAC,GAAD,EAAU,WAAU,gBAAiB,CAAA,EAAA,YAE9B;;GAEa,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,IAAU,QAAQ,EAAW,SAAS,aACjB,CAAA,EACxB,kBAAC,GAAD,EAAA,UAA6B,mEAEC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS,GAAG,EAAS;OAAQ,WAAU;iBAAsB;OAE5D,CAAA,EACR,kBAAC,GAAD;OACE,IAAI,GAAG,EAAS;OAChB,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,MAAM,EAAE,OAAO;QAAO,CAAC;OAC3D,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS,GAAG,EAAS;OAAQ,WAAU;iBAAsB;OAE5D,CAAA,EACR,kBAAC,UAAD;OACE,IAAI,GAAG,EAAS;OAChB,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,WAAW,EAAE,OAAO;QAAO,CAAC;iBAE/D,EAAmB,KAAK,MACvB,kBAAC,UAAD;QAAgB,OAAO;kBACpB,EAAqB,EAAE;QACjB,EAFI,EAEJ,CACT;OACK,CAAA,CACL,EAAA,CAAA;MACL,KACC,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,SAAS,GAAG,EAAS;QAAW,WAAU;kBAAsB;QAE/D,CAAA;OACR,kBAAC,YAAD;QACE,IAAI,GAAG,EAAS;QAChB,WAAU;QACV,OAAO,EAAK;QACZ,WAAW,MAAM,EAAQ;SAAE,GAAG;SAAM,aAAa,EAAE,OAAO;SAAO,CAAC;QAClE,CAAA;OACD,EAAQ,WAAW,KAAK,kBAAC,GAAD,EAAW,SAAQ,4BAA6B,CAAA;OACrE,EAAA,CAAA;MAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS,GAAG,EAAS;OAAe,WAAU;iBAAsB;OAEnE,CAAA,EACR,kBAAC,YAAD;OACE,IAAI,GAAG,EAAS;OAChB,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,aAAa,EAAE,OAAO;QAAO,CAAC;OAClE,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAS,GAAG,EAAS;QAAQ,WAAU;kBAAsB;QAE5D,CAAA,EACR,kBAAC,GAAD;QACE,IAAI,GAAG,EAAS;QAChB,MAAK;QACL,KAAK;QACL,MAAM;QACN,WAAU;QACV,OAAO,EAAK;QACZ,WAAW,MAAM,EAAQ;SAAE,GAAG;SAAM,eAAe,EAAE,OAAO;SAAO,CAAC;QACpE,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACE,kBAAC,SAAD;SACE,MAAK;SACL,SAAS,EAAK;SACd,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,YAAY,EAAE,OAAO;UAAS,CAAC;SACnE,CAAA,EAAA,WAEI;UACJ;;MACL,EAAU,WAAW,kBAAC,GAAD,EAAW,SAAQ,4CAA6C,CAAA;MACrF,EAAK,KAAK,MAAM,KAAK,MAAM,kBAAC,GAAD,EAAW,SAAQ,0BAA2B,CAAA;MACzE,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;MACnC;;IACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAQ,GAAM;eAAE;KAEhD,CAAA,EACT,kBAAC,GAAD;KACE,eAAe,KAAK,GAAM;KAC1B,UAAU,KAAW;KACrB,eAAa,GAAG,EAAS;eAExB,IAAO,YAAY,IAAU,SAAS;KAChC,CAAA,CACc,EAAA,CAAA;IACD;KACT"}
@@ -1,11 +1,11 @@
1
1
  import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
2
  import { throwOnError as t } from "../utils/mutationResult.js";
3
- import { detailPath as n } from "../utils/rowNavigation.js";
3
+ import { useEntityId as n } from "../hooks/useEntityId.js";
4
4
  import { ProgramSelect as r } from "../controls/Pickers.js";
5
- import { endBeforeStart as i } from "../utils/formValidation.js";
6
- import { enumLabel as a } from "../controls/WorkItemControls.js";
7
- import { PROJECT_STATUSES as o } from "../controls/ProjectControls.js";
8
- import { useEntityId as s } from "../hooks/useEntityId.js";
5
+ import { enumLabel as i } from "../controls/WorkItemControls.js";
6
+ import { detailPath as a } from "../utils/rowNavigation.js";
7
+ import { endBeforeStart as o } from "../utils/formValidation.js";
8
+ import { PROJECT_STATUSES as s } from "../controls/ProjectControls.js";
9
9
  import { useMemo as c, useState as l } from "react";
10
10
  import { ArrowLeft as u, Save as d } from "lucide-react";
11
11
  import { gql as f } from "@apollo/client";
@@ -51,7 +51,7 @@ var C = f`
51
51
  }
52
52
  `;
53
53
  function T() {
54
- let f = s("projectId"), T = e(), { data: E, loading: D, error: O } = m(C, {
54
+ let f = n("projectId"), T = e(), { data: E, loading: D, error: O } = m(C, {
55
55
  variables: { id: f },
56
56
  skip: !f
57
57
  }), [k, A] = l(null), j = E?.planmagnetProject, M = c(() => ({
@@ -74,7 +74,7 @@ function T() {
74
74
  "AdminPlanmagnetProjects",
75
75
  "GetProgram",
76
76
  "ListPrograms"
77
- ] }), [z, B] = l(null), V = i(F.startDate, F.endDate);
77
+ ] }), [z, B] = l(null), V = o(F.startDate, F.endDate);
78
78
  return f ? D && !E ? /* @__PURE__ */ h("div", {
79
79
  className: "flex items-center justify-center p-12",
80
80
  children: /* @__PURE__ */ h("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
@@ -92,7 +92,7 @@ function T() {
92
92
  variant: "ghost",
93
93
  size: "sm",
94
94
  "aria-label": "Back to the project",
95
- onClick: () => T(n("/projects/project-detail-page", f)),
95
+ onClick: () => T(a("/projects/project-detail-page", f)),
96
96
  children: /* @__PURE__ */ h(u, { className: "h-4 w-4" })
97
97
  }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
98
98
  className: "text-2xl font-semibold",
@@ -215,9 +215,9 @@ function T() {
215
215
  ...F,
216
216
  status: e.target.value
217
217
  }),
218
- children: (o.includes(F.status) ? o : [F.status, ...o]).map((e) => /* @__PURE__ */ h("option", {
218
+ children: (s.includes(F.status) ? s : [F.status, ...s]).map((e) => /* @__PURE__ */ h("option", {
219
219
  value: e,
220
- children: a(e)
220
+ children: i(e)
221
221
  }, e))
222
222
  })] }),
223
223
  /* @__PURE__ */ g("div", {