@burdenoff/microfe-planmagnet 2026.1002.2 → 2026.1002.3
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/wspace-operations.d.ts +574 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +486 -16
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +174 -168
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +63 -59
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/board/BulkActions.d.ts.map +1 -1
- package/dist/planmagnet/board/BulkActions.js +106 -55
- package/dist/planmagnet/board/BulkActions.js.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +3 -3
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/dashboards/DashboardUI.d.ts +0 -4
- package/dist/planmagnet/dashboards/DashboardUI.d.ts.map +1 -1
- package/dist/planmagnet/dashboards/DashboardUI.js +6 -13
- package/dist/planmagnet/dashboards/DashboardUI.js.map +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +70 -59
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
- package/dist/planmagnet/forecast/ResourceForecastCard.js +1 -1
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -1
- package/dist/planmagnet/index.d.ts +1 -1
- package/dist/planmagnet/index.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts +3 -3
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +26 -19
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts +2 -2
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +18 -12
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js +251 -0
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +94 -92
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +116 -114
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +174 -159
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +159 -145
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +161 -143
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +157 -155
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +97 -99
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js.map +1 -1
- package/dist/planmagnet/product/FeatureLinkPanels.js +66 -66
- package/dist/planmagnet/product/ProductFeatureControls.js +38 -38
- package/dist/planmagnet/product/ProductFeatureLinkCard.js +8 -8
- package/dist/planmagnet/product/ProductLineControls.js +83 -83
- package/dist/planmagnet/product/ProductTeamsPanel.js +6 -6
- package/dist/planmagnet/product/routes.d.ts +6 -0
- package/dist/planmagnet/product/routes.d.ts.map +1 -1
- package/dist/planmagnet/product/routes.js +2 -2
- package/dist/planmagnet/product/routes.js.map +1 -1
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts +20 -0
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js +55 -0
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts +30 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.js +282 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts +9 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js +390 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts +17 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js +77 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts +18 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js +87 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts +13 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js +326 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts +23 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js +258 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts +6 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js +56 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts +16 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js +102 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfaction.d.ts +154 -0
- package/dist/planmagnet/satisfaction/satisfaction.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfaction.js +210 -0
- package/dist/planmagnet/satisfaction/satisfaction.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts +108 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.js +211 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.d.ts +90 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.js +90 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts +116 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.js +86 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.js.map +1 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts +20 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.js +31 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.js.map +1 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts +35 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts.map +1 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.js +124 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.js.map +1 -0
- package/dist/planmagnet/skills/SkillChips.d.ts +30 -0
- package/dist/planmagnet/skills/SkillChips.d.ts.map +1 -0
- package/dist/planmagnet/skills/SkillChips.js +57 -0
- package/dist/planmagnet/skills/SkillChips.js.map +1 -0
- package/dist/planmagnet/skills/SkillCoverageCard.d.ts +24 -0
- package/dist/planmagnet/skills/SkillCoverageCard.d.ts.map +1 -0
- package/dist/planmagnet/skills/SkillCoverageCard.js +256 -0
- package/dist/planmagnet/skills/SkillCoverageCard.js.map +1 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts +10 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts.map +1 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.js +65 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.js.map +1 -0
- package/dist/planmagnet/skills/skillOperations.d.ts +52 -0
- package/dist/planmagnet/skills/skillOperations.d.ts.map +1 -0
- package/dist/planmagnet/skills/skillOperations.js +44 -0
- package/dist/planmagnet/skills/skillOperations.js.map +1 -0
- package/dist/planmagnet/skills/skillTags.d.ts +103 -0
- package/dist/planmagnet/skills/skillTags.d.ts.map +1 -0
- package/dist/planmagnet/skills/skillTags.js +115 -0
- package/dist/planmagnet/skills/skillTags.js.map +1 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts +28 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts.map +1 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.js +30 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,9 +4,9 @@ import { throwOnError as r } from "../utils/mutationResult.js";
|
|
|
4
4
|
import { ConfirmButton as i, ErrorLine as a } from "../controls/ConfirmButton.js";
|
|
5
5
|
import { AssigneeSelect as o } from "../controls/WorkItemControls.js";
|
|
6
6
|
import { detailPath as s } from "../utils/rowNavigation.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { refusalMessage as c } from "./refusal.js";
|
|
8
|
+
import { ARCHIVE_PRODUCT_LINE as l, ASSIGN_PRODUCT_LINE as u, CREATE_PRODUCT_LINE as d, DELETE_PRODUCT_LINE as f, PRODUCT_LINE_ASSIGNMENT as p, PRODUCT_LINE_CANDIDATE_PRODUCTS as m, PRODUCT_LINE_OPTIONS as h, PRODUCT_LINE_QUERIES as g, UNARCHIVE_PRODUCT_LINE as _, UPDATE_PRODUCT_LINE as v } from "./hierarchyOperations.js";
|
|
9
|
+
import { changedFields as y, normalizeText as b } from "./hierarchy.js";
|
|
10
10
|
import { useState as x } from "react";
|
|
11
11
|
import { Archive as S, ArchiveRestore as C, Pencil as w, Plus as T, Trash2 as E, X as D } from "lucide-react";
|
|
12
12
|
import { useMutation as O, useQuery as k } from "@apollo/client/react";
|
|
@@ -14,29 +14,29 @@ import { jsx as A, jsxs as j } from "react/jsx-runtime";
|
|
|
14
14
|
import { Badge as M, Button as N, Checkbox as P, Input as F, ResponsiveDialog as I, ResponsiveDialogContent as L, ResponsiveDialogDescription as R, ResponsiveDialogFooter as z, ResponsiveDialogHeader as B, ResponsiveDialogTitle as V, ResponsiveDialogTrigger as H } from "@burdenoff/fe-libs/ui";
|
|
15
15
|
//#region src/planmagnet/product/ProductLineControls.tsx
|
|
16
16
|
var U = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]", W = "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60", G = {
|
|
17
|
-
refetchQueries:
|
|
17
|
+
refetchQueries: g,
|
|
18
18
|
awaitRefetchQueries: !0
|
|
19
19
|
};
|
|
20
20
|
function K() {
|
|
21
21
|
return n()("planmagnet.productLines.errors.forbidden", "Your role cannot change product lines in this workspace.");
|
|
22
22
|
}
|
|
23
23
|
function q({ line: t }) {
|
|
24
|
-
let i = n(), s = K(), { currentUser:
|
|
24
|
+
let i = n(), s = K(), { currentUser: l } = e(), u = {
|
|
25
25
|
name: t?.name ?? "",
|
|
26
26
|
description: t?.description ?? "",
|
|
27
|
-
ownerId: t ? t.ownerId ?? "" :
|
|
28
|
-
}, [
|
|
29
|
-
|
|
27
|
+
ownerId: t ? t.ownerId ?? "" : l?.id ?? ""
|
|
28
|
+
}, [f, p] = x(!1), [m, h] = x(u), [g, _] = x(null), [S, C] = O(d, G), [E, D] = O(v, G), k = C.loading || D.loading, M = m.name.trim() !== "" && !k, P = t ? `product-line-edit-${t.id}` : "product-line-new", W = async () => {
|
|
29
|
+
_(null);
|
|
30
30
|
let e = {
|
|
31
|
-
name:
|
|
32
|
-
description:
|
|
33
|
-
ownerId:
|
|
31
|
+
name: m.name.trim(),
|
|
32
|
+
description: b(m.description),
|
|
33
|
+
ownerId: m.ownerId || null
|
|
34
34
|
};
|
|
35
35
|
try {
|
|
36
36
|
if (t) {
|
|
37
|
-
let n =
|
|
37
|
+
let n = y({
|
|
38
38
|
name: t.name.trim(),
|
|
39
|
-
description:
|
|
39
|
+
description: b(t.description ?? ""),
|
|
40
40
|
ownerId: t.ownerId
|
|
41
41
|
}, e);
|
|
42
42
|
Object.keys(n).length > 0 && await r(E({ variables: {
|
|
@@ -48,15 +48,15 @@ function q({ line: t }) {
|
|
|
48
48
|
...e.description ? { description: e.description } : {},
|
|
49
49
|
...e.ownerId ? { ownerId: e.ownerId } : {}
|
|
50
50
|
} } }));
|
|
51
|
-
|
|
51
|
+
p(!1);
|
|
52
52
|
} catch (e) {
|
|
53
|
-
|
|
53
|
+
_(c(e, t ? i("planmagnet.productLines.errors.save", "The product line could not be saved.") : i("planmagnet.productLines.errors.create", "The product line could not be created."), s));
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
return /* @__PURE__ */ j(I, {
|
|
57
|
-
open:
|
|
57
|
+
open: f,
|
|
58
58
|
onOpenChange: (e) => {
|
|
59
|
-
|
|
59
|
+
p(e), e ? h(u) : _(null);
|
|
60
60
|
},
|
|
61
61
|
children: [/* @__PURE__ */ A(H, {
|
|
62
62
|
asChild: !0,
|
|
@@ -85,10 +85,10 @@ function q({ line: t }) {
|
|
|
85
85
|
}), /* @__PURE__ */ A(F, {
|
|
86
86
|
id: `${P}-name`,
|
|
87
87
|
className: "mt-1",
|
|
88
|
-
value:
|
|
88
|
+
value: m.name,
|
|
89
89
|
placeholder: i("planmagnet.productLines.fields.namePlaceholder", "e.g. Mobile apps"),
|
|
90
|
-
onChange: (e) =>
|
|
91
|
-
...
|
|
90
|
+
onChange: (e) => h({
|
|
91
|
+
...m,
|
|
92
92
|
name: e.target.value
|
|
93
93
|
}),
|
|
94
94
|
"data-testid": "product-line-name"
|
|
@@ -100,9 +100,9 @@ function q({ line: t }) {
|
|
|
100
100
|
}), /* @__PURE__ */ A("textarea", {
|
|
101
101
|
id: `${P}-description`,
|
|
102
102
|
className: U,
|
|
103
|
-
value:
|
|
104
|
-
onChange: (e) =>
|
|
105
|
-
...
|
|
103
|
+
value: m.description,
|
|
104
|
+
onChange: (e) => h({
|
|
105
|
+
...m,
|
|
106
106
|
description: e.target.value
|
|
107
107
|
}),
|
|
108
108
|
"data-testid": "product-line-description"
|
|
@@ -115,17 +115,17 @@ function q({ line: t }) {
|
|
|
115
115
|
className: "mt-1",
|
|
116
116
|
children: /* @__PURE__ */ A(o, {
|
|
117
117
|
id: `${P}-owner`,
|
|
118
|
-
value:
|
|
118
|
+
value: m.ownerId,
|
|
119
119
|
noneLabel: i("planmagnet.productLines.fields.noOwner", "No owner"),
|
|
120
|
-
onChange: (e) =>
|
|
121
|
-
...
|
|
120
|
+
onChange: (e) => h({
|
|
121
|
+
...m,
|
|
122
122
|
ownerId: e
|
|
123
123
|
})
|
|
124
124
|
})
|
|
125
125
|
})] })
|
|
126
126
|
]
|
|
127
127
|
}),
|
|
128
|
-
|
|
128
|
+
g && /* @__PURE__ */ A(a, { message: g }),
|
|
129
129
|
/* @__PURE__ */ A(z, { children: /* @__PURE__ */ A(N, {
|
|
130
130
|
onClick: () => void W(),
|
|
131
131
|
disabled: !M,
|
|
@@ -137,19 +137,19 @@ function q({ line: t }) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
function J({ line: e, productCount: t, onArchived: o, onDeleted: s }) {
|
|
140
|
-
let
|
|
140
|
+
let u = n(), d = K(), [p, m] = O(l, G), [h, g] = O(_, G), [v, y] = O(f, G), [b, w] = x(null), T = m.loading || g.loading || y.loading, D = async () => {
|
|
141
141
|
w(null);
|
|
142
142
|
try {
|
|
143
|
-
e.archivedAt ? await r(
|
|
143
|
+
e.archivedAt ? await r(h({ variables: { id: e.id } })) : (await r(p({ variables: { id: e.id } })), o?.(e.name));
|
|
144
144
|
} catch (e) {
|
|
145
|
-
w(
|
|
145
|
+
w(c(e, u("planmagnet.productLines.errors.update", "The product line could not be updated."), d));
|
|
146
146
|
}
|
|
147
147
|
}, k = async () => {
|
|
148
148
|
w(null);
|
|
149
149
|
try {
|
|
150
|
-
await r(
|
|
150
|
+
await r(v({ variables: { id: e.id } })), s?.(e.name);
|
|
151
151
|
} catch (e) {
|
|
152
|
-
w(
|
|
152
|
+
w(c(e, u("planmagnet.productLines.errors.delete", "The product line could not be deleted."), d));
|
|
153
153
|
}
|
|
154
154
|
};
|
|
155
155
|
return /* @__PURE__ */ j("div", {
|
|
@@ -164,36 +164,36 @@ function J({ line: e, productCount: t, onArchived: o, onDeleted: s }) {
|
|
|
164
164
|
disabled: T,
|
|
165
165
|
onClick: () => void D(),
|
|
166
166
|
"data-testid": "product-line-toggle-archive",
|
|
167
|
-
children: [e.archivedAt ? /* @__PURE__ */ A(C, { className: "mr-1 h-4 w-4" }) : /* @__PURE__ */ A(S, { className: "mr-1 h-4 w-4" }), e.archivedAt ?
|
|
167
|
+
children: [e.archivedAt ? /* @__PURE__ */ A(C, { className: "mr-1 h-4 w-4" }) : /* @__PURE__ */ A(S, { className: "mr-1 h-4 w-4" }), e.archivedAt ? u("planmagnet.productLines.actions.unarchive", "Unarchive") : u("planmagnet.productLines.actions.archive", "Archive")]
|
|
168
168
|
}),
|
|
169
169
|
/* @__PURE__ */ A(i, {
|
|
170
|
-
label:
|
|
170
|
+
label: u("planmagnet.productLines.actions.delete", "Delete"),
|
|
171
171
|
variant: "outline",
|
|
172
172
|
icon: /* @__PURE__ */ A(E, { className: "mr-1 h-4 w-4" }),
|
|
173
173
|
testId: "product-line-delete",
|
|
174
|
-
title:
|
|
175
|
-
description: t === 0 ?
|
|
176
|
-
confirmLabel:
|
|
174
|
+
title: u("planmagnet.productLines.confirmDelete.title", "Delete {{name}}?", { name: e.name }),
|
|
175
|
+
description: t === 0 ? u("planmagnet.productLines.confirmDelete.empty", "The line has no products. Nothing else changes. This cannot be undone here.") : u("planmagnet.productLines.confirmDelete.withProducts", "Its {{count}} product(s) are kept — they are only taken out of this line and show under \"No product line\". Nothing else about them changes. This cannot be undone here.", { count: t }),
|
|
176
|
+
confirmLabel: u("planmagnet.productLines.confirmDelete.confirm", "Delete product line"),
|
|
177
177
|
destructive: !0,
|
|
178
178
|
disabled: T,
|
|
179
179
|
onConfirm: () => void k()
|
|
180
180
|
})
|
|
181
181
|
]
|
|
182
|
-
}),
|
|
182
|
+
}), b && /* @__PURE__ */ A(a, { message: b })]
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
function Y({ line: e, products: i }) {
|
|
186
|
-
let o = n(),
|
|
187
|
-
|
|
186
|
+
let o = n(), l = K(), d = t(), [f, p] = O(u, G), [m, h] = x(null), [g, _] = x(null), v = async (e) => {
|
|
187
|
+
_(null), h(e);
|
|
188
188
|
try {
|
|
189
|
-
await r(
|
|
189
|
+
await r(f({ variables: {
|
|
190
190
|
productId: e,
|
|
191
191
|
productLineId: null
|
|
192
192
|
} }));
|
|
193
193
|
} catch (e) {
|
|
194
|
-
|
|
194
|
+
_(c(e, o("planmagnet.productLines.errors.unassign", "The product could not be taken out of the line."), l));
|
|
195
195
|
} finally {
|
|
196
|
-
|
|
196
|
+
h(null);
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
199
|
return /* @__PURE__ */ j("div", {
|
|
@@ -220,7 +220,7 @@ function Y({ line: e, products: i }) {
|
|
|
220
220
|
children: [/* @__PURE__ */ A("button", {
|
|
221
221
|
type: "button",
|
|
222
222
|
className: "min-w-0 truncate text-left hover:underline",
|
|
223
|
-
onClick: () =>
|
|
223
|
+
onClick: () => d(s("/products/product-detail-page", e.id)),
|
|
224
224
|
children: e.name
|
|
225
225
|
}), /* @__PURE__ */ j("div", {
|
|
226
226
|
className: "flex shrink-0 items-center gap-2",
|
|
@@ -232,43 +232,43 @@ function Y({ line: e, products: i }) {
|
|
|
232
232
|
size: "sm",
|
|
233
233
|
variant: "ghost",
|
|
234
234
|
"aria-label": o("planmagnet.productLines.products.removeNamed", "Take {{name}} out of this line", { name: e.name }),
|
|
235
|
-
disabled:
|
|
236
|
-
onClick: () => void
|
|
235
|
+
disabled: p.loading,
|
|
236
|
+
onClick: () => void v(e.id),
|
|
237
237
|
"data-testid": "product-line-product-remove",
|
|
238
|
-
children:
|
|
238
|
+
children: m === e.id ? "…" : /* @__PURE__ */ A(D, { className: "h-4 w-4" })
|
|
239
239
|
})]
|
|
240
240
|
})]
|
|
241
241
|
}, e.id))
|
|
242
242
|
}),
|
|
243
|
-
|
|
243
|
+
g && /* @__PURE__ */ A(a, { message: g })
|
|
244
244
|
]
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
247
|
function X({ line: t, existingIds: i }) {
|
|
248
|
-
let o = n(), s = K(), { workspaceId:
|
|
249
|
-
variables: { workspaceId:
|
|
250
|
-
skip: !
|
|
251
|
-
}), [S, C] = O(
|
|
248
|
+
let o = n(), s = K(), { workspaceId: l } = e(), [d, f] = x(!1), [p, h] = x(/* @__PURE__ */ new Set()), [g, _] = x(null), { data: v, loading: y, error: b } = k(m, {
|
|
249
|
+
variables: { workspaceId: l ?? "" },
|
|
250
|
+
skip: !d || !l
|
|
251
|
+
}), [S, C] = O(u, G), w = v?.planmagnetProducts.items ?? [], E = w.filter((e) => !i.includes(e.id)), D = v?.planmagnetProducts.totalCount ?? 0, F = (e, t) => h((n) => {
|
|
252
252
|
let r = new Set(n);
|
|
253
253
|
return t ? r.add(e) : r.delete(e), r;
|
|
254
254
|
}), U = async () => {
|
|
255
|
-
|
|
255
|
+
_(null);
|
|
256
256
|
try {
|
|
257
|
-
await Promise.all([...
|
|
257
|
+
await Promise.all([...p].map((e) => r(S({ variables: {
|
|
258
258
|
productId: e,
|
|
259
259
|
productLineId: t.id
|
|
260
|
-
} })))),
|
|
260
|
+
} })))), h(/* @__PURE__ */ new Set()), f(!1);
|
|
261
261
|
} catch (e) {
|
|
262
|
-
|
|
262
|
+
_(c(e, o("planmagnet.productLines.errors.assign", "The products could not be added."), s));
|
|
263
263
|
}
|
|
264
264
|
};
|
|
265
265
|
return t.archivedAt ? /* @__PURE__ */ A("span", {
|
|
266
266
|
className: "text-xs text-muted-foreground",
|
|
267
267
|
children: o("planmagnet.productLines.products.archivedNoAdd", "Unarchive to add products")
|
|
268
268
|
}) : /* @__PURE__ */ j(I, {
|
|
269
|
-
open:
|
|
269
|
+
open: d,
|
|
270
270
|
onOpenChange: (e) => {
|
|
271
|
-
|
|
271
|
+
f(e), e || (h(/* @__PURE__ */ new Set()), _(null));
|
|
272
272
|
},
|
|
273
273
|
children: [/* @__PURE__ */ A(H, {
|
|
274
274
|
asChild: !0,
|
|
@@ -286,12 +286,12 @@ function X({ line: t, existingIds: i }) {
|
|
|
286
286
|
className: "max-h-80 space-y-1 overflow-y-auto",
|
|
287
287
|
"data-testid": "product-line-add-candidates",
|
|
288
288
|
children: [
|
|
289
|
-
|
|
289
|
+
y && !v && /* @__PURE__ */ A("p", {
|
|
290
290
|
className: "py-4 text-center text-sm text-muted-foreground",
|
|
291
291
|
children: o("planmagnet.productLines.loading", "Loading…")
|
|
292
292
|
}),
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
b && /* @__PURE__ */ A(a, { message: b.message }),
|
|
294
|
+
v && E.length === 0 && /* @__PURE__ */ A("p", {
|
|
295
295
|
className: "py-4 text-center text-sm text-muted-foreground",
|
|
296
296
|
children: w.length === 0 ? o("planmagnet.productLines.products.noProducts", "No products yet.") : o("planmagnet.productLines.products.noCandidates", "Every product is already in this line.")
|
|
297
297
|
}),
|
|
@@ -303,7 +303,7 @@ function X({ line: t, existingIds: i }) {
|
|
|
303
303
|
children: [
|
|
304
304
|
/* @__PURE__ */ A(P, {
|
|
305
305
|
id: n,
|
|
306
|
-
checked:
|
|
306
|
+
checked: p.has(e.id),
|
|
307
307
|
onCheckedChange: (t) => F(e.id, t === !0)
|
|
308
308
|
}),
|
|
309
309
|
/* @__PURE__ */ A("span", {
|
|
@@ -331,38 +331,38 @@ function X({ line: t, existingIds: i }) {
|
|
|
331
331
|
})
|
|
332
332
|
]
|
|
333
333
|
}),
|
|
334
|
-
|
|
334
|
+
g && /* @__PURE__ */ A(a, { message: g }),
|
|
335
335
|
/* @__PURE__ */ A(z, { children: /* @__PURE__ */ A(N, {
|
|
336
336
|
onClick: () => void U(),
|
|
337
|
-
disabled:
|
|
337
|
+
disabled: p.size === 0 || C.loading,
|
|
338
338
|
"data-testid": "product-line-add-confirm",
|
|
339
|
-
children: C.loading ? o("planmagnet.productLines.actions.adding", "Adding…") : o("planmagnet.productLines.products.addCount", "Add {{count}} product(s)", { count:
|
|
339
|
+
children: C.loading ? o("planmagnet.productLines.actions.adding", "Adding…") : o("planmagnet.productLines.products.addCount", "Add {{count}} product(s)", { count: p.size })
|
|
340
340
|
}) })
|
|
341
341
|
]
|
|
342
342
|
})]
|
|
343
343
|
});
|
|
344
344
|
}
|
|
345
345
|
function Z({ productId: i }) {
|
|
346
|
-
let o = n(), s = K(), { workspaceId:
|
|
346
|
+
let o = n(), s = K(), { workspaceId: l } = e(), d = t(), f = k(p, {
|
|
347
347
|
variables: { productId: i },
|
|
348
348
|
skip: !i
|
|
349
|
-
}),
|
|
350
|
-
variables: { workspaceId:
|
|
351
|
-
skip: !
|
|
352
|
-
}), [
|
|
353
|
-
if (e !== (
|
|
354
|
-
|
|
349
|
+
}), m = k(h, {
|
|
350
|
+
variables: { workspaceId: l ?? "" },
|
|
351
|
+
skip: !l
|
|
352
|
+
}), [g, _] = O(u, G), [v, y] = x(null), b = f.data?.planmagnetProduct?.productLine ?? null, S = m.data?.planmagnetProductLines.items ?? [], C = !!b && S.some((e) => e.id === b.id), w = async (e) => {
|
|
353
|
+
if (e !== (b?.id ?? "")) {
|
|
354
|
+
y(null);
|
|
355
355
|
try {
|
|
356
|
-
await r(
|
|
356
|
+
await r(g({ variables: {
|
|
357
357
|
productId: i,
|
|
358
358
|
productLineId: e || null
|
|
359
359
|
} }));
|
|
360
360
|
} catch (e) {
|
|
361
|
-
|
|
361
|
+
y(c(e, o("planmagnet.productLines.errors.assignOne", "The product line could not be changed."), s));
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
};
|
|
365
|
-
return
|
|
365
|
+
return f.error ? /* @__PURE__ */ A(a, { message: o("planmagnet.productLines.errors.loadAssignment", "The product line could not be loaded: {{message}}", { message: f.error.message }) }) : /* @__PURE__ */ j("div", {
|
|
366
366
|
className: "space-y-2",
|
|
367
367
|
"data-testid": "product-line-picker",
|
|
368
368
|
children: [
|
|
@@ -374,18 +374,18 @@ function Z({ productId: i }) {
|
|
|
374
374
|
/* @__PURE__ */ j("select", {
|
|
375
375
|
id: "product-line-select",
|
|
376
376
|
className: W,
|
|
377
|
-
value:
|
|
378
|
-
disabled:
|
|
377
|
+
value: b?.id ?? "",
|
|
378
|
+
disabled: _.loading || f.loading && !f.data,
|
|
379
379
|
onChange: (e) => void w(e.target.value),
|
|
380
380
|
children: [
|
|
381
381
|
/* @__PURE__ */ A("option", {
|
|
382
382
|
value: "",
|
|
383
383
|
children: o("planmagnet.productLines.picker.none", "No product line")
|
|
384
384
|
}),
|
|
385
|
-
|
|
386
|
-
value:
|
|
385
|
+
b && !C && /* @__PURE__ */ A("option", {
|
|
386
|
+
value: b.id,
|
|
387
387
|
disabled: !0,
|
|
388
|
-
children:
|
|
388
|
+
children: b.archivedAt ? o("planmagnet.productLines.picker.archivedOption", "{{name}} (archived)", { name: b.name }) : b.name
|
|
389
389
|
}),
|
|
390
390
|
S.map((e) => /* @__PURE__ */ A("option", {
|
|
391
391
|
value: e.id,
|
|
@@ -393,8 +393,8 @@ function Z({ productId: i }) {
|
|
|
393
393
|
}, e.id))
|
|
394
394
|
]
|
|
395
395
|
}),
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
m.error && /* @__PURE__ */ A(a, { message: m.error.message }),
|
|
397
|
+
v && /* @__PURE__ */ A(a, { message: v }),
|
|
398
398
|
/* @__PURE__ */ j("p", {
|
|
399
399
|
className: "text-xs text-muted-foreground",
|
|
400
400
|
children: [
|
|
@@ -403,7 +403,7 @@ function Z({ productId: i }) {
|
|
|
403
403
|
/* @__PURE__ */ A("button", {
|
|
404
404
|
type: "button",
|
|
405
405
|
className: "text-primary hover:underline",
|
|
406
|
-
onClick: () =>
|
|
406
|
+
onClick: () => d("/products/product-lines-page"),
|
|
407
407
|
children: o("planmagnet.productLines.picker.manage", "Manage product lines")
|
|
408
408
|
})
|
|
409
409
|
]
|
|
@@ -4,8 +4,8 @@ import { throwOnError as n } from "../utils/mutationResult.js";
|
|
|
4
4
|
import { ErrorLine as r } from "../controls/ConfirmButton.js";
|
|
5
5
|
import { detailPath as i } from "../utils/rowNavigation.js";
|
|
6
6
|
import { TeamSelect as a } from "../controls/Pickers.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { refusalMessage as o } from "./refusal.js";
|
|
8
|
+
import { LINK_PRODUCT_TEAM as s, PRODUCT_TEAMS as c, UNLINK_PRODUCT_TEAM as l } from "./hierarchyOperations.js";
|
|
9
9
|
import { useState as u } from "react";
|
|
10
10
|
import { Plus as d, Users as f, X as p } from "lucide-react";
|
|
11
11
|
import { useMutation as m, useQuery as h } from "@apollo/client/react";
|
|
@@ -13,13 +13,13 @@ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
|
13
13
|
import { Button as v } from "@burdenoff/fe-libs/ui";
|
|
14
14
|
//#region src/planmagnet/product/ProductTeamsPanel.tsx
|
|
15
15
|
function y({ productId: y }) {
|
|
16
|
-
let b = t(), x = e(), { data: S, loading: C, error: w } = h(
|
|
16
|
+
let b = t(), x = e(), { data: S, loading: C, error: w } = h(c, {
|
|
17
17
|
variables: { productId: y },
|
|
18
18
|
skip: !y
|
|
19
19
|
}), T = {
|
|
20
20
|
refetchQueries: ["ProductTeams"],
|
|
21
21
|
awaitRefetchQueries: !0
|
|
22
|
-
}, [E, D] = m(
|
|
22
|
+
}, [E, D] = m(s, T), [O, k] = m(l, T), [A, j] = u(""), [M, N] = u(null), [P, F] = u(null), I = S?.planmagnetProduct ?? null, L = I?.teams ?? [], R = !!I?.archivedAt, z = b("planmagnet.productTeams.errors.forbidden", "Your role cannot change which teams a product has."), B = async () => {
|
|
23
23
|
if (A) {
|
|
24
24
|
F(null);
|
|
25
25
|
try {
|
|
@@ -28,7 +28,7 @@ function y({ productId: y }) {
|
|
|
28
28
|
teamId: A
|
|
29
29
|
} })), j("");
|
|
30
30
|
} catch (e) {
|
|
31
|
-
F(
|
|
31
|
+
F(o(e, b("planmagnet.productTeams.errors.link", "The team could not be linked."), z));
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}, V = async (e) => {
|
|
@@ -39,7 +39,7 @@ function y({ productId: y }) {
|
|
|
39
39
|
teamId: e
|
|
40
40
|
} }));
|
|
41
41
|
} catch (e) {
|
|
42
|
-
F(
|
|
42
|
+
F(o(e, b("planmagnet.productTeams.errors.unlink", "The team could not be unlinked."), z));
|
|
43
43
|
} finally {
|
|
44
44
|
N(null);
|
|
45
45
|
}
|
|
@@ -11,4 +11,10 @@ export declare const PRIORITIZATION_PATH = "products/prioritization-page";
|
|
|
11
11
|
export declare const PRODUCT_LINES_PATH = "products/product-lines-page";
|
|
12
12
|
/** Product hierarchy page (PRM-01): Product line > Product > Feature > Epic > Story. */
|
|
13
13
|
export declare const PRODUCT_HIERARCHY_PATH = "products/product-hierarchy-page";
|
|
14
|
+
/**
|
|
15
|
+
* Customer satisfaction page (ANR-21): CSAT and NPS with their responses, add / edit / delete and the CSV import. It
|
|
16
|
+
* takes `?window=&from=&to=&product=&release=&project=&metric=` so a product, release or delivery-KPI card can open it
|
|
17
|
+
* already filtered (see satisfaction/satisfaction.ts `satisfactionPageLink`).
|
|
18
|
+
*/
|
|
19
|
+
export declare const CUSTOMER_SATISFACTION_PATH = "products/customer-satisfaction-page";
|
|
14
20
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/product/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,iCAAiC,CAAC;AAElE,iHAAiH;AACjH,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,wFAAwF;AACxF,eAAO,MAAM,sBAAsB,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/product/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,iCAAiC,CAAC;AAElE,iHAAiH;AACjH,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,wFAAwF;AACxF,eAAO,MAAM,sBAAsB,oCAAoC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,wCAAwC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/planmagnet/product/routes.ts
|
|
2
|
-
var e = "products/prioritization-page", t = "products/product-lines-page", n = "products/product-hierarchy-page";
|
|
2
|
+
var e = "products/prioritization-page", t = "products/product-lines-page", n = "products/product-hierarchy-page", r = "products/customer-satisfaction-page";
|
|
3
3
|
//#endregion
|
|
4
|
-
export { e as PRIORITIZATION_PATH, n as PRODUCT_HIERARCHY_PATH, t as PRODUCT_LINES_PATH };
|
|
4
|
+
export { r as CUSTOMER_SATISFACTION_PATH, e as PRIORITIZATION_PATH, n as PRODUCT_HIERARCHY_PATH, t as PRODUCT_LINES_PATH };
|
|
5
5
|
|
|
6
6
|
//# sourceMappingURL=routes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":[],"sources":["../../../src/planmagnet/product/routes.ts"],"sourcesContent":["/**\n * Prioritization page route target (PRM-27), exported so the host shell can build a `NavItem` against the real,\n * routed path instead of a hand-typed string that can drift from `PlanMagnetRoutes.tsx` — same reasoning as\n * `integrations/routes.ts`. Relative, no leading slash — the same form `PlanMagnetRoot.tsx` passes through\n * `joinPath(basePath, ...)` for every other nav entry in this package, and the same form `path=\"…\"` carries in\n * `PlanMagnetRoutes.tsx`. This MFE also registers its own \"Products\" nav tree internally (PlanMagnetRoot.tsx),\n * so the export here is additionally consumed there.\n */\nexport const PRIORITIZATION_PATH = 'products/prioritization-page';\n\n/** Product lines page (PRM-01) — the top of the product hierarchy. Same relative form as PRIORITIZATION_PATH. */\nexport const PRODUCT_LINES_PATH = 'products/product-lines-page';\n\n/** Product hierarchy page (PRM-01): Product line > Product > Feature > Epic > Story. */\nexport const PRODUCT_HIERARCHY_PATH = 'products/product-hierarchy-page';\n"],"mappings":";AAQA,IAAa,IAAsB,gCAGtB,IAAqB,+BAGrB,IAAyB"}
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../../src/planmagnet/product/routes.ts"],"sourcesContent":["/**\n * Prioritization page route target (PRM-27), exported so the host shell can build a `NavItem` against the real,\n * routed path instead of a hand-typed string that can drift from `PlanMagnetRoutes.tsx` — same reasoning as\n * `integrations/routes.ts`. Relative, no leading slash — the same form `PlanMagnetRoot.tsx` passes through\n * `joinPath(basePath, ...)` for every other nav entry in this package, and the same form `path=\"…\"` carries in\n * `PlanMagnetRoutes.tsx`. This MFE also registers its own \"Products\" nav tree internally (PlanMagnetRoot.tsx),\n * so the export here is additionally consumed there.\n */\nexport const PRIORITIZATION_PATH = 'products/prioritization-page';\n\n/** Product lines page (PRM-01) — the top of the product hierarchy. Same relative form as PRIORITIZATION_PATH. */\nexport const PRODUCT_LINES_PATH = 'products/product-lines-page';\n\n/** Product hierarchy page (PRM-01): Product line > Product > Feature > Epic > Story. */\nexport const PRODUCT_HIERARCHY_PATH = 'products/product-hierarchy-page';\n\n/**\n * Customer satisfaction page (ANR-21): CSAT and NPS with their responses, add / edit / delete and the CSV import. It\n * takes `?window=&from=&to=&product=&release=&project=&metric=` so a product, release or delivery-KPI card can open it\n * already filtered (see satisfaction/satisfaction.ts `satisfactionPageLink`).\n */\nexport const CUSTOMER_SATISFACTION_PATH = 'products/customer-satisfaction-page';\n"],"mappings":";AAQA,IAAa,IAAsB,gCAGtB,IAAqB,+BAGrB,IAAyB,mCAOzB,IAA6B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANR-21 on the delivery KPIs card: CSAT — the default delivery KPI — and NPS next to the on-time rate and scope
|
|
3
|
+
* adherence, for the same scope (the workspace, or responses linked to the selected project) and the same UTC days as
|
|
4
|
+
* the rest of the card.
|
|
5
|
+
*
|
|
6
|
+
* It reads `planmagnetCustomerSatisfaction` with the card's own range as a CUSTOM window rather than selecting
|
|
7
|
+
* `planmagnetDeliveryKpis.customerSatisfaction` on the card's query. The figures are the same — the service resolves
|
|
8
|
+
* both from one summary over the same UTC days when dates are given — but a query of its own means a satisfaction
|
|
9
|
+
* failure (or a role that cannot read `planmagnet-product`) leaves the on-time and scope figures standing: a non-null
|
|
10
|
+
* field that fails nulls the whole `planmagnetDeliveryKpis`. A second `planmagnetDeliveryKpis` query just for this
|
|
11
|
+
* field would recompute every other KPI on the server.
|
|
12
|
+
*/
|
|
13
|
+
export declare function DeliverySatisfactionSection({ workspaceId, projectId, startDate, endDate, }: {
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
projectId?: string;
|
|
16
|
+
/** ISO instants: the start of the range's first UTC day and the end of its last. */
|
|
17
|
+
startDate: string;
|
|
18
|
+
endDate: string;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=DeliverySatisfactionSection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeliverySatisfactionSection.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/satisfaction/DeliverySatisfactionSection.tsx"],"names":[],"mappings":"AAaA;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,EAC1C,WAAW,EACX,SAAS,EACT,SAAS,EACT,OAAO,GACR,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,2CAiEA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { useTr as t } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { isForbidden as n } from "../utils/graphqlErrors.js";
|
|
4
|
+
import { satisfactionPageLink as r, utcDay as i } from "./satisfaction.js";
|
|
5
|
+
import { CompactSatisfactionFigures as a } from "./SatisfactionFigures.js";
|
|
6
|
+
import { CUSTOMER_SATISFACTION_SUMMARY as o } from "./satisfactionOperations.js";
|
|
7
|
+
import { ArrowRight as s } from "lucide-react";
|
|
8
|
+
import { useQuery as c } from "@apollo/client/react";
|
|
9
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
10
|
+
import { Button as d } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
//#region src/planmagnet/satisfaction/DeliverySatisfactionSection.tsx
|
|
12
|
+
function f({ workspaceId: f, projectId: p, startDate: m, endDate: h }) {
|
|
13
|
+
let g = t(), _ = e(), { data: v, loading: y, error: b } = c(o, { variables: {
|
|
14
|
+
workspaceId: f,
|
|
15
|
+
filter: {
|
|
16
|
+
...p ? { projectId: p } : {},
|
|
17
|
+
window: {
|
|
18
|
+
preset: "CUSTOM",
|
|
19
|
+
from: m,
|
|
20
|
+
to: h
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} }), x = v?.planmagnetCustomerSatisfaction, S = i(m), C = i(h);
|
|
24
|
+
return /* @__PURE__ */ u("div", {
|
|
25
|
+
"data-testid": "delivery-satisfaction",
|
|
26
|
+
children: [/* @__PURE__ */ u("div", {
|
|
27
|
+
className: "mb-1 flex items-center justify-between gap-2",
|
|
28
|
+
children: [/* @__PURE__ */ l("h3", {
|
|
29
|
+
className: "text-sm font-medium",
|
|
30
|
+
children: g("planmagnet.analytics.delivery.satisfaction", "Customer satisfaction")
|
|
31
|
+
}), /* @__PURE__ */ u(d, {
|
|
32
|
+
size: "sm",
|
|
33
|
+
variant: "ghost",
|
|
34
|
+
className: "h-7 px-2 text-xs",
|
|
35
|
+
onClick: () => _(r({ projectId: p }, S && C ? {
|
|
36
|
+
preset: "CUSTOM",
|
|
37
|
+
from: S,
|
|
38
|
+
to: C
|
|
39
|
+
} : void 0)),
|
|
40
|
+
"data-testid": "delivery-satisfaction-open",
|
|
41
|
+
children: [g("planmagnet.analytics.delivery.satisfactionOpen", "Responses"), /* @__PURE__ */ l(s, { className: "ml-1 h-3.5 w-3.5" })]
|
|
42
|
+
})]
|
|
43
|
+
}), y && !x ? /* @__PURE__ */ l("p", {
|
|
44
|
+
className: "py-2 text-sm text-muted-foreground",
|
|
45
|
+
children: g("planmagnet.satisfaction.loading", "Loading…")
|
|
46
|
+
}) : b && !x ? /* @__PURE__ */ l("p", {
|
|
47
|
+
className: "py-2 text-sm text-status-error-text",
|
|
48
|
+
children: n(b) ? g("planmagnet.satisfaction.errors.readForbidden", "Your role cannot see customer satisfaction in this workspace.") : g("planmagnet.satisfaction.errors.load", "Customer satisfaction could not be loaded: {{message}}", { message: b.message })
|
|
49
|
+
}) : x ? /* @__PURE__ */ l(a, { summary: x }) : null]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { f as DeliverySatisfactionSection };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=DeliverySatisfactionSection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeliverySatisfactionSection.js","names":[],"sources":["../../../src/planmagnet/satisfaction/DeliverySatisfactionSection.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ArrowRight } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { CompactSatisfactionFigures } from './SatisfactionFigures';\nimport { satisfactionPageLink, utcDay } from './satisfaction';\nimport {\n CUSTOMER_SATISFACTION_SUMMARY,\n type CustomerSatisfactionSummaryData,\n} from './satisfactionOperations';\n\n/**\n * ANR-21 on the delivery KPIs card: CSAT — the default delivery KPI — and NPS next to the on-time rate and scope\n * adherence, for the same scope (the workspace, or responses linked to the selected project) and the same UTC days as\n * the rest of the card.\n *\n * It reads `planmagnetCustomerSatisfaction` with the card's own range as a CUSTOM window rather than selecting\n * `planmagnetDeliveryKpis.customerSatisfaction` on the card's query. The figures are the same — the service resolves\n * both from one summary over the same UTC days when dates are given — but a query of its own means a satisfaction\n * failure (or a role that cannot read `planmagnet-product`) leaves the on-time and scope figures standing: a non-null\n * field that fails nulls the whole `planmagnetDeliveryKpis`. A second `planmagnetDeliveryKpis` query just for this\n * field would recompute every other KPI on the server.\n */\nexport function DeliverySatisfactionSection({\n workspaceId,\n projectId,\n startDate,\n endDate,\n}: {\n workspaceId: string;\n projectId?: string;\n /** ISO instants: the start of the range's first UTC day and the end of its last. */\n startDate: string;\n endDate: string;\n}) {\n const tr = useTr();\n const navigate = usePlanMagnetNavigate();\n const { data, loading, error } = useQuery<CustomerSatisfactionSummaryData>(\n CUSTOMER_SATISFACTION_SUMMARY,\n {\n variables: {\n workspaceId,\n filter: {\n ...(projectId ? { projectId } : {}),\n window: { preset: 'CUSTOM', from: startDate, to: endDate },\n },\n },\n }\n );\n const summary = data?.planmagnetCustomerSatisfaction;\n const from = utcDay(startDate);\n const to = utcDay(endDate);\n\n return (\n <div data-testid=\"delivery-satisfaction\">\n <div className=\"mb-1 flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-medium\">\n {tr('planmagnet.analytics.delivery.satisfaction', 'Customer satisfaction')}\n </h3>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-7 px-2 text-xs\"\n onClick={() =>\n navigate(\n satisfactionPageLink(\n { projectId },\n from && to ? { preset: 'CUSTOM', from, to } : undefined\n )\n )\n }\n data-testid=\"delivery-satisfaction-open\"\n >\n {tr('planmagnet.analytics.delivery.satisfactionOpen', 'Responses')}\n <ArrowRight className=\"ml-1 h-3.5 w-3.5\" />\n </Button>\n </div>\n {loading && !summary ? (\n <p className=\"py-2 text-sm text-muted-foreground\">\n {tr('planmagnet.satisfaction.loading', 'Loading…')}\n </p>\n ) : error && !summary ? (\n <p className=\"py-2 text-sm text-status-error-text\">\n {isForbidden(error)\n ? tr(\n 'planmagnet.satisfaction.errors.readForbidden',\n 'Your role cannot see customer satisfaction in this workspace.'\n )\n : tr(\n 'planmagnet.satisfaction.errors.load',\n 'Customer satisfaction could not be loaded: {{message}}',\n { message: error.message }\n )}\n </p>\n ) : summary ? (\n <CompactSatisfactionFigures summary={summary} />\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAyBA,SAAgB,EAA4B,EAC1C,gBACA,cACA,cACA,cAOC;CACD,IAAM,IAAK,GAAO,EACZ,IAAW,GAAuB,EAClC,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA,EACE,WAAW;EACT;EACA,QAAQ;GACN,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE;GAClC,QAAQ;IAAE,QAAQ;IAAU,MAAM;IAAW,IAAI;IAAS;GAC3D;EACF,EACF,CACF,EACK,IAAU,GAAM,gCAChB,IAAO,EAAO,EAAU,EACxB,IAAK,EAAO,EAAQ;AAE1B,QACE,kBAAC,OAAD;EAAK,eAAY;YAAjB,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAG,8CAA8C,wBAAwB;IACvE,CAAA,EACL,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,WAAU;IACV,eACE,EACE,EACE,EAAE,cAAW,EACb,KAAQ,IAAK;KAAE,QAAQ;KAAU;KAAM;KAAI,GAAG,KAAA,EAC/C,CACF;IAEH,eAAY;cAZd,CAcG,EAAG,kDAAkD,YAAY,EAClE,kBAAC,GAAD,EAAY,WAAU,oBAAqB,CAAA,CACpC;MACL;MACL,KAAW,CAAC,IACX,kBAAC,KAAD;GAAG,WAAU;aACV,EAAG,mCAAmC,WAAW;GAChD,CAAA,GACF,KAAS,CAAC,IACZ,kBAAC,KAAD;GAAG,WAAU;aACV,EAAY,EAAM,GACf,EACE,gDACA,gEACD,GACD,EACE,uCACA,0DACA,EAAE,SAAS,EAAM,SAAS,CAC3B;GACH,CAAA,GACF,IACF,kBAAC,GAAD,EAAqC,YAAW,CAAA,GAC9C,KACA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useTr } from '../../shared/hooks/useTr';
|
|
2
|
+
import { ScoreCategory } from './satisfaction';
|
|
3
|
+
import { CustomerSatisfactionSummary, SatisfactionWindowEcho } from './satisfactionOperations';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ANR-21 figures. Every figure carries its response count and the window it covers (owner decision 2026-10-02), and
|
|
7
|
+
* a metric below its minimum responses reads "Not enough responses" — never 0 and never a score computed from too few
|
|
8
|
+
* answers. CSAT and NPS are counted separately: twelve NPS answers never lend a score to four CSAT answers.
|
|
9
|
+
*/
|
|
10
|
+
type Tr = ReturnType<typeof useTr>;
|
|
11
|
+
export declare function responseCountText(tr: Tr, count: number): string;
|
|
12
|
+
export declare function windowText(tr: Tr, window: SatisfactionWindowEcho): string;
|
|
13
|
+
export declare function windowPresetLabel(tr: Tr, preset: SatisfactionWindowEcho['preset']): string;
|
|
14
|
+
/**
|
|
15
|
+
* Badge colours for one answer's category — semantic status tokens only, and only those the host shells' Tailwind
|
|
16
|
+
* theme maps (planmagnet-app styles.css @theme): `bg-status-*-bg`, `bg-status-*-bg-subtle`, `text-status-success-text`,
|
|
17
|
+
* `text-status-error-text`. A token missing there generates no CSS at all.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CATEGORY_CLASS: Record<ScoreCategory, string>;
|
|
20
|
+
export declare function categoryLabel(tr: Tr, category: ScoreCategory): string;
|
|
21
|
+
/** The page's two figure cards, side by side. */
|
|
22
|
+
export declare function SatisfactionFigureCards({ summary }: {
|
|
23
|
+
summary: CustomerSatisfactionSummary;
|
|
24
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
/** CSAT and NPS as two small tiles with their counts, and the window under them — for cards on other pages. */
|
|
26
|
+
export declare function CompactSatisfactionFigures({ summary }: {
|
|
27
|
+
summary: CustomerSatisfactionSummary;
|
|
28
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=SatisfactionFigures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SatisfactionFigures.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/satisfaction/SatisfactionFigures.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEjD,OAAO,EAAgD,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAClG,OAAO,KAAK,EAEV,2BAA2B,EAE3B,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAElC;;;;GAIG;AAEH,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAEnC,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAUzE;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAa1F;AAUD;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAOxD,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,aAAa,GAAG,MAAM,CAarE;AA8JD,iDAAiD;AACjD,wBAAgB,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,2BAA2B,CAAA;CAAE,2CAO5F;AA+BD,+GAA+G;AAC/G,wBAAgB,0BAA0B,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,2BAA2B,CAAA;CAAE,2CAuC/F"}
|