@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,315 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { PersonName as t } from "../people/People.js";
3
+ import { BUDGET_STATE_STYLE as n, budgetState as r, budgetUsage as i, money as a } from "../budgets/budgetMath.js";
4
+ import { useEntityId as o } from "../hooks/useEntityId.js";
5
+ import { enumLabel as s } from "../controls/WorkItemControls.js";
6
+ import { BudgetHeaderActions as c } from "../budgets/BudgetControls.js";
7
+ import { DeleteExpenseButton as l, EditExpenseDialog as u, LogExpenseDialog as d } from "../budgets/ExpenseControls.js";
8
+ import { ArrowLeft as f, Receipt as p } from "lucide-react";
9
+ import { gql as m } from "@apollo/client";
10
+ import { useQuery as h } from "@apollo/client/react";
11
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
12
+ import { Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S } from "@burdenoff/fe-libs/ui";
13
+ //#region src/planmagnet/pages/ProjectsBudgetDetailPagePage.tsx
14
+ var C = m`
15
+ query GetBudget($id: ID!) {
16
+ planmagnetBudget(id: $id) {
17
+ id
18
+ name
19
+ description
20
+ status
21
+ plannedAmount
22
+ spentAmount
23
+ currency
24
+ periodStart
25
+ periodEnd
26
+ approverId
27
+ approvedAt
28
+ createdBy
29
+ createdAt
30
+ projectId
31
+ project {
32
+ id
33
+ name
34
+ key
35
+ }
36
+ expenses {
37
+ id
38
+ description
39
+ amount
40
+ category
41
+ date
42
+ workItemId
43
+ referenceUrl
44
+ createdBy
45
+ createdAt
46
+ }
47
+ }
48
+ }
49
+ `;
50
+ function w() {
51
+ let m = o("budgetId"), w = e(), { data: T, loading: E, error: D } = h(C, {
52
+ variables: { id: m },
53
+ skip: !m
54
+ });
55
+ if (!m) return /* @__PURE__ */ g("div", {
56
+ className: "p-6 text-sm text-muted-foreground",
57
+ children: "No budget selected. Open one from the list."
58
+ });
59
+ if (E && !T) return /* @__PURE__ */ g("div", {
60
+ className: "flex items-center justify-center p-12",
61
+ children: /* @__PURE__ */ g("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
62
+ });
63
+ if (D) return /* @__PURE__ */ _("div", {
64
+ className: "p-6 space-y-3",
65
+ children: [/* @__PURE__ */ _("p", {
66
+ role: "alert",
67
+ className: "text-sm text-destructive",
68
+ children: ["The budget could not be loaded: ", D.message]
69
+ }), /* @__PURE__ */ _(v, {
70
+ size: "sm",
71
+ variant: "ghost",
72
+ onClick: () => w("/projects/budgets-page"),
73
+ children: [/* @__PURE__ */ g(f, { className: "mr-1 h-4 w-4" }), "All budgets"]
74
+ })]
75
+ });
76
+ let O = T?.planmagnetBudget;
77
+ if (!O) return /* @__PURE__ */ _("div", {
78
+ className: "p-6 space-y-3",
79
+ children: [/* @__PURE__ */ g("p", {
80
+ className: "text-sm text-muted-foreground",
81
+ children: "This budget does not exist in this workspace, or it was deleted."
82
+ }), /* @__PURE__ */ _(v, {
83
+ size: "sm",
84
+ variant: "ghost",
85
+ onClick: () => w("/projects/budgets-page"),
86
+ children: [/* @__PURE__ */ g(f, { className: "mr-1 h-4 w-4" }), "All budgets"]
87
+ })]
88
+ });
89
+ let k = i(O.plannedAmount, O.spentAmount), A = n[r(k)], j = k === null ? null : Math.round(k * 100), M = O.plannedAmount - O.spentAmount, N = [...O.expenses].sort((e, t) => t.date.localeCompare(e.date)), P = [
90
+ "GetBudget",
91
+ "BudgetsList",
92
+ "GetBudgetPerformance"
93
+ ], F = O.project;
94
+ return /* @__PURE__ */ _("div", {
95
+ className: "space-y-6 p-6",
96
+ children: [
97
+ /* @__PURE__ */ _(v, {
98
+ size: "sm",
99
+ variant: "ghost",
100
+ onClick: () => w("/projects/budgets-page"),
101
+ children: [/* @__PURE__ */ g(f, { className: "mr-1 h-4 w-4" }), "All budgets"]
102
+ }),
103
+ /* @__PURE__ */ _("div", {
104
+ className: "flex flex-wrap items-start justify-between gap-4",
105
+ children: [/* @__PURE__ */ _("div", { children: [
106
+ /* @__PURE__ */ g("h1", {
107
+ className: "text-2xl font-semibold",
108
+ children: O.name
109
+ }),
110
+ /* @__PURE__ */ g("p", {
111
+ className: "text-sm text-muted-foreground mt-1",
112
+ children: O.description ?? "No description"
113
+ }),
114
+ /* @__PURE__ */ g("p", {
115
+ className: "text-xs text-muted-foreground mt-1",
116
+ children: F ? /* @__PURE__ */ _("button", {
117
+ type: "button",
118
+ className: "hover:underline",
119
+ onClick: () => w(`/projects/project-detail-page/${encodeURIComponent(F.id)}`),
120
+ children: [
121
+ F.name,
122
+ " (",
123
+ F.key,
124
+ ")"
125
+ ]
126
+ }) : "No project"
127
+ })
128
+ ] }), /* @__PURE__ */ g(c, { budget: O })]
129
+ }),
130
+ /* @__PURE__ */ _("div", {
131
+ className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
132
+ children: [/* @__PURE__ */ _("div", {
133
+ className: "lg:col-span-2 space-y-6",
134
+ children: [/* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ g(S, { children: "Plan vs actual" }) }), /* @__PURE__ */ _(b, {
135
+ className: "space-y-3",
136
+ children: [
137
+ /* @__PURE__ */ _("div", {
138
+ className: "grid grid-cols-3 gap-4 text-center",
139
+ children: [
140
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
141
+ className: "text-lg font-semibold tabular-nums",
142
+ children: a(O.plannedAmount, O.currency)
143
+ }), /* @__PURE__ */ g("p", {
144
+ className: "text-xs text-muted-foreground",
145
+ children: "Planned"
146
+ })] }),
147
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
148
+ className: "text-lg font-semibold tabular-nums",
149
+ children: a(O.spentAmount, O.currency)
150
+ }), /* @__PURE__ */ g("p", {
151
+ className: "text-xs text-muted-foreground",
152
+ children: "Spent"
153
+ })] }),
154
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
155
+ className: `text-lg font-semibold tabular-nums ${M < 0 ? "text-status-error-text" : ""}`,
156
+ children: a(M, O.currency)
157
+ }), /* @__PURE__ */ g("p", {
158
+ className: "text-xs text-muted-foreground",
159
+ children: "Remaining"
160
+ })] })
161
+ ]
162
+ }),
163
+ /* @__PURE__ */ g("div", {
164
+ role: "meter",
165
+ "aria-valuemin": 0,
166
+ "aria-valuemax": Math.max(100, j ?? 0),
167
+ "aria-valuenow": j ?? 0,
168
+ "aria-label": `${j === null ? "no plan" : `${j}%`} spent`,
169
+ className: `h-3 w-full overflow-hidden rounded-full ${A.track}`,
170
+ children: /* @__PURE__ */ g("div", {
171
+ className: `h-full rounded-full ${A.fill}`,
172
+ style: { width: `${Math.min(j ?? 0, 100)}%` }
173
+ })
174
+ }),
175
+ /* @__PURE__ */ _("span", {
176
+ className: `flex items-center gap-1 text-xs ${A.text}`,
177
+ children: [/* @__PURE__ */ g(A.icon, {
178
+ className: "h-3 w-3",
179
+ "aria-hidden": !0
180
+ }), j === null ? "No plan set" : `${j}% of plan spent`]
181
+ })
182
+ ]
183
+ })] }), /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ _(x, {
184
+ className: "flex flex-row items-center justify-between space-y-0",
185
+ children: [/* @__PURE__ */ _(S, {
186
+ className: "flex items-center gap-2",
187
+ children: [
188
+ /* @__PURE__ */ g(p, { className: "h-4 w-4" }),
189
+ "Expenses (",
190
+ N.length,
191
+ ")"
192
+ ]
193
+ }), /* @__PURE__ */ g(d, {
194
+ budgetId: O.id,
195
+ projectId: O.projectId,
196
+ refetchQueries: P
197
+ })]
198
+ }), /* @__PURE__ */ _(b, {
199
+ className: "space-y-2",
200
+ children: [N.length === 0 && /* @__PURE__ */ g("p", {
201
+ className: "text-sm text-muted-foreground text-center py-4",
202
+ children: "No expenses logged yet."
203
+ }), N.map((e) => /* @__PURE__ */ _("div", {
204
+ className: "flex items-start justify-between gap-3 rounded-md border p-3",
205
+ children: [/* @__PURE__ */ _("div", {
206
+ className: "min-w-0 space-y-1",
207
+ children: [
208
+ /* @__PURE__ */ g("p", {
209
+ className: "text-sm font-medium truncate",
210
+ children: e.description
211
+ }),
212
+ /* @__PURE__ */ _("p", {
213
+ className: "text-xs text-muted-foreground",
214
+ children: [
215
+ new Date(e.date).toLocaleDateString(),
216
+ e.category ? ` · ${e.category}` : "",
217
+ " ·",
218
+ " ",
219
+ /* @__PURE__ */ g(t, { id: e.createdBy })
220
+ ]
221
+ }),
222
+ e.referenceUrl && /* @__PURE__ */ g("a", {
223
+ href: e.referenceUrl,
224
+ target: "_blank",
225
+ rel: "noreferrer",
226
+ className: "text-xs text-primary hover:underline",
227
+ children: "Receipt / reference"
228
+ })
229
+ ]
230
+ }), /* @__PURE__ */ _("div", {
231
+ className: "flex shrink-0 items-center gap-2",
232
+ children: [
233
+ /* @__PURE__ */ g("span", {
234
+ className: "text-sm font-semibold tabular-nums",
235
+ children: a(e.amount, O.currency)
236
+ }),
237
+ /* @__PURE__ */ g(u, {
238
+ expense: e,
239
+ refetchQueries: P
240
+ }),
241
+ /* @__PURE__ */ g(l, {
242
+ id: e.id,
243
+ description: e.description,
244
+ refetchQueries: P
245
+ })
246
+ ]
247
+ })]
248
+ }, e.id))]
249
+ })] })]
250
+ }), /* @__PURE__ */ g("div", {
251
+ className: "space-y-6",
252
+ children: /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ g(S, { children: "Details" }) }), /* @__PURE__ */ _(b, {
253
+ className: "space-y-2 text-sm",
254
+ children: [
255
+ /* @__PURE__ */ _("div", {
256
+ className: "flex justify-between",
257
+ children: [/* @__PURE__ */ g("span", {
258
+ className: "text-muted-foreground",
259
+ children: "Status"
260
+ }), /* @__PURE__ */ g("span", { children: s(O.status) })]
261
+ }),
262
+ /* @__PURE__ */ _("div", {
263
+ className: "flex justify-between",
264
+ children: [/* @__PURE__ */ g("span", {
265
+ className: "text-muted-foreground",
266
+ children: "Currency"
267
+ }), /* @__PURE__ */ g("span", {
268
+ className: "font-mono",
269
+ children: O.currency
270
+ })]
271
+ }),
272
+ /* @__PURE__ */ _("div", {
273
+ className: "flex justify-between",
274
+ children: [/* @__PURE__ */ g("span", {
275
+ className: "text-muted-foreground",
276
+ children: "Period"
277
+ }), /* @__PURE__ */ _("span", { children: [
278
+ O.periodStart ? new Date(O.periodStart).toLocaleDateString() : "—",
279
+ " –",
280
+ " ",
281
+ O.periodEnd ? new Date(O.periodEnd).toLocaleDateString() : "—"
282
+ ] })]
283
+ }),
284
+ /* @__PURE__ */ _("div", {
285
+ className: "flex justify-between",
286
+ children: [/* @__PURE__ */ g("span", {
287
+ className: "text-muted-foreground",
288
+ children: "Created by"
289
+ }), /* @__PURE__ */ g(t, { id: O.createdBy })]
290
+ }),
291
+ O.approverId && /* @__PURE__ */ _("div", {
292
+ className: "flex justify-between",
293
+ children: [/* @__PURE__ */ g("span", {
294
+ className: "text-muted-foreground",
295
+ children: "Approved by"
296
+ }), /* @__PURE__ */ g(t, { id: O.approverId })]
297
+ }),
298
+ O.approvedAt && /* @__PURE__ */ _("div", {
299
+ className: "flex justify-between",
300
+ children: [/* @__PURE__ */ g("span", {
301
+ className: "text-muted-foreground",
302
+ children: "Approved"
303
+ }), /* @__PURE__ */ g("span", { children: new Date(O.approvedAt).toLocaleDateString() })]
304
+ })
305
+ ]
306
+ })] })
307
+ })]
308
+ })
309
+ ]
310
+ });
311
+ }
312
+ //#endregion
313
+ export { w as ProjectsBudgetDetailPagePage };
314
+
315
+ //# sourceMappingURL=ProjectsBudgetDetailPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsBudgetDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsBudgetDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Receipt } from 'lucide-react';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { BudgetHeaderActions } from '../budgets/BudgetControls';\nimport {\n LogExpenseDialog,\n EditExpenseDialog,\n DeleteExpenseButton,\n} from '../budgets/ExpenseControls';\nimport { budgetState, budgetUsage, BUDGET_STATE_STYLE, money } from '../budgets/budgetMath';\nimport { PersonName } from '../people/People';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\n/** One budget (BOFF-8047): its plan vs spend, and every expense logged against it. */\n\nconst GET_BUDGET = gql`\n query GetBudget($id: ID!) {\n planmagnetBudget(id: $id) {\n id\n name\n description\n status\n plannedAmount\n spentAmount\n currency\n periodStart\n periodEnd\n approverId\n approvedAt\n createdBy\n createdAt\n projectId\n project {\n id\n name\n key\n }\n expenses {\n id\n description\n amount\n category\n date\n workItemId\n referenceUrl\n createdBy\n createdAt\n }\n }\n }\n`;\n\ninterface ExpenseNode {\n id: string;\n description: string;\n amount: number;\n category: string | null;\n date: string;\n workItemId: string | null;\n referenceUrl: string | null;\n createdBy: string;\n createdAt: string;\n}\n\ninterface BudgetNode {\n id: string;\n name: string;\n description: string | null;\n status: string;\n plannedAmount: number;\n spentAmount: number;\n currency: string;\n periodStart: string | null;\n periodEnd: string | null;\n approverId: string | null;\n approvedAt: string | null;\n createdBy: string;\n createdAt: string;\n projectId: string;\n project: { id: string; name: string; key: string } | null;\n expenses: ExpenseNode[];\n}\n\nexport function ProjectsBudgetDetailPagePage() {\n const budgetId = useEntityId('budgetId');\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<{ planmagnetBudget: BudgetNode | null }>(GET_BUDGET, {\n variables: { id: budgetId },\n skip: !budgetId,\n });\n\n if (!budgetId) {\n return (\n <div className=\"p-6 text-sm text-muted-foreground\">\n No budget selected. Open one from the list.\n </div>\n );\n }\n // Only before the first load: a refetch after a write 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 }\n if (error) {\n return (\n <div className=\"p-6 space-y-3\">\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The budget could not be loaded: {error.message}\n </p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/projects/budgets-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All budgets\n </Button>\n </div>\n );\n }\n const budget = data?.planmagnetBudget;\n if (!budget) {\n return (\n <div className=\"p-6 space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n This budget does not exist in this workspace, or it was deleted.\n </p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/projects/budgets-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All budgets\n </Button>\n </div>\n );\n }\n\n const usage = budgetUsage(budget.plannedAmount, budget.spentAmount);\n const state = budgetState(usage);\n const style = BUDGET_STATE_STYLE[state];\n const percent = usage === null ? null : Math.round(usage * 100);\n const remaining = budget.plannedAmount - budget.spentAmount;\n const expenses = [...budget.expenses].sort((a, b) => b.date.localeCompare(a.date));\n const refetchQueries = ['GetBudget', 'BudgetsList', 'GetBudgetPerformance'];\n // A local const so the null-check narrows through the closure below (a nested arrow reading\n // `budget.project` directly would not stay narrowed, forcing a non-null assertion instead).\n const project = budget.project;\n\n return (\n <div className=\"space-y-6 p-6\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/projects/budgets-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All budgets\n </Button>\n\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{budget.name}</h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {budget.description ?? 'No description'}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {project ? (\n <button\n type=\"button\"\n className=\"hover:underline\"\n onClick={() =>\n navigate(`/projects/project-detail-page/${encodeURIComponent(project.id)}`)\n }\n >\n {project.name} ({project.key})\n </button>\n ) : (\n 'No project'\n )}\n </p>\n </div>\n <BudgetHeaderActions budget={budget} />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Plan vs actual</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"grid grid-cols-3 gap-4 text-center\">\n <div>\n <p className=\"text-lg font-semibold tabular-nums\">\n {money(budget.plannedAmount, budget.currency)}\n </p>\n <p className=\"text-xs text-muted-foreground\">Planned</p>\n </div>\n <div>\n <p className=\"text-lg font-semibold tabular-nums\">\n {money(budget.spentAmount, budget.currency)}\n </p>\n <p className=\"text-xs text-muted-foreground\">Spent</p>\n </div>\n <div>\n <p\n className={`text-lg font-semibold tabular-nums ${remaining < 0 ? 'text-status-error-text' : ''}`}\n >\n {money(remaining, budget.currency)}\n </p>\n <p className=\"text-xs text-muted-foreground\">Remaining</p>\n </div>\n </div>\n <div\n role=\"meter\"\n aria-valuemin={0}\n aria-valuemax={Math.max(100, percent ?? 0)}\n aria-valuenow={percent ?? 0}\n aria-label={`${percent === null ? 'no plan' : `${percent}%`} spent`}\n className={`h-3 w-full overflow-hidden rounded-full ${style.track}`}\n >\n <div\n className={`h-full rounded-full ${style.fill}`}\n style={{ width: `${Math.min(percent ?? 0, 100)}%` }}\n />\n </div>\n <span className={`flex items-center gap-1 text-xs ${style.text}`}>\n <style.icon className=\"h-3 w-3\" aria-hidden={true} />\n {percent === null ? 'No plan set' : `${percent}% of plan spent`}\n </span>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <Receipt className=\"h-4 w-4\" />\n Expenses ({expenses.length})\n </CardTitle>\n <LogExpenseDialog\n budgetId={budget.id}\n projectId={budget.projectId}\n refetchQueries={refetchQueries}\n />\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {expenses.length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No expenses logged yet.\n </p>\n )}\n {expenses.map((expense) => (\n <div\n key={expense.id}\n className=\"flex items-start justify-between gap-3 rounded-md border p-3\"\n >\n <div className=\"min-w-0 space-y-1\">\n <p className=\"text-sm font-medium truncate\">{expense.description}</p>\n <p className=\"text-xs text-muted-foreground\">\n {new Date(expense.date).toLocaleDateString()}\n {expense.category ? ` · ${expense.category}` : ''} ·{' '}\n <PersonName id={expense.createdBy} />\n </p>\n {expense.referenceUrl && (\n <a\n href={expense.referenceUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-xs text-primary hover:underline\"\n >\n Receipt / reference\n </a>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"text-sm font-semibold tabular-nums\">\n {money(expense.amount, budget.currency)}\n </span>\n <EditExpenseDialog expense={expense} refetchQueries={refetchQueries} />\n <DeleteExpenseButton\n id={expense.id}\n description={expense.description}\n refetchQueries={refetchQueries}\n />\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Status</span>\n <span>{enumLabel(budget.status)}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Currency</span>\n <span className=\"font-mono\">{budget.currency}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Period</span>\n <span>\n {budget.periodStart ? new Date(budget.periodStart).toLocaleDateString() : '—'} –{' '}\n {budget.periodEnd ? new Date(budget.periodEnd).toLocaleDateString() : '—'}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created by</span>\n <PersonName id={budget.createdBy} />\n </div>\n {budget.approverId && (\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Approved by</span>\n <PersonName id={budget.approverId} />\n </div>\n )}\n {budget.approvedAt && (\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Approved</span>\n <span>{new Date(budget.approvedAt).toLocaleDateString()}</span>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProjectsBudgetsPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProjectsBudgetsPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsBudgetsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsBudgetsPagePage.tsx"],"names":[],"mappings":"AA8DA,wBAAgB,uBAAuB,4CAmKtC"}
@@ -0,0 +1,186 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { BUDGET_STATE_STYLE as n, budgetState as r, budgetUsage as i, money as a } from "../budgets/budgetMath.js";
3
+ import { ProjectSelect as o } from "../controls/Pickers.js";
4
+ import { enumLabel as s } from "../controls/WorkItemControls.js";
5
+ import { detailPath as c, rowLinkProps as l } from "../utils/rowNavigation.js";
6
+ import { BUDGET_STATUSES as u, CreateBudgetDialog as d } from "../budgets/BudgetControls.js";
7
+ import { useDeferredValue as f, useState as p } from "react";
8
+ import { Wallet as m } from "lucide-react";
9
+ import { useSearchParams as h } from "react-router";
10
+ import { gql as g } from "@apollo/client";
11
+ import { useQuery as _ } from "@apollo/client/react";
12
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
13
+ import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
+ import { Card as x, CardContent as S, CardHeader as C, CardTitle as w, Input as T } from "@burdenoff/fe-libs/ui";
15
+ //#region src/planmagnet/pages/ProjectsBudgetsPagePage.tsx
16
+ var E = g`
17
+ query BudgetsList(
18
+ $workspaceId: ID!
19
+ $filter: PlanmagnetBudgetFilter
20
+ $pagination: PlanmagnetPaginationInput
21
+ ) {
22
+ planmagnetBudgets(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {
23
+ items {
24
+ id
25
+ name
26
+ status
27
+ plannedAmount
28
+ spentAmount
29
+ currency
30
+ projectId
31
+ project {
32
+ id
33
+ name
34
+ }
35
+ }
36
+ totalCount
37
+ }
38
+ }
39
+ `, D = ["ALL", ...u];
40
+ function O() {
41
+ let { workspaceId: u } = e(), g = t(), { t: O } = b(), [k] = h(), [A, j] = p(k.get("projectId") ?? ""), [M, N] = p("ALL"), [P, F] = p(""), I = f(P.trim()), L = {
42
+ ...A ? { projectId: A } : {},
43
+ ...M === "ALL" ? {} : { status: M },
44
+ ...I ? { search: I } : {}
45
+ }, { data: R, loading: z, error: B } = _(E, {
46
+ variables: {
47
+ workspaceId: u ?? "",
48
+ filter: L,
49
+ pagination: { first: 100 }
50
+ },
51
+ skip: !u
52
+ }), V = R?.planmagnetBudgets.items ?? [], H = R?.planmagnetBudgets.totalCount ?? 0;
53
+ return /* @__PURE__ */ y("div", {
54
+ className: "space-y-6 p-6",
55
+ children: [
56
+ /* @__PURE__ */ y("div", {
57
+ className: "flex flex-wrap items-center justify-between gap-3",
58
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
59
+ className: "text-2xl font-semibold",
60
+ children: O("planmagnet.budgets.title", "Budgets")
61
+ }), /* @__PURE__ */ v("p", {
62
+ className: "text-sm text-muted-foreground",
63
+ children: R ? O("planmagnet.budgets.subtitle", "{{count}} budget(s) across projects", { count: H }) : O("planmagnet.budgets.subtitleFallback", "Financial plans, tracked against actuals")
64
+ })] }), /* @__PURE__ */ v(d, {
65
+ presetProjectId: A || void 0,
66
+ onCreated: (e) => g(c("/projects/budget-detail-page", e))
67
+ })]
68
+ }),
69
+ /* @__PURE__ */ y("div", {
70
+ className: "flex flex-wrap items-center gap-3",
71
+ children: [/* @__PURE__ */ v("div", {
72
+ className: "w-full max-w-xs",
73
+ children: /* @__PURE__ */ v(o, {
74
+ id: "budgets-project-filter",
75
+ value: A,
76
+ onChange: j,
77
+ noneLabel: O("planmagnet.budgets.allProjects", "All projects")
78
+ })
79
+ }), /* @__PURE__ */ v(T, {
80
+ "aria-label": O("planmagnet.budgets.search", "Search budgets"),
81
+ placeholder: O("planmagnet.budgets.searchPlaceholder", "Search by name or description"),
82
+ className: "w-full max-w-xs",
83
+ value: P,
84
+ onChange: (e) => F(e.target.value)
85
+ })]
86
+ }),
87
+ /* @__PURE__ */ v("div", {
88
+ className: "flex flex-wrap gap-1",
89
+ role: "group",
90
+ "aria-label": O("planmagnet.budgets.statusFilter", "Filter by status"),
91
+ children: D.map((e) => /* @__PURE__ */ v("button", {
92
+ type: "button",
93
+ "aria-pressed": M === e,
94
+ onClick: () => N(e),
95
+ className: `px-3 py-1.5 text-sm rounded-md ${M === e ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
96
+ children: e === "ALL" ? O("planmagnet.budgets.allStatuses", "All") : s(e)
97
+ }, e))
98
+ }),
99
+ B && /* @__PURE__ */ y("p", {
100
+ role: "alert",
101
+ className: "text-sm text-destructive",
102
+ children: [
103
+ O("planmagnet.budgets.errorLoading", "Budgets could not be loaded"),
104
+ ": ",
105
+ B.message
106
+ ]
107
+ }),
108
+ z && !R && /* @__PURE__ */ v("div", {
109
+ className: "flex items-center justify-center p-12",
110
+ children: /* @__PURE__ */ v("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
111
+ }),
112
+ R && V.length === 0 && !B && /* @__PURE__ */ v("p", {
113
+ className: "text-center text-sm text-muted-foreground py-8",
114
+ children: A || M !== "ALL" || I ? O("planmagnet.budgets.emptyFiltered", "No budgets match these filters.") : O("planmagnet.budgets.empty", "No budgets yet. Create the first one.")
115
+ }),
116
+ /* @__PURE__ */ v("div", {
117
+ className: "grid grid-cols-1 md:grid-cols-2 gap-4",
118
+ children: V.map((e) => {
119
+ let t = i(e.plannedAmount, e.spentAmount), o = n[r(t)], u = t === null ? null : Math.round(t * 100);
120
+ return /* @__PURE__ */ y(x, {
121
+ ...l(() => g(c("/projects/budget-detail-page", e.id)), e.name),
122
+ children: [/* @__PURE__ */ v(C, {
123
+ className: "pb-2",
124
+ children: /* @__PURE__ */ y("div", {
125
+ className: "flex items-center justify-between gap-2",
126
+ children: [/* @__PURE__ */ y(w, {
127
+ className: "text-base flex items-center gap-2",
128
+ children: [/* @__PURE__ */ v(m, { className: "h-4 w-4 text-primary" }), e.name]
129
+ }), /* @__PURE__ */ v("span", {
130
+ className: "text-xs px-2 py-0.5 rounded-full bg-muted capitalize",
131
+ children: s(e.status)
132
+ })]
133
+ })
134
+ }), /* @__PURE__ */ y(S, {
135
+ className: "space-y-3",
136
+ children: [
137
+ /* @__PURE__ */ v("p", {
138
+ className: "text-xs text-muted-foreground",
139
+ children: e.project?.name ?? O("planmagnet.budgets.noProject", "No project")
140
+ }),
141
+ /* @__PURE__ */ y("div", {
142
+ className: "flex justify-between text-sm",
143
+ children: [/* @__PURE__ */ v("span", {
144
+ className: "text-muted-foreground",
145
+ children: O("planmagnet.budgets.spent", "Spent")
146
+ }), /* @__PURE__ */ y("span", {
147
+ className: "tabular-nums",
148
+ children: [
149
+ a(e.spentAmount, e.currency),
150
+ " /",
151
+ " ",
152
+ a(e.plannedAmount, e.currency)
153
+ ]
154
+ })]
155
+ }),
156
+ /* @__PURE__ */ v("div", {
157
+ role: "meter",
158
+ "aria-valuemin": 0,
159
+ "aria-valuemax": Math.max(100, u ?? 0),
160
+ "aria-valuenow": u ?? 0,
161
+ "aria-label": `${e.name}: ${u === null ? "no plan" : `${u}%`} spent`,
162
+ className: `h-2 w-full overflow-hidden rounded-full ${o.track}`,
163
+ children: /* @__PURE__ */ v("div", {
164
+ className: `h-full rounded-full ${o.fill}`,
165
+ style: { width: `${Math.min(u ?? 0, 100)}%` }
166
+ })
167
+ }),
168
+ /* @__PURE__ */ y("span", {
169
+ className: `flex items-center gap-1 text-xs ${o.text}`,
170
+ children: [/* @__PURE__ */ v(o.icon, {
171
+ className: "h-3 w-3",
172
+ "aria-hidden": !0
173
+ }), u === null ? O("planmagnet.budgets.noPlan", "No plan set") : `${u}%`]
174
+ })
175
+ ]
176
+ })]
177
+ }, e.id);
178
+ })
179
+ })
180
+ ]
181
+ });
182
+ }
183
+ //#endregion
184
+ export { O as ProjectsBudgetsPagePage };
185
+
186
+ //# sourceMappingURL=ProjectsBudgetsPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsBudgetsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsBudgetsPagePage.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Wallet } from 'lucide-react';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ProjectSelect } from '../controls/Pickers';\nimport { CreateBudgetDialog, BUDGET_STATUSES } from '../budgets/BudgetControls';\nimport { budgetState, budgetUsage, BUDGET_STATE_STYLE, money } from '../budgets/budgetMath';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath } from '../utils/rowNavigation';\n\n/**\n * Budgets (BOFF-8047). The service and the read side (spent-vs-planned meter on Analytics,\n * `analytics/DeliveryHealthCards.tsx`) have existed since 2026-09-24; this page is the first place a budget\n * can be created or edited, closing the `api-only` gap in `planmagnet-specs/CAPABILITY_REGISTER.md`.\n */\n\nconst BUDGETS = gql`\n query BudgetsList(\n $workspaceId: ID!\n $filter: PlanmagnetBudgetFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetBudgets(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n name\n status\n plannedAmount\n spentAmount\n currency\n projectId\n project {\n id\n name\n }\n }\n totalCount\n }\n }\n`;\n\ninterface BudgetRow {\n id: string;\n name: string;\n status: string;\n plannedAmount: number;\n spentAmount: number;\n currency: string;\n projectId: string;\n project: { id: string; name: string } | null;\n}\n\ninterface BudgetsData {\n planmagnetBudgets: { items: BudgetRow[]; totalCount: number };\n}\n\nconst STATUS_TABS = ['ALL', ...BUDGET_STATUSES] as const;\n\nexport function ProjectsBudgetsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const { t } = useI18n();\n const [params] = useSearchParams();\n // Entry point from a project's own page (\"View budgets\"): preset, but still changeable here.\n const [projectId, setProjectId] = useState(params.get('projectId') ?? '');\n const [status, setStatus] = useState<(typeof STATUS_TABS)[number]>('ALL');\n const [search, setSearch] = useState('');\n const term = useDeferredValue(search.trim());\n\n const filter = {\n ...(projectId ? { projectId } : {}),\n ...(status !== 'ALL' ? { status } : {}),\n ...(term ? { search: term } : {}),\n };\n const { data, loading, error } = useQuery<BudgetsData>(BUDGETS, {\n variables: { workspaceId: workspaceId ?? '', filter, pagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n const budgets = data?.planmagnetBudgets.items ?? [];\n const totalCount = data?.planmagnetBudgets.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{t('planmagnet.budgets.title', 'Budgets')}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {data\n ? t('planmagnet.budgets.subtitle', '{{count}} budget(s) across projects', {\n count: totalCount,\n })\n : t(\n 'planmagnet.budgets.subtitleFallback',\n 'Financial plans, tracked against actuals'\n )}\n </p>\n </div>\n <CreateBudgetDialog\n presetProjectId={projectId || undefined}\n onCreated={(id) => navigate(detailPath('/projects/budget-detail-page', id))}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-xs\">\n <ProjectSelect\n id=\"budgets-project-filter\"\n value={projectId}\n onChange={setProjectId}\n noneLabel={t('planmagnet.budgets.allProjects', 'All projects')}\n />\n </div>\n <Input\n aria-label={t('planmagnet.budgets.search', 'Search budgets')}\n placeholder={t('planmagnet.budgets.searchPlaceholder', 'Search by name or description')}\n className=\"w-full max-w-xs\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n <div\n className=\"flex flex-wrap gap-1\"\n role=\"group\"\n aria-label={t('planmagnet.budgets.statusFilter', 'Filter by status')}\n >\n {STATUS_TABS.map((s) => (\n <button\n key={s}\n type=\"button\"\n aria-pressed={status === s}\n onClick={() => setStatus(s)}\n className={`px-3 py-1.5 text-sm rounded-md ${status === s ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n {s === 'ALL' ? t('planmagnet.budgets.allStatuses', 'All') : enumLabel(s)}\n </button>\n ))}\n </div>\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {t('planmagnet.budgets.errorLoading', 'Budgets could not be loaded')}: {error.message}\n </p>\n )}\n {/* Only before the first load: a refetch (after create/edit/delete) must not blank the page. */}\n {loading && !data && (\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\n {data && budgets.length === 0 && !error && (\n <p className=\"text-center text-sm text-muted-foreground py-8\">\n {projectId || status !== 'ALL' || term\n ? t('planmagnet.budgets.emptyFiltered', 'No budgets match these filters.')\n : t('planmagnet.budgets.empty', 'No budgets yet. Create the first one.')}\n </p>\n )}\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {budgets.map((budget) => {\n const usage = budgetUsage(budget.plannedAmount, budget.spentAmount);\n const state = budgetState(usage);\n const style = BUDGET_STATE_STYLE[state];\n const percent = usage === null ? null : Math.round(usage * 100);\n return (\n <Card\n key={budget.id}\n {...rowLinkProps(\n () => navigate(detailPath('/projects/budget-detail-page', budget.id)),\n budget.name\n )}\n >\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Wallet className=\"h-4 w-4 text-primary\" />\n {budget.name}\n </CardTitle>\n <span className=\"text-xs px-2 py-0.5 rounded-full bg-muted capitalize\">\n {enumLabel(budget.status)}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-xs text-muted-foreground\">\n {budget.project?.name ?? t('planmagnet.budgets.noProject', 'No project')}\n </p>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {t('planmagnet.budgets.spent', 'Spent')}\n </span>\n <span className=\"tabular-nums\">\n {money(budget.spentAmount, budget.currency)} /{' '}\n {money(budget.plannedAmount, budget.currency)}\n </span>\n </div>\n <div\n role=\"meter\"\n aria-valuemin={0}\n aria-valuemax={Math.max(100, percent ?? 0)}\n aria-valuenow={percent ?? 0}\n aria-label={`${budget.name}: ${percent === null ? 'no plan' : `${percent}%`} spent`}\n className={`h-2 w-full overflow-hidden rounded-full ${style.track}`}\n >\n <div\n className={`h-full rounded-full ${style.fill}`}\n style={{ width: `${Math.min(percent ?? 0, 100)}%` }}\n />\n </div>\n <span className={`flex items-center gap-1 text-xs ${style.text}`}>\n <style.icon className=\"h-3 w-3\" aria-hidden={true} />\n {percent === null ? t('planmagnet.budgets.noPlan', 'No plan set') : `${percent}%`}\n </span>\n </CardContent>\n </Card>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProjectsCriticalPathPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProjectsCriticalPathPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsCriticalPathPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"names":[],"mappings":"AAwDA,wBAAgB,4BAA4B,4CAgL3C"}