@burdenoff/microfe-planmagnet 2026.928.2 → 2026.928.4

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 (85) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +102 -84
  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 +103 -55
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/controls/CompetitorControls.d.ts +20 -0
  8. package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -0
  9. package/dist/planmagnet/controls/CompetitorControls.js +172 -0
  10. package/dist/planmagnet/controls/CompetitorControls.js.map +1 -0
  11. package/dist/planmagnet/controls/FeatureControls.js +29 -29
  12. package/dist/planmagnet/controls/FeatureTriage.js +11 -11
  13. package/dist/planmagnet/controls/ObjectiveControls.js +24 -24
  14. package/dist/planmagnet/controls/Pickers.d.ts +17 -2
  15. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  16. package/dist/planmagnet/controls/Pickers.js +100 -9
  17. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  18. package/dist/planmagnet/controls/PortfolioControls.d.ts +13 -0
  19. package/dist/planmagnet/controls/PortfolioControls.d.ts.map +1 -0
  20. package/dist/planmagnet/controls/PortfolioControls.js +114 -0
  21. package/dist/planmagnet/controls/PortfolioControls.js.map +1 -0
  22. package/dist/planmagnet/controls/ProgramControls.d.ts +25 -0
  23. package/dist/planmagnet/controls/ProgramControls.d.ts.map +1 -0
  24. package/dist/planmagnet/controls/ProgramControls.js +187 -0
  25. package/dist/planmagnet/controls/ProgramControls.js.map +1 -0
  26. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts +2 -0
  27. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -0
  28. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +127 -0
  29. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -0
  30. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts +2 -0
  31. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -0
  32. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +129 -0
  33. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -0
  34. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts +2 -0
  35. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -0
  36. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +132 -0
  37. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -0
  38. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts +2 -0
  39. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -0
  40. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +181 -0
  41. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -0
  42. package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts +2 -0
  43. package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts.map +1 -0
  44. package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js +242 -0
  45. package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js.map +1 -0
  46. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts +2 -0
  47. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts.map +1 -0
  48. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +302 -0
  49. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js.map +1 -0
  50. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts +2 -0
  51. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -0
  52. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +335 -0
  53. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -0
  54. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts +2 -0
  55. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts.map +1 -0
  56. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +154 -0
  57. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js.map +1 -0
  58. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  59. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +124 -138
  60. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  61. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts +2 -0
  62. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts.map +1 -0
  63. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +221 -0
  64. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js.map +1 -0
  65. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts +2 -0
  66. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts.map +1 -0
  67. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +279 -0
  68. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js.map +1 -0
  69. package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts +2 -0
  70. package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts.map +1 -0
  71. package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js +178 -0
  72. package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js.map +1 -0
  73. package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts +2 -0
  74. package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts.map +1 -0
  75. package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +157 -0
  76. package/dist/planmagnet/pages/ProgramsProgramsPagePage.js.map +1 -0
  77. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
  78. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +130 -103
  79. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
  80. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  81. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +156 -136
  82. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  83. package/dist/planmagnet/pages/TasksImportPagePage.js +5 -5
  84. package/dist/planmagnet/types/index.d.ts.map +1 -1
  85. package/package.json +1 -1
