@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,316 @@
1
+ import { throwOnError as e } from "../utils/mutationResult.js";
2
+ import { useEntityId as t } from "../hooks/useEntityId.js";
3
+ import { ErrorLine as n } from "../controls/ConfirmButton.js";
4
+ import { TEMPLATE_QUERIES as r, TemplateDeleteButton as i } from "../controls/TemplateControls.js";
5
+ import { useState as a } from "react";
6
+ import { LayoutTemplate as o, Save as s } from "lucide-react";
7
+ import { gql as c } from "@apollo/client";
8
+ import { useMutation as l, useQuery as u } from "@apollo/client/react";
9
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ import { Badge as p, Button as m, Card as h, CardContent as g, CardHeader as _, CardTitle as v, Input as y, Switch as b } from "@burdenoff/fe-libs/ui";
11
+ //#region src/planmagnet/pages/TemplatesEditTemplatePagePage.tsx
12
+ var x = c`
13
+ query GetTemplate($id: ID!) {
14
+ planmagnetProjectTemplate(id: $id) {
15
+ id
16
+ name
17
+ description
18
+ category
19
+ isBuiltIn
20
+ isPublished
21
+ tags
22
+ usageCount
23
+ configuration
24
+ boardConfig
25
+ sprintConfig
26
+ customFieldsConfig
27
+ }
28
+ }
29
+ `, S = c`
30
+ mutation UpdateProjectTemplate($id: ID!, $input: PlanmagnetUpdateProjectTemplateInput!) {
31
+ planmagnetUpdateProjectTemplate(id: $id, input: $input) {
32
+ id
33
+ name
34
+ description
35
+ category
36
+ isPublished
37
+ tags
38
+ configuration
39
+ boardConfig
40
+ sprintConfig
41
+ customFieldsConfig
42
+ }
43
+ }
44
+ `, C = (e) => e == null ? "" : JSON.stringify(e, null, 2);
45
+ function w(e) {
46
+ if (e.trim() === "") return {
47
+ ok: !0,
48
+ value: null
49
+ };
50
+ try {
51
+ return {
52
+ ok: !0,
53
+ value: JSON.parse(e)
54
+ };
55
+ } catch {
56
+ return {
57
+ ok: !1,
58
+ error: "Not valid JSON."
59
+ };
60
+ }
61
+ }
62
+ function T({ id: e, label: t, hint: r, value: i, onChange: a, error: o }) {
63
+ return /* @__PURE__ */ f("div", { children: [
64
+ /* @__PURE__ */ d("label", {
65
+ htmlFor: e,
66
+ className: "text-sm font-medium",
67
+ children: t
68
+ }),
69
+ /* @__PURE__ */ d("p", {
70
+ className: "text-xs text-muted-foreground",
71
+ children: r
72
+ }),
73
+ /* @__PURE__ */ d("textarea", {
74
+ id: e,
75
+ spellCheck: !1,
76
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs min-h-[100px] disabled:opacity-60",
77
+ value: i,
78
+ onChange: (e) => a(e.target.value),
79
+ "aria-invalid": !!o
80
+ }),
81
+ o && /* @__PURE__ */ d(n, { message: o })
82
+ ] });
83
+ }
84
+ function E() {
85
+ let c = t(), { data: E, loading: D, error: O } = u(x, {
86
+ variables: { id: c ?? "" },
87
+ skip: !c,
88
+ fetchPolicy: "cache-and-network"
89
+ }), [k, { loading: A }] = l(S, { refetchQueries: r }), j = E?.planmagnetProjectTemplate ?? null, [M, N] = a(null), [P, F] = a(null), [I, L] = a(!1), [R, z] = a(null);
90
+ if (j && R !== j.id && (z(j.id), N({
91
+ name: j.name,
92
+ description: j.description ?? "",
93
+ category: j.category ?? "",
94
+ tags: j.tags.join(", "),
95
+ isPublished: j.isPublished,
96
+ configuration: C(j.configuration),
97
+ boardConfig: C(j.boardConfig),
98
+ sprintConfig: C(j.sprintConfig),
99
+ customFieldsConfig: C(j.customFieldsConfig)
100
+ })), !c) return /* @__PURE__ */ d("div", {
101
+ className: "p-6 text-muted-foreground",
102
+ children: "No template selected. Open one from the list."
103
+ });
104
+ if (D && !E) return /* @__PURE__ */ d("div", {
105
+ className: "flex items-center justify-center p-12",
106
+ children: /* @__PURE__ */ d("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
107
+ });
108
+ if (O) return /* @__PURE__ */ f("div", {
109
+ className: "p-6 text-destructive",
110
+ children: ["Template could not be loaded: ", O.message]
111
+ });
112
+ if (!j || !M) return /* @__PURE__ */ d("div", {
113
+ className: "p-6 text-muted-foreground",
114
+ children: "Template not found."
115
+ });
116
+ let B = w(M.configuration), V = w(M.boardConfig), H = w(M.sprintConfig), U = w(M.customFieldsConfig), W = [
117
+ B,
118
+ V,
119
+ H,
120
+ U
121
+ ].filter((e) => !e.ok).length, G = async () => {
122
+ if (F(null), L(!1), W > 0 || !B.ok || !V.ok || !H.ok || !U.ok) return;
123
+ let t = {};
124
+ M.name.trim() !== j.name && (t.name = M.name.trim()), M.description.trim() !== (j.description ?? "") && (t.description = M.description.trim() || null), M.category.trim() !== (j.category ?? "") && (t.category = M.category.trim() || null), M.isPublished !== j.isPublished && (t.isPublished = M.isPublished);
125
+ let n = M.tags.split(",").map((e) => e.trim()).filter(Boolean);
126
+ if (JSON.stringify(n) !== JSON.stringify(j.tags) && (t.tags = n), M.configuration !== C(j.configuration) && (t.configuration = B.value), M.boardConfig !== C(j.boardConfig) && (t.boardConfig = V.value), M.sprintConfig !== C(j.sprintConfig) && (t.sprintConfig = H.value), M.customFieldsConfig !== C(j.customFieldsConfig) && (t.customFieldsConfig = U.value), Object.keys(t).length === 0) {
127
+ L(!0);
128
+ return;
129
+ }
130
+ try {
131
+ await e(k({ variables: {
132
+ id: j.id,
133
+ input: t
134
+ } })), L(!0);
135
+ } catch (e) {
136
+ F(e instanceof Error ? e.message : "The template could not be saved.");
137
+ }
138
+ };
139
+ return /* @__PURE__ */ f("div", {
140
+ className: "space-y-6 p-6 max-w-3xl",
141
+ children: [
142
+ /* @__PURE__ */ f("div", {
143
+ className: "flex flex-wrap items-start justify-between gap-3",
144
+ children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ f("div", {
145
+ className: "flex items-center gap-2",
146
+ children: [/* @__PURE__ */ d("h1", {
147
+ className: "text-2xl font-semibold",
148
+ children: j.name
149
+ }), j.isBuiltIn && /* @__PURE__ */ d(p, {
150
+ variant: "secondary",
151
+ children: "Built-in"
152
+ })]
153
+ }), /* @__PURE__ */ f("p", {
154
+ className: "text-sm text-muted-foreground",
155
+ children: [
156
+ "Used ",
157
+ j.usageCount,
158
+ " time",
159
+ j.usageCount === 1 ? "" : "s"
160
+ ]
161
+ })] }), /* @__PURE__ */ d(i, {
162
+ templateId: j.id,
163
+ name: j.name,
164
+ isBuiltIn: j.isBuiltIn
165
+ })]
166
+ }),
167
+ P && /* @__PURE__ */ d(n, { message: P }),
168
+ I && !P && /* @__PURE__ */ d("p", {
169
+ className: "text-sm text-muted-foreground",
170
+ children: "Saved."
171
+ }),
172
+ /* @__PURE__ */ f(h, { children: [/* @__PURE__ */ d(_, { children: /* @__PURE__ */ f(v, {
173
+ className: "flex items-center gap-2",
174
+ children: [/* @__PURE__ */ d(o, { className: "h-4 w-4" }), "Template details"]
175
+ }) }), /* @__PURE__ */ f(g, {
176
+ className: "space-y-4",
177
+ children: [
178
+ /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
179
+ htmlFor: "template-name",
180
+ className: "text-sm font-medium",
181
+ children: "Name"
182
+ }), /* @__PURE__ */ d(y, {
183
+ id: "template-name",
184
+ className: "mt-1",
185
+ value: M.name,
186
+ onChange: (e) => N({
187
+ ...M,
188
+ name: e.target.value
189
+ })
190
+ })] }),
191
+ /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
192
+ htmlFor: "template-description",
193
+ className: "text-sm font-medium",
194
+ children: "Description"
195
+ }), /* @__PURE__ */ d("textarea", {
196
+ id: "template-description",
197
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]",
198
+ value: M.description,
199
+ onChange: (e) => N({
200
+ ...M,
201
+ description: e.target.value
202
+ })
203
+ })] }),
204
+ /* @__PURE__ */ f("div", {
205
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
206
+ children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
207
+ htmlFor: "template-category",
208
+ className: "text-sm font-medium",
209
+ children: "Category"
210
+ }), /* @__PURE__ */ d(y, {
211
+ id: "template-category",
212
+ className: "mt-1",
213
+ value: M.category,
214
+ onChange: (e) => N({
215
+ ...M,
216
+ category: e.target.value
217
+ }),
218
+ placeholder: "e.g. Engineering"
219
+ })] }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
220
+ htmlFor: "template-tags",
221
+ className: "text-sm font-medium",
222
+ children: "Tags (comma-separated)"
223
+ }), /* @__PURE__ */ d(y, {
224
+ id: "template-tags",
225
+ className: "mt-1",
226
+ value: M.tags,
227
+ onChange: (e) => N({
228
+ ...M,
229
+ tags: e.target.value
230
+ }),
231
+ placeholder: "agile, web"
232
+ })] })]
233
+ }),
234
+ /* @__PURE__ */ f("div", {
235
+ className: "flex items-center gap-2",
236
+ children: [/* @__PURE__ */ d(b, {
237
+ id: "template-published",
238
+ checked: M.isPublished,
239
+ onCheckedChange: (e) => N({
240
+ ...M,
241
+ isPublished: e
242
+ })
243
+ }), /* @__PURE__ */ d("label", {
244
+ htmlFor: "template-published",
245
+ className: "text-sm",
246
+ children: "Published — available to start new projects from"
247
+ })]
248
+ })
249
+ ]
250
+ })] }),
251
+ /* @__PURE__ */ f(h, { children: [/* @__PURE__ */ d(_, { children: /* @__PURE__ */ d(v, { children: "Advanced configuration" }) }), /* @__PURE__ */ f(g, {
252
+ className: "space-y-4",
253
+ children: [
254
+ /* @__PURE__ */ d(T, {
255
+ id: "template-configuration",
256
+ label: "Configuration",
257
+ hint: "Project settings and work item types applied when this template is used.",
258
+ value: M.configuration,
259
+ onChange: (e) => N({
260
+ ...M,
261
+ configuration: e
262
+ }),
263
+ error: B.ok ? null : B.error
264
+ }),
265
+ /* @__PURE__ */ d(T, {
266
+ id: "template-board-config",
267
+ label: "Board layout",
268
+ hint: "Optional. Leave empty to clear.",
269
+ value: M.boardConfig,
270
+ onChange: (e) => N({
271
+ ...M,
272
+ boardConfig: e
273
+ }),
274
+ error: V.ok ? null : V.error
275
+ }),
276
+ /* @__PURE__ */ d(T, {
277
+ id: "template-sprint-config",
278
+ label: "Default sprint configuration",
279
+ hint: "Optional. Leave empty to clear.",
280
+ value: M.sprintConfig,
281
+ onChange: (e) => N({
282
+ ...M,
283
+ sprintConfig: e
284
+ }),
285
+ error: H.ok ? null : H.error
286
+ }),
287
+ /* @__PURE__ */ d(T, {
288
+ id: "template-custom-fields-config",
289
+ label: "Custom field definitions",
290
+ hint: "Optional. Leave empty to clear.",
291
+ value: M.customFieldsConfig,
292
+ onChange: (e) => N({
293
+ ...M,
294
+ customFieldsConfig: e
295
+ }),
296
+ error: U.ok ? null : U.error
297
+ })
298
+ ]
299
+ })] }),
300
+ /* @__PURE__ */ d("div", {
301
+ className: "flex justify-end",
302
+ children: /* @__PURE__ */ f(m, {
303
+ size: "sm",
304
+ onClick: () => void G(),
305
+ disabled: A || W > 0,
306
+ "data-testid": "template-save",
307
+ children: [/* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }), A ? "Saving…" : "Save changes"]
308
+ })
309
+ })
310
+ ]
311
+ });
312
+ }
313
+ //#endregion
314
+ export { E as TemplatesEditTemplatePagePage };
315
+
316
+ //# sourceMappingURL=TemplatesEditTemplatePagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplatesEditTemplatePagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TemplatesEditTemplatePagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n Switch,\n} from '@burdenoff/fe-libs/ui';\nimport { LayoutTemplate, Save } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { TEMPLATE_QUERIES, TemplateDeleteButton } from '../controls/TemplateControls';\n\nconst GET_TEMPLATE = gql`\n query GetTemplate($id: ID!) {\n planmagnetProjectTemplate(id: $id) {\n id\n name\n description\n category\n isBuiltIn\n isPublished\n tags\n usageCount\n configuration\n boardConfig\n sprintConfig\n customFieldsConfig\n }\n }\n`;\n\nconst UPDATE = gql`\n mutation UpdateProjectTemplate($id: ID!, $input: PlanmagnetUpdateProjectTemplateInput!) {\n planmagnetUpdateProjectTemplate(id: $id, input: $input) {\n id\n name\n description\n category\n isPublished\n tags\n configuration\n boardConfig\n sprintConfig\n customFieldsConfig\n }\n }\n`;\n\ninterface TemplateDetail {\n id: string;\n name: string;\n description: string | null;\n category: string | null;\n isBuiltIn: boolean;\n isPublished: boolean;\n tags: string[];\n usageCount: number;\n configuration: unknown;\n boardConfig: unknown;\n sprintConfig: unknown;\n customFieldsConfig: unknown;\n}\n\nconst jsonText = (value: unknown) => (value == null ? '' : JSON.stringify(value, null, 2));\n\n/** Parses one JSON textarea's raw text. Empty means \"clear this field\" (null); invalid text is reported, not sent. */\nfunction parseJsonField(raw: string): { ok: true; value: unknown } | { ok: false; error: string } {\n if (raw.trim() === '') return { ok: true, value: null };\n try {\n return { ok: true, value: JSON.parse(raw) };\n } catch {\n return { ok: false, error: 'Not valid JSON.' };\n }\n}\n\nfunction JsonField({\n id,\n label,\n hint,\n value,\n onChange,\n error,\n}: {\n id: string;\n label: string;\n hint: string;\n value: string;\n onChange: (raw: string) => void;\n error: string | null;\n}) {\n return (\n <div>\n <label htmlFor={id} className=\"text-sm font-medium\">\n {label}\n </label>\n <p className=\"text-xs text-muted-foreground\">{hint}</p>\n <textarea\n id={id}\n spellCheck={false}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs min-h-[100px] disabled:opacity-60\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n aria-invalid={!!error}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nexport function TemplatesEditTemplatePagePage() {\n const templateId = useEntityId();\n const { data, loading, error } = useQuery<{ planmagnetProjectTemplate: TemplateDetail | null }>(\n GET_TEMPLATE,\n {\n variables: { id: templateId ?? '' },\n skip: !templateId,\n fetchPolicy: 'cache-and-network',\n }\n );\n const [update, { loading: saving }] = useMutation(UPDATE, { refetchQueries: TEMPLATE_QUERIES });\n const template = data?.planmagnetProjectTemplate ?? null;\n\n const [form, setForm] = useState<{\n name: string;\n description: string;\n category: string;\n tags: string;\n isPublished: boolean;\n configuration: string;\n boardConfig: string;\n sprintConfig: string;\n customFieldsConfig: string;\n } | null>(null);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n const [seededFor, setSeededFor] = useState<string | null>(null);\n\n // Seeds the form the first time the template arrives, adjusted during render (React's documented pattern for\n // deriving state from a query change) rather than in an effect — a later refetch (after Save) must not stomp\n // in-progress edits, and this takes effect on the very render the template shows up.\n if (template && seededFor !== template.id) {\n setSeededFor(template.id);\n setForm({\n name: template.name,\n description: template.description ?? '',\n category: template.category ?? '',\n tags: template.tags.join(', '),\n isPublished: template.isPublished,\n configuration: jsonText(template.configuration),\n boardConfig: jsonText(template.boardConfig),\n sprintConfig: jsonText(template.sprintConfig),\n customFieldsConfig: jsonText(template.customFieldsConfig),\n });\n }\n\n if (!templateId)\n return (\n <div className=\"p-6 text-muted-foreground\">No template selected. Open one from the list.</div>\n );\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 (\n <div className=\"p-6 text-destructive\">Template could not be loaded: {error.message}</div>\n );\n if (!template || !form)\n return <div className=\"p-6 text-muted-foreground\">Template not found.</div>;\n\n const configuration = parseJsonField(form.configuration);\n const boardConfig = parseJsonField(form.boardConfig);\n const sprintConfig = parseJsonField(form.sprintConfig);\n const customFieldsConfig = parseJsonField(form.customFieldsConfig);\n const jsonErrors = [configuration, boardConfig, sprintConfig, customFieldsConfig].filter(\n (r) => !r.ok\n ).length;\n\n const handleSave = async () => {\n setSaveError(null);\n setSaved(false);\n if (\n jsonErrors > 0 ||\n !configuration.ok ||\n !boardConfig.ok ||\n !sprintConfig.ok ||\n !customFieldsConfig.ok\n )\n return;\n\n const input: Record<string, unknown> = {};\n if (form.name.trim() !== template.name) input.name = form.name.trim();\n if (form.description.trim() !== (template.description ?? ''))\n input.description = form.description.trim() || null;\n if (form.category.trim() !== (template.category ?? ''))\n input.category = form.category.trim() || null;\n if (form.isPublished !== template.isPublished) input.isPublished = form.isPublished;\n const tags = form.tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n if (JSON.stringify(tags) !== JSON.stringify(template.tags)) input.tags = tags;\n if (form.configuration !== jsonText(template.configuration))\n input.configuration = configuration.value;\n if (form.boardConfig !== jsonText(template.boardConfig)) input.boardConfig = boardConfig.value;\n if (form.sprintConfig !== jsonText(template.sprintConfig))\n input.sprintConfig = sprintConfig.value;\n if (form.customFieldsConfig !== jsonText(template.customFieldsConfig))\n input.customFieldsConfig = customFieldsConfig.value;\n\n if (Object.keys(input).length === 0) {\n setSaved(true);\n return;\n }\n try {\n await throwOnError(update({ variables: { id: template.id, input } }));\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The template could not be saved.');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-3xl\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <div className=\"flex items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">{template.name}</h1>\n {template.isBuiltIn && <Badge variant=\"secondary\">Built-in</Badge>}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n Used {template.usageCount} time{template.usageCount === 1 ? '' : 's'}\n </p>\n </div>\n <TemplateDeleteButton\n templateId={template.id}\n name={template.name}\n isBuiltIn={template.isBuiltIn}\n />\n </div>\n\n {saveError && <ErrorLine message={saveError} />}\n {saved && !saveError && <p className=\"text-sm text-muted-foreground\">Saved.</p>}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <LayoutTemplate className=\"h-4 w-4\" />\n Template details\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"template-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"template-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=\"template-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"template-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"template-category\" className=\"text-sm font-medium\">\n Category\n </label>\n <Input\n id=\"template-category\"\n className=\"mt-1\"\n value={form.category}\n onChange={(e) => setForm({ ...form, category: e.target.value })}\n placeholder=\"e.g. Engineering\"\n />\n </div>\n <div>\n <label htmlFor=\"template-tags\" className=\"text-sm font-medium\">\n Tags (comma-separated)\n </label>\n <Input\n id=\"template-tags\"\n className=\"mt-1\"\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n placeholder=\"agile, web\"\n />\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Switch\n id=\"template-published\"\n checked={form.isPublished}\n onCheckedChange={(checked) => setForm({ ...form, isPublished: checked })}\n />\n <label htmlFor=\"template-published\" className=\"text-sm\">\n Published — available to start new projects from\n </label>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Advanced configuration</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <JsonField\n id=\"template-configuration\"\n label=\"Configuration\"\n hint=\"Project settings and work item types applied when this template is used.\"\n value={form.configuration}\n onChange={(v) => setForm({ ...form, configuration: v })}\n error={configuration.ok ? null : configuration.error}\n />\n <JsonField\n id=\"template-board-config\"\n label=\"Board layout\"\n hint=\"Optional. Leave empty to clear.\"\n value={form.boardConfig}\n onChange={(v) => setForm({ ...form, boardConfig: v })}\n error={boardConfig.ok ? null : boardConfig.error}\n />\n <JsonField\n id=\"template-sprint-config\"\n label=\"Default sprint configuration\"\n hint=\"Optional. Leave empty to clear.\"\n value={form.sprintConfig}\n onChange={(v) => setForm({ ...form, sprintConfig: v })}\n error={sprintConfig.ok ? null : sprintConfig.error}\n />\n <JsonField\n id=\"template-custom-fields-config\"\n label=\"Custom field definitions\"\n hint=\"Optional. Leave empty to clear.\"\n value={form.customFieldsConfig}\n onChange={(v) => setForm({ ...form, customFieldsConfig: v })}\n error={customFieldsConfig.ok ? null : customFieldsConfig.error}\n />\n </CardContent>\n </Card>\n\n <div className=\"flex justify-end\">\n <Button\n size=\"sm\"\n onClick={() => void handleSave()}\n disabled={saving || jsonErrors > 0}\n data-testid=\"template-save\"\n >\n <Save className=\"mr-1 h-4 w-4\" />\n {saving ? 'Saving…' : 'Save changes'}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,IAAe,CAAG;;;;;;;;;;;;;;;;;GAmBlB,IAAS,CAAG;;;;;;;;;;;;;;;GAgCZ,KAAY,MAAoB,KAAS,OAAO,KAAK,KAAK,UAAU,GAAO,MAAM,EAAE;AAGzF,SAAS,EAAe,GAA0E;AAChG,KAAI,EAAI,MAAM,KAAK,GAAI,QAAO;EAAE,IAAI;EAAM,OAAO;EAAM;AACvD,KAAI;AACF,SAAO;GAAE,IAAI;GAAM,OAAO,KAAK,MAAM,EAAI;GAAE;SACrC;AACN,SAAO;GAAE,IAAI;GAAO,OAAO;GAAmB;;;AAIlD,SAAS,EAAU,EACjB,OACA,UACA,SACA,UACA,aACA,YAQC;AACD,QACE,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,SAAD;GAAO,SAAS;GAAI,WAAU;aAC3B;GACK,CAAA;EACR,kBAAC,KAAD;GAAG,WAAU;aAAiC;GAAS,CAAA;EACvD,kBAAC,YAAD;GACM;GACJ,YAAY;GACZ,WAAU;GACH;GACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;GACzC,gBAAc,CAAC,CAAC;GAChB,CAAA;EACD,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;EACnC,EAAA,CAAA;;AAIV,SAAgB,IAAgC;CAC9C,IAAM,IAAa,GAAa,EAC1B,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA;EACE,WAAW,EAAE,IAAI,KAAc,IAAI;EACnC,MAAM,CAAC;EACP,aAAa;EACd,CACF,EACK,CAAC,GAAQ,EAAE,SAAS,OAAY,EAAY,GAAQ,EAAE,gBAAgB,GAAkB,CAAC,EACzF,IAAW,GAAM,6BAA6B,MAE9C,CAAC,GAAM,KAAW,EAUd,KAAK,EACT,CAAC,GAAW,KAAgB,EAAwB,KAAK,EACzD,CAAC,GAAO,KAAY,EAAS,GAAM,EACnC,CAAC,GAAW,KAAgB,EAAwB,KAAK;AAoB/D,KAfI,KAAY,MAAc,EAAS,OACrC,EAAa,EAAS,GAAG,EACzB,EAAQ;EACN,MAAM,EAAS;EACf,aAAa,EAAS,eAAe;EACrC,UAAU,EAAS,YAAY;EAC/B,MAAM,EAAS,KAAK,KAAK,KAAK;EAC9B,aAAa,EAAS;EACtB,eAAe,EAAS,EAAS,cAAc;EAC/C,aAAa,EAAS,EAAS,YAAY;EAC3C,cAAc,EAAS,EAAS,aAAa;EAC7C,oBAAoB,EAAS,EAAS,mBAAmB;EAC1D,CAAC,GAGA,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAAmD,CAAA;AAElG,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;EAC9F,CAAA;AAEV,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAsC,kCAA+B,EAAM,QAAc;;AAE7F,KAAI,CAAC,KAAY,CAAC,EAChB,QAAO,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAAyB,CAAA;CAE7E,IAAM,IAAgB,EAAe,EAAK,cAAc,EAClD,IAAc,EAAe,EAAK,YAAY,EAC9C,IAAe,EAAe,EAAK,aAAa,EAChD,IAAqB,EAAe,EAAK,mBAAmB,EAC5D,IAAa;EAAC;EAAe;EAAa;EAAc;EAAmB,CAAC,QAC/E,MAAM,CAAC,EAAE,GACX,CAAC,QAEI,IAAa,YAAY;AAG7B,MAFA,EAAa,KAAK,EAClB,EAAS,GAAM,EAEb,IAAa,KACb,CAAC,EAAc,MACf,CAAC,EAAY,MACb,CAAC,EAAa,MACd,CAAC,EAAmB,GAEpB;EAEF,IAAM,IAAiC,EAAE;AAMzC,EALI,EAAK,KAAK,MAAM,KAAK,EAAS,SAAM,EAAM,OAAO,EAAK,KAAK,MAAM,GACjE,EAAK,YAAY,MAAM,MAAM,EAAS,eAAe,QACvD,EAAM,cAAc,EAAK,YAAY,MAAM,IAAI,OAC7C,EAAK,SAAS,MAAM,MAAM,EAAS,YAAY,QACjD,EAAM,WAAW,EAAK,SAAS,MAAM,IAAI,OACvC,EAAK,gBAAgB,EAAS,gBAAa,EAAM,cAAc,EAAK;EACxE,IAAM,IAAO,EAAK,KACf,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAUlB,MATI,KAAK,UAAU,EAAK,KAAK,KAAK,UAAU,EAAS,KAAK,KAAE,EAAM,OAAO,IACrE,EAAK,kBAAkB,EAAS,EAAS,cAAc,KACzD,EAAM,gBAAgB,EAAc,QAClC,EAAK,gBAAgB,EAAS,EAAS,YAAY,KAAE,EAAM,cAAc,EAAY,QACrF,EAAK,iBAAiB,EAAS,EAAS,aAAa,KACvD,EAAM,eAAe,EAAa,QAChC,EAAK,uBAAuB,EAAS,EAAS,mBAAmB,KACnE,EAAM,qBAAqB,EAAmB,QAE5C,OAAO,KAAK,EAAM,CAAC,WAAW,GAAG;AACnC,KAAS,GAAK;AACd;;AAEF,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW;IAAE,IAAI,EAAS;IAAI;IAAO,EAAE,CAAC,CAAC,EACrE,EAAS,GAAK;WACP,GAAG;AACV,KAAa,aAAa,QAAQ,EAAE,UAAU,mCAAmC;;;AAIrF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA0B,EAAS;MAAU,CAAA,EAC1D,EAAS,aAAa,kBAAC,GAAD;MAAO,SAAQ;gBAAY;MAAgB,CAAA,CAC9D;QACN,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA6C;MACrC,EAAS;MAAW;MAAM,EAAS,eAAe,IAAI,KAAK;MAC/D;OACA,EAAA,CAAA,EACN,kBAAC,GAAD;KACE,YAAY,EAAS;KACrB,MAAM,EAAS;KACf,WAAW,EAAS;KACpB,CAAA,CACE;;GAEL,KAAa,kBAAC,GAAD,EAAW,SAAS,GAAa,CAAA;GAC9C,KAAS,CAAC,KAAa,kBAAC,KAAD;IAAG,WAAU;cAAgC;IAAU,CAAA;GAE/E,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAW,WAAU;cAArB,CACE,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA,EAAA,mBAE5B;OACD,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAgB,WAAU;gBAAsB;MAEvD,CAAA,EACR,kBAAC,GAAD;MACE,IAAG;MACH,WAAU;MACV,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,MAAM,EAAE,OAAO;OAAO,CAAC;MAC3D,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAuB,WAAU;gBAAsB;MAE9D,CAAA,EACR,kBAAC,YAAD;MACE,IAAG;MACH,WAAU;MACV,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,aAAa,EAAE,OAAO;OAAO,CAAC;MAClE,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAoB,WAAU;iBAAsB;OAE3D,CAAA,EACR,kBAAC,GAAD;OACE,IAAG;OACH,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,UAAU,EAAE,OAAO;QAAO,CAAC;OAC/D,aAAY;OACZ,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAgB,WAAU;iBAAsB;OAEvD,CAAA,EACR,kBAAC,GAAD;OACE,IAAG;OACH,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,MAAM,EAAE,OAAO;QAAO,CAAC;OAC3D,aAAY;OACZ,CAAA,CACE,EAAA,CAAA,CACF;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,IAAG;OACH,SAAS,EAAK;OACd,kBAAkB,MAAY,EAAQ;QAAE,GAAG;QAAM,aAAa;QAAS,CAAC;OACxE,CAAA,EACF,kBAAC,SAAD;OAAO,SAAQ;OAAqB,WAAU;iBAAU;OAEhD,CAAA,CACJ;;KACM;MACT,EAAA,CAAA;GAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,0BAAkC,CAAA,EAClC,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,eAAe;OAAG,CAAC;MACvD,OAAO,EAAc,KAAK,OAAO,EAAc;MAC/C,CAAA;KACF,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,aAAa;OAAG,CAAC;MACrD,OAAO,EAAY,KAAK,OAAO,EAAY;MAC3C,CAAA;KACF,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,cAAc;OAAG,CAAC;MACtD,OAAO,EAAa,KAAK,OAAO,EAAa;MAC7C,CAAA;KACF,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,oBAAoB;OAAG,CAAC;MAC5D,OAAO,EAAmB,KAAK,OAAO,EAAmB;MACzD,CAAA;KACU;MACT,EAAA,CAAA;GAEP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,MAAK;KACL,eAAe,KAAK,GAAY;KAChC,UAAU,KAAU,IAAa;KACjC,eAAY;eAJd,CAME,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAChC,IAAS,YAAY,eACf;;IACL,CAAA;GACF"}
@@ -0,0 +1,2 @@
1
+ export declare function TemplatesTemplatesPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=TemplatesTemplatesPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplatesTemplatesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TemplatesTemplatesPagePage.tsx"],"names":[],"mappings":"AA2DA,wBAAgB,0BAA0B,4CAsLzC"}
@@ -0,0 +1,208 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { detailPath as n, stopRowActivation as r } from "../utils/rowNavigation.js";
3
+ import { ApplyTemplateDialog as i, NewTemplateFromProjectDialog as a } from "../create/CreateDialogs.js";
4
+ import { TemplateDeleteButton as o } from "../controls/TemplateControls.js";
5
+ import { useDeferredValue as s, useState as c } from "react";
6
+ import { LayoutTemplate as l, Pencil as u, Search as d } from "lucide-react";
7
+ import { gql as f } from "@apollo/client";
8
+ import { useQuery as p } from "@apollo/client/react";
9
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
+ import { Badge as g, Button as _, EmptyState as v, Input as y } from "@burdenoff/fe-libs/ui";
11
+ //#region src/planmagnet/pages/TemplatesTemplatesPagePage.tsx
12
+ var b = f`
13
+ query TemplatesList(
14
+ $workspaceId: ID!
15
+ $filter: PlanmagnetProjectTemplateFilter
16
+ $pagination: PlanmagnetPaginationInput
17
+ ) {
18
+ planmagnetProjectTemplates(
19
+ workspaceId: $workspaceId
20
+ filter: $filter
21
+ pagination: $pagination
22
+ ) {
23
+ totalCount
24
+ pageInfo {
25
+ hasNextPage
26
+ endCursor
27
+ }
28
+ items {
29
+ id
30
+ name
31
+ description
32
+ category
33
+ isBuiltIn
34
+ isPublished
35
+ tags
36
+ usageCount
37
+ }
38
+ }
39
+ }
40
+ `, x = 50;
41
+ function S() {
42
+ let { workspaceId: f } = e(), S = t(), [C, w] = c(""), [T, E] = c(!1), [D, O] = c(!1), k = s(C.trim()), A = {
43
+ ...k ? { search: k } : {},
44
+ ...T ? { isPublished: !0 } : {}
45
+ }, { data: j, loading: M, error: N, fetchMore: P } = p(b, {
46
+ variables: {
47
+ workspaceId: f ?? "",
48
+ filter: A,
49
+ pagination: { first: x }
50
+ },
51
+ skip: !f,
52
+ fetchPolicy: "cache-and-network"
53
+ }), F = j?.planmagnetProjectTemplates, I = F?.items ?? [], L = async () => {
54
+ let e = F?.pageInfo.endCursor;
55
+ if (e) {
56
+ O(!0);
57
+ try {
58
+ await P({
59
+ variables: { pagination: {
60
+ first: x,
61
+ after: e
62
+ } },
63
+ updateQuery: (e, { fetchMoreResult: t }) => ({ planmagnetProjectTemplates: {
64
+ ...t.planmagnetProjectTemplates,
65
+ items: [...e.planmagnetProjectTemplates.items, ...t.planmagnetProjectTemplates.items]
66
+ } })
67
+ });
68
+ } finally {
69
+ O(!1);
70
+ }
71
+ }
72
+ };
73
+ return /* @__PURE__ */ h("div", {
74
+ className: "space-y-6 p-6",
75
+ "data-testid": "templates-page",
76
+ children: [
77
+ /* @__PURE__ */ h("div", {
78
+ className: "flex flex-wrap items-center justify-between gap-3",
79
+ children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h1", {
80
+ className: "text-2xl font-semibold",
81
+ children: "Project templates"
82
+ }), /* @__PURE__ */ m("p", {
83
+ className: "text-sm text-muted-foreground",
84
+ children: F ? `${F.totalCount} template${F.totalCount === 1 ? "" : "s"}` : "Reusable project configurations — capture one from a project, or start a project from one"
85
+ })] }), /* @__PURE__ */ m(a, { onCreated: (e) => S(n("/templates/edit-template-page", e)) })]
86
+ }),
87
+ /* @__PURE__ */ h("div", {
88
+ className: "flex flex-wrap items-center gap-3",
89
+ children: [/* @__PURE__ */ h("div", {
90
+ className: "relative w-full max-w-sm",
91
+ children: [/* @__PURE__ */ m(d, { className: "absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" }), /* @__PURE__ */ m(y, {
92
+ "aria-label": "Search templates",
93
+ placeholder: "Search by name or description",
94
+ className: "pl-8",
95
+ value: C,
96
+ onChange: (e) => w(e.target.value)
97
+ })]
98
+ }), /* @__PURE__ */ m("button", {
99
+ type: "button",
100
+ "aria-pressed": T,
101
+ onClick: () => E((e) => !e),
102
+ className: `px-3 py-1.5 text-sm rounded-md ${T ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
103
+ children: "Published only"
104
+ })]
105
+ }),
106
+ N && /* @__PURE__ */ h("p", {
107
+ role: "alert",
108
+ className: "text-sm text-destructive",
109
+ children: ["Templates could not be loaded: ", N.message]
110
+ }),
111
+ M && !j && /* @__PURE__ */ m("p", {
112
+ className: "text-sm text-muted-foreground",
113
+ children: "Loading templates…"
114
+ }),
115
+ F && I.length === 0 && /* @__PURE__ */ m(v, {
116
+ icon: /* @__PURE__ */ m(l, { className: "h-8 w-8 text-muted-foreground" }),
117
+ title: k || T ? "No templates match" : "No templates yet",
118
+ description: "Save an existing project's setup as a template, then start new projects from it in one step.",
119
+ action: k || T ? void 0 : /* @__PURE__ */ m(a, { onCreated: (e) => S(n("/templates/edit-template-page", e)) })
120
+ }),
121
+ /* @__PURE__ */ m("ul", {
122
+ className: "grid grid-cols-1 gap-3 md:grid-cols-2",
123
+ children: I.map((e) => /* @__PURE__ */ h("li", {
124
+ "data-testid": "template-row",
125
+ className: "flex flex-col gap-2 rounded-md border p-3",
126
+ children: [
127
+ /* @__PURE__ */ h("div", {
128
+ className: "flex items-start justify-between gap-2",
129
+ children: [/* @__PURE__ */ h("div", {
130
+ className: "min-w-0",
131
+ children: [/* @__PURE__ */ m("p", {
132
+ className: "truncate text-sm font-medium",
133
+ children: e.name
134
+ }), /* @__PURE__ */ m("p", {
135
+ className: "truncate text-xs text-muted-foreground",
136
+ children: e.description ?? "No description"
137
+ })]
138
+ }), /* @__PURE__ */ h("div", {
139
+ className: "flex shrink-0 gap-1",
140
+ children: [e.isBuiltIn && /* @__PURE__ */ m(g, {
141
+ variant: "secondary",
142
+ children: "Built-in"
143
+ }), /* @__PURE__ */ m(g, {
144
+ variant: e.isPublished ? "success" : "outline",
145
+ children: e.isPublished ? "Published" : "Draft"
146
+ })]
147
+ })]
148
+ }),
149
+ /* @__PURE__ */ h("div", {
150
+ className: "flex flex-wrap items-center gap-1",
151
+ children: [e.category && /* @__PURE__ */ m(g, {
152
+ variant: "outline",
153
+ className: "text-xs",
154
+ children: e.category
155
+ }), e.tags.map((e) => /* @__PURE__ */ m(g, {
156
+ variant: "outline",
157
+ className: "text-xs",
158
+ children: e
159
+ }, e))]
160
+ }),
161
+ /* @__PURE__ */ h("p", {
162
+ className: "text-xs text-muted-foreground",
163
+ children: [
164
+ "Used ",
165
+ e.usageCount,
166
+ " time",
167
+ e.usageCount === 1 ? "" : "s"
168
+ ]
169
+ }),
170
+ /* @__PURE__ */ h("div", {
171
+ className: "flex items-center justify-between gap-2 border-t pt-2",
172
+ onClick: r,
173
+ children: [/* @__PURE__ */ h("div", {
174
+ className: "flex gap-2",
175
+ children: [/* @__PURE__ */ h(_, {
176
+ size: "sm",
177
+ variant: "outline",
178
+ onClick: () => S(n("/templates/edit-template-page", e.id)),
179
+ "data-testid": "template-edit",
180
+ children: [/* @__PURE__ */ m(u, { className: "mr-1 h-4 w-4" }), "Edit"]
181
+ }), /* @__PURE__ */ m(o, {
182
+ templateId: e.id,
183
+ name: e.name,
184
+ isBuiltIn: e.isBuiltIn
185
+ })]
186
+ }), /* @__PURE__ */ m(i, {
187
+ templateId: e.id,
188
+ templateName: e.name,
189
+ onApplied: (e) => S(n("/projects/project-detail-page", e))
190
+ })]
191
+ })
192
+ ]
193
+ }, e.id))
194
+ }),
195
+ F?.pageInfo.hasNextPage && /* @__PURE__ */ m(_, {
196
+ size: "sm",
197
+ variant: "outline",
198
+ onClick: () => void L(),
199
+ disabled: D,
200
+ children: D ? "Loading…" : `Show more (${I.length} of ${F.totalCount})`
201
+ })
202
+ ]
203
+ });
204
+ }
205
+ //#endregion
206
+ export { S as TemplatesTemplatesPagePage };
207
+
208
+ //# sourceMappingURL=TemplatesTemplatesPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplatesTemplatesPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TemplatesTemplatesPagePage.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge, Button, EmptyState, Input } from '@burdenoff/fe-libs/ui';\nimport { LayoutTemplate, Pencil, Search } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ApplyTemplateDialog, NewTemplateFromProjectDialog } from '../create/CreateDialogs';\nimport { TemplateDeleteButton } from '../controls/TemplateControls';\nimport { detailPath, stopRowActivation } from '../utils/rowNavigation';\n\n/**\n * Project templates browse surface (BOFF-8047, capability register \"Project templates\": model/API/authz were\n * already real and prod-verified — `planmagnetProjectTemplates`/Create/Update/Delete/Apply/CreateFromProject —\n * gate 3 (UI) was the only gap; \"No UI anywhere\" until now.\n */\n\nconst TEMPLATES = gql`\n query TemplatesList(\n $workspaceId: ID!\n $filter: PlanmagnetProjectTemplateFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetProjectTemplates(\n workspaceId: $workspaceId\n filter: $filter\n pagination: $pagination\n ) {\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n name\n description\n category\n isBuiltIn\n isPublished\n tags\n usageCount\n }\n }\n }\n`;\n\ninterface TemplateRow {\n id: string;\n name: string;\n description: string | null;\n category: string | null;\n isBuiltIn: boolean;\n isPublished: boolean;\n tags: string[];\n usageCount: number;\n}\n\nconst PAGE = 50;\n\nexport function TemplatesTemplatesPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [search, setSearch] = useState('');\n const [publishedOnly, setPublishedOnly] = useState(false);\n const [loadingMore, setLoadingMore] = useState(false);\n const term = useDeferredValue(search.trim());\n\n const filter = {\n ...(term ? { search: term } : {}),\n ...(publishedOnly ? { isPublished: true } : {}),\n };\n\n const { data, loading, error, fetchMore } = useQuery<{\n planmagnetProjectTemplates: {\n totalCount: number;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n items: TemplateRow[];\n };\n }>(TEMPLATES, {\n variables: { workspaceId: workspaceId ?? '', filter, pagination: { first: PAGE } },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const list = data?.planmagnetProjectTemplates;\n const templates = list?.items ?? [];\n\n const loadMore = async () => {\n const after = list?.pageInfo.endCursor;\n if (!after) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { first: PAGE, after } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetProjectTemplates: {\n ...fetchMoreResult.planmagnetProjectTemplates,\n items: [\n ...prev.planmagnetProjectTemplates.items,\n ...fetchMoreResult.planmagnetProjectTemplates.items,\n ],\n },\n }),\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"templates-page\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Project templates</h1>\n <p className=\"text-sm text-muted-foreground\">\n {list\n ? `${list.totalCount} template${list.totalCount === 1 ? '' : 's'}`\n : 'Reusable project configurations — capture one from a project, or start a project from one'}\n </p>\n </div>\n <NewTemplateFromProjectDialog\n onCreated={(id) => navigate(detailPath('/templates/edit-template-page', id))}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"relative w-full max-w-sm\">\n <Search className=\"absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground\" />\n <Input\n aria-label=\"Search templates\"\n placeholder=\"Search by name or description\"\n className=\"pl-8\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n <button\n type=\"button\"\n aria-pressed={publishedOnly}\n onClick={() => setPublishedOnly((v) => !v)}\n className={`px-3 py-1.5 text-sm rounded-md ${publishedOnly ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n Published only\n </button>\n </div>\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n Templates could not be loaded: {error.message}\n </p>\n )}\n {loading && !data && <p className=\"text-sm text-muted-foreground\">Loading templates…</p>}\n {list && templates.length === 0 && (\n <EmptyState\n icon={<LayoutTemplate className=\"h-8 w-8 text-muted-foreground\" />}\n title={term || publishedOnly ? 'No templates match' : 'No templates yet'}\n description=\"Save an existing project's setup as a template, then start new projects from it in one step.\"\n action={\n !(term || publishedOnly) ? (\n <NewTemplateFromProjectDialog\n onCreated={(id) => navigate(detailPath('/templates/edit-template-page', id))}\n />\n ) : undefined\n }\n />\n )}\n\n <ul className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {templates.map((template) => (\n <li\n key={template.id}\n data-testid=\"template-row\"\n className=\"flex flex-col gap-2 rounded-md border p-3\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium\">{template.name}</p>\n <p className=\"truncate text-xs text-muted-foreground\">\n {template.description ?? 'No description'}\n </p>\n </div>\n <div className=\"flex shrink-0 gap-1\">\n {template.isBuiltIn && <Badge variant=\"secondary\">Built-in</Badge>}\n <Badge variant={template.isPublished ? 'success' : 'outline'}>\n {template.isPublished ? 'Published' : 'Draft'}\n </Badge>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-1\">\n {template.category && (\n <Badge variant=\"outline\" className=\"text-xs\">\n {template.category}\n </Badge>\n )}\n {template.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n Used {template.usageCount} time{template.usageCount === 1 ? '' : 's'}\n </p>\n <div\n className=\"flex items-center justify-between gap-2 border-t pt-2\"\n onClick={stopRowActivation}\n >\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => navigate(detailPath('/templates/edit-template-page', template.id))}\n data-testid=\"template-edit\"\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <TemplateDeleteButton\n templateId={template.id}\n name={template.name}\n isBuiltIn={template.isBuiltIn}\n />\n </div>\n <ApplyTemplateDialog\n templateId={template.id}\n templateName={template.name}\n onApplied={(projectId) =>\n navigate(detailPath('/projects/project-detail-page', projectId))\n }\n />\n </div>\n </li>\n ))}\n </ul>\n {list?.pageInfo.hasNextPage && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => void loadMore()} disabled={loadingMore}>\n {loadingMore ? 'Loading…' : `Show more (${templates.length} of ${list.totalCount})`}\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -4,12 +4,7 @@ export declare function CycleTimeCard({ workspaceId, projectId, }: {
4
4
  workspaceId?: string | null;
5
5
  projectId: string;
6
6
  }): import("react/jsx-runtime").JSX.Element;
7
- type BudgetState = 'no-plan' | 'on-track' | 'near-limit' | 'over-budget';
8
- /** Share of the plan spent; `null` when there is no plan to measure against. */
9
- export declare function budgetUsage(planned: number, spent: number): number | null;
10
- export declare function budgetState(usage: number | null): BudgetState;
11
7
  export declare function BudgetPerformanceCard({ workspaceId }: {
12
8
  workspaceId?: string | null;
13
9
  }): import("react/jsx-runtime").JSX.Element;
14
- export {};
15
10
  //# sourceMappingURL=DeliveryHealthCards.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DeliveryHealthCards.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryHealthCards.tsx"],"names":[],"mappings":"AA2FA,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKvE;AAYD,wBAAgB,aAAa,CAAC,EAC5B,WAAW,EACX,SAAS,GACV,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,2CAkGA;AAED,KAAK,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;AAEzE,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAM7D;AA6CD,wBAAgB,qBAAqB,CAAC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,2CA8HrF"}
1
+ {"version":3,"file":"DeliveryHealthCards.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryHealthCards.tsx"],"names":[],"mappings":"AA2FA,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKvE;AAYD,wBAAgB,aAAa,CAAC,EAC5B,WAAW,EACX,SAAS,GACV,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,2CAkGA;AAED,wBAAgB,qBAAqB,CAAC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,2CA8HrF"}