@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.
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +130 -97
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +256 -156
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts +5 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts.map +1 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.js +107 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.js.map +1 -0
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts +29 -0
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -0
- package/dist/planmagnet/automation/AutomationRuleFields.js +308 -0
- package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -0
- package/dist/planmagnet/automation/automationRuleTypes.d.ts +60 -0
- package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -0
- package/dist/planmagnet/automation/automationRuleTypes.js +113 -0
- package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -0
- package/dist/planmagnet/board/BulkActions.d.ts +11 -0
- package/dist/planmagnet/board/BulkActions.d.ts.map +1 -0
- package/dist/planmagnet/board/BulkActions.js +182 -0
- package/dist/planmagnet/board/BulkActions.js.map +1 -0
- package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.js +289 -254
- package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
- package/dist/planmagnet/budgets/BudgetControls.d.ts +30 -0
- package/dist/planmagnet/budgets/BudgetControls.d.ts.map +1 -0
- package/dist/planmagnet/budgets/BudgetControls.js +470 -0
- package/dist/planmagnet/budgets/BudgetControls.js.map +1 -0
- package/dist/planmagnet/budgets/ExpenseControls.d.ts +27 -0
- package/dist/planmagnet/budgets/ExpenseControls.d.ts.map +1 -0
- package/dist/planmagnet/budgets/ExpenseControls.js +366 -0
- package/dist/planmagnet/budgets/ExpenseControls.js.map +1 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts +5 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts.map +1 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.js +112 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.js.map +1 -0
- package/dist/planmagnet/budgets/budgetMath.d.ts +26 -0
- package/dist/planmagnet/budgets/budgetMath.d.ts.map +1 -0
- package/dist/planmagnet/budgets/budgetMath.js +49 -0
- package/dist/planmagnet/budgets/budgetMath.js.map +1 -0
- package/dist/planmagnet/controls/AutomationRuleControls.d.ts +23 -0
- package/dist/planmagnet/controls/AutomationRuleControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/AutomationRuleControls.js +172 -0
- package/dist/planmagnet/controls/AutomationRuleControls.js.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.js +15 -15
- package/dist/planmagnet/controls/CustomFieldControls.d.ts +42 -0
- package/dist/planmagnet/controls/CustomFieldControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CustomFieldControls.js +84 -0
- package/dist/planmagnet/controls/CustomFieldControls.js.map +1 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts +8 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts.map +1 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.js +315 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.js.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.js +54 -54
- package/dist/planmagnet/controls/ObjectiveControls.js +15 -15
- package/dist/planmagnet/controls/Pickers.d.ts +7 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +58 -20
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PortfolioControls.js +7 -7
- package/dist/planmagnet/controls/ProgramControls.js +19 -19
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +54 -47
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.js +7 -7
- package/dist/planmagnet/controls/RiskControls.d.ts +27 -0
- package/dist/planmagnet/controls/RiskControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/RiskControls.js +346 -0
- package/dist/planmagnet/controls/RiskControls.js.map +1 -0
- package/dist/planmagnet/controls/TemplateControls.d.ts +15 -0
- package/dist/planmagnet/controls/TemplateControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/TemplateControls.js +55 -0
- package/dist/planmagnet/controls/TemplateControls.js.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts +6 -2
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.js +9 -9
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
- package/dist/planmagnet/create/CreateDialogs.d.ts +20 -0
- package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
- package/dist/planmagnet/create/CreateDialogs.js +455 -116
- package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
- package/dist/planmagnet/dependency/DependencyPanel.d.ts +6 -0
- package/dist/planmagnet/dependency/DependencyPanel.d.ts.map +1 -0
- package/dist/planmagnet/dependency/DependencyPanel.js +310 -0
- package/dist/planmagnet/dependency/DependencyPanel.js.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +171 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +232 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +93 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js.map +1 -0
- package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -3
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +5 -5
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +9 -9
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +6 -6
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +4 -4
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +6 -6
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +5 -5
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js +315 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +186 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +164 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js +348 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +10 -10
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +20 -10
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +13 -8
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +76 -56
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +372 -203
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +81 -72
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.js +278 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js +191 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js.map +1 -0
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +168 -122
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +5 -5
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +161 -137
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +124 -113
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +263 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js +254 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.js +165 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TeamCreateTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamCreateTeamPagePage.js +288 -91
- package/dist/planmagnet/pages/TeamCreateTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +217 -0
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.js +211 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.js +118 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js +316 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js.map +1 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +208 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +0 -5
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +47 -89
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +24 -24
- package/dist/planmagnet/sprints/SprintControls.d.ts +15 -3
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +293 -190
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/dist/planmagnet/team/TeamControls.d.ts +31 -0
- package/dist/planmagnet/team/TeamControls.d.ts.map +1 -0
- package/dist/planmagnet/team/TeamControls.js +407 -0
- package/dist/planmagnet/team/TeamControls.js.map +1 -0
- package/dist/planmagnet/time/TaskTimePanel.d.ts +7 -0
- package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
- package/dist/planmagnet/time/TaskTimePanel.js +104 -0
- package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.js +338 -0
- package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.js +244 -0
- package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
- package/dist/planmagnet/time/timeShared.d.ts +29 -0
- package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
- package/dist/planmagnet/time/timeShared.js +34 -0
- package/dist/planmagnet/time/timeShared.js.map +1 -0
- package/dist/planmagnet/utils/rowNavigation.js +5 -2
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- 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 @@
|
|
|
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,
|
|
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"}
|