@@ -0,0 +1,221 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { throwOnError as n } from "../utils/mutationResult.js";
3
+ import { endBeforeStart as r } from "../utils/formValidation.js";
4
+ import { enumLabel as i } from "../controls/WorkItemControls.js";
5
+ import { PROGRAM_STATUSES as a } from "../controls/ProgramControls.js";
6
+ import { useState as o } from "react";
7
+ import { FolderKanban as s, Loader2 as c, Save as l, X as u } from "lucide-react";
8
+ import { gql as d } from "@apollo/client";
9
+ import { useMutation as f } from "@apollo/client/react";
10
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
11
+ import { Button as h, Card as g, CardContent as _, CardHeader as v, CardTitle as y, Input as b } from "@burdenoff/fe-libs/ui";
12
+ //#region src/planmagnet/pages/ProgramsCreateProgramPagePage.tsx
13
+ var x = d`
14
+ mutation CreateProgram($input: PlanmagnetCreateProgramInput!) {
15
+ planmagnetCreateProgram(input: $input) {
16
+ id
17
+ name
18
+ }
19
+ }
20
+ `;
21
+ function S() {
22
+ let { currentUser: d } = e(), S = t(), [C, w] = o({
23
+ name: "",
24
+ description: "",
25
+ status: "PLANNING",
26
+ startDate: "",
27
+ endDate: "",
28
+ budgetTotal: "",
29
+ currency: "",
30
+ tags: ""
31
+ }), [T, E] = o(null), [D, { loading: O }] = f(x, { refetchQueries: ["ListPrograms"] }), k = r(C.startDate, C.endDate), A = C.budgetTotal !== "" && Number(C.budgetTotal) < 0;
32
+ return /* @__PURE__ */ m("div", {
33
+ className: "space-y-6 p-6 max-w-4xl",
34
+ children: [
35
+ /* @__PURE__ */ m("div", {
36
+ className: "flex items-center justify-between",
37
+ children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h1", {
38
+ className: "text-2xl font-semibold",
39
+ children: "Create Program"
40
+ }), /* @__PURE__ */ p("p", {
41
+ className: "text-sm text-muted-foreground",
42
+ children: "Group related projects together for portfolio-level visibility and budget tracking."
43
+ })] }), /* @__PURE__ */ m("div", {
44
+ className: "flex gap-2",
45
+ children: [/* @__PURE__ */ m(h, {
46
+ variant: "outline",
47
+ size: "sm",
48
+ onClick: () => S("/products/programs-page"),
49
+ children: [/* @__PURE__ */ p(u, { className: "mr-1 h-4 w-4" }), "Cancel"]
50
+ }), /* @__PURE__ */ m(h, {
51
+ size: "sm",
52
+ onClick: async () => {
53
+ if (!(!C.name.trim() || k || A)) {
54
+ E(null);
55
+ try {
56
+ await n(D({ variables: { input: {
57
+ name: C.name.trim(),
58
+ description: C.description.trim() || void 0,
59
+ status: C.status,
60
+ startDate: C.startDate || void 0,
61
+ endDate: C.endDate || void 0,
62
+ ownerId: d?.id ?? "",
63
+ ...C.budgetTotal === "" ? {} : { budgetTotal: Number(C.budgetTotal) },
64
+ currency: C.currency.trim() || void 0,
65
+ tags: C.tags.split(",").map((e) => e.trim()).filter(Boolean)
66
+ } } })), S("/products/programs-page");
67
+ } catch (e) {
68
+ E(e instanceof Error ? e.message : "The program could not be created.");
69
+ }
70
+ }
71
+ },
72
+ disabled: O || !C.name.trim() || k || A,
73
+ children: [O ? /* @__PURE__ */ p(c, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ p(l, { className: "mr-1 h-4 w-4" }), "Create Program"]
74
+ })]
75
+ })]
76
+ }),
77
+ T && /* @__PURE__ */ m("div", {
78
+ role: "alert",
79
+ className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
80
+ children: ["Failed to create program: ", T]
81
+ }),
82
+ /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, {
83
+ className: "flex items-center gap-2",
84
+ children: [/* @__PURE__ */ p(s, { className: "h-4 w-4" }), "Program Details"]
85
+ }) }), /* @__PURE__ */ m(_, {
86
+ className: "space-y-4",
87
+ children: [
88
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
89
+ className: "text-sm font-medium",
90
+ children: "Name"
91
+ }), /* @__PURE__ */ p(b, {
92
+ value: C.name,
93
+ onChange: (e) => w({
94
+ ...C,
95
+ name: e.target.value
96
+ }),
97
+ className: "mt-1",
98
+ placeholder: "e.g. Platform Modernization"
99
+ })] }),
100
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
101
+ className: "text-sm font-medium",
102
+ children: "Description"
103
+ }), /* @__PURE__ */ p("textarea", {
104
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
105
+ value: C.description,
106
+ onChange: (e) => w({
107
+ ...C,
108
+ description: e.target.value
109
+ }),
110
+ placeholder: "Describe what this program covers…"
111
+ })] }),
112
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
113
+ htmlFor: "program-status",
114
+ className: "text-sm font-medium",
115
+ children: "Status"
116
+ }), /* @__PURE__ */ p("select", {
117
+ id: "program-status",
118
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
119
+ value: C.status,
120
+ onChange: (e) => w({
121
+ ...C,
122
+ status: e.target.value
123
+ }),
124
+ children: a.map((e) => /* @__PURE__ */ p("option", {
125
+ value: e,
126
+ children: i(e)
127
+ }, e))
128
+ })] }),
129
+ /* @__PURE__ */ m("div", {
130
+ className: "grid grid-cols-2 gap-4",
131
+ children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
132
+ htmlFor: "program-start",
133
+ className: "text-sm font-medium",
134
+ children: "Start Date"
135
+ }), /* @__PURE__ */ p(b, {
136
+ id: "program-start",
137
+ type: "date",
138
+ value: C.startDate,
139
+ max: C.endDate || void 0,
140
+ onChange: (e) => w({
141
+ ...C,
142
+ startDate: e.target.value
143
+ }),
144
+ className: "mt-1"
145
+ })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
146
+ htmlFor: "program-end",
147
+ className: "text-sm font-medium",
148
+ children: "End Date"
149
+ }), /* @__PURE__ */ p(b, {
150
+ id: "program-end",
151
+ type: "date",
152
+ value: C.endDate,
153
+ min: C.startDate || void 0,
154
+ onChange: (e) => w({
155
+ ...C,
156
+ endDate: e.target.value
157
+ }),
158
+ "aria-invalid": k,
159
+ className: "mt-1"
160
+ })] })]
161
+ }),
162
+ k && /* @__PURE__ */ p("p", {
163
+ role: "alert",
164
+ className: "text-sm text-destructive",
165
+ children: "The end date is before the start date."
166
+ }),
167
+ /* @__PURE__ */ m("div", {
168
+ className: "grid grid-cols-2 gap-4",
169
+ children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
170
+ className: "text-sm font-medium",
171
+ children: "Total Budget"
172
+ }), /* @__PURE__ */ p(b, {
173
+ type: "number",
174
+ min: "0",
175
+ value: C.budgetTotal,
176
+ onChange: (e) => w({
177
+ ...C,
178
+ budgetTotal: e.target.value
179
+ }),
180
+ className: "mt-1",
181
+ "aria-invalid": A,
182
+ placeholder: "0"
183
+ })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
184
+ className: "text-sm font-medium",
185
+ children: "Currency"
186
+ }), /* @__PURE__ */ p(b, {
187
+ value: C.currency,
188
+ onChange: (e) => w({
189
+ ...C,
190
+ currency: e.target.value
191
+ }),
192
+ className: "mt-1",
193
+ placeholder: "USD"
194
+ })] })]
195
+ }),
196
+ A && /* @__PURE__ */ p("p", {
197
+ role: "alert",
198
+ className: "text-sm text-destructive",
199
+ children: "The budget cannot be negative."
200
+ }),
201
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
202
+ className: "text-sm font-medium",
203
+ children: "Tags (comma-separated)"
204
+ }), /* @__PURE__ */ p(b, {
205
+ value: C.tags,
206
+ onChange: (e) => w({
207
+ ...C,
208
+ tags: e.target.value
209
+ }),
210
+ className: "mt-1",
211
+ placeholder: "e.g. strategic, infra"
212
+ })] })
213
+ ]
214
+ })] })
215
+ ]
216
+ });
217
+ }
218
+ //#endregion
219
+ export { S as ProgramsCreateProgramPagePage };
220
+
221
+ //# sourceMappingURL=ProgramsCreateProgramPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgramsCreateProgramPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProgramsCreateProgramPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, FolderKanban, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PROGRAM_STATUSES } from '../controls/ProgramControls';\n\nconst CREATE_PROGRAM = gql`\n mutation CreateProgram($input: PlanmagnetCreateProgramInput!) {\n planmagnetCreateProgram(input: $input) {\n id\n name\n }\n }\n`;\n\nexport function ProgramsCreateProgramPagePage() {\n const { currentUser } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [form, setForm] = useState({\n name: '',\n description: '',\n status: 'PLANNING' as (typeof PROGRAM_STATUSES)[number],\n startDate: '',\n endDate: '',\n budgetTotal: '',\n currency: '',\n tags: '',\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [createProgram, { loading: saving }] = useMutation(CREATE_PROGRAM, {\n refetchQueries: ['ListPrograms'],\n });\n\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n const invalidBudget = form.budgetTotal !== '' && Number(form.budgetTotal) < 0;\n\n const handleCreate = async () => {\n if (!form.name.trim() || invalidDates || invalidBudget) return;\n setSaveError(null);\n try {\n await throwOnError(\n createProgram({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n ownerId: currentUser?.id ?? '',\n ...(form.budgetTotal !== '' ? { budgetTotal: Number(form.budgetTotal) } : {}),\n currency: form.currency.trim() || undefined,\n tags: form.tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean),\n },\n },\n })\n );\n navigate('/products/programs-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The program could not be created.');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Create Program</h1>\n <p className=\"text-sm text-muted-foreground\">\n Group related projects together for portfolio-level visibility and budget tracking.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/products/programs-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={handleCreate}\n disabled={saving || !form.name.trim() || invalidDates || invalidBudget}\n >\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Create Program\n </Button>\n </div>\n </div>\n\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to create program: {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <FolderKanban className=\"h-4 w-4\" />\n Program Details\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. Platform Modernization\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Describe what this program covers…\"\n />\n </div>\n <div>\n <label htmlFor=\"program-status\" className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id=\"program-status\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.status}\n onChange={(e) =>\n setForm({ ...form, status: e.target.value as (typeof PROGRAM_STATUSES)[number] })\n }\n >\n {PROGRAM_STATUSES.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"program-start\" className=\"text-sm font-medium\">\n Start Date\n </label>\n <Input\n id=\"program-start\"\n type=\"date\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label htmlFor=\"program-end\" className=\"text-sm font-medium\">\n End Date\n </label>\n <Input\n id=\"program-end\"\n type=\"date\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n aria-invalid={invalidDates}\n className=\"mt-1\"\n />\n </div>\n </div>\n {invalidDates && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The end date is before the start date.\n </p>\n )}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Total Budget</label>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.budgetTotal}\n onChange={(e) => setForm({ ...form, budgetTotal: e.target.value })}\n className=\"mt-1\"\n aria-invalid={invalidBudget}\n placeholder=\"0\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Currency</label>\n <Input\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n className=\"mt-1\"\n placeholder=\"USD\"\n />\n </div>\n </div>\n {invalidBudget && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The budget cannot be negative.\n </p>\n )}\n <div>\n <label className=\"text-sm font-medium\">Tags (comma-separated)</label>\n <Input\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. strategic, infra\"\n />\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProgramsEditProgramPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProgramsEditProgramPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgramsEditProgramPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProgramsEditProgramPagePage.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,2BAA2B,4CAyQ1C"}
@@ -0,0 +1,279 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { throwOnError as t } from "../utils/mutationResult.js";
3
+ import { detailPath as n } from "../utils/rowNavigation.js";
4
+ import { endBeforeStart as r } from "../utils/formValidation.js";
5
+ import { enumLabel as i } from "../controls/WorkItemControls.js";
6
+ import { PROGRAM_STATUSES as a } from "../controls/ProgramControls.js";
7
+ import { useMemo as o, useState as s } from "react";
8
+ import { ArrowLeft as c, Loader2 as l, Save as u } from "lucide-react";
9
+ import { useParams as d } from "react-router";
10
+ import { gql as f } from "@apollo/client";
11
+ import { useMutation as p, useQuery as m } from "@apollo/client/react";
12
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
13
+ import { Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, Input as S } from "@burdenoff/fe-libs/ui";
14
+ //#region src/planmagnet/pages/ProgramsEditProgramPagePage.tsx
15
+ var C = f`
16
+ query GetProgramForEdit($id: ID!) {
17
+ planmagnetProgram(id: $id) {
18
+ id
19
+ name
20
+ description
21
+ status
22
+ startDate
23
+ endDate
24
+ budgetTotal
25
+ currency
26
+ tags
27
+ }
28
+ }
29
+ `, w = f`
30
+ mutation UpdateProgram($id: ID!, $input: PlanmagnetUpdateProgramInput!) {
31
+ planmagnetUpdateProgram(id: $id, input: $input) {
32
+ id
33
+ name
34
+ description
35
+ status
36
+ startDate
37
+ endDate
38
+ budgetTotal
39
+ currency
40
+ tags
41
+ updatedAt
42
+ }
43
+ }
44
+ `;
45
+ function T() {
46
+ let f = e(), T = d().id ?? "", { data: E, loading: D, error: O } = m(C, {
47
+ variables: { id: T },
48
+ skip: !T
49
+ }), [k, { loading: A }] = p(w, {
50
+ refetchQueries: [
51
+ "ListPrograms",
52
+ "GetProgramForEdit",
53
+ "GetProgram"
54
+ ],
55
+ awaitRefetchQueries: !0
56
+ }), [j, M] = s(null), [N, P] = s(!1), F = E?.planmagnetProgram, I = o(() => ({
57
+ name: F?.name ?? "",
58
+ description: F?.description ?? "",
59
+ status: F?.status ?? "PLANNING",
60
+ startDate: F?.startDate ? F.startDate.split("T")[0] : "",
61
+ endDate: F?.endDate ? F.endDate.split("T")[0] : "",
62
+ budgetTotal: F?.budgetTotal == null ? "" : String(F.budgetTotal),
63
+ currency: F?.currency ?? "",
64
+ tags: F?.tags?.join(", ") ?? ""
65
+ }), [F]), [L, R] = s({}), z = {
66
+ ...I,
67
+ ...L
68
+ }, B = (e) => R(e), V = a.includes(z.status) ? a : [z.status, ...a], H = r(z.startDate, z.endDate), U = z.budgetTotal !== "" && Number(z.budgetTotal) < 0;
69
+ return D && !E ? /* @__PURE__ */ h("div", {
70
+ className: "flex items-center justify-center p-12",
71
+ children: /* @__PURE__ */ h(l, { className: "h-8 w-8 animate-spin text-muted-foreground" })
72
+ }) : O ? /* @__PURE__ */ g("div", {
73
+ className: "p-6 text-destructive",
74
+ children: ["Failed to load program: ", O.message]
75
+ }) : F ? /* @__PURE__ */ g("div", {
76
+ className: "space-y-6 p-6 max-w-4xl",
77
+ children: [
78
+ /* @__PURE__ */ g("div", {
79
+ className: "flex items-center justify-between",
80
+ children: [/* @__PURE__ */ g("div", {
81
+ className: "flex items-center gap-3",
82
+ children: [/* @__PURE__ */ h(_, {
83
+ variant: "ghost",
84
+ size: "sm",
85
+ "aria-label": "Back to the program",
86
+ onClick: () => f(n("/products/program-detail-page", T)),
87
+ children: /* @__PURE__ */ h(c, { className: "h-4 w-4" })
88
+ }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
89
+ className: "text-2xl font-semibold",
90
+ children: "Edit Program"
91
+ }), /* @__PURE__ */ h("p", {
92
+ className: "text-sm text-muted-foreground",
93
+ children: F.name
94
+ })] })]
95
+ }), /* @__PURE__ */ g(_, {
96
+ size: "sm",
97
+ onClick: async () => {
98
+ if (!F || !z.name.trim() || H || U) return;
99
+ let e = {
100
+ ...z.startDate !== I.startDate && { startDate: z.startDate || null },
101
+ ...z.endDate !== I.endDate && { endDate: z.endDate || null }
102
+ }, n = (e) => e.split(",").map((e) => e.trim()).filter(Boolean), r = {
103
+ name: z.name.trim(),
104
+ description: z.description.trim() || null,
105
+ status: z.status,
106
+ ...e,
107
+ budgetTotal: z.budgetTotal === "" ? null : Number(z.budgetTotal),
108
+ currency: z.currency.trim() || null,
109
+ tags: n(z.tags)
110
+ }, i = {
111
+ name: I.name,
112
+ description: I.description.trim() || null,
113
+ status: I.status,
114
+ budgetTotal: I.budgetTotal === "" ? null : Number(I.budgetTotal),
115
+ currency: I.currency.trim() || null,
116
+ tags: n(I.tags)
117
+ }, a = Object.fromEntries(Object.entries(r).filter(([t, n]) => t in e || JSON.stringify(n) !== JSON.stringify(i[t])));
118
+ if (M(null), P(!1), Object.keys(a).length === 0) {
119
+ P(!0);
120
+ return;
121
+ }
122
+ try {
123
+ await t(k({ variables: {
124
+ id: T,
125
+ input: a
126
+ } })), P(!0);
127
+ } catch (e) {
128
+ M(e instanceof Error ? e.message : "The program could not be saved.");
129
+ }
130
+ },
131
+ disabled: A || H || U,
132
+ children: [A ? /* @__PURE__ */ h(l, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ h(u, { className: "mr-1 h-4 w-4" }), "Save Changes"]
133
+ })]
134
+ }),
135
+ N && /* @__PURE__ */ h("div", {
136
+ role: "status",
137
+ className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
138
+ children: "Changes saved."
139
+ }),
140
+ j && /* @__PURE__ */ h("div", {
141
+ role: "alert",
142
+ className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
143
+ children: j
144
+ }),
145
+ /* @__PURE__ */ g(v, { children: [/* @__PURE__ */ h(b, { children: /* @__PURE__ */ h(x, { children: "Program Details" }) }), /* @__PURE__ */ g(y, {
146
+ className: "space-y-4",
147
+ children: [
148
+ /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
149
+ className: "text-sm font-medium",
150
+ children: "Name"
151
+ }), /* @__PURE__ */ h(S, {
152
+ value: z.name,
153
+ onChange: (e) => B({
154
+ ...z,
155
+ name: e.target.value
156
+ }),
157
+ className: "mt-1"
158
+ })] }),
159
+ /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
160
+ className: "text-sm font-medium",
161
+ children: "Description"
162
+ }), /* @__PURE__ */ h("textarea", {
163
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
164
+ value: z.description,
165
+ onChange: (e) => B({
166
+ ...z,
167
+ description: e.target.value
168
+ })
169
+ })] }),
170
+ /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
171
+ htmlFor: "program-edit-status",
172
+ className: "text-sm font-medium",
173
+ children: "Status"
174
+ }), /* @__PURE__ */ h("select", {
175
+ id: "program-edit-status",
176
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
177
+ value: z.status,
178
+ onChange: (e) => B({
179
+ ...z,
180
+ status: e.target.value
181
+ }),
182
+ children: V.map((e) => /* @__PURE__ */ h("option", {
183
+ value: e,
184
+ children: i(e)
185
+ }, e))
186
+ })] }),
187
+ /* @__PURE__ */ g("div", {
188
+ className: "grid grid-cols-2 gap-4",
189
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
190
+ htmlFor: "program-edit-start",
191
+ className: "text-sm font-medium",
192
+ children: "Start Date"
193
+ }), /* @__PURE__ */ h(S, {
194
+ id: "program-edit-start",
195
+ type: "date",
196
+ value: z.startDate,
197
+ max: z.endDate || void 0,
198
+ onChange: (e) => B({
199
+ ...z,
200
+ startDate: e.target.value
201
+ }),
202
+ className: "mt-1"
203
+ })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
204
+ htmlFor: "program-edit-end",
205
+ className: "text-sm font-medium",
206
+ children: "End Date"
207
+ }), /* @__PURE__ */ h(S, {
208
+ id: "program-edit-end",
209
+ type: "date",
210
+ value: z.endDate,
211
+ min: z.startDate || void 0,
212
+ onChange: (e) => B({
213
+ ...z,
214
+ endDate: e.target.value
215
+ }),
216
+ "aria-invalid": H,
217
+ className: "mt-1"
218
+ })] })]
219
+ }),
220
+ H && /* @__PURE__ */ h("p", {
221
+ role: "alert",
222
+ className: "text-sm text-destructive",
223
+ children: "The end date is before the start date."
224
+ }),
225
+ /* @__PURE__ */ g("div", {
226
+ className: "grid grid-cols-2 gap-4",
227
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
228
+ className: "text-sm font-medium",
229
+ children: "Total Budget"
230
+ }), /* @__PURE__ */ h(S, {
231
+ type: "number",
232
+ min: "0",
233
+ value: z.budgetTotal,
234
+ onChange: (e) => B({
235
+ ...z,
236
+ budgetTotal: e.target.value
237
+ }),
238
+ className: "mt-1",
239
+ "aria-invalid": U
240
+ })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
241
+ className: "text-sm font-medium",
242
+ children: "Currency"
243
+ }), /* @__PURE__ */ h(S, {
244
+ value: z.currency,
245
+ onChange: (e) => B({
246
+ ...z,
247
+ currency: e.target.value
248
+ }),
249
+ className: "mt-1"
250
+ })] })]
251
+ }),
252
+ U && /* @__PURE__ */ h("p", {
253
+ role: "alert",
254
+ className: "text-sm text-destructive",
255
+ children: "The budget cannot be negative."
256
+ }),
257
+ /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
258
+ className: "text-sm font-medium",
259
+ children: "Tags (comma-separated)"
260
+ }), /* @__PURE__ */ h(S, {
261
+ value: z.tags,
262
+ onChange: (e) => B({
263
+ ...z,
264
+ tags: e.target.value
265
+ }),
266
+ className: "mt-1"
267
+ })] })
268
+ ]
269
+ })] })
270
+ ]
271
+ }) : /* @__PURE__ */ h("div", {
272
+ className: "p-6 text-muted-foreground",
273
+ children: "Program not found."
274
+ });
275
+ }
276
+ //#endregion
277
+ export { T as ProgramsEditProgramPagePage };
278
+
279
+ //# sourceMappingURL=ProgramsEditProgramPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgramsEditProgramPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProgramsEditProgramPagePage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft, Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PROGRAM_STATUSES } from '../controls/ProgramControls';\n\nconst GET_PROGRAM = gql`\n query GetProgramForEdit($id: ID!) {\n planmagnetProgram(id: $id) {\n id\n name\n description\n status\n startDate\n endDate\n budgetTotal\n currency\n tags\n }\n }\n`;\n\nconst UPDATE_PROGRAM = gql`\n mutation UpdateProgram($id: ID!, $input: PlanmagnetUpdateProgramInput!) {\n planmagnetUpdateProgram(id: $id, input: $input) {\n id\n name\n description\n status\n startDate\n endDate\n budgetTotal\n currency\n tags\n updatedAt\n }\n }\n`;\n\nexport function ProgramsEditProgramPagePage() {\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const programId = params.id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetProgram: {\n id: string;\n name: string;\n description: string | null;\n status: string;\n startDate: string | null;\n endDate: string | null;\n budgetTotal: number | null;\n currency: string | null;\n tags: string[];\n } | null;\n }>(GET_PROGRAM, { variables: { id: programId }, skip: !programId });\n\n const [updateProgram, { loading: saving }] = useMutation(UPDATE_PROGRAM, {\n refetchQueries: ['ListPrograms', 'GetProgramForEdit', 'GetProgram'],\n awaitRefetchQueries: true,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const program = data?.planmagnetProgram;\n\n const defaultForm = useMemo(\n () => ({\n name: program?.name ?? '',\n description: program?.description ?? '',\n status: program?.status ?? 'PLANNING',\n startDate: program?.startDate ? program.startDate.split('T')[0] : '',\n endDate: program?.endDate ? program.endDate.split('T')[0] : '',\n budgetTotal: program?.budgetTotal != null ? String(program.budgetTotal) : '',\n currency: program?.currency ?? '',\n tags: program?.tags?.join(', ') ?? '',\n }),\n [program]\n );\n\n const [formOverrides, setFormOverrides] = useState<Record<string, string>>({});\n const form = { ...defaultForm, ...formOverrides };\n const setForm = (val: Record<string, string>) => setFormOverrides(val);\n\n const statuses = (PROGRAM_STATUSES as readonly string[]).includes(form.status)\n ? PROGRAM_STATUSES\n : [form.status, ...PROGRAM_STATUSES];\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n const invalidBudget = form.budgetTotal !== '' && Number(form.budgetTotal) < 0;\n\n // Dates are sent only when changed (BOFF-7833 pattern): re-sending an untouched pair could refuse a plain\n // rename against a record stored with an older, looser rule.\n const handleSave = async () => {\n if (!program || !form.name.trim() || invalidDates || invalidBudget) return;\n const dateChanges = {\n ...(form.startDate !== defaultForm.startDate && { startDate: form.startDate || null }),\n ...(form.endDate !== defaultForm.endDate && { endDate: form.endDate || null }),\n };\n const tagList = (v: string) =>\n v\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const next: Record<string, unknown> = {\n name: form.name.trim(),\n description: form.description.trim() || null,\n status: form.status,\n ...dateChanges,\n budgetTotal: form.budgetTotal !== '' ? Number(form.budgetTotal) : null,\n currency: form.currency.trim() || null,\n tags: tagList(form.tags),\n };\n const before: Record<string, unknown> = {\n name: defaultForm.name,\n description: defaultForm.description.trim() || null,\n status: defaultForm.status,\n budgetTotal: defaultForm.budgetTotal !== '' ? Number(defaultForm.budgetTotal) : null,\n currency: defaultForm.currency.trim() || null,\n tags: tagList(defaultForm.tags),\n };\n const input = Object.fromEntries(\n Object.entries(next).filter(\n ([k, v]) => k in dateChanges || JSON.stringify(v) !== JSON.stringify(before[k])\n )\n );\n setSaveError(null);\n setSaved(false);\n if (Object.keys(input).length === 0) {\n setSaved(true);\n return;\n }\n try {\n await throwOnError(updateProgram({ variables: { id: programId, input } }));\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The program could not be saved.');\n }\n };\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load program: {error.message}</div>;\n }\n\n if (!program) return <div className=\"p-6 text-muted-foreground\">Program not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Back to the program\"\n onClick={() => navigate(detailPath('/products/program-detail-page', programId))}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Program</h1>\n <p className=\"text-sm text-muted-foreground\">{program.name}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving || invalidDates || invalidBudget}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Save Changes\n </Button>\n </div>\n\n {saved && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n Changes saved.\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Program Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"program-edit-status\" className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id=\"program-edit-status\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"program-edit-start\" className=\"text-sm font-medium\">\n Start Date\n </label>\n <Input\n id=\"program-edit-start\"\n type=\"date\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label htmlFor=\"program-edit-end\" className=\"text-sm font-medium\">\n End Date\n </label>\n <Input\n id=\"program-edit-end\"\n type=\"date\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n aria-invalid={invalidDates}\n className=\"mt-1\"\n />\n </div>\n </div>\n {invalidDates && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The end date is before the start date.\n </p>\n )}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Total Budget</label>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.budgetTotal}\n onChange={(e) => setForm({ ...form, budgetTotal: e.target.value })}\n className=\"mt-1\"\n aria-invalid={invalidBudget}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Currency</label>\n <Input\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </div>\n {invalidBudget && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The budget cannot be negative.\n </p>\n )}\n <div>\n <label className=\"text-sm font-medium\">Tags (comma-separated)</label>\n <Input\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProgramsProgramDetailPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProgramsProgramDetailPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgramsProgramDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProgramsProgramDetailPagePage.tsx"],"names":[],"mappings":"AAwCA,wBAAgB,6BAA6B,4CAmJ5C"}