@burdenoff/microfe-planmagnet 2026.928.10 → 2026.928.11

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.
@@ -1,15 +1,15 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
2
  import { throwOnError as n } from "../utils/mutationResult.js";
3
3
  import { WORK_ITEM_PRIORITIES as r } from "../controls/WorkItemControls.js";
4
- import { parseIntField as i } from "../utils/formValidation.js";
5
- import { useMemo as a, useState as o } from "react";
6
- import { ArrowLeft as s, Save as c } from "lucide-react";
7
- import { gql as l } from "@apollo/client";
8
- import { useMutation as u, useQuery as d } from "@apollo/client/react";
9
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
10
- import { Button as m, Card as h, CardContent as g, CardHeader as _, CardTitle as v, Input as y } from "@burdenoff/fe-libs/ui";
4
+ import { estimateHoursText as i, parseEstimateHours as a, parseIntField as o } from "../utils/formValidation.js";
5
+ import { useMemo as s, useState as c } from "react";
6
+ import { ArrowLeft as l, Save as u } from "lucide-react";
7
+ import { gql as d } from "@apollo/client";
8
+ import { useMutation as f, useQuery as p } from "@apollo/client/react";
9
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
+ import { Button as g, Card as _, CardContent as v, CardHeader as y, CardTitle as b, Input as x } from "@burdenoff/fe-libs/ui";
11
11
  //#region src/planmagnet/pages/TasksEditTaskPagePage.tsx
12
- var b = l`
12
+ var S = d`
13
13
  query GetWorkItem($workItemId: ID!) {
14
14
  planmagnetWorkItem(id: $workItemId) {
15
15
  id
@@ -20,6 +20,7 @@ var b = l`
20
20
  priority
21
21
  key
22
22
  storyPoints
23
+ estimateMinutes
23
24
  dueDate
24
25
  labels
25
26
  tags
@@ -27,7 +28,7 @@ var b = l`
27
28
  updatedAt
28
29
  }
29
30
  }
