@burdenoff/microfe-planmagnet 2026.1002.1 → 2026.1002.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/generated/wspace-operations.d.ts +1814 -401
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +5167 -54
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/index.js +8 -8
  6. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  7. package/dist/planmagnet/PlanMagnetRoot.js +172 -160
  8. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  9. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  10. package/dist/planmagnet/PlanMagnetRoutes.js +95 -83
  11. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  12. package/dist/planmagnet/controls/Pickers.d.ts +7 -2
  13. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  14. package/dist/planmagnet/controls/Pickers.js +10 -9
  15. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  16. package/dist/planmagnet/index.d.ts +1 -1
  17. package/dist/planmagnet/index.d.ts.map +1 -1
  18. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
  19. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  20. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  21. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
  22. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  23. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  24. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
  25. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  26. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
  27. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  28. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  29. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +105 -82
  30. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  31. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
  32. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
  33. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
  34. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
  35. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
  36. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
  37. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
  38. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
  39. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
  40. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
  41. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
  42. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
  43. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  45. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +156 -154
  48. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  49. package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
  50. package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
  51. package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
  52. package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
  53. package/dist/planmagnet/product/ProductControls.js +1 -1
  54. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  55. package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
  56. package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
  57. package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
  58. package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
  59. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
  60. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
  61. package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
  62. package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
  63. package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
  64. package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
  65. package/dist/planmagnet/product/ProductLineControls.js +417 -0
  66. package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
  67. package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
  68. package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
  69. package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
  70. package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
  71. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
  72. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
  73. package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
  74. package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
  75. package/dist/planmagnet/product/hierarchy.d.ts +127 -0
  76. package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
  77. package/dist/planmagnet/product/hierarchy.js +173 -0
  78. package/dist/planmagnet/product/hierarchy.js.map +1 -0
  79. package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
  80. package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
  81. package/dist/planmagnet/product/hierarchyOperations.js +440 -0
  82. package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
  83. package/dist/planmagnet/product/refusal.d.ts +14 -0
  84. package/dist/planmagnet/product/refusal.d.ts.map +1 -0
  85. package/dist/planmagnet/product/refusal.js +10 -0
  86. package/dist/planmagnet/product/refusal.js.map +1 -0
  87. package/dist/planmagnet/product/routes.d.ts +4 -0
  88. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  89. package/dist/planmagnet/product/routes.js +2 -2
  90. package/dist/planmagnet/product/routes.js.map +1 -1
  91. package/dist/shared/utils/i18n.d.ts +3 -0
  92. package/dist/shared/utils/i18n.d.ts.map +1 -1
  93. package/dist/shared/utils/i18n.js +1 -1
  94. package/dist/shared/utils/i18n.js.map +1 -1
  95. package/package.json +4 -4
@@ -0,0 +1,129 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { useTr as t } from "../../shared/hooks/useTr.js";
3
+ import { throwOnError as n } from "../utils/mutationResult.js";
4
+ import { ErrorLine as r } from "../controls/ConfirmButton.js";
5
+ import { detailPath as i } from "../utils/rowNavigation.js";
6
+ import { TeamSelect as a } from "../controls/Pickers.js";
7
+ import { LINK_PRODUCT_TEAM as o, PRODUCT_TEAMS as s, UNLINK_PRODUCT_TEAM as c } from "./hierarchyOperations.js";
8
+ import { refusalMessage as l } from "./refusal.js";
9
+ import { useState as u } from "react";
10
+ import { Plus as d, Users as f, X as p } from "lucide-react";
11
+ import { useMutation as m, useQuery as h } from "@apollo/client/react";
12
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
13
+ import { Button as v } from "@burdenoff/fe-libs/ui";
14
+ //#region src/planmagnet/product/ProductTeamsPanel.tsx
15
+ function y({ productId: y }) {
16
+ let b = t(), x = e(), { data: S, loading: C, error: w } = h(s, {
17
+ variables: { productId: y },
18
+ skip: !y
19
+ }), T = {
20
+ refetchQueries: ["ProductTeams"],
21
+ awaitRefetchQueries: !0
22
+ }, [E, D] = m(o, T), [O, k] = m(c, 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
+ if (A) {
24
+ F(null);
25
+ try {
26
+ await n(E({ variables: {
27
+ productId: y,
28
+ teamId: A
29
+ } })), j("");
30
+ } catch (e) {
31
+ F(l(e, b("planmagnet.productTeams.errors.link", "The team could not be linked."), z));
32
+ }
33
+ }
34
+ }, V = async (e) => {
35
+ F(null), N(e);
36
+ try {
37
+ await n(O({ variables: {
38
+ productId: y,
39
+ teamId: e
40
+ } }));
41
+ } catch (e) {
42
+ F(l(e, b("planmagnet.productTeams.errors.unlink", "The team could not be unlinked."), z));
43
+ } finally {
44
+ N(null);
45
+ }
46
+ };
47
+ return /* @__PURE__ */ _("div", {
48
+ className: "space-y-3",
49
+ "data-testid": "product-teams",
50
+ children: [
51
+ /* @__PURE__ */ g("p", {
52
+ className: "text-sm text-muted-foreground",
53
+ "data-testid": "product-teams-access-note",
54
+ children: b("planmagnet.productTeams.accessNote", "Teams that own, plan or report on this product. Linking or unlinking a team does not change anyone’s access — every member keeps exactly the access their own role gives them.")
55
+ }),
56
+ w && /* @__PURE__ */ g(r, { message: b("planmagnet.productTeams.errors.load", "Teams could not be loaded: {{message}}", { message: w.message }) }),
57
+ C && !S && /* @__PURE__ */ g("p", {
58
+ className: "text-sm text-muted-foreground",
59
+ children: b("planmagnet.productTeams.loading", "Loading teams…")
60
+ }),
61
+ I && L.length === 0 && /* @__PURE__ */ g("p", {
62
+ className: "py-2 text-center text-sm text-muted-foreground",
63
+ children: b("planmagnet.productTeams.empty", "No teams linked to this product yet.")
64
+ }),
65
+ L.length > 0 && /* @__PURE__ */ g("ul", {
66
+ className: "space-y-1",
67
+ children: L.map((e) => /* @__PURE__ */ _("li", {
68
+ className: "flex flex-wrap items-center justify-between gap-2 rounded-md border p-2 text-sm",
69
+ children: [/* @__PURE__ */ _("button", {
70
+ type: "button",
71
+ className: "flex min-w-0 items-center gap-2 text-left hover:underline",
72
+ onClick: () => x(i("/team/team-detail-page", e.id)),
73
+ children: [/* @__PURE__ */ g(f, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ g("span", {
74
+ className: "truncate",
75
+ children: e.name
76
+ })]
77
+ }), /* @__PURE__ */ _("div", {
78
+ className: "flex shrink-0 items-center gap-2",
79
+ children: [/* @__PURE__ */ g("span", {
80
+ className: "text-xs text-muted-foreground",
81
+ children: e.members.length === 1 ? b("planmagnet.productTeams.members.one", "1 member") : b("planmagnet.productTeams.members.count", "{{count}} members", { count: e.members.length })
82
+ }), /* @__PURE__ */ g(v, {
83
+ size: "sm",
84
+ variant: "ghost",
85
+ "aria-label": b("planmagnet.productTeams.unlinkNamed", "Unlink {{name}}", { name: e.name }),
86
+ disabled: k.loading,
87
+ onClick: () => void V(e.id),
88
+ "data-testid": "product-team-unlink",
89
+ children: M === e.id ? "…" : /* @__PURE__ */ g(p, { className: "h-4 w-4" })
90
+ })]
91
+ })]
92
+ }, e.id))
93
+ }),
94
+ I && /* @__PURE__ */ _("div", {
95
+ className: "flex items-start gap-2 border-t pt-2",
96
+ children: [/* @__PURE__ */ _("div", {
97
+ className: "flex-1",
98
+ children: [/* @__PURE__ */ g("label", {
99
+ htmlFor: `product-add-team-${y}`,
100
+ className: "sr-only",
101
+ children: b("planmagnet.productTeams.pickLabel", "Team to link")
102
+ }), /* @__PURE__ */ g(a, {
103
+ id: `product-add-team-${y}`,
104
+ value: A,
105
+ onChange: j,
106
+ noneLabel: b("planmagnet.productTeams.pick", "Choose a team"),
107
+ exclude: L.map((e) => e.id),
108
+ disabled: R
109
+ })]
110
+ }), /* @__PURE__ */ _(v, {
111
+ size: "sm",
112
+ disabled: !A || R || D.loading,
113
+ onClick: () => void B(),
114
+ "data-testid": "product-team-link",
115
+ children: [/* @__PURE__ */ g(d, { className: "mr-1 h-4 w-4" }), b("planmagnet.productTeams.link", "Link team")]
116
+ })]
117
+ }),
118
+ R && /* @__PURE__ */ g("p", {
119
+ className: "text-xs text-muted-foreground",
120
+ children: b("planmagnet.productTeams.archived", "This product is archived — unarchive it to link more teams.")
121
+ }),
122
+ P && /* @__PURE__ */ g(r, { message: P })
123
+ ]
124
+ });
125
+ }
126
+ //#endregion
127
+ export { y as ProductTeamsPanel };
128
+
129
+ //# sourceMappingURL=ProductTeamsPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductTeamsPanel.js","names":[],"sources":["../../../src/planmagnet/product/ProductTeamsPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Users, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { TeamSelect } from '../controls/Pickers';\nimport { throwOnError } from '../utils/mutationResult';\nimport { detailPath } from '../utils/rowNavigation';\nimport { LINK_PRODUCT_TEAM, PRODUCT_TEAMS, UNLINK_PRODUCT_TEAM } from './hierarchyOperations';\nimport { refusalMessage } from './refusal';\n\n/**\n * The teams that own, plan or report on a product (PRM-01, Product ↔ Team many-to-many). The link is a record of\n * responsibility only: the service writes the join row and nothing else — no role, no team membership, no project\n * link — so linking or unlinking a team never changes what anyone can see or do. The panel says so where the\n * decision is made.\n */\n\ninterface LinkedTeam {\n id: string;\n name: string;\n description: string | null;\n members: { id: string }[];\n}\n\nexport function ProductTeamsPanel({ productId }: { productId: string }) {\n const tr = useTr();\n const navigate = usePlanMagnetNavigate();\n const { data, loading, error } = useQuery<{\n planmagnetProduct: { id: string; archivedAt: string | null; teams: LinkedTeam[] } | null;\n }>(PRODUCT_TEAMS, { variables: { productId }, skip: !productId });\n const options = { refetchQueries: ['ProductTeams'], awaitRefetchQueries: true };\n const [link, linkState] = useMutation(LINK_PRODUCT_TEAM, options);\n const [unlink, unlinkState] = useMutation(UNLINK_PRODUCT_TEAM, options);\n const [teamId, setTeamId] = useState('');\n const [removingId, setRemovingId] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const product = data?.planmagnetProduct ?? null;\n const teams = product?.teams ?? [];\n const archived = !!product?.archivedAt;\n const forbidden = tr(\n 'planmagnet.productTeams.errors.forbidden',\n 'Your role cannot change which teams a product has.'\n );\n\n const onLink = async () => {\n if (!teamId) return;\n setActionError(null);\n try {\n await throwOnError(link({ variables: { productId, teamId } }));\n setTeamId('');\n } catch (e) {\n setActionError(\n refusalMessage(\n e,\n tr('planmagnet.productTeams.errors.link', 'The team could not be linked.'),\n forbidden\n )\n );\n }\n };\n\n const onUnlink = async (id: string) => {\n setActionError(null);\n setRemovingId(id);\n try {\n await throwOnError(unlink({ variables: { productId, teamId: id } }));\n } catch (e) {\n setActionError(\n refusalMessage(\n e,\n tr('planmagnet.productTeams.errors.unlink', 'The team could not be unlinked.'),\n forbidden\n )\n );\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-teams\">\n <p className=\"text-sm text-muted-foreground\" data-testid=\"product-teams-access-note\">\n {tr(\n 'planmagnet.productTeams.accessNote',\n 'Teams that own, plan or report on this product. Linking or unlinking a team does not change anyone’s access — every member keeps exactly the access their own role gives them.'\n )}\n </p>\n {error && (\n <ErrorLine\n message={tr(\n 'planmagnet.productTeams.errors.load',\n 'Teams could not be loaded: {{message}}',\n {\n message: error.message,\n }\n )}\n />\n )}\n {loading && !data && (\n <p className=\"text-sm text-muted-foreground\">\n {tr('planmagnet.productTeams.loading', 'Loading teams…')}\n </p>\n )}\n {product && teams.length === 0 && (\n <p className=\"py-2 text-center text-sm text-muted-foreground\">\n {tr('planmagnet.productTeams.empty', 'No teams linked to this product yet.')}\n </p>\n )}\n {teams.length > 0 && (\n <ul className=\"space-y-1\">\n {teams.map((team) => (\n <li\n key={team.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-md border p-2 text-sm\"\n >\n <button\n type=\"button\"\n className=\"flex min-w-0 items-center gap-2 text-left hover:underline\"\n onClick={() => navigate(detailPath('/team/team-detail-page', team.id))}\n >\n <Users className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{team.name}</span>\n </button>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {team.members.length === 1\n ? tr('planmagnet.productTeams.members.one', '1 member')\n : tr('planmagnet.productTeams.members.count', '{{count}} members', {\n count: team.members.length,\n })}\n </span>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={tr('planmagnet.productTeams.unlinkNamed', 'Unlink {{name}}', {\n name: team.name,\n })}\n disabled={unlinkState.loading}\n onClick={() => void onUnlink(team.id)}\n data-testid=\"product-team-unlink\"\n >\n {removingId === team.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n {product && (\n <div className=\"flex items-start gap-2 border-t pt-2\">\n <div className=\"flex-1\">\n <label htmlFor={`product-add-team-${productId}`} className=\"sr-only\">\n {tr('planmagnet.productTeams.pickLabel', 'Team to link')}\n </label>\n <TeamSelect\n id={`product-add-team-${productId}`}\n value={teamId}\n onChange={setTeamId}\n noneLabel={tr('planmagnet.productTeams.pick', 'Choose a team')}\n exclude={teams.map((t) => t.id)}\n disabled={archived}\n />\n </div>\n <Button\n size=\"sm\"\n disabled={!teamId || archived || linkState.loading}\n onClick={() => void onLink()}\n data-testid=\"product-team-link\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n {tr('planmagnet.productTeams.link', 'Link team')}\n </Button>\n </div>\n )}\n {archived && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productTeams.archived',\n 'This product is archived — unarchive it to link more teams.'\n )}\n </p>\n )}\n {actionError && <ErrorLine message={actionError} />}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function WorkItemProductFeatureChip({ workItemId }: {
2
+ workItemId: string;
3
+ }): import("react/jsx-runtime").JSX.Element | null;
4
+ //# sourceMappingURL=WorkItemProductFeatureChip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkItemProductFeatureChip.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/product/WorkItemProductFeatureChip.tsx"],"names":[],"mappings":"AAkBA,wBAAgB,0BAA0B,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,kDAsChF"}
@@ -0,0 +1,41 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { useTr as t } from "../../shared/hooks/useTr.js";
3
+ import { detailPath as n } from "../utils/rowNavigation.js";
4
+ import { WORK_ITEM_PRODUCT_FEATURE as r } from "./hierarchyOperations.js";
5
+ import { Puzzle as i } from "lucide-react";
6
+ import { useQuery as a } from "@apollo/client/react";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ //#region src/planmagnet/product/WorkItemProductFeatureChip.tsx
9
+ function c({ workItemId: c }) {
10
+ let l = t(), u = e(), { data: d } = a(r, {
11
+ variables: { id: c },
12
+ skip: !c
13
+ }), f = d?.planmagnetWorkItem;
14
+ if (!f) return null;
15
+ let p = f.productFeature, m = !p && f.parent?.type === "EPIC" ? f.parent.productFeature : null, h = p ?? m;
16
+ return h ? /* @__PURE__ */ s("button", {
17
+ type: "button",
18
+ className: "mt-2 inline-flex max-w-full items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs hover:bg-muted",
19
+ onClick: () => u(n("/products/product-feature-detail-page", h.id)),
20
+ "data-testid": "work-item-product-feature",
21
+ children: [
22
+ /* @__PURE__ */ o(i, { className: "h-3 w-3 shrink-0 text-primary" }),
23
+ /* @__PURE__ */ o("span", {
24
+ className: "text-muted-foreground",
25
+ children: l("planmagnet.productFeatures.chip.label", "Product feature")
26
+ }),
27
+ /* @__PURE__ */ o("span", {
28
+ className: "truncate font-medium",
29
+ children: h.name
30
+ }),
31
+ m && f.parent && /* @__PURE__ */ o("span", {
32
+ className: "shrink-0 text-muted-foreground",
33
+ children: l("planmagnet.productFeatures.chip.viaEpic", "via {{key}}", { key: f.parent.key })
34
+ })
35
+ ]
36
+ }) : null;
37
+ }
38
+ //#endregion
39
+ export { c as WorkItemProductFeatureChip };
40
+
41
+ //# sourceMappingURL=WorkItemProductFeatureChip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkItemProductFeatureChip.js","names":[],"sources":["../../../src/planmagnet/product/WorkItemProductFeatureChip.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport { Puzzle } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { WORK_ITEM_PRODUCT_FEATURE } from './hierarchyOperations';\n\n/**\n * The read-only \"Product feature\" chip on a work item's page (PRM-01). An EPIC shows the feature it is linked to; a\n * story (or any item filed under an epic) shows its epic's feature, since stories reach a feature through their\n * epic. Links are made from the feature's page. Its own query: a failure here hides the chip, never the task page.\n */\n\ninterface FeatureRef {\n id: string;\n name: string;\n}\n\nexport function WorkItemProductFeatureChip({ workItemId }: { workItemId: string }) {\n const tr = useTr();\n const navigate = usePlanMagnetNavigate();\n const { data } = useQuery<{\n planmagnetWorkItem: {\n id: string;\n type: string;\n productFeature: FeatureRef | null;\n parent: { id: string; key: string; type: string; productFeature: FeatureRef | null } | null;\n } | null;\n }>(WORK_ITEM_PRODUCT_FEATURE, { variables: { id: workItemId }, skip: !workItemId });\n\n const item = data?.planmagnetWorkItem;\n if (!item) return null;\n const own = item.productFeature;\n const viaEpic = !own && item.parent?.type === 'EPIC' ? item.parent.productFeature : null;\n const feature = own ?? viaEpic;\n if (!feature) return null;\n\n return (\n <button\n type=\"button\"\n className=\"mt-2 inline-flex max-w-full items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs hover:bg-muted\"\n onClick={() => navigate(detailPath('/products/product-feature-detail-page', feature.id))}\n data-testid=\"work-item-product-feature\"\n >\n <Puzzle className=\"h-3 w-3 shrink-0 text-primary\" />\n <span className=\"text-muted-foreground\">\n {tr('planmagnet.productFeatures.chip.label', 'Product feature')}\n </span>\n <span className=\"truncate font-medium\">{feature.name}</span>\n {viaEpic && item.parent && (\n <span className=\"shrink-0 text-muted-foreground\">\n {tr('planmagnet.productFeatures.chip.viaEpic', 'via {{key}}', { key: item.parent.key })}\n </span>\n )}\n </button>\n );\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,EAA2B,EAAE,iBAAsC;CACjF,IAAM,IAAK,GAAO,EACZ,IAAW,GAAuB,EAClC,EAAE,YAAS,EAOd,GAA2B;EAAE,WAAW,EAAE,IAAI,GAAY;EAAE,MAAM,CAAC;EAAY,CAAC,EAE7E,IAAO,GAAM;AACnB,KAAI,CAAC,EAAM,QAAO;CAClB,IAAM,IAAM,EAAK,gBACX,IAAU,CAAC,KAAO,EAAK,QAAQ,SAAS,SAAS,EAAK,OAAO,iBAAiB,MAC9E,IAAU,KAAO;AAGvB,QAFK,IAGH,kBAAC,UAAD;EACE,MAAK;EACL,WAAU;EACV,eAAe,EAAS,EAAW,yCAAyC,EAAQ,GAAG,CAAC;EACxF,eAAY;YAJd;GAME,kBAAC,GAAD,EAAQ,WAAU,iCAAkC,CAAA;GACpD,kBAAC,QAAD;IAAM,WAAU;cACb,EAAG,yCAAyC,kBAAkB;IAC1D,CAAA;GACP,kBAAC,QAAD;IAAM,WAAU;cAAwB,EAAQ;IAAY,CAAA;GAC3D,KAAW,EAAK,UACf,kBAAC,QAAD;IAAM,WAAU;cACb,EAAG,2CAA2C,eAAe,EAAE,KAAK,EAAK,OAAO,KAAK,CAAC;IAClF,CAAA;GAEF;MAnBU"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * PRM-01 product hierarchy — the pure helpers behind the product line, product feature and hierarchy pages:
3
+ * Product line > Product > Feature > Epic > Story. No React and no GraphQL here, so every rule is unit-tested
4
+ * (tests/productHierarchy.test.ts).
5
+ *
6
+ * Vocabulary (owner decision, binding): a FEATURE REQUEST is customer/user feedback asking for something; a
7
+ * FEATURE (`PlanmagnetProductFeature`) is a product capability the team decided to build. A request may point at
8
+ * the feature that addresses it — it is never converted into one.
9
+ */
10
+ /** `PlanmagnetProductFeatureStatus`, in lifecycle order (the SDL lists it alphabetically). */
11
+ export declare const PRODUCT_FEATURE_STATUSES: readonly ["BACKLOG", "DISCOVERY", "PLANNING", "IN_DEVELOPMENT", "RELEASED"];
12
+ export type ProductFeatureStatus = (typeof PRODUCT_FEATURE_STATUSES)[number];
13
+ export declare function isProductFeatureStatus(value: string): value is ProductFeatureStatus;
14
+ /** The fe-libs Badge variant for a feature status — semantic tokens only, never a raw colour. */
15
+ export type FeatureStatusBadgeVariant = 'outline' | 'secondary' | 'info' | 'default' | 'success';
16
+ /** A status the API returns that this list does not know yet still renders, as a plain outline badge. */
17
+ export declare function productFeatureStatusVariant(status: string): FeatureStatusBadgeVariant;
18
+ /** A text input's value as the API wants it: trimmed, and an emptied field is `null` (clears it). */
19
+ export declare function normalizeText(value: string): string | null;
20
+ /**
21
+ * The entries of `next` whose value differs from `initial` — what an edit form sends. Forms here send only the
22
+ * fields the person changed, so a save never rewrites a field someone else edited meanwhile. Values are compared
23
+ * with `===`, so callers normalize both sides first (see `normalizeText`).
24
+ */
25
+ export declare function changedFields<T extends Record<string, unknown>>(initial: T, next: T): Partial<T>;
26
+ /**
27
+ * A server date as a `Date`, or null when there is none or it cannot be read. `DateTime` fields arrive as ISO
28
+ * strings, but a date carried in a GraphQL `String` serializes as epoch milliseconds ("1727740800000"), so both
29
+ * forms are accepted rather than rendering "Invalid Date".
30
+ */
31
+ export declare function parseServerDate(value: string | null | undefined): Date | null;
32
+ export interface RequestProductRef {
33
+ id: string;
34
+ productId: string | null;
35
+ }
36
+ /**
37
+ * The service's rule (planmagnetLinkFeatureRequestToProductFeature): a request that has a product may only point
38
+ * at a feature of that same product; a request with no product may point at any feature. The UI offers only what
39
+ * the service accepts — and still shows the service's own reason if it refuses.
40
+ */
41
+ export declare function canLinkRequestToFeature(request: {
42
+ productId: string | null;
43
+ }, feature: {
44
+ productId: string;
45
+ }): boolean;
46
+ /**
47
+ * The requests a feature's "Link a request" picker offers: the feature's own product's requests first, then the
48
+ * requests with no product, each once, minus the ones already linked to this feature and any the service would
49
+ * refuse (another product's).
50
+ */
51
+ export declare function linkableRequests<T extends RequestProductRef>(sameProduct: readonly T[], anyProduct: readonly T[], featureProductId: string, alreadyLinkedIds: readonly string[]): T[];
52
+ export interface HierarchyWorkItem {
53
+ id: string;
54
+ key: string;
55
+ title: string;
56
+ type: string;
57
+ status: string;
58
+ }
59
+ export interface HierarchyEpic extends HierarchyWorkItem {
60
+ /** The epic's direct children (stories, and any task or bug filed straight under it). */
61
+ children: readonly HierarchyWorkItem[];
62
+ }
63
+ export interface HierarchyFeature {
64
+ id: string;
65
+ name: string;
66
+ status: string;
67
+ archivedAt: string | null;
68
+ epics: readonly HierarchyEpic[];
69
+ }
70
+ export interface HierarchyLineRef {
71
+ id: string;
72
+ name: string;
73
+ archivedAt: string | null;
74
+ }
75
+ export interface HierarchyProduct {
76
+ id: string;
77
+ name: string;
78
+ lifecycleStage: string;
79
+ archivedAt: string | null;
80
+ /** Null when the product is in no line. Still returned while its line is archived. */
81
+ productLine: HierarchyLineRef | null;
82
+ features: readonly HierarchyFeature[];
83
+ }
84
+ export interface HierarchyGroup {
85
+ /** Null for the "No product line" group. */
86
+ line: HierarchyLineRef | null;
87
+ products: HierarchyProduct[];
88
+ }
89
+ /**
90
+ * Groups products under their product lines: every loaded line appears (an empty one too, so a new line is
91
+ * visible), lines by name, then the products in no line under a final "No product line" group (`line: null`),
92
+ * present only when it has products. Grouping reads each product's own `productLine`, never the line list alone,
93
+ * so a product whose line was left out of `lines` (an archived line while archived ones are hidden, or one past
94
+ * the first page) still lands under its real line instead of being shown as unassigned. Lines, products and
95
+ * features are sorted by name and epics by key, naturally (PM-2 before PM-10); an epic's stories keep their
96
+ * backlog order. The input is never mutated.
97
+ */
98
+ export declare function buildProductHierarchy(lines: readonly HierarchyLineRef[], products: readonly HierarchyProduct[]): HierarchyGroup[];
99
+ /** Expand/collapse key of a tree node. The "No product line" group's key is `line:none`. */
100
+ export declare function lineKey(line: HierarchyLineRef | null): string;
101
+ export declare const productKey: (id: string) => string;
102
+ export declare const featureKey: (id: string) => string;
103
+ export declare const epicKey: (id: string) => string;
104
+ /** Every expandable node's key — what "Expand all" opens. */
105
+ export declare function allNodeKeys(groups: readonly HierarchyGroup[]): Set<string>;
106
+ export interface HierarchyTotals {
107
+ lines: number;
108
+ products: number;
109
+ features: number;
110
+ epics: number;
111
+ children: number;
112
+ }
113
+ /** How many of each level the tree holds (the "No product line" group is not a line). */
114
+ export declare function hierarchyTotals(groups: readonly HierarchyGroup[]): HierarchyTotals;
115
+ export interface FilteredHierarchy {
116
+ groups: HierarchyGroup[];
117
+ /** Keys of the nodes to open so every match is visible — the ancestors of each match. */
118
+ expand: Set<string>;
119
+ }
120
+ /**
121
+ * Narrows the tree to what matches `query` (case-insensitive, anywhere in a line, product or feature name, or an
122
+ * epic's or child's key or title). A node that matches keeps its whole subtree — searching a product shows all of
123
+ * its features — and a node is kept with only its matching descendants when they match and it does not. A blank
124
+ * query returns the tree unchanged and opens nothing.
125
+ */
126
+ export declare function filterProductHierarchy(groups: readonly HierarchyGroup[], query: string): FilteredHierarchy;
127
+ //# sourceMappingURL=hierarchy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/product/hierarchy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ //#region src/planmagnet/product/hierarchy.ts
2
+ var e = [
3
+ "BACKLOG",
4
+ "DISCOVERY",
5
+ "PLANNING",
6
+ "IN_DEVELOPMENT",
7
+ "RELEASED"
8
+ ];
9
+ function t(t) {
10
+ return e.includes(t);
11
+ }
12
+ var n = {
13
+ BACKLOG: "outline",
14
+ DISCOVERY: "secondary",
15
+ PLANNING: "info",
16
+ IN_DEVELOPMENT: "default",
17
+ RELEASED: "success"
18
+ };
19
+ function r(e) {
20
+ return t(e) ? n[e] : "outline";
21
+ }
22
+ function i(e) {
23
+ let t = e.trim();
24
+ return t === "" ? null : t;
25
+ }
26
+ function a(e, t) {
27
+ let n = {};
28
+ for (let r of Object.keys(t)) t[r] !== e[r] && (n[r] = t[r]);
29
+ return n;
30
+ }
31
+ function o(e) {
32
+ if (!e) return null;
33
+ let t = /^-?\d+$/.test(e.trim()) ? new Date(Number(e)) : new Date(e);
34
+ return Number.isNaN(t.getTime()) ? null : t;
35
+ }
36
+ function s(e, t) {
37
+ return e.productId === null || e.productId === t.productId;
38
+ }
39
+ function c(e, t, n, r) {
40
+ let i = new Set(r), a = [];
41
+ for (let r of [...e, ...t]) i.has(r.id) || s(r, { productId: n }) && (i.add(r.id), a.push(r));
42
+ return a;
43
+ }
44
+ var l = new Intl.Collator(void 0, {
45
+ numeric: !0,
46
+ sensitivity: "base"
47
+ });
48
+ function u(e, t) {
49
+ return [...e].sort((e, n) => l.compare(t(e), t(n)));
50
+ }
51
+ function d(e) {
52
+ return {
53
+ ...e,
54
+ features: u(e.features, (e) => e.name).map((e) => ({
55
+ ...e,
56
+ epics: u(e.epics, (e) => e.key)
57
+ }))
58
+ };
59
+ }
60
+ function f(e, t) {
61
+ let n = /* @__PURE__ */ new Map();
62
+ for (let t of e) n.set(t.id, {
63
+ line: t,
64
+ products: []
65
+ });
66
+ let r = [];
67
+ for (let e of t) {
68
+ let t = e.productLine;
69
+ if (!t) {
70
+ r.push(e);
71
+ continue;
72
+ }
73
+ let i = n.get(t.id) ?? {
74
+ line: t,
75
+ products: []
76
+ };
77
+ i.products.push(e), n.set(t.id, i);
78
+ }
79
+ let i = u([...n.values()], (e) => e.line?.name ?? "").map((e) => ({
80
+ line: e.line,
81
+ products: u(e.products, (e) => e.name).map(d)
82
+ }));
83
+ return r.length > 0 && i.push({
84
+ line: null,
85
+ products: u(r, (e) => e.name).map(d)
86
+ }), i;
87
+ }
88
+ function p(e) {
89
+ return e ? `line:${e.id}` : "line:none";
90
+ }
91
+ var m = (e) => `product:${e}`, h = (e) => `feature:${e}`, g = (e) => `epic:${e}`;
92
+ function _(e) {
93
+ let t = /* @__PURE__ */ new Set();
94
+ for (let n of e) {
95
+ t.add(p(n.line));
96
+ for (let e of n.products) {
97
+ t.add(m(e.id));
98
+ for (let n of e.features) {
99
+ t.add(h(n.id));
100
+ for (let e of n.epics) t.add(g(e.id));
101
+ }
102
+ }
103
+ }
104
+ return t;
105
+ }
106
+ function v(e) {
107
+ let t = {
108
+ lines: 0,
109
+ products: 0,
110
+ features: 0,
111
+ epics: 0,
112
+ children: 0
113
+ };
114
+ for (let n of e) {
115
+ n.line && t.lines++;
116
+ for (let e of n.products) {
117
+ t.products++;
118
+ for (let n of e.features) {
119
+ t.features++;
120
+ for (let e of n.epics) t.epics++, t.children += e.children.length;
121
+ }
122
+ }
123
+ }
124
+ return t;
125
+ }
126
+ function y(e, t) {
127
+ let n = t.trim().toLocaleLowerCase(), r = /* @__PURE__ */ new Set();
128
+ if (!n) return {
129
+ groups: [...e],
130
+ expand: r
131
+ };
132
+ let i = (...e) => e.some((e) => e.toLocaleLowerCase().includes(n)), a = (e) => {
133
+ if (i(e.key, e.title)) return e;
134
+ let t = e.children.filter((e) => i(e.key, e.title));
135
+ return t.length === 0 ? null : (r.add(g(e.id)), {
136
+ ...e,
137
+ children: t
138
+ });
139
+ }, o = (e) => {
140
+ if (i(e.name)) return e;
141
+ let t = e.epics.map(a).filter((e) => e !== null);
142
+ return t.length === 0 ? null : (r.add(h(e.id)), {
143
+ ...e,
144
+ epics: t
145
+ });
146
+ }, s = (e) => {
147
+ if (i(e.name)) return e;
148
+ let t = e.features.map(o).filter((e) => e !== null);
149
+ return t.length === 0 ? null : (r.add(m(e.id)), {
150
+ ...e,
151
+ features: t
152
+ });
153
+ }, c = [];
154
+ for (let t of e) {
155
+ if (t.line && i(t.line.name)) {
156
+ c.push(t);
157
+ continue;
158
+ }
159
+ let e = t.products.map(s).filter((e) => e !== null);
160
+ e.length !== 0 && (r.add(p(t.line)), c.push({
161
+ line: t.line,
162
+ products: e
163
+ }));
164
+ }
165
+ return {
166
+ groups: c,
167
+ expand: r
168
+ };
169
+ }
170
+ //#endregion
171
+ export { e as PRODUCT_FEATURE_STATUSES, _ as allNodeKeys, f as buildProductHierarchy, a as changedFields, g as epicKey, h as featureKey, y as filterProductHierarchy, v as hierarchyTotals, p as lineKey, c as linkableRequests, i as normalizeText, o as parseServerDate, r as productFeatureStatusVariant, m as productKey };
172
+
173
+ //# sourceMappingURL=hierarchy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy.js","names":[],"sources":["../../../src/planmagnet/product/hierarchy.ts"],"sourcesContent":["/**\n * PRM-01 product hierarchy — the pure helpers behind the product line, product feature and hierarchy pages:\n * Product line > Product > Feature > Epic > Story. No React and no GraphQL here, so every rule is unit-tested\n * (tests/productHierarchy.test.ts).\n *\n * Vocabulary (owner decision, binding): a FEATURE REQUEST is customer/user feedback asking for something; a\n * FEATURE (`PlanmagnetProductFeature`) is a product capability the team decided to build. A request may point at\n * the feature that addresses it — it is never converted into one.\n */\n\n// ─── Feature status ─────────────────────────────────────────────────────────\n\n/** `PlanmagnetProductFeatureStatus`, in lifecycle order (the SDL lists it alphabetically). */\nexport const PRODUCT_FEATURE_STATUSES = [\n 'BACKLOG',\n 'DISCOVERY',\n 'PLANNING',\n 'IN_DEVELOPMENT',\n 'RELEASED',\n] as const;\nexport type ProductFeatureStatus = (typeof PRODUCT_FEATURE_STATUSES)[number];\n\nexport function isProductFeatureStatus(value: string): value is ProductFeatureStatus {\n return (PRODUCT_FEATURE_STATUSES as readonly string[]).includes(value);\n}\n\n/** The fe-libs Badge variant for a feature status — semantic tokens only, never a raw colour. */\nexport type FeatureStatusBadgeVariant = 'outline' | 'secondary' | 'info' | 'default' | 'success';\n\nconst STATUS_VARIANTS: Record<ProductFeatureStatus, FeatureStatusBadgeVariant> = {\n BACKLOG: 'outline',\n DISCOVERY: 'secondary',\n PLANNING: 'info',\n IN_DEVELOPMENT: 'default',\n RELEASED: 'success',\n};\n\n/** A status the API returns that this list does not know yet still renders, as a plain outline badge. */\nexport function productFeatureStatusVariant(status: string): FeatureStatusBadgeVariant {\n return isProductFeatureStatus(status) ? STATUS_VARIANTS[status] : 'outline';\n}\n\n// ─── Forms ──────────────────────────────────────────────────────────────────\n\n/** A text input's value as the API wants it: trimmed, and an emptied field is `null` (clears it). */\nexport function normalizeText(value: string): string | null {\n const trimmed = value.trim();\n return trimmed === '' ? null : trimmed;\n}\n\n/**\n * The entries of `next` whose value differs from `initial` — what an edit form sends. Forms here send only the\n * fields the person changed, so a save never rewrites a field someone else edited meanwhile. Values are compared\n * with `===`, so callers normalize both sides first (see `normalizeText`).\n */\nexport function changedFields<T extends Record<string, unknown>>(initial: T, next: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(next) as (keyof T)[]) {\n if (next[key] !== initial[key]) out[key] = next[key];\n }\n return out;\n}\n\n// ─── Dates ──────────────────────────────────────────────────────────────────\n\n/**\n * A server date as a `Date`, or null when there is none or it cannot be read. `DateTime` fields arrive as ISO\n * strings, but a date carried in a GraphQL `String` serializes as epoch milliseconds (\"1727740800000\"), so both\n * forms are accepted rather than rendering \"Invalid Date\".\n */\nexport function parseServerDate(value: string | null | undefined): Date | null {\n if (!value) return null;\n const date = /^-?\\d+$/.test(value.trim()) ? new Date(Number(value)) : new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n// ─── Feature requests → features ────────────────────────────────────────────\n\nexport interface RequestProductRef {\n id: string;\n productId: string | null;\n}\n\n/**\n * The service's rule (planmagnetLinkFeatureRequestToProductFeature): a request that has a product may only point\n * at a feature of that same product; a request with no product may point at any feature. The UI offers only what\n * the service accepts — and still shows the service's own reason if it refuses.\n */\nexport function canLinkRequestToFeature(\n request: { productId: string | null },\n feature: { productId: string }\n): boolean {\n return request.productId === null || request.productId === feature.productId;\n}\n\n/**\n * The requests a feature's \"Link a request\" picker offers: the feature's own product's requests first, then the\n * requests with no product, each once, minus the ones already linked to this feature and any the service would\n * refuse (another product's).\n */\nexport function linkableRequests<T extends RequestProductRef>(\n sameProduct: readonly T[],\n anyProduct: readonly T[],\n featureProductId: string,\n alreadyLinkedIds: readonly string[]\n): T[] {\n const seen = new Set<string>(alreadyLinkedIds);\n const out: T[] = [];\n for (const request of [...sameProduct, ...anyProduct]) {\n if (seen.has(request.id)) continue;\n if (!canLinkRequestToFeature(request, { productId: featureProductId })) continue;\n seen.add(request.id);\n out.push(request);\n }\n return out;\n}\n\n// ─── The hierarchy tree ─────────────────────────────────────────────────────\n\nexport interface HierarchyWorkItem {\n id: string;\n key: string;\n title: string;\n type: string;\n status: string;\n}\n\nexport interface HierarchyEpic extends HierarchyWorkItem {\n /** The epic's direct children (stories, and any task or bug filed straight under it). */\n children: readonly HierarchyWorkItem[];\n}\n\nexport interface HierarchyFeature {\n id: string;\n name: string;\n status: string;\n archivedAt: string | null;\n epics: readonly HierarchyEpic[];\n}\n\nexport interface HierarchyLineRef {\n id: string;\n name: string;\n archivedAt: string | null;\n}\n\nexport interface HierarchyProduct {\n id: string;\n name: string;\n lifecycleStage: string;\n archivedAt: string | null;\n /** Null when the product is in no line. Still returned while its line is archived. */\n productLine: HierarchyLineRef | null;\n features: readonly HierarchyFeature[];\n}\n\nexport interface HierarchyGroup {\n /** Null for the \"No product line\" group. */\n line: HierarchyLineRef | null;\n products: HierarchyProduct[];\n}\n\nconst collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });\n\nfunction sortBy<T>(items: readonly T[], label: (item: T) => string): T[] {\n // A copy: Apollo hands out frozen arrays, so sorting in place would throw.\n return [...items].sort((a, b) => collator.compare(label(a), label(b)));\n}\n\nfunction sortProduct(product: HierarchyProduct): HierarchyProduct {\n return {\n ...product,\n features: sortBy(product.features, (f) => f.name).map((feature) => ({\n ...feature,\n // An epic's children keep the service's order — their backlog rank (position), then age.\n epics: sortBy(feature.epics, (e) => e.key),\n })),\n };\n}\n\n/**\n * Groups products under their product lines: every loaded line appears (an empty one too, so a new line is\n * visible), lines by name, then the products in no line under a final \"No product line\" group (`line: null`),\n * present only when it has products. Grouping reads each product's own `productLine`, never the line list alone,\n * so a product whose line was left out of `lines` (an archived line while archived ones are hidden, or one past\n * the first page) still lands under its real line instead of being shown as unassigned. Lines, products and\n * features are sorted by name and epics by key, naturally (PM-2 before PM-10); an epic's stories keep their\n * backlog order. The input is never mutated.\n */\nexport function buildProductHierarchy(\n lines: readonly HierarchyLineRef[],\n products: readonly HierarchyProduct[]\n): HierarchyGroup[] {\n const byLine = new Map<string, HierarchyGroup>();\n for (const line of lines) byLine.set(line.id, { line, products: [] });\n const unassigned: HierarchyProduct[] = [];\n for (const product of products) {\n const line = product.productLine;\n if (!line) {\n unassigned.push(product);\n continue;\n }\n const group = byLine.get(line.id) ?? { line, products: [] };\n group.products.push(product);\n byLine.set(line.id, group);\n }\n const groups = sortBy([...byLine.values()], (g) => g.line?.name ?? '').map((group) => ({\n line: group.line,\n products: sortBy(group.products, (p) => p.name).map(sortProduct),\n }));\n if (unassigned.length > 0) {\n groups.push({ line: null, products: sortBy(unassigned, (p) => p.name).map(sortProduct) });\n }\n return groups;\n}\n\n/** Expand/collapse key of a tree node. The \"No product line\" group's key is `line:none`. */\nexport function lineKey(line: HierarchyLineRef | null): string {\n return line ? `line:${line.id}` : 'line:none';\n}\nexport const productKey = (id: string) => `product:${id}`;\nexport const featureKey = (id: string) => `feature:${id}`;\nexport const epicKey = (id: string) => `epic:${id}`;\n\n/** Every expandable node's key — what \"Expand all\" opens. */\nexport function allNodeKeys(groups: readonly HierarchyGroup[]): Set<string> {\n const keys = new Set<string>();\n for (const group of groups) {\n keys.add(lineKey(group.line));\n for (const product of group.products) {\n keys.add(productKey(product.id));\n for (const feature of product.features) {\n keys.add(featureKey(feature.id));\n for (const epic of feature.epics) keys.add(epicKey(epic.id));\n }\n }\n }\n return keys;\n}\n\nexport interface HierarchyTotals {\n lines: number;\n products: number;\n features: number;\n epics: number;\n children: number;\n}\n\n/** How many of each level the tree holds (the \"No product line\" group is not a line). */\nexport function hierarchyTotals(groups: readonly HierarchyGroup[]): HierarchyTotals {\n const totals: HierarchyTotals = { lines: 0, products: 0, features: 0, epics: 0, children: 0 };\n for (const group of groups) {\n if (group.line) totals.lines++;\n for (const product of group.products) {\n totals.products++;\n for (const feature of product.features) {\n totals.features++;\n for (const epic of feature.epics) {\n totals.epics++;\n totals.children += epic.children.length;\n }\n }\n }\n }\n return totals;\n}\n\nexport interface FilteredHierarchy {\n groups: HierarchyGroup[];\n /** Keys of the nodes to open so every match is visible — the ancestors of each match. */\n expand: Set<string>;\n}\n\n/**\n * Narrows the tree to what matches `query` (case-insensitive, anywhere in a line, product or feature name, or an\n * epic's or child's key or title). A node that matches keeps its whole subtree — searching a product shows all of\n * its features — and a node is kept with only its matching descendants when they match and it does not. A blank\n * query returns the tree unchanged and opens nothing.\n */\nexport function filterProductHierarchy(\n groups: readonly HierarchyGroup[],\n query: string\n): FilteredHierarchy {\n const needle = query.trim().toLocaleLowerCase();\n const expand = new Set<string>();\n if (!needle) return { groups: [...groups], expand };\n const hit = (...texts: string[]) => texts.some((t) => t.toLocaleLowerCase().includes(needle));\n\n const filterEpic = (epic: HierarchyEpic): HierarchyEpic | null => {\n if (hit(epic.key, epic.title)) return epic;\n const children = epic.children.filter((child) => hit(child.key, child.title));\n if (children.length === 0) return null;\n expand.add(epicKey(epic.id));\n return { ...epic, children };\n };\n const filterFeature = (feature: HierarchyFeature): HierarchyFeature | null => {\n if (hit(feature.name)) return feature;\n const epics = feature.epics.map(filterEpic).filter((e): e is HierarchyEpic => e !== null);\n if (epics.length === 0) return null;\n expand.add(featureKey(feature.id));\n return { ...feature, epics };\n };\n const filterProduct = (product: HierarchyProduct): HierarchyProduct | null => {\n if (hit(product.name)) return product;\n const features = product.features\n .map(filterFeature)\n .filter((f): f is HierarchyFeature => f !== null);\n if (features.length === 0) return null;\n expand.add(productKey(product.id));\n return { ...product, features };\n };\n\n const out: HierarchyGroup[] = [];\n for (const group of groups) {\n if (group.line && hit(group.line.name)) {\n out.push(group);\n continue;\n }\n const products = group.products\n .map(filterProduct)\n .filter((p): p is HierarchyProduct => p !== null);\n if (products.length === 0) continue;\n expand.add(lineKey(group.line));\n out.push({ line: group.line, products });\n }\n return { groups: out, expand };\n}\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ export declare const PRODUCT_LINES_PAGE: import('graphql').DocumentNode;
2
+ export declare const PRODUCT_LINE_OPTIONS: import('graphql').DocumentNode;
3
+ export declare const PRODUCT_LINE_ASSIGNMENT: import('graphql').DocumentNode;
4
+ export declare const PRODUCT_LINE_CANDIDATE_PRODUCTS: import('graphql').DocumentNode;
5
+ export declare const PRODUCT_TEAMS: import('graphql').DocumentNode;
6
+ export declare const PRODUCT_FEATURES_LIST: import('graphql').DocumentNode;
7
+ export declare const PRODUCT_FEATURE_DETAIL: import('graphql').DocumentNode;
8
+ export declare const PRODUCT_FEATURE_EPIC_CANDIDATES: import('graphql').DocumentNode;
9
+ export declare const PRODUCT_FEATURE_REQUEST_CANDIDATES: import('graphql').DocumentNode;
10
+ export declare const PRODUCT_HIERARCHY: import('graphql').DocumentNode;
11
+ export declare const FEATURE_REQUEST_PRODUCT_FEATURE: import('graphql').DocumentNode;
12
+ export declare const FEATURE_REQUEST_FEATURE_OPTIONS: import('graphql').DocumentNode;
13
+ export declare const WORK_ITEM_PRODUCT_FEATURE: import('graphql').DocumentNode;
14
+ export declare const CREATE_PRODUCT_LINE: import('graphql').DocumentNode;
15
+ export declare const UPDATE_PRODUCT_LINE: import('graphql').DocumentNode;
16
+ export declare const ARCHIVE_PRODUCT_LINE: import('graphql').DocumentNode;
17
+ export declare const UNARCHIVE_PRODUCT_LINE: import('graphql').DocumentNode;
18
+ export declare const DELETE_PRODUCT_LINE: import('graphql').DocumentNode;
19
+ export declare const ASSIGN_PRODUCT_LINE: import('graphql').DocumentNode;
20
+ export declare const LINK_PRODUCT_TEAM: import('graphql').DocumentNode;
21
+ export declare const UNLINK_PRODUCT_TEAM: import('graphql').DocumentNode;
22
+ export declare const CREATE_PRODUCT_FEATURE: import('graphql').DocumentNode;
23
+ export declare const UPDATE_PRODUCT_FEATURE: import('graphql').DocumentNode;
24
+ export declare const ARCHIVE_PRODUCT_FEATURE: import('graphql').DocumentNode;
25
+ export declare const UNARCHIVE_PRODUCT_FEATURE: import('graphql').DocumentNode;
26
+ export declare const DELETE_PRODUCT_FEATURE: import('graphql').DocumentNode;
27
+ export declare const LINK_FEATURE_REQUEST_TO_PRODUCT_FEATURE: import('graphql').DocumentNode;
28
+ export declare const UNLINK_FEATURE_REQUEST_FROM_PRODUCT_FEATURE: import('graphql').DocumentNode;
29
+ export declare const LINK_EPIC_TO_PRODUCT_FEATURE: import('graphql').DocumentNode;
30
+ export declare const UNLINK_EPIC_FROM_PRODUCT_FEATURE: import('graphql').DocumentNode;
31
+ /** Every query that shows a product line, or which line a product is in. */
32
+ export declare const PRODUCT_LINE_QUERIES: string[];
33
+ /** Every query that shows a product feature, its epics or its requests. */
34
+ export declare const PRODUCT_FEATURE_QUERIES: string[];
35
+ //# sourceMappingURL=hierarchyOperations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchyOperations.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/product/hierarchyOperations.ts"],"names":[],"mappings":"AAuCA,eAAO,MAAM,kBAAkB,gCAiB9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,gCAchC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAYnC,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAe3C,CAAC;AAEF,eAAO,MAAM,aAAa,gCAezB,CAAC;AAEF,eAAO,MAAM,qBAAqB,gCA2BjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAoClC,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAgB3C,CAAC;AAEF,eAAO,MAAM,kCAAkC,gCAyC9C,CAAC;AAEF,eAAO,MAAM,iBAAiB,gCA0D7B,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAiB3C,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAgB3C,CAAC;AAEF,eAAO,MAAM,yBAAyB,gCAoBrC,CAAC;AAIF,eAAO,MAAM,mBAAmB,gCAM/B,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAM/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,gCAOhC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAOlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAI/B,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAY/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,gCAM7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAI/B,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAMlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAMlC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAOnC,CAAC;AAEF,eAAO,MAAM,yBAAyB,gCAOrC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAIlC,CAAC;AAEF,eAAO,MAAM,uCAAuC,gCASnD,CAAC;AAEF,eAAO,MAAM,2CAA2C,gCASvD,CAAC;AAEF,eAAO,MAAM,4BAA4B,gCASxC,CAAC;AAEF,eAAO,MAAM,gCAAgC,gCAO5C,CAAC;AAIF,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,UAMhC,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,UAOnC,CAAC"}