@burdenoff/microfe-planmagnet 2026.927.4 → 2026.927.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/generated/global-types.d.ts +9 -1
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-operations.d.ts +0 -12
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +14 -10
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +149 -127
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.d.ts +2 -0
- package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.js +492 -225
- package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +142 -139
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/FeatureTriage.d.ts +21 -0
- package/dist/planmagnet/controls/FeatureTriage.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeatureTriage.js +398 -0
- package/dist/planmagnet/controls/FeatureTriage.js.map +1 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.js +76 -73
- package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +47 -44
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.js +47 -44
- package/dist/planmagnet/controls/ReleaseControls.js.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.js +68 -65
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
- package/dist/planmagnet/hooks/useCounts.d.ts +39 -0
- package/dist/planmagnet/hooks/useCounts.d.ts.map +1 -0
- package/dist/planmagnet/hooks/useCounts.js +209 -0
- package/dist/planmagnet/hooks/useCounts.js.map +1 -0
- package/dist/planmagnet/import/exportColumns.d.ts +52 -0
- package/dist/planmagnet/import/exportColumns.d.ts.map +1 -0
- package/dist/planmagnet/import/exportColumns.js +81 -0
- package/dist/planmagnet/import/exportColumns.js.map +1 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +255 -242
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +199 -160
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +147 -144
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsGoalsPagePage.js +2 -7
- package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +173 -173
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +120 -117
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +97 -117
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +168 -103
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +117 -103
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +4 -8
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -90
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +12 -2
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -2
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +48 -71
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +17 -13
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +11 -8
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +1 -1
- 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 +152 -139
- 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 +75 -74
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +7 -0
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +39 -201
- package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +118 -99
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksImportPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksImportPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksImportPagePage.js +309 -0
- package/dist/planmagnet/pages/TasksImportPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +110 -104
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +99 -177
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -4
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -1
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +10 -4
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +151 -138
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +32 -30
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/dist/planmagnet/utils/mutationResult.d.ts +10 -0
- package/dist/planmagnet/utils/mutationResult.d.ts.map +1 -1
- package/dist/planmagnet/utils/mutationResult.js +2 -1
- package/dist/planmagnet/utils/mutationResult.js.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.d.ts +2 -0
- package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.js +9 -8
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { useState as
|
|
7
|
-
import { Pencil as
|
|
8
|
-
import { gql as
|
|
9
|
-
import { useMutation as
|
|
10
|
-
import { jsx as
|
|
11
|
-
import { Button as
|
|
2
|
+
import { LEAVES_PAGE_CONTEXT as t, throwOnError as n } from "../utils/mutationResult.js";
|
|
3
|
+
import { detailPath as r } from "../utils/rowNavigation.js";
|
|
4
|
+
import { ConfirmButton as i, ErrorLine as a } from "./ConfirmButton.js";
|
|
5
|
+
import { enumLabel as o } from "./WorkItemControls.js";
|
|
6
|
+
import { useState as s } from "react";
|
|
7
|
+
import { Pencil as c, Trash2 as l } from "lucide-react";
|
|
8
|
+
import { gql as u } from "@apollo/client";
|
|
9
|
+
import { useMutation as d } from "@apollo/client/react";
|
|
10
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
11
|
+
import { Button as m } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/planmagnet/controls/ReleaseControls.tsx
|
|
13
|
-
var
|
|
13
|
+
var h = [
|
|
14
14
|
"DRAFT",
|
|
15
15
|
"PLANNING",
|
|
16
16
|
"IN_PROGRESS",
|
|
@@ -19,14 +19,14 @@ var m = [
|
|
|
19
19
|
"RELEASED",
|
|
20
20
|
"ROLLED_BACK",
|
|
21
21
|
"CANCELLED"
|
|
22
|
-
],
|
|
22
|
+
], g = [
|
|
23
23
|
"GetRelease",
|
|
24
24
|
"ListReleases",
|
|
25
25
|
"ListReleasesForProduct",
|
|
26
26
|
"ListReleasesForProducts",
|
|
27
27
|
"ReleaseReadiness",
|
|
28
28
|
"GetReleaseForEdit"
|
|
29
|
-
],
|
|
29
|
+
], _ = u`
|
|
30
30
|
mutation ReleaseControlsStatus($id: ID!, $input: PlanmagnetUpdateReleaseInput!) {
|
|
31
31
|
planmagnetUpdateRelease(id: $id, input: $input) {
|
|
32
32
|
id
|
|
@@ -34,75 +34,78 @@ var m = [
|
|
|
34
34
|
releasedAt
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
`,
|
|
37
|
+
`, v = u`
|
|
38
38
|
mutation ReleaseControlsDelete($id: ID!) {
|
|
39
39
|
planmagnetDeleteRelease(id: $id)
|
|
40
40
|
}
|
|
41
41
|
`;
|
|
42
|
-
function
|
|
43
|
-
let
|
|
44
|
-
refetchQueries:
|
|
42
|
+
function y({ releaseId: u, version: y, status: b }) {
|
|
43
|
+
let x = e(), [S, C] = d(_, {
|
|
44
|
+
refetchQueries: g,
|
|
45
45
|
awaitRefetchQueries: !0
|
|
46
|
-
}), [
|
|
47
|
-
|
|
46
|
+
}), [w, T] = d(v, {
|
|
47
|
+
refetchQueries: g.filter((e) => e !== "GetRelease" && e !== "ReleaseReadiness"),
|
|
48
|
+
context: t
|
|
49
|
+
}), [E, D] = s(null), O = C.loading || T.loading, k = h.includes(b) ? h : [b, ...h], A = async (e) => {
|
|
50
|
+
D(null);
|
|
48
51
|
try {
|
|
49
|
-
await
|
|
50
|
-
id:
|
|
52
|
+
await n(S({ variables: {
|
|
53
|
+
id: u,
|
|
51
54
|
input: { status: e }
|
|
52
55
|
} }));
|
|
53
56
|
} catch (e) {
|
|
54
|
-
|
|
57
|
+
D(e instanceof Error ? e.message : "The status could not be changed.");
|
|
55
58
|
}
|
|
56
|
-
},
|
|
57
|
-
|
|
59
|
+
}, j = async () => {
|
|
60
|
+
D(null);
|
|
58
61
|
try {
|
|
59
|
-
await
|
|
62
|
+
await n(w({ variables: { id: u } })), x("/products/releases-page");
|
|
60
63
|
} catch (e) {
|
|
61
|
-
|
|
64
|
+
D(e instanceof Error ? e.message : "The release could not be deleted.");
|
|
62
65
|
}
|
|
63
66
|
};
|
|
64
|
-
return /* @__PURE__ */
|
|
67
|
+
return /* @__PURE__ */ p("div", {
|
|
65
68
|
className: "flex flex-col items-end gap-2",
|
|
66
|
-
children: [/* @__PURE__ */
|
|
69
|
+
children: [/* @__PURE__ */ p("div", {
|
|
67
70
|
className: "flex flex-wrap items-center justify-end gap-2",
|
|
68
71
|
children: [
|
|
69
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ f("select", {
|
|
70
73
|
"aria-label": "Release status",
|
|
71
74
|
"data-testid": "release-status",
|
|
72
75
|
className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
|
|
73
|
-
value:
|
|
74
|
-
disabled:
|
|
76
|
+
value: b,
|
|
77
|
+
disabled: O,
|
|
75
78
|
onChange: (e) => {
|
|
76
|
-
e.target.value !==
|
|
79
|
+
e.target.value !== b && A(e.target.value);
|
|
77
80
|
},
|
|
78
|
-
children:
|
|
81
|
+
children: k.map((e) => /* @__PURE__ */ f("option", {
|
|
79
82
|
value: e,
|
|
80
|
-
children:
|
|
83
|
+
children: o(e)
|
|
81
84
|
}, e))
|
|
82
85
|
}),
|
|
83
|
-
/* @__PURE__ */
|
|
86
|
+
/* @__PURE__ */ p(m, {
|
|
84
87
|
size: "sm",
|
|
85
88
|
variant: "outline",
|
|
86
89
|
"data-testid": "release-edit",
|
|
87
|
-
onClick: () =>
|
|
88
|
-
children: [/* @__PURE__ */
|
|
90
|
+
onClick: () => x(r("/products/edit-release-page", u)),
|
|
91
|
+
children: [/* @__PURE__ */ f(c, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
89
92
|
}),
|
|
90
|
-
/* @__PURE__ */
|
|
93
|
+
/* @__PURE__ */ f(i, {
|
|
91
94
|
label: "Delete",
|
|
92
95
|
variant: "outline",
|
|
93
|
-
icon: /* @__PURE__ */
|
|
94
|
-
title: `Delete release ${
|
|
96
|
+
icon: /* @__PURE__ */ f(l, { className: "mr-1 h-4 w-4" }),
|
|
97
|
+
title: `Delete release ${y}?`,
|
|
95
98
|
description: "The release is deleted; its work items stay. This cannot be undone here.",
|
|
96
99
|
confirmLabel: "Delete release",
|
|
97
100
|
destructive: !0,
|
|
98
|
-
disabled:
|
|
99
|
-
onConfirm: () => void
|
|
101
|
+
disabled: O,
|
|
102
|
+
onConfirm: () => void j()
|
|
100
103
|
})
|
|
101
104
|
]
|
|
102
|
-
}),
|
|
105
|
+
}), E && /* @__PURE__ */ f(a, { message: E })]
|
|
103
106
|
});
|
|
104
107
|
}
|
|
105
108
|
//#endregion
|
|
106
|
-
export {
|
|
109
|
+
export { h as RELEASE_STATUSES, y as ReleaseHeaderActions };
|
|
107
110
|
|
|
108
111
|
//# sourceMappingURL=ReleaseControls.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReleaseControls.js","names":[],"sources":["../../../src/planmagnet/controls/ReleaseControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Release controls (BOFF-7952). A release started in Planning and could never be released: status showed only as\n * badges, the Edit Release page was linked from nowhere, and nothing deleted a release. The Quality Gates card even\n * said the release \"cannot be released until the gates pass\" — with nothing to click once they had. The service\n * refuses RELEASED while a gate is open; that reason is shown here.\n */\n\n/** Every ReleaseStatus value (the Edit page's list omitted STAGING — BOFF-7985). */\nexport const RELEASE_STATUSES = [\n 'DRAFT',\n 'PLANNING',\n 'IN_PROGRESS',\n 'READY',\n 'STAGING',\n 'RELEASED',\n 'ROLLED_BACK',\n 'CANCELLED',\n] as const;\n\nconst RELEASE_QUERIES = [\n 'GetRelease',\n 'ListReleases',\n 'ListReleasesForProduct',\n 'ListReleasesForProducts',\n 'ReleaseReadiness',\n 'GetReleaseForEdit',\n];\n\nconst UPDATE = gql`\n mutation ReleaseControlsStatus($id: ID!, $input: PlanmagnetUpdateReleaseInput!) {\n planmagnetUpdateRelease(id: $id, input: $input) {\n id\n status\n releasedAt\n }\n }\n`;\nconst DELETE = gql`\n mutation ReleaseControlsDelete($id: ID!) {\n planmagnetDeleteRelease(id: $id)\n }\n`;\n\nexport function ReleaseHeaderActions({\n releaseId,\n version,\n status,\n}: {\n releaseId: string;\n version: string;\n status: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, {\n refetchQueries: RELEASE_QUERIES,\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: RELEASE_QUERIES.filter((q) => q !== 'GetRelease' && q !== 'ReleaseReadiness'),\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (RELEASE_STATUSES as readonly string[]).includes(status)\n ? RELEASE_STATUSES\n : [status, ...RELEASE_STATUSES];\n\n const onStatus = async (next: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: releaseId, input: { status: next } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: releaseId } }));\n navigate('/products/releases-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The release could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Release status\"\n data-testid=\"release-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"release-edit\"\n onClick={() => navigate(detailPath('/products/edit-release-page', releaseId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete release ${version}?`}\n description=\"The release is deleted; its work items stay. This cannot be undone here.\"\n confirmLabel=\"Delete release\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,IAAa,IAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAS,CAAG;;;;;;;;GASZ,IAAS,CAAG;;;;;AAMlB,SAAgB,EAAqB,EACnC,cACA,YACA,aAKC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAQ;EAChD,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAQ
|
|
1
|
+
{"version":3,"file":"ReleaseControls.js","names":[],"sources":["../../../src/planmagnet/controls/ReleaseControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Release controls (BOFF-7952). A release started in Planning and could never be released: status showed only as\n * badges, the Edit Release page was linked from nowhere, and nothing deleted a release. The Quality Gates card even\n * said the release \"cannot be released until the gates pass\" — with nothing to click once they had. The service\n * refuses RELEASED while a gate is open; that reason is shown here.\n */\n\n/** Every ReleaseStatus value (the Edit page's list omitted STAGING — BOFF-7985). */\nexport const RELEASE_STATUSES = [\n 'DRAFT',\n 'PLANNING',\n 'IN_PROGRESS',\n 'READY',\n 'STAGING',\n 'RELEASED',\n 'ROLLED_BACK',\n 'CANCELLED',\n] as const;\n\nconst RELEASE_QUERIES = [\n 'GetRelease',\n 'ListReleases',\n 'ListReleasesForProduct',\n 'ListReleasesForProducts',\n 'ReleaseReadiness',\n 'GetReleaseForEdit',\n];\n\nconst UPDATE = gql`\n mutation ReleaseControlsStatus($id: ID!, $input: PlanmagnetUpdateReleaseInput!) {\n planmagnetUpdateRelease(id: $id, input: $input) {\n id\n status\n releasedAt\n }\n }\n`;\nconst DELETE = gql`\n mutation ReleaseControlsDelete($id: ID!) {\n planmagnetDeleteRelease(id: $id)\n }\n`;\n\nexport function ReleaseHeaderActions({\n releaseId,\n version,\n status,\n}: {\n releaseId: string;\n version: string;\n status: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, {\n refetchQueries: RELEASE_QUERIES,\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: RELEASE_QUERIES.filter((q) => q !== 'GetRelease' && q !== 'ReleaseReadiness'),\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (RELEASE_STATUSES as readonly string[]).includes(status)\n ? RELEASE_STATUSES\n : [status, ...RELEASE_STATUSES];\n\n const onStatus = async (next: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: releaseId, input: { status: next } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: releaseId } }));\n navigate('/products/releases-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The release could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Release status\"\n data-testid=\"release-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"release-edit\"\n onClick={() => navigate(detailPath('/products/edit-release-page', releaseId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete release ${version}?`}\n description=\"The release is deleted; its work items stay. This cannot be undone here.\"\n confirmLabel=\"Delete release\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,IAAa,IAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAS,CAAG;;;;;;;;GASZ,IAAS,CAAG;;;;;AAMlB,SAAgB,EAAqB,EACnC,cACA,YACA,aAKC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAQ;EAChD,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAQ;EAChD,gBAAgB,EAAgB,QAAQ,MAAM,MAAM,gBAAgB,MAAM,mBAAmB;EAC7F,SAAS;EACV,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAO,EAAY,WAAW,EAAY,SAC1C,IAAY,EAAuC,SAAS,EAAO,GACrE,IACA,CAAC,GAAQ,GAAG,EAAiB,EAE3B,IAAW,OAAO,MAAiB;AACvC,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE,IAAI;IAAW,OAAO,EAAE,QAAQ,GAAM;IAAE,EAAE,CAAC,CAAC;WAC9E,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,mCAAmC;;IAG3E,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC,EAC5D,EAAS,0BAA0B;WAC5B,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,cAAW;KACX,eAAY;KACZ,WAAU;KACV,OAAO;KACP,UAAU;KACV,WAAW,MAAM;AACf,MAAI,EAAE,OAAO,UAAU,KAAa,EAAS,EAAE,OAAO,MAAM;;eAG7D,EAAS,KAAK,MACb,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAU,EAAE;MACN,EAFI,EAEJ,CACT;KACK,CAAA;IACT,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAY;KACZ,eAAe,EAAS,EAAW,+BAA+B,EAAU,CAAC;eAJ/E,CAME,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;IACT,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;KACzC,OAAO,kBAAkB,EAAQ;KACjC,aAAY;KACZ,cAAa;KACb,aAAA;KACA,UAAU;KACV,iBAAiB,KAAK,GAAU;KAChC,CAAA;IACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkItemControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"names":[],"mappings":"AAWA;;;;;;;;GAQG;AAEH,eAAO,MAAM,kBAAkB,0FAQrB,CAAC;AACX,eAAO,MAAM,oBAAoB,wDAAyD,CAAC;AAE3F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C;AA0CD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,EACjC,UAAU,EACV,KAAK,EACL,KAAK,EACL,OAAO,EACP,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,2CAyBA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;EAmBtE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,2CAkCA;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,UAAU,GACX,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,2CAcA;AAED,kDAAkD;AAClD,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,
|
|
1
|
+
{"version":3,"file":"WorkItemControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"names":[],"mappings":"AAWA;;;;;;;;GAQG;AAEH,eAAO,MAAM,kBAAkB,0FAQrB,CAAC;AACX,eAAO,MAAM,oBAAoB,wDAAyD,CAAC;AAE3F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C;AA0CD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,EACjC,UAAU,EACV,KAAK,EACL,KAAK,EACL,OAAO,EACP,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,2CAyBA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;EAmBtE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,2CAkCA;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,UAAU,GACX,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,2CAcA;AAED,kDAAkD;AAClD,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,2CA6CA"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { usePersonProfile as n } from "../people/People.js";
|
|
3
|
-
import {
|
|
4
|
-
import { ConfirmButton as
|
|
5
|
-
import { useState as
|
|
6
|
-
import { Pencil as
|
|
7
|
-
import { gql as
|
|
8
|
-
import { useMutation as
|
|
9
|
-
import { jsx as
|
|
10
|
-
import { Button as
|
|
11
|
-
import { useWorkspaceDirectory as
|
|
3
|
+
import { LEAVES_PAGE_CONTEXT as r, throwOnError as i } from "../utils/mutationResult.js";
|
|
4
|
+
import { ConfirmButton as a, ErrorLine as o } from "./ConfirmButton.js";
|
|
5
|
+
import { useState as s } from "react";
|
|
6
|
+
import { Pencil as c, Trash2 as l } from "lucide-react";
|
|
7
|
+
import { gql as u } from "@apollo/client";
|
|
8
|
+
import { useMutation as d } from "@apollo/client/react";
|
|
9
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
10
|
+
import { Button as m } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { useWorkspaceDirectory as h } from "@burdenoff/fe-libs/sharing";
|
|
12
12
|
//#region src/planmagnet/controls/WorkItemControls.tsx
|
|
13
|
-
var
|
|
13
|
+
var g = [
|
|
14
14
|
"BACKLOG",
|
|
15
15
|
"TODO",
|
|
16
16
|
"IN_PROGRESS",
|
|
@@ -18,18 +18,18 @@ var h = [
|
|
|
18
18
|
"TESTING",
|
|
19
19
|
"DONE",
|
|
20
20
|
"CANCELLED"
|
|
21
|
-
],
|
|
21
|
+
], _ = [
|
|
22
22
|
"NONE",
|
|
23
23
|
"LOW",
|
|
24
24
|
"MEDIUM",
|
|
25
25
|
"HIGH",
|
|
26
26
|
"CRITICAL"
|
|
27
27
|
];
|
|
28
|
-
function
|
|
28
|
+
function v(e) {
|
|
29
29
|
let t = e.toLowerCase().replace(/_/g, " ");
|
|
30
30
|
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
31
31
|
}
|
|
32
|
-
var
|
|
32
|
+
var y = u`
|
|
33
33
|
mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {
|
|
34
34
|
planmagnetUpdateWorkItem(id: $id, input: $input) {
|
|
35
35
|
id
|
|
@@ -38,30 +38,30 @@ var v = l`
|
|
|
38
38
|
assigneeId
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
`,
|
|
41
|
+
`, b = u`
|
|
42
42
|
mutation WorkItemControlsDelete($id: ID!) {
|
|
43
43
|
planmagnetDeleteWorkItem(id: $id)
|
|
44
44
|
}
|
|
45
|
-
`,
|
|
45
|
+
`, x = [
|
|
46
46
|
"GetWorkItems",
|
|
47
47
|
"GetWorkItemCounts",
|
|
48
48
|
"ProjectBoard",
|
|
49
49
|
"GetDashboardData"
|
|
50
|
-
],
|
|
50
|
+
], S = [
|
|
51
51
|
"GetWorkItemDetail",
|
|
52
52
|
"WorkItemActivity",
|
|
53
|
-
...
|
|
54
|
-
],
|
|
55
|
-
function
|
|
56
|
-
let [t, n] =
|
|
57
|
-
refetchQueries:
|
|
53
|
+
...x
|
|
54
|
+
], C = "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]";
|
|
55
|
+
function w(e) {
|
|
56
|
+
let [t, n] = d(y, {
|
|
57
|
+
refetchQueries: S,
|
|
58
58
|
awaitRefetchQueries: !0
|
|
59
|
-
}), [
|
|
59
|
+
}), [r, a] = s(null);
|
|
60
60
|
return {
|
|
61
61
|
save: async (n) => {
|
|
62
62
|
a(null);
|
|
63
63
|
try {
|
|
64
|
-
await
|
|
64
|
+
await i(t({ variables: {
|
|
65
65
|
id: e,
|
|
66
66
|
input: n
|
|
67
67
|
} }));
|
|
@@ -70,31 +70,31 @@ function C(e) {
|
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
saving: n.loading,
|
|
73
|
-
error:
|
|
73
|
+
error: r
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
function
|
|
77
|
-
let { save:
|
|
78
|
-
return /* @__PURE__ */
|
|
76
|
+
function T({ workItemId: e, field: t, value: n, options: r, label: i }) {
|
|
77
|
+
let { save: a, saving: s, error: c } = w(e), l = r.includes(n) ? r : [n, ...r];
|
|
78
|
+
return /* @__PURE__ */ p("div", {
|
|
79
79
|
className: "flex flex-col items-end gap-1",
|
|
80
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ f("select", {
|
|
81
81
|
"aria-label": i,
|
|
82
82
|
"data-testid": `task-${t}`,
|
|
83
|
-
className:
|
|
83
|
+
className: C,
|
|
84
84
|
value: n,
|
|
85
85
|
disabled: s,
|
|
86
86
|
onChange: (e) => {
|
|
87
|
-
e.target.value !== n &&
|
|
87
|
+
e.target.value !== n && a({ [t]: e.target.value });
|
|
88
88
|
},
|
|
89
|
-
children: l.map((e) => /* @__PURE__ */
|
|
89
|
+
children: l.map((e) => /* @__PURE__ */ f("option", {
|
|
90
90
|
value: e,
|
|
91
|
-
children:
|
|
91
|
+
children: v(e)
|
|
92
92
|
}, e))
|
|
93
|
-
}), c && /* @__PURE__ */
|
|
93
|
+
}), c && /* @__PURE__ */ f(o, { message: c })]
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
function
|
|
97
|
-
let { workspaceId: r, authToken: i, apiGatewayUrl: a } = e(), o =
|
|
96
|
+
function E(t) {
|
|
97
|
+
let { workspaceId: r, authToken: i, apiGatewayUrl: a } = e(), o = h({
|
|
98
98
|
workspaceId: r ?? "",
|
|
99
99
|
authToken: i ?? void 0,
|
|
100
100
|
apiGatewayUrl: a ?? void 0
|
|
@@ -112,32 +112,32 @@ function T(t) {
|
|
|
112
112
|
fetchMore: o.fetchMore
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
-
function
|
|
116
|
-
let { options: i, loading: a, hasMore: o, fetchMore: s } =
|
|
117
|
-
return /* @__PURE__ */
|
|
115
|
+
function D({ id: e, value: t, onChange: n, disabled: r = !1 }) {
|
|
116
|
+
let { options: i, loading: a, hasMore: o, fetchMore: s } = E(t || null);
|
|
117
|
+
return /* @__PURE__ */ p("div", {
|
|
118
118
|
className: "flex flex-col gap-1",
|
|
119
119
|
children: [
|
|
120
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ p("select", {
|
|
121
121
|
id: e,
|
|
122
122
|
"aria-label": e ? void 0 : "Assignee",
|
|
123
123
|
"data-testid": "assignee-select",
|
|
124
|
-
className:
|
|
124
|
+
className: C,
|
|
125
125
|
value: t,
|
|
126
126
|
disabled: r,
|
|
127
127
|
onChange: (e) => n(e.target.value),
|
|
128
|
-
children: [/* @__PURE__ */
|
|
128
|
+
children: [/* @__PURE__ */ f("option", {
|
|
129
129
|
value: "",
|
|
130
130
|
children: "Unassigned"
|
|
131
|
-
}), i.map((e) => /* @__PURE__ */
|
|
131
|
+
}), i.map((e) => /* @__PURE__ */ f("option", {
|
|
132
132
|
value: e.id,
|
|
133
133
|
children: e.name
|
|
134
134
|
}, e.id))]
|
|
135
135
|
}),
|
|
136
|
-
a && i.length === 0 && /* @__PURE__ */
|
|
136
|
+
a && i.length === 0 && /* @__PURE__ */ f("span", {
|
|
137
137
|
className: "text-xs text-muted-foreground",
|
|
138
138
|
children: "Loading members…"
|
|
139
139
|
}),
|
|
140
|
-
o && /* @__PURE__ */
|
|
140
|
+
o && /* @__PURE__ */ f("button", {
|
|
141
141
|
type: "button",
|
|
142
142
|
className: "self-start text-xs text-primary hover:underline",
|
|
143
143
|
onClick: s,
|
|
@@ -146,53 +146,56 @@ function E({ id: e, value: t, onChange: n, disabled: r = !1 }) {
|
|
|
146
146
|
]
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
|
-
function
|
|
150
|
-
let { save: n, saving: r, error: i } =
|
|
151
|
-
return /* @__PURE__ */
|
|
149
|
+
function O({ workItemId: e, assigneeId: t }) {
|
|
150
|
+
let { save: n, saving: r, error: i } = w(e);
|
|
151
|
+
return /* @__PURE__ */ p("div", {
|
|
152
152
|
className: "flex flex-col items-end gap-1",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
153
|
+
children: [/* @__PURE__ */ f(D, {
|
|
154
154
|
value: t ?? "",
|
|
155
155
|
disabled: r,
|
|
156
156
|
onChange: (e) => {
|
|
157
157
|
e !== (t ?? "") && n({ assigneeId: e || null });
|
|
158
158
|
}
|
|
159
|
-
}), i && /* @__PURE__ */
|
|
159
|
+
}), i && /* @__PURE__ */ f(o, { message: i })]
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
|
-
function
|
|
163
|
-
let
|
|
164
|
-
|
|
162
|
+
function k({ workItemId: e, title: n }) {
|
|
163
|
+
let u = t(), [h, g] = d(b, {
|
|
164
|
+
refetchQueries: x,
|
|
165
|
+
context: r
|
|
166
|
+
}), [_, v] = s(null), y = async () => {
|
|
167
|
+
v(null);
|
|
165
168
|
try {
|
|
166
|
-
await
|
|
169
|
+
await i(h({ variables: { id: e } })), u("/tasks/tasks-page");
|
|
167
170
|
} catch (e) {
|
|
168
|
-
|
|
171
|
+
v(e instanceof Error ? e.message : "The task could not be deleted.");
|
|
169
172
|
}
|
|
170
173
|
};
|
|
171
|
-
return /* @__PURE__ */
|
|
174
|
+
return /* @__PURE__ */ p("div", {
|
|
172
175
|
className: "flex flex-col items-end gap-2",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ p("div", {
|
|
174
177
|
className: "flex gap-2",
|
|
175
|
-
children: [/* @__PURE__ */
|
|
178
|
+
children: [/* @__PURE__ */ p(m, {
|
|
176
179
|
size: "sm",
|
|
177
180
|
variant: "outline",
|
|
178
181
|
"data-testid": "task-edit",
|
|
179
|
-
onClick: () =>
|
|
180
|
-
children: [/* @__PURE__ */
|
|
181
|
-
}), /* @__PURE__ */
|
|
182
|
+
onClick: () => u(`/tasks/edit-task-page?taskId=${encodeURIComponent(e)}`),
|
|
183
|
+
children: [/* @__PURE__ */ f(c, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
184
|
+
}), /* @__PURE__ */ f(a, {
|
|
182
185
|
label: "Delete",
|
|
183
186
|
variant: "outline",
|
|
184
|
-
icon: /* @__PURE__ */
|
|
187
|
+
icon: /* @__PURE__ */ f(l, { className: "mr-1 h-4 w-4" }),
|
|
185
188
|
title: `Delete "${n}"?`,
|
|
186
189
|
description: "The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.",
|
|
187
190
|
confirmLabel: "Delete task",
|
|
188
191
|
destructive: !0,
|
|
189
|
-
disabled:
|
|
190
|
-
onConfirm: () => void
|
|
192
|
+
disabled: g.loading,
|
|
193
|
+
onConfirm: () => void y()
|
|
191
194
|
})]
|
|
192
|
-
}),
|
|
195
|
+
}), _ && /* @__PURE__ */ f(o, { message: _ })]
|
|
193
196
|
});
|
|
194
197
|
}
|
|
195
198
|
//#endregion
|
|
196
|
-
export {
|
|
199
|
+
export { D as AssigneeSelect, _ as WORK_ITEM_PRIORITIES, g as WORK_ITEM_STATUSES, O as WorkItemAssigneeSelect, T as WorkItemEnumSelect, k as WorkItemHeaderActions, v as enumLabel };
|
|
197
200
|
|
|
198
201
|
//# sourceMappingURL=WorkItemControls.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkItemControls.js","names":[],"sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { useWorkspaceDirectory } from '@burdenoff/fe-libs/sharing';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePersonProfile } from '../people/People';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\n\n/**\n * Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or\n * assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status\n * could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team\n * page, sprint \"Team Members\" list and board card read \"Unassigned\".\n *\n * Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records\n * its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.\n */\n\nexport const WORK_ITEM_STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nexport const WORK_ITEM_PRIORITIES = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const;\n\nexport function enumLabel(value: string): string {\n const words = value.toLowerCase().replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst UPDATE_WORK_ITEM = gql`\n mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $id, input: $input) {\n id\n status\n priority\n assigneeId\n }\n }\n`;\nconst DELETE_WORK_ITEM = gql`\n mutation WorkItemControlsDelete($id: ID!) {\n planmagnetDeleteWorkItem(id: $id)\n }\n`;\n\n/** Every query that shows a task's status, priority or assignee. */\nconst LIST_QUERIES = ['GetWorkItems', 'GetWorkItemCounts', 'ProjectBoard', 'GetDashboardData'];\nconst DETAIL_QUERIES = ['GetWorkItemDetail', 'WorkItemActivity', ...LIST_QUERIES];\n\nconst selectClass =\n 'rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]';\n\nfunction useWorkItemUpdate(workItemId: string) {\n const [update, state] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: DETAIL_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const save = async (input: Record<string, unknown>) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: workItemId, input } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be updated.');\n }\n };\n return { save, saving: state.loading, error };\n}\n\n/** A status or priority select that saves on change. */\nexport function WorkItemEnumSelect({\n workItemId,\n field,\n value,\n options,\n label,\n}: {\n workItemId: string;\n field: 'status' | 'priority';\n value: string;\n options: readonly string[];\n label: string;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n // A stored value the list does not know (older data) stays selectable rather than silently showing another one.\n const all = options.includes(value) ? options : [value, ...options];\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label={label}\n data-testid={`task-${field}`}\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ [field]: e.target.value });\n }}\n >\n {all.map((option) => (\n <option key={option} value={option}>\n {enumLabel(option)}\n </option>\n ))}\n </select>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any\n * member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet\n * admin rights, so an ordinary member would get an empty picker.\n */\nexport function useAssigneeOptions(currentId: string | null | undefined) {\n const { workspaceId, authToken, apiGatewayUrl } = usePlanMagnet();\n const directory = useWorkspaceDirectory({\n workspaceId: workspaceId ?? '',\n authToken: authToken ?? undefined,\n apiGatewayUrl: apiGatewayUrl ?? undefined,\n });\n const current = usePersonProfile(currentId);\n const options = directory.members.map((m) => ({ id: m.id, name: m.name }));\n // The current assignee may be outside the loaded page (or no longer a member): keep them selectable by name.\n if (currentId && current && !options.some((o) => o.id === currentId)) {\n options.unshift({ id: currentId, name: current.displayName });\n }\n return {\n options,\n loading: directory.loading,\n hasMore: directory.hasMore,\n fetchMore: directory.fetchMore,\n };\n}\n\n/** A plain assignee select (for forms). '' means unassigned. */\nexport function AssigneeSelect({\n id,\n value,\n onChange,\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (userId: string) => void;\n disabled?: boolean;\n}) {\n const { options, loading, hasMore, fetchMore } = useAssigneeOptions(value || null);\n return (\n <div className=\"flex flex-col gap-1\">\n <select\n id={id}\n aria-label={id ? undefined : 'Assignee'}\n data-testid=\"assignee-select\"\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Unassigned</option>\n {options.map((o) => (\n <option key={o.id} value={o.id}>\n {o.name}\n </option>\n ))}\n </select>\n {loading && options.length === 0 && (\n <span className=\"text-xs text-muted-foreground\">Loading members…</span>\n )}\n {hasMore && (\n <button\n type=\"button\"\n className=\"self-start text-xs text-primary hover:underline\"\n onClick={fetchMore}\n >\n Show more members\n </button>\n )}\n </div>\n );\n}\n\n/** The task detail's assignee control: saves on change. */\nexport function WorkItemAssigneeSelect({\n workItemId,\n assigneeId,\n}: {\n workItemId: string;\n assigneeId: string | null;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <AssigneeSelect\n value={assigneeId ?? ''}\n disabled={saving}\n onChange={(next) => {\n if (next !== (assigneeId ?? '')) void save({ assigneeId: next || null });\n }}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit and Delete for the task detail header. */\nexport function WorkItemHeaderActions({\n workItemId,\n title,\n}: {\n workItemId: string;\n title: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [remove, removeState] = useMutation(DELETE_WORK_ITEM, { refetchQueries: LIST_QUERIES });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: workItemId } }));\n navigate('/tasks/tasks-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"task-edit\"\n onClick={() => navigate(`/tasks/edit-task-page?taskId=${encodeURIComponent(workItemId)}`)}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${title}\"?`}\n description=\"The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.\"\n confirmLabel=\"Delete task\"\n destructive\n disabled={removeState.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkItemControls.js","names":[],"sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { useWorkspaceDirectory } from '@burdenoff/fe-libs/sharing';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePersonProfile } from '../people/People';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\n\n/**\n * Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or\n * assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status\n * could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team\n * page, sprint \"Team Members\" list and board card read \"Unassigned\".\n *\n * Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records\n * its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.\n */\n\nexport const WORK_ITEM_STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nexport const WORK_ITEM_PRIORITIES = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const;\n\nexport function enumLabel(value: string): string {\n const words = value.toLowerCase().replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst UPDATE_WORK_ITEM = gql`\n mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $id, input: $input) {\n id\n status\n priority\n assigneeId\n }\n }\n`;\nconst DELETE_WORK_ITEM = gql`\n mutation WorkItemControlsDelete($id: ID!) {\n planmagnetDeleteWorkItem(id: $id)\n }\n`;\n\n/** Every query that shows a task's status, priority or assignee. */\nconst LIST_QUERIES = ['GetWorkItems', 'GetWorkItemCounts', 'ProjectBoard', 'GetDashboardData'];\nconst DETAIL_QUERIES = ['GetWorkItemDetail', 'WorkItemActivity', ...LIST_QUERIES];\n\nconst selectClass =\n 'rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]';\n\nfunction useWorkItemUpdate(workItemId: string) {\n const [update, state] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: DETAIL_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const save = async (input: Record<string, unknown>) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: workItemId, input } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be updated.');\n }\n };\n return { save, saving: state.loading, error };\n}\n\n/** A status or priority select that saves on change. */\nexport function WorkItemEnumSelect({\n workItemId,\n field,\n value,\n options,\n label,\n}: {\n workItemId: string;\n field: 'status' | 'priority';\n value: string;\n options: readonly string[];\n label: string;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n // A stored value the list does not know (older data) stays selectable rather than silently showing another one.\n const all = options.includes(value) ? options : [value, ...options];\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label={label}\n data-testid={`task-${field}`}\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ [field]: e.target.value });\n }}\n >\n {all.map((option) => (\n <option key={option} value={option}>\n {enumLabel(option)}\n </option>\n ))}\n </select>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any\n * member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet\n * admin rights, so an ordinary member would get an empty picker.\n */\nexport function useAssigneeOptions(currentId: string | null | undefined) {\n const { workspaceId, authToken, apiGatewayUrl } = usePlanMagnet();\n const directory = useWorkspaceDirectory({\n workspaceId: workspaceId ?? '',\n authToken: authToken ?? undefined,\n apiGatewayUrl: apiGatewayUrl ?? undefined,\n });\n const current = usePersonProfile(currentId);\n const options = directory.members.map((m) => ({ id: m.id, name: m.name }));\n // The current assignee may be outside the loaded page (or no longer a member): keep them selectable by name.\n if (currentId && current && !options.some((o) => o.id === currentId)) {\n options.unshift({ id: currentId, name: current.displayName });\n }\n return {\n options,\n loading: directory.loading,\n hasMore: directory.hasMore,\n fetchMore: directory.fetchMore,\n };\n}\n\n/** A plain assignee select (for forms). '' means unassigned. */\nexport function AssigneeSelect({\n id,\n value,\n onChange,\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (userId: string) => void;\n disabled?: boolean;\n}) {\n const { options, loading, hasMore, fetchMore } = useAssigneeOptions(value || null);\n return (\n <div className=\"flex flex-col gap-1\">\n <select\n id={id}\n aria-label={id ? undefined : 'Assignee'}\n data-testid=\"assignee-select\"\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Unassigned</option>\n {options.map((o) => (\n <option key={o.id} value={o.id}>\n {o.name}\n </option>\n ))}\n </select>\n {loading && options.length === 0 && (\n <span className=\"text-xs text-muted-foreground\">Loading members…</span>\n )}\n {hasMore && (\n <button\n type=\"button\"\n className=\"self-start text-xs text-primary hover:underline\"\n onClick={fetchMore}\n >\n Show more members\n </button>\n )}\n </div>\n );\n}\n\n/** The task detail's assignee control: saves on change. */\nexport function WorkItemAssigneeSelect({\n workItemId,\n assigneeId,\n}: {\n workItemId: string;\n assigneeId: string | null;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <AssigneeSelect\n value={assigneeId ?? ''}\n disabled={saving}\n onChange={(next) => {\n if (next !== (assigneeId ?? '')) void save({ assigneeId: next || null });\n }}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit and Delete for the task detail header. */\nexport function WorkItemHeaderActions({\n workItemId,\n title,\n}: {\n workItemId: string;\n title: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [remove, removeState] = useMutation(DELETE_WORK_ITEM, {\n refetchQueries: LIST_QUERIES,\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: workItemId } }));\n navigate('/tasks/tasks-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"task-edit\"\n onClick={() => navigate(`/tasks/edit-task-page?taskId=${encodeURIComponent(workItemId)}`)}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${title}\"?`}\n description=\"The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.\"\n confirmLabel=\"Delete task\"\n destructive\n disabled={removeState.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ProjectCounts {
|
|
2
|
+
all: number;
|
|
3
|
+
active: number;
|
|
4
|
+
planning: number;
|
|
5
|
+
onHold: number;
|
|
6
|
+
completed: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function useProjectCounts(): ProjectCounts | null;
|
|
9
|
+
export interface SprintCounts {
|
|
10
|
+
all: number;
|
|
11
|
+
/** ACTIVE + REVIEW: a sprint in review is still running (the same rule on every page). */
|
|
12
|
+
running: number;
|
|
13
|
+
planning: number;
|
|
14
|
+
completed: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function useSprintCounts(): SprintCounts | null;
|
|
17
|
+
export interface FeatureCounts {
|
|
18
|
+
all: number;
|
|
19
|
+
/** Still wanted: SUBMITTED, UNDER_REVIEW, PLANNED or IN_PROGRESS. */
|
|
20
|
+
open: number;
|
|
21
|
+
/** Waiting for a triage decision: SUBMITTED and not snoozed (P1.3). */
|
|
22
|
+
triage: number;
|
|
23
|
+
/** SUBMITTED and snoozed into the future. */
|
|
24
|
+
snoozed: number;
|
|
25
|
+
byStatus: Record<'SUBMITTED' | 'UNDER_REVIEW' | 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED' | 'DECLINED' | 'DUPLICATE', number>;
|
|
26
|
+
}
|
|
27
|
+
export declare function useFeatureCounts(): FeatureCounts | null;
|
|
28
|
+
/** "—" for a count that is loading or could not be read — never a number derived from a loaded page. */
|
|
29
|
+
export declare function countLabel(value: number | null | undefined): string;
|
|
30
|
+
/**
|
|
31
|
+
* Progress of a set of work: DONE over everything except CANCELLED (cancelled work no longer needs doing), by
|
|
32
|
+
* story points when there are any, otherwise by task count (BOFF-7989: an un-estimated sprint read 0% with every
|
|
33
|
+
* task done, and cancelled work counted as unfinished). null when nothing is left to measure.
|
|
34
|
+
*/
|
|
35
|
+
export declare function workProgress(items: {
|
|
36
|
+
status: string;
|
|
37
|
+
storyPoints?: number | null;
|
|
38
|
+
}[]): number | null;
|
|
39
|
+
//# sourceMappingURL=useCounts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCounts.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/hooks/useCounts.ts"],"names":[],"mappings":"AAmDA,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAcvD;AAsCD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAgBrD;AAyED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CACZ,WAAW,GACX,cAAc,GACd,SAAS,GACT,aAAa,GACb,WAAW,GACX,UAAU,GACV,WAAW,EACb,MAAM,CACP,CAAC;CACH;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAiCvD;AAED,wGAAwG;AACxG,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,GACvD,MAAM,GAAG,IAAI,CAWf"}
|