30
- `, x = l`
31
+ `, C = d`
31
32
  mutation UpdateWorkItem($workItemId: ID!, $input: PlanmagnetUpdateWorkItemInput!) {
32
33
  planmagnetUpdateWorkItem(id: $workItemId, input: $input) {
33
34
  id
@@ -37,17 +38,18 @@ var b = l`
37
38
  status
38
39
  priority
39
40
  storyPoints
41
+ estimateMinutes
40
42
  dueDate
41
43
  labels
42
44
  updatedAt
43
45
  }
44
46
  }
45
47
  `;
46
- function S() {
47
- let { workspaceId: l } = e(), S = new URLSearchParams(window.location.search).get("taskId") ?? "", C = t(), { data: w, loading: T, error: E } = d(b, {
48
- variables: { workItemId: S },
49
- skip: !l || !S
50
- }), [D, { loading: O }] = u(x, {
48
+ function w() {
49
+ let { workspaceId: d } = e(), w = new URLSearchParams(window.location.search).get("taskId") ?? "", T = t(), { data: E, loading: D, error: O } = p(S, {
50
+ variables: { workItemId: w },
51
+ skip: !d || !w
52
+ }), [k, { loading: A }] = f(C, {
51
53
  refetchQueries: [
52
54
  "GetWorkItems",
53
55
  "GetWorkItemDetail",
@@ -55,154 +57,157 @@ function S() {
55
57
  "ProjectBoard"
56
58
  ],
57
59
  awaitRefetchQueries: !0
58
- }), [k, A] = o(null), [j, M] = o(!1), N = w?.planmagnetWorkItem, P = a(() => ({
59
- title: N?.title ?? "",
60
- description: N?.description ?? "",
61
- priority: N?.priority ?? "MEDIUM",
62
- type: N?.type ?? "STORY",
63
- storyPoints: N?.storyPoints?.toString() ?? "",
64
- labels: (N?.labels ?? []).join(", "),
65
- dueDate: N?.dueDate ? String(N.dueDate).slice(0, 10) : ""
66
- }), [N]), [F, I] = o({}), L = {
67
- ...P,
68
- ...F
69
- }, R = (e) => I(e), z = i(L.storyPoints, { min: 0 }), B = async () => {
70
- if (!l || !S || z.invalid) return;
71
- if (!L.title.trim()) {
72
- A("A task needs a title.");
60
+ }), [j, M] = c(null), [N, P] = c(!1), F = E?.planmagnetWorkItem, I = s(() => ({
61
+ title: F?.title ?? "",
62
+ description: F?.description ?? "",
63
+ priority: F?.priority ?? "MEDIUM",
64
+ type: F?.type ?? "STORY",
65
+ storyPoints: F?.storyPoints?.toString() ?? "",
66
+ labels: (F?.labels ?? []).join(", "),
67
+ dueDate: F?.dueDate ? String(F.dueDate).slice(0, 10) : "",
68
+ estimateHours: i(F?.estimateMinutes)
69
+ }), [F]), [L, R] = c({}), z = {
70
+ ...I,
71
+ ...L
72
+ }, B = (e) => R(e), V = o(z.storyPoints, { min: 0 }), H = a(z.estimateHours), U = async () => {
73
+ if (!d || !w || V.invalid || H.invalid) return;
74
+ if (!z.title.trim()) {
75
+ M("A task needs a title.");
73
76
  return;
74
77
  }
75
78
  let e = (e) => e.split(",").map((e) => e.trim()).filter(Boolean), t = {
76
- title: L.title.trim(),
77
- description: L.description.trim() || null,
78
- priority: L.priority,
79
- type: L.type,
80
- storyPoints: z.value ?? null,
81
- labels: e(L.labels),
82
- dueDate: L.dueDate || null
79
+ title: z.title.trim(),
80
+ description: z.description.trim() || null,
81
+ priority: z.priority,
82
+ type: z.type,
83
+ storyPoints: V.value ?? null,
84
+ labels: e(z.labels),
85
+ dueDate: z.dueDate || null,
86
+ estimateMinutes: H.minutes ?? null
83
87
  }, r = {
84
- title: P.title,
85
- description: P.description.trim() || null,
86
- priority: P.priority,
87
- type: P.type,
88
- storyPoints: P.storyPoints === "" ? null : Number(P.storyPoints),
89
- labels: e(P.labels),
90
- dueDate: P.dueDate || null
88
+ title: I.title,
89
+ description: I.description.trim() || null,
90
+ priority: I.priority,
91
+ type: I.type,
92
+ storyPoints: I.storyPoints === "" ? null : Number(I.storyPoints),
93
+ labels: e(I.labels),
94
+ dueDate: I.dueDate || null,
95
+ estimateMinutes: a(I.estimateHours).minutes ?? null
91
96
  }, i = Object.fromEntries(Object.entries(t).filter(([e, t]) => JSON.stringify(t) !== JSON.stringify(r[e])));
92
- if (A(null), M(!1), Object.keys(i).length === 0) {
93
- M(!0);
97
+ if (M(null), P(!1), Object.keys(i).length === 0) {
98
+ P(!0);
94
99
  return;
95
100
  }
96
101
  try {
97
- await n(D({ variables: {
98
- workItemId: S,
102
+ await n(k({ variables: {
103
+ workItemId: w,
99
104
  input: i
100
- } })), M(!0);
105
+ } })), P(!0);
101
106
  } catch (e) {
102
- A(e instanceof Error ? e.message : "The task could not be saved.");
107
+ M(e instanceof Error ? e.message : "The task could not be saved.");
103
108
  }
104
109
  };
105
- if (T && !w) return /* @__PURE__ */ f("div", {
110
+ if (D && !E) return /* @__PURE__ */ m("div", {
106
111
  className: "flex items-center justify-center p-12",
107
- children: /* @__PURE__ */ f("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
112
+ children: /* @__PURE__ */ m("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
108
113
  });
109
- if (E) return /* @__PURE__ */ p("div", {
114
+ if (O) return /* @__PURE__ */ h("div", {
110
115
  className: "p-6 text-destructive",
111
- children: ["Error: ", E.message]
116
+ children: ["Error: ", O.message]
112
117
  });
113
- if (!w?.planmagnetWorkItem) return /* @__PURE__ */ f("div", {
118
+ if (!E?.planmagnetWorkItem) return /* @__PURE__ */ m("div", {
114
119
  className: "p-6 text-muted-foreground",
115
120
  children: "Task not found."
116
121
  });
117
- let V = w.planmagnetWorkItem;
118
- return /* @__PURE__ */ p("div", {
122
+ let W = E.planmagnetWorkItem;
123
+ return /* @__PURE__ */ h("div", {
119
124
  className: "space-y-6 p-6 max-w-4xl",
120
125
  children: [
121
- /* @__PURE__ */ p("div", {
126
+ /* @__PURE__ */ h("div", {
122
127
  className: "flex items-center justify-between",
123
- children: [/* @__PURE__ */ p("div", {
128
+ children: [/* @__PURE__ */ h("div", {
124
129
  className: "flex items-center gap-3",
125
- children: [/* @__PURE__ */ f(m, {
130
+ children: [/* @__PURE__ */ m(g, {
126
131
  variant: "ghost",
127
132
  size: "sm",
128
133
  "aria-label": "Back to the task",
129
- onClick: () => C(`/tasks/task-detail-page?taskId=${encodeURIComponent(S)}`),
130
- children: /* @__PURE__ */ f(s, { className: "h-4 w-4" })
131
- }), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h1", {
134
+ onClick: () => T(`/tasks/task-detail-page?taskId=${encodeURIComponent(w)}`),
135
+ children: /* @__PURE__ */ m(l, { className: "h-4 w-4" })
136
+ }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h1", {
132
137
  className: "text-2xl font-semibold",
133
138
  children: "Edit Task"
134
- }), /* @__PURE__ */ f("p", {
139
+ }), /* @__PURE__ */ m("p", {
135
140
  className: "text-sm text-muted-foreground",
136
- children: V.title
141
+ children: W.title
137
142
  })] })]
138
- }), /* @__PURE__ */ p(m, {
143
+ }), /* @__PURE__ */ h(g, {
139
144
  size: "sm",
140
- onClick: B,
141
- disabled: O || z.invalid,
142
- children: [/* @__PURE__ */ f(c, { className: "mr-1 h-4 w-4" }), O ? "Saving..." : "Save Changes"]
145
+ onClick: U,
146
+ disabled: A || V.invalid || H.invalid,
147
+ children: [/* @__PURE__ */ m(u, { className: "mr-1 h-4 w-4" }), A ? "Saving..." : "Save Changes"]
143
148
  })]
144
149
  }),
145
- j && /* @__PURE__ */ f("div", {
150
+ N && /* @__PURE__ */ m("div", {
146
151
  role: "status",
147
152
  className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
148
153
  children: "Changes saved."
149
154
  }),
150
- k && /* @__PURE__ */ f("div", {
155
+ j && /* @__PURE__ */ m("div", {
151
156
  role: "alert",
152
157
  className: "p-3 bg-destructive/10 text-destructive rounded-md text-sm",
153
- children: k
158
+ children: j
154
159
  }),
155
- /* @__PURE__ */ p(h, { children: [/* @__PURE__ */ f(_, { children: /* @__PURE__ */ f(v, { children: "Task Details" }) }), /* @__PURE__ */ p(g, {
160
+ /* @__PURE__ */ h(_, { children: [/* @__PURE__ */ m(y, { children: /* @__PURE__ */ m(b, { children: "Task Details" }) }), /* @__PURE__ */ h(v, {
156
161
  className: "space-y-4",
157
162
  children: [
158
- /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
163
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
159
164
  className: "text-sm font-medium",
160
165
  children: "Title"
161
- }), /* @__PURE__ */ f(y, {
162
- value: L.title,
163
- onChange: (e) => R({
164
- ...L,
166
+ }), /* @__PURE__ */ m(x, {
167
+ value: z.title,
168
+ onChange: (e) => B({
169
+ ...z,
165
170
  title: e.target.value
166
171
  }),
167
172
  className: "mt-1"
168
173
  })] }),
169
- /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
174
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
170
175
  className: "text-sm font-medium",
171
176
  children: "Description"
172
- }), /* @__PURE__ */ f("textarea", {
177
+ }), /* @__PURE__ */ m("textarea", {
173
178
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[100px]",
174
- value: L.description,
175
- onChange: (e) => R({
176
- ...L,
179
+ value: z.description,
180
+ onChange: (e) => B({
181
+ ...z,
177
182
  description: e.target.value
178
183
  })
179
184
  })] }),
180
- /* @__PURE__ */ p("div", {
185
+ /* @__PURE__ */ h("div", {
181
186
  className: "grid grid-cols-3 gap-4",
182
187
  children: [
183
- /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
188
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
184
189
  className: "text-sm font-medium",
185
190
  children: "Priority"
186
- }), /* @__PURE__ */ f("select", {
191
+ }), /* @__PURE__ */ m("select", {
187
192
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
188
- value: L.priority,
189
- onChange: (e) => R({
190
- ...L,
193
+ value: z.priority,
194
+ onChange: (e) => B({
195
+ ...z,
191
196
  priority: e.target.value
192
197
  }),
193
- children: r.map((e) => /* @__PURE__ */ f("option", {
198
+ children: r.map((e) => /* @__PURE__ */ m("option", {
194
199
  value: e,
195
200
  children: e.toLowerCase()
196
201
  }, e))
197
202
  })] }),
198
- /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
203
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
199
204
  className: "text-sm font-medium",
200
205
  children: "Type"
201
- }), /* @__PURE__ */ f("select", {
206
+ }), /* @__PURE__ */ m("select", {
202
207
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
203
- value: L.type,
204
- onChange: (e) => R({
205
- ...L,
208
+ value: z.type,
209
+ onChange: (e) => B({
210
+ ...z,
206
211
  type: e.target.value
207
212
  }),
208
213
  children: [
@@ -211,31 +216,31 @@ function S() {
211
216
  "TASK",
212
217
  "EPIC",
213
218
  "SUB_TASK"
214
- ].map((e) => /* @__PURE__ */ f("option", {
219
+ ].map((e) => /* @__PURE__ */ m("option", {
215
220
  value: e,
216
221
  children: e.toLowerCase().replace("_", " ")
217
222
  }, e))
218
223
  })] }),
219
- /* @__PURE__ */ p("div", { children: [
220
- /* @__PURE__ */ f("label", {
224
+ /* @__PURE__ */ h("div", { children: [
225
+ /* @__PURE__ */ m("label", {
221
226
  htmlFor: "task-story-points",
222
227
  className: "text-sm font-medium",
223
228
  children: "Story Points"
224
229
  }),
225
- /* @__PURE__ */ f(y, {
230
+ /* @__PURE__ */ m(x, {
226
231
  id: "task-story-points",
227
232
  type: "number",
228
233
  min: 0,
229
234
  step: 1,
230
- value: L.storyPoints,
231
- onChange: (e) => R({
232
- ...L,
235
+ value: z.storyPoints,
236
+ onChange: (e) => B({
237
+ ...z,
233
238
  storyPoints: e.target.value
234
239
  }),
235
- "aria-invalid": z.invalid,
240
+ "aria-invalid": V.invalid,
236
241
  className: "mt-1"
237
242
  }),
238
- z.invalid && /* @__PURE__ */ f("p", {
243
+ V.invalid && /* @__PURE__ */ m("p", {
239
244
  role: "alert",
240
245
  className: "mt-1 text-xs text-destructive",
241
246
  children: "Story points must be a whole number, 0 or more."
@@ -243,30 +248,63 @@ function S() {
243
248
  ] })
244
249
  ]
245
250
  }),
246
- /* @__PURE__ */ p("div", {
247
- className: "grid grid-cols-2 gap-4",
248
- children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
249
- className: "text-sm font-medium",
250
- children: "Due Date"
251
- }), /* @__PURE__ */ f(y, {
252
- type: "date",
253
- value: L.dueDate,
254
- onChange: (e) => R({
255
- ...L,
256
- dueDate: e.target.value
257
- }),
258
- className: "mt-1"
259
- })] }), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
260
- className: "text-sm font-medium",
261
- children: "Labels"
262
- }), /* @__PURE__ */ f(y, {
263
- value: L.labels,
264
- onChange: (e) => R({
265
- ...L,
266
- labels: e.target.value
267
- }),
268
- className: "mt-1"
269
- })] })]
251
+ /* @__PURE__ */ h("div", {
252
+ className: "grid grid-cols-3 gap-4",
253
+ children: [
254
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
255
+ className: "text-sm font-medium",
256
+ children: "Due Date"
257
+ }), /* @__PURE__ */ m(x, {
258
+ type: "date",
259
+ value: z.dueDate,
260
+ onChange: (e) => B({
261
+ ...z,
262
+ dueDate: e.target.value
263
+ }),
264
+ className: "mt-1"
265
+ })] }),
266
+ /* @__PURE__ */ h("div", { children: [
267
+ /* @__PURE__ */ m("label", {
268
+ htmlFor: "task-estimate-hours",
269
+ className: "text-sm font-medium",
270
+ children: "Estimate (hours)"
271
+ }),
272
+ /* @__PURE__ */ m(x, {
273
+ id: "task-estimate-hours",
274
+ type: "number",
275
+ min: 0,
276
+ step: .25,
277
+ value: z.estimateHours,
278
+ onChange: (e) => B({
279
+ ...z,
280
+ estimateHours: e.target.value
281
+ }),
282
+ "aria-invalid": H.invalid,
283
+ "aria-describedby": "task-estimate-hint",
284
+ className: "mt-1"
285
+ }),
286
+ H.invalid ? /* @__PURE__ */ m("p", {
287
+ role: "alert",
288
+ className: "mt-1 text-xs text-destructive",
289
+ children: "The estimate must be a number of hours, 0 or more."
290
+ }) : /* @__PURE__ */ m("p", {
291
+ id: "task-estimate-hint",
292
+ className: "mt-1 text-xs text-muted-foreground",
293
+ children: "Used as the task's duration on the critical path."
294
+ })
295
+ ] }),
296
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
297
+ className: "text-sm font-medium",
298
+ children: "Labels"
299
+ }), /* @__PURE__ */ m(x, {
300
+ value: z.labels,
301
+ onChange: (e) => B({
302
+ ...z,
303
+ labels: e.target.value
304
+ }),
305
+ className: "mt-1"
306
+ })] })
307
+ ]
270
308
  })
271
309
  ]
272
310
  })] })
@@ -274,6 +312,6 @@ function S() {
274
312
  });
275
313
  }
276
314
  //#endregion
277
- export { S as TasksEditTaskPagePage };
315
+ export { w as TasksEditTaskPagePage };
278
316
 
279
317
  //# sourceMappingURL=TasksEditTaskPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TasksEditTaskPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksEditTaskPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { 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 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { parseIntField } from '../utils/formValidation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WORK_ITEM_PRIORITIES } from '../controls/WorkItemControls';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItem($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n title\n description\n type\n status\n priority\n key\n storyPoints\n dueDate\n labels\n tags\n createdAt\n updatedAt\n }\n }\n`;\n\nconst UPDATE_WORK_ITEM = gql`\n mutation UpdateWorkItem($workItemId: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $workItemId, input: $input) {\n id\n title\n description\n type\n status\n priority\n storyPoints\n dueDate\n labels\n updatedAt\n }\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n storyPoints: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport function TasksEditTaskPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from the route params.\n // For now we use a placeholder that the host shell should provide.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n const [updateWorkItem, { loading: saving }] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: ['GetWorkItems', 'GetWorkItemDetail', 'GetWorkItemCounts', 'ProjectBoard'],\n awaitRefetchQueries: true,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const workItem = data?.planmagnetWorkItem;\n\n const defaultForm = useMemo(\n () => ({\n title: workItem?.title ?? '',\n description: workItem?.description ?? '',\n priority: workItem?.priority ?? 'MEDIUM',\n type: workItem?.type ?? 'STORY',\n storyPoints: workItem?.storyPoints?.toString() ?? '',\n labels: (workItem?.labels ?? []).join(', '),\n // A date input needs YYYY-MM-DD; the API returns a full ISO timestamp.\n dueDate: workItem?.dueDate ? String(workItem.dueDate).slice(0, 10) : '',\n }),\n [workItem]\n );\n\n const [formOverrides, setFormOverrides] = useState<Record<string, string>>({});\n const form = { ...defaultForm, ...formOverrides };\n const setForm = (updates: Record<string, string>) => setFormOverrides(updates);\n\n // Story points are a whole number, 0 or more (BOFF-7833); the service refuses anything else.\n const storyPoints = parseIntField(form.storyPoints, { min: 0 });\n\n // BOFF-7979: only changed fields are sent; an emptied field is cleared (it used to be omitted, so the old value\n // stayed); success is confirmed (there was no message at all).\n const handleSave = async () => {\n if (!workspaceId || !workItemId || storyPoints.invalid) return;\n if (!form.title.trim()) {\n setSaveError('A task needs a title.');\n return;\n }\n const labelList = (v: string) =>\n v\n .split(',')\n .map((l) => l.trim())\n .filter(Boolean);\n const next: Record<string, unknown> = {\n title: form.title.trim(),\n description: form.description.trim() || null,\n priority: form.priority,\n type: form.type,\n storyPoints: storyPoints.value ?? null,\n labels: labelList(form.labels),\n dueDate: form.dueDate || null,\n };\n const before: Record<string, unknown> = {\n title: defaultForm.title,\n description: defaultForm.description.trim() || null,\n priority: defaultForm.priority,\n type: defaultForm.type,\n storyPoints: defaultForm.storyPoints === '' ? null : Number(defaultForm.storyPoints),\n labels: labelList(defaultForm.labels),\n dueDate: defaultForm.dueDate || null,\n };\n const input = Object.fromEntries(\n Object.entries(next).filter(([k, v]) => JSON.stringify(v) !== JSON.stringify(before[k]))\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(updateWorkItem({ variables: { workItemId, input } }));\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The task could not be saved.');\n }\n };\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\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 }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Error: {error.message}</div>;\n }\n\n if (!data?.planmagnetWorkItem) {\n return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n }\n\n const task = data.planmagnetWorkItem;\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 task\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(workItemId)}`)\n }\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Task</h1>\n <p className=\"text-sm text-muted-foreground\">{task.title}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving || storyPoints.invalid}>\n <Save className=\"mr-1 h-4 w-4\" />\n {saving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\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 bg-destructive/10 text-destructive rounded-md text-sm\">\n {saveError}\n </div>\n )}\n <Card>\n <CardHeader>\n <CardTitle>Task Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Title</label>\n <Input\n value={form.title}\n onChange={(e) => setForm({ ...form, title: 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-[100px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Priority</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n >\n {/* Every WorkItemPriority: NONE was missing, so a NONE task showed as \"low\" (BOFF-7985). */}\n {WORK_ITEM_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p.toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Type</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.type}\n onChange={(e) => setForm({ ...form, type: e.target.value })}\n >\n {['STORY', 'BUG', 'TASK', 'EPIC', 'SUB_TASK'].map((t) => (\n <option key={t} value={t}>\n {t.toLowerCase().replace('_', ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"task-story-points\" className=\"text-sm font-medium\">\n Story Points\n </label>\n <Input\n id=\"task-story-points\"\n type=\"number\"\n min={0}\n step={1}\n value={form.storyPoints}\n onChange={(e) => setForm({ ...form, storyPoints: e.target.value })}\n aria-invalid={storyPoints.invalid}\n className=\"mt-1\"\n />\n {storyPoints.invalid && (\n <p role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n Story points must be a whole number, 0 or more.\n </p>\n )}\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Due Date</label>\n <Input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Labels</label>\n <Input\n value={form.labels}\n onChange={(e) => setForm({ ...form, labels: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TasksEditTaskPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksEditTaskPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { 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 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { estimateHoursText, parseEstimateHours, parseIntField } from '../utils/formValidation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WORK_ITEM_PRIORITIES } from '../controls/WorkItemControls';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItem($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n title\n description\n type\n status\n priority\n key\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n }\n }\n`;\n\nconst UPDATE_WORK_ITEM = gql`\n mutation UpdateWorkItem($workItemId: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $workItemId, input: $input) {\n id\n title\n description\n type\n status\n priority\n storyPoints\n estimateMinutes\n dueDate\n labels\n updatedAt\n }\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport function TasksEditTaskPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from the route params.\n // For now we use a placeholder that the host shell should provide.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n const [updateWorkItem, { loading: saving }] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: ['GetWorkItems', 'GetWorkItemDetail', 'GetWorkItemCounts', 'ProjectBoard'],\n awaitRefetchQueries: true,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const workItem = data?.planmagnetWorkItem;\n\n const defaultForm = useMemo(\n () => ({\n title: workItem?.title ?? '',\n description: workItem?.description ?? '',\n priority: workItem?.priority ?? 'MEDIUM',\n type: workItem?.type ?? 'STORY',\n storyPoints: workItem?.storyPoints?.toString() ?? '',\n labels: (workItem?.labels ?? []).join(', '),\n // A date input needs YYYY-MM-DD; the API returns a full ISO timestamp.\n dueDate: workItem?.dueDate ? String(workItem.dueDate).slice(0, 10) : '',\n estimateHours: estimateHoursText(workItem?.estimateMinutes),\n }),\n [workItem]\n );\n\n const [formOverrides, setFormOverrides] = useState<Record<string, string>>({});\n const form = { ...defaultForm, ...formOverrides };\n const setForm = (updates: Record<string, string>) => setFormOverrides(updates);\n\n // Story points are a whole number, 0 or more (BOFF-7833); the service refuses anything else.\n const storyPoints = parseIntField(form.storyPoints, { min: 0 });\n const estimate = parseEstimateHours(form.estimateHours);\n\n // BOFF-7979: only changed fields are sent; an emptied field is cleared (it used to be omitted, so the old value\n // stayed); success is confirmed (there was no message at all).\n const handleSave = async () => {\n if (!workspaceId || !workItemId || storyPoints.invalid || estimate.invalid) return;\n if (!form.title.trim()) {\n setSaveError('A task needs a title.');\n return;\n }\n const labelList = (v: string) =>\n v\n .split(',')\n .map((l) => l.trim())\n .filter(Boolean);\n const next: Record<string, unknown> = {\n title: form.title.trim(),\n description: form.description.trim() || null,\n priority: form.priority,\n type: form.type,\n storyPoints: storyPoints.value ?? null,\n labels: labelList(form.labels),\n dueDate: form.dueDate || null,\n estimateMinutes: estimate.minutes ?? null,\n };\n const before: Record<string, unknown> = {\n title: defaultForm.title,\n description: defaultForm.description.trim() || null,\n priority: defaultForm.priority,\n type: defaultForm.type,\n storyPoints: defaultForm.storyPoints === '' ? null : Number(defaultForm.storyPoints),\n labels: labelList(defaultForm.labels),\n dueDate: defaultForm.dueDate || null,\n // Compared through the same hours round-trip as `next`, so an untouched estimate never counts as a change.\n estimateMinutes: parseEstimateHours(defaultForm.estimateHours).minutes ?? null,\n };\n const input = Object.fromEntries(\n Object.entries(next).filter(([k, v]) => JSON.stringify(v) !== JSON.stringify(before[k]))\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(updateWorkItem({ variables: { workItemId, input } }));\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The task could not be saved.');\n }\n };\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\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 }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Error: {error.message}</div>;\n }\n\n if (!data?.planmagnetWorkItem) {\n return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n }\n\n const task = data.planmagnetWorkItem;\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 task\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(workItemId)}`)\n }\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Task</h1>\n <p className=\"text-sm text-muted-foreground\">{task.title}</p>\n </div>\n </div>\n <Button\n size=\"sm\"\n onClick={handleSave}\n disabled={saving || storyPoints.invalid || estimate.invalid}\n >\n <Save className=\"mr-1 h-4 w-4\" />\n {saving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\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 bg-destructive/10 text-destructive rounded-md text-sm\">\n {saveError}\n </div>\n )}\n <Card>\n <CardHeader>\n <CardTitle>Task Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Title</label>\n <Input\n value={form.title}\n onChange={(e) => setForm({ ...form, title: 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-[100px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Priority</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n >\n {/* Every WorkItemPriority: NONE was missing, so a NONE task showed as \"low\" (BOFF-7985). */}\n {WORK_ITEM_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p.toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Type</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.type}\n onChange={(e) => setForm({ ...form, type: e.target.value })}\n >\n {['STORY', 'BUG', 'TASK', 'EPIC', 'SUB_TASK'].map((t) => (\n <option key={t} value={t}>\n {t.toLowerCase().replace('_', ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"task-story-points\" className=\"text-sm font-medium\">\n Story Points\n </label>\n <Input\n id=\"task-story-points\"\n type=\"number\"\n min={0}\n step={1}\n value={form.storyPoints}\n onChange={(e) => setForm({ ...form, storyPoints: e.target.value })}\n aria-invalid={storyPoints.invalid}\n className=\"mt-1\"\n />\n {storyPoints.invalid && (\n <p role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n Story points must be a whole number, 0 or more.\n </p>\n )}\n </div>\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Due Date</label>\n <Input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label htmlFor=\"task-estimate-hours\" className=\"text-sm font-medium\">\n Estimate (hours)\n </label>\n <Input\n id=\"task-estimate-hours\"\n type=\"number\"\n min={0}\n step={0.25}\n value={form.estimateHours}\n onChange={(e) => setForm({ ...form, estimateHours: e.target.value })}\n aria-invalid={estimate.invalid}\n aria-describedby=\"task-estimate-hint\"\n className=\"mt-1\"\n />\n {estimate.invalid ? (\n <p role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n The estimate must be a number of hours, 0 or more.\n </p>\n ) : (\n <p id=\"task-estimate-hint\" className=\"mt-1 text-xs text-muted-foreground\">\n Used as the task&apos;s duration on the critical path.\n </p>\n )}\n </div>\n <div>\n <label className=\"text-sm font-medium\">Labels</label>\n <Input\n value={form.labels}\n onChange={(e) => setForm({ ...form, labels: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -15,4 +15,15 @@ export declare function parseIntField(raw: string, opts: {
15
15
  value?: number;
16
16
  invalid: boolean;
17
17
  };
18
+ /**
19
+ * Parse an "estimate in hours" field into the API's `estimateMinutes` (whole minutes). Empty is "not set"
20
+ * (valid). Decimals are allowed (1.5 = 90 min); anything negative, non-numeric or above `maxHours` is invalid.
21
+ * The critical path uses this estimate as a task's duration when it has no start/due span.
22
+ */
23
+ export declare function parseEstimateHours(raw: string, maxHours?: number): {
24
+ minutes?: number;
25
+ invalid: boolean;
26
+ };
27
+ /** `estimateMinutes` → the hours shown in an estimate field ('' when unset); at most 2 decimals. */
28
+ export declare function estimateHoursText(minutes: number | null | undefined): string;
18
29
  //# sourceMappingURL=formValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formValidation.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/utils/formValidation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,yGAAyG;AACzG,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAOtC"}
1
+ {"version":3,"file":"formValidation.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/utils/formValidation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,yGAAyG;AACzG,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAOtC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,SAAQ,GACf;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAMxC;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAG5E"}
@@ -11,7 +11,19 @@ function t(e, t) {
11
11
  invalid: !1
12
12
  } : { invalid: !0 };
13
13
  }
14
+ function n(e, t = 1e4) {
15
+ let n = e.trim();
16
+ if (n === "") return { invalid: !1 };
17
+ let r = Number(n);
18
+ return Number.isFinite(r) && r >= 0 && r <= t ? {
19
+ minutes: Math.round(r * 60),
20
+ invalid: !1
21
+ } : { invalid: !0 };
22
+ }
23
+ function r(e) {
24
+ return e == null ? "" : String(Math.round(e / 60 * 100) / 100);
25
+ }
14
26
  //#endregion
15
- export { e as endBeforeStart, t as parseIntField };
27
+ export { e as endBeforeStart, r as estimateHoursText, n as parseEstimateHours, t as parseIntField };
16
28
 
17
29
  //# sourceMappingURL=formValidation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formValidation.js","names":[],"sources":["../../../src/planmagnet/utils/formValidation.ts"],"sourcesContent":["/**\n * Form-side checks (BOFF-7833). The service is the real gate — every rule here is also enforced there, and a\n * refusal comes back with its reason — these only let a form say so before sending.\n */\n\n/** True when both dates are set and the end (YYYY-MM-DD) is before the start. Equal days are allowed. */\nexport function endBeforeStart(\n start: string | null | undefined,\n end: string | null | undefined\n): boolean {\n if (!start || !end) return false;\n return end.slice(0, 10) < start.slice(0, 10);\n}\n\n/**\n * Parse a whole-number field. Empty is \"not set\" (valid). Anything that is not a whole number inside\n * [min, max] is invalid.\n */\nexport function parseIntField(\n raw: string,\n opts: { min: number; max?: number }\n): { value?: number; invalid: boolean } {\n const trimmed = raw.trim();\n if (trimmed === '') return { invalid: false };\n const value = Number(trimmed);\n const ok =\n Number.isInteger(value) && value >= opts.min && (opts.max === undefined || value <= opts.max);\n return ok ? { value, invalid: false } : { invalid: true };\n}\n"],"mappings":";AAMA,SAAgB,EACd,GACA,GACS;AAET,QADI,CAAC,KAAS,CAAC,IAAY,KACpB,EAAI,MAAM,GAAG,GAAG,GAAG,EAAM,MAAM,GAAG,GAAG;;AAO9C,SAAgB,EACd,GACA,GACsC;CACtC,IAAM,IAAU,EAAI,MAAM;AAC1B,KAAI,MAAY,GAAI,QAAO,EAAE,SAAS,IAAO;CAC7C,IAAM,IAAQ,OAAO,EAAQ;AAG7B,QADE,OAAO,UAAU,EAAM,IAAI,KAAS,EAAK,QAAQ,EAAK,QAAQ,KAAA,KAAa,KAAS,EAAK,OAC/E;EAAE;EAAO,SAAS;EAAO,GAAG,EAAE,SAAS,IAAM"}
1
+ {"version":3,"file":"formValidation.js","names":[],"sources":["../../../src/planmagnet/utils/formValidation.ts"],"sourcesContent":["/**\n * Form-side checks (BOFF-7833). The service is the real gate — every rule here is also enforced there, and a\n * refusal comes back with its reason — these only let a form say so before sending.\n */\n\n/** True when both dates are set and the end (YYYY-MM-DD) is before the start. Equal days are allowed. */\nexport function endBeforeStart(\n start: string | null | undefined,\n end: string | null | undefined\n): boolean {\n if (!start || !end) return false;\n return end.slice(0, 10) < start.slice(0, 10);\n}\n\n/**\n * Parse a whole-number field. Empty is \"not set\" (valid). Anything that is not a whole number inside\n * [min, max] is invalid.\n */\nexport function parseIntField(\n raw: string,\n opts: { min: number; max?: number }\n): { value?: number; invalid: boolean } {\n const trimmed = raw.trim();\n if (trimmed === '') return { invalid: false };\n const value = Number(trimmed);\n const ok =\n Number.isInteger(value) && value >= opts.min && (opts.max === undefined || value <= opts.max);\n return ok ? { value, invalid: false } : { invalid: true };\n}\n\n/**\n * Parse an \"estimate in hours\" field into the API's `estimateMinutes` (whole minutes). Empty is \"not set\"\n * (valid). Decimals are allowed (1.5 = 90 min); anything negative, non-numeric or above `maxHours` is invalid.\n * The critical path uses this estimate as a task's duration when it has no start/due span.\n */\nexport function parseEstimateHours(\n raw: string,\n maxHours = 10000\n): { minutes?: number; invalid: boolean } {\n const trimmed = raw.trim();\n if (trimmed === '') return { invalid: false };\n const hours = Number(trimmed);\n const ok = Number.isFinite(hours) && hours >= 0 && hours <= maxHours;\n return ok ? { minutes: Math.round(hours * 60), invalid: false } : { invalid: true };\n}\n\n/** `estimateMinutes` → the hours shown in an estimate field ('' when unset); at most 2 decimals. */\nexport function estimateHoursText(minutes: number | null | undefined): string {\n if (minutes === null || minutes === undefined) return '';\n return String(Math.round((minutes / 60) * 100) / 100);\n}\n"],"mappings":";AAMA,SAAgB,EACd,GACA,GACS;AAET,QADI,CAAC,KAAS,CAAC,IAAY,KACpB,EAAI,MAAM,GAAG,GAAG,GAAG,EAAM,MAAM,GAAG,GAAG;;AAO9C,SAAgB,EACd,GACA,GACsC;CACtC,IAAM,IAAU,EAAI,MAAM;AAC1B,KAAI,MAAY,GAAI,QAAO,EAAE,SAAS,IAAO;CAC7C,IAAM,IAAQ,OAAO,EAAQ;AAG7B,QADE,OAAO,UAAU,EAAM,IAAI,KAAS,EAAK,QAAQ,EAAK,QAAQ,KAAA,KAAa,KAAS,EAAK,OAC/E;EAAE;EAAO,SAAS;EAAO,GAAG,EAAE,SAAS,IAAM;;AAQ3D,SAAgB,EACd,GACA,IAAW,KAC6B;CACxC,IAAM,IAAU,EAAI,MAAM;AAC1B,KAAI,MAAY,GAAI,QAAO,EAAE,SAAS,IAAO;CAC7C,IAAM,IAAQ,OAAO,EAAQ;AAE7B,QADW,OAAO,SAAS,EAAM,IAAI,KAAS,KAAK,KAAS,IAChD;EAAE,SAAS,KAAK,MAAM,IAAQ,GAAG;EAAE,SAAS;EAAO,GAAG,EAAE,SAAS,IAAM;;AAIrF,SAAgB,EAAkB,GAA4C;AAE5E,QADI,KAAY,OAAsC,KAC/C,OAAO,KAAK,MAAO,IAAU,KAAM,IAAI,GAAG,IAAI"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.928.10",
3
+ "version": "2026.928.11",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [