@burdenoff/microfe-planmagnet 2026.930.21 → 2026.930.23
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/node_modules/lodash/_arrayEach.js +14 -0
- package/dist/node_modules/lodash/_arrayEach.js.map +1 -0
- package/dist/node_modules/lodash/_assignValue.js +17 -0
- package/dist/node_modules/lodash/_assignValue.js.map +1 -0
- package/dist/node_modules/lodash/_baseAssign.js +16 -0
- package/dist/node_modules/lodash/_baseAssign.js.map +1 -0
- package/dist/node_modules/lodash/_baseAssignIn.js +16 -0
- package/dist/node_modules/lodash/_baseAssignIn.js.map +1 -0
- package/dist/node_modules/lodash/_baseClone.js +64 -0
- package/dist/node_modules/lodash/_baseClone.js.map +1 -0
- package/dist/node_modules/lodash/_baseCreate.js +21 -0
- package/dist/node_modules/lodash/_baseCreate.js.map +1 -0
- package/dist/node_modules/lodash/_baseIsMap.js +16 -0
- package/dist/node_modules/lodash/_baseIsMap.js.map +1 -0
- package/dist/node_modules/lodash/_baseIsSet.js +16 -0
- package/dist/node_modules/lodash/_baseIsSet.js.map +1 -0
- package/dist/node_modules/lodash/_baseKeysIn.js +20 -0
- package/dist/node_modules/lodash/_baseKeysIn.js.map +1 -0
- package/dist/node_modules/lodash/_baseSum.js +17 -0
- package/dist/node_modules/lodash/_baseSum.js.map +1 -0
- package/dist/node_modules/lodash/_baseUnset.js +26 -0
- package/dist/node_modules/lodash/_baseUnset.js.map +1 -0
- package/dist/node_modules/lodash/_cloneArrayBuffer.js +16 -0
- package/dist/node_modules/lodash/_cloneArrayBuffer.js.map +1 -0
- package/dist/node_modules/lodash/_cloneBuffer.js +17 -0
- package/dist/node_modules/lodash/_cloneBuffer.js.map +1 -0
- package/dist/node_modules/lodash/_cloneDataView.js +16 -0
- package/dist/node_modules/lodash/_cloneDataView.js.map +1 -0
- package/dist/node_modules/lodash/_cloneRegExp.js +15 -0
- package/dist/node_modules/lodash/_cloneRegExp.js.map +1 -0
- package/dist/node_modules/lodash/_cloneSymbol.js +15 -0
- package/dist/node_modules/lodash/_cloneSymbol.js.map +1 -0
- package/dist/node_modules/lodash/_cloneTypedArray.js +16 -0
- package/dist/node_modules/lodash/_cloneTypedArray.js.map +1 -0
- package/dist/node_modules/lodash/_copyArray.js +15 -0
- package/dist/node_modules/lodash/_copyArray.js.map +1 -0
- package/dist/node_modules/lodash/_copyObject.js +22 -0
- package/dist/node_modules/lodash/_copyObject.js.map +1 -0
- package/dist/node_modules/lodash/_copySymbols.js +16 -0
- package/dist/node_modules/lodash/_copySymbols.js.map +1 -0
- package/dist/node_modules/lodash/_copySymbolsIn.js +16 -0
- package/dist/node_modules/lodash/_copySymbolsIn.js.map +1 -0
- package/dist/node_modules/lodash/_customOmitClone.js +15 -0
- package/dist/node_modules/lodash/_customOmitClone.js.map +1 -0
- package/dist/node_modules/lodash/_flatRest.js +17 -0
- package/dist/node_modules/lodash/_flatRest.js.map +1 -0
- package/dist/node_modules/lodash/_getAllKeysIn.js +17 -0
- package/dist/node_modules/lodash/_getAllKeysIn.js.map +1 -0
- package/dist/node_modules/lodash/_getSymbolsIn.js +18 -0
- package/dist/node_modules/lodash/_getSymbolsIn.js.map +1 -0
- package/dist/node_modules/lodash/_initCloneArray.js +15 -0
- package/dist/node_modules/lodash/_initCloneArray.js.map +1 -0
- package/dist/node_modules/lodash/_initCloneByTag.js +40 -0
- package/dist/node_modules/lodash/_initCloneByTag.js.map +1 -0
- package/dist/node_modules/lodash/_initCloneObject.js +17 -0
- package/dist/node_modules/lodash/_initCloneObject.js.map +1 -0
- package/dist/node_modules/lodash/_nativeKeysIn.js +15 -0
- package/dist/node_modules/lodash/_nativeKeysIn.js.map +1 -0
- package/dist/node_modules/lodash/_parent.js +16 -0
- package/dist/node_modules/lodash/_parent.js.map +1 -0
- package/dist/node_modules/lodash/flatten.js +15 -0
- package/dist/node_modules/lodash/flatten.js.map +1 -0
- package/dist/node_modules/lodash/isMap.js +14 -0
- package/dist/node_modules/lodash/isMap.js.map +1 -0
- package/dist/node_modules/lodash/isSet.js +14 -0
- package/dist/node_modules/lodash/isSet.js.map +1 -0
- package/dist/node_modules/lodash/keysIn.js +17 -0
- package/dist/node_modules/lodash/keysIn.js.map +1 -0
- package/dist/node_modules/lodash/maxBy.js +17 -0
- package/dist/node_modules/lodash/maxBy.js.map +1 -0
- package/dist/node_modules/lodash/omit.js +28 -0
- package/dist/node_modules/lodash/omit.js.map +1 -0
- package/dist/node_modules/lodash/sumBy.js +16 -0
- package/dist/node_modules/lodash/sumBy.js.map +1 -0
- package/dist/node_modules/recharts/es6/chart/Sankey.js +556 -0
- package/dist/node_modules/recharts/es6/chart/Sankey.js.map +1 -0
- package/dist/node_modules/recharts/es6/chart/Treemap.js +545 -0
- package/dist/node_modules/recharts/es6/chart/Treemap.js.map +1 -0
- package/dist/node_modules/recharts/es6/shape/Polygon.js +112 -0
- package/dist/node_modules/recharts/es6/shape/Polygon.js.map +1 -0
- package/dist/node_modules/recharts/es6/util/Constants.js +31 -0
- package/dist/node_modules/recharts/es6/util/Constants.js.map +1 -0
- package/dist/node_modules/recharts/es6/util/ReactUtils.js +15 -10
- package/dist/node_modules/recharts/es6/util/ReactUtils.js.map +1 -1
- package/dist/planmagnet/controls/AutomationRuleControls.js +10 -10
- package/dist/planmagnet/controls/CompetitorControls.js +7 -7
- package/dist/planmagnet/controls/FeatureControls.js +38 -38
- package/dist/planmagnet/controls/ProgramControls.js +8 -8
- package/dist/planmagnet/dependency/DependencyGraph.d.ts.map +1 -1
- package/dist/planmagnet/dependency/DependencyGraph.js +13 -10
- package/dist/planmagnet/dependency/DependencyGraph.js.map +1 -1
- package/dist/planmagnet/dependency/DependencyPanel.js +13 -13
- package/dist/planmagnet/dependency/GanttChart.d.ts.map +1 -1
- package/dist/planmagnet/dependency/GanttChart.js +11 -5
- package/dist/planmagnet/dependency/GanttChart.js.map +1 -1
- package/dist/planmagnet/pages/ActivityActivityPagePage.js +16 -16
- package/dist/planmagnet/pages/ActivityActivityPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +91 -73
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +6 -6
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -11
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +5 -5
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +5 -5
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +6 -6
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +8 -8
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +10 -10
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +5 -5
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +8 -8
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +4 -4
- package/dist/planmagnet/pages/analytics/StatusFlowCard.d.ts +8 -0
- package/dist/planmagnet/pages/analytics/StatusFlowCard.d.ts.map +1 -0
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +234 -0
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js.map +1 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.d.ts +6 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.d.ts.map +1 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +367 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js +17 -17
- package/dist/planmagnet/utils/statusColor.d.ts +8 -9
- package/dist/planmagnet/utils/statusColor.d.ts.map +1 -1
- package/dist/planmagnet/utils/statusColor.js +8 -40
- package/dist/planmagnet/utils/statusColor.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkBreakdownCard.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/WorkBreakdownCard.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { LayoutGrid } from 'lucide-react';\nimport { ResponsiveContainer, Tooltip, Treemap } from 'recharts';\nimport { usePlanMagnetNavigate } from '../../providers/PlanMagnetProvider';\nimport { rowLinkProps } from '../../utils/rowNavigation';\nimport { isForbidden } from '../../utils/graphqlErrors';\nimport { truncate } from '../../dependency/DependencyGraph';\n\n/**\n * ANR-24: work breakdown Treemap (wspace-planmagnet-svc#65 `planmagnetWorkBreakdown`) — live (not date-ranged;\n * see the field's own doc comment) open/done counts grouped project -> epic, with a \"No epic\" bucket per\n * project for items an epic rollup doesn't reach (see PlanmagnetNoEpicWorkBreakdown's doc comment: direct\n * children only, same as PlanmagnetEpicWorkBreakdown — a sub-task nested two levels under an epic falls here\n * too, not under its grandparent epic).\n *\n * recharts' Treemap computes a node's own size as the SUM of its children's sizes when `node.children` is set\n * (see `computeNode` in recharts' source) — so each project's own cell size, and its done-ratio color, both\n * come from summing this card's own visualized epic + \"No epic\" leaves, not from the server's separate\n * project-level totals (which include every live item in the project, not only the ones a leaf here\n * represents — see PlanmagnetProjectWorkBreakdown's doc comment). What the treemap draws and what the table\n * below it lists are therefore always the same numbers.\n */\n\ntype BreakdownMetric = 'items' | 'points';\n\nconst GET_WORK_BREAKDOWN = gql`\n query GetAnalyticsWorkBreakdown($workspaceId: ID!, $projectIds: [ID!]) {\n planmagnetWorkBreakdown(workspaceId: $workspaceId, projectIds: $projectIds) {\n truncatedProjects\n projects {\n projectId\n projectName\n truncatedEpics\n epics {\n epicId\n epicTitle\n openCount\n doneCount\n openPoints\n donePoints\n }\n noEpic {\n openCount\n doneCount\n openPoints\n donePoints\n }\n }\n }\n }\n`;\n\ninterface EpicBreakdown {\n epicId: string;\n epicTitle: string;\n openCount: number;\n doneCount: number;\n openPoints: number;\n donePoints: number;\n}\n\ninterface NoEpicBreakdown {\n openCount: number;\n doneCount: number;\n openPoints: number;\n donePoints: number;\n}\n\ninterface ProjectBreakdown {\n projectId: string;\n projectName: string;\n truncatedEpics: boolean;\n epics: EpicBreakdown[];\n noEpic: NoEpicBreakdown;\n}\n\ninterface WorkBreakdownData {\n planmagnetWorkBreakdown: {\n truncatedProjects: boolean;\n projects: ProjectBreakdown[];\n };\n}\n\ninterface CountsLike {\n openCount: number;\n doneCount: number;\n openPoints: number;\n donePoints: number;\n}\n\nfunction metricTotals(c: CountsLike, metric: BreakdownMetric): { size: number; done: number } {\n return metric === 'items'\n ? { size: c.openCount + c.doneCount, done: c.doneCount }\n : { size: c.openPoints + c.donePoints, done: c.donePoints };\n}\n\nfunction doneRatioOf(c: CountsLike, metric: BreakdownMetric): number | null {\n const { size, done } = metricTotals(c, metric);\n return size > 0 ? done / size : null;\n}\n\ninterface BreakdownLeafDatum extends CountsLike {\n name: string;\n size: number;\n doneRatio: number | null;\n /** null = not an individually openable work item (the project-group frame, or the \"No epic\" bucket). */\n epicId: string | null;\n isNoEpic: boolean;\n isHeader: boolean;\n projectId: string;\n projectName: string;\n}\n\ninterface BreakdownGroupDatum {\n name: string;\n projectId: string;\n children: BreakdownLeafDatum[];\n}\n\n/** A label cell reserves this share of each project's own area (never less than HEADER_MIN_SIZE). Small relative\n * to its real epic siblings so it barely affects the epics' own visual weight, but consistently sized so every\n * project's header reads the same size. */\nconst HEADER_FRACTION = 0.14;\nconst HEADER_MIN_SIZE = 1;\n\nfunction buildBreakdownGroups(\n projects: ProjectBreakdown[],\n metric: BreakdownMetric,\n noEpicLabel: string\n): BreakdownGroupDatum[] {\n const groups: BreakdownGroupDatum[] = [];\n\n for (const project of projects) {\n const epicLeaves: BreakdownLeafDatum[] = project.epics\n .map((epic) => ({\n name: epic.epicTitle,\n size: metricTotals(epic, metric).size,\n doneRatio: doneRatioOf(epic, metric),\n epicId: epic.epicId,\n isNoEpic: false,\n isHeader: false,\n projectId: project.projectId,\n projectName: project.projectName,\n openCount: epic.openCount,\n doneCount: epic.doneCount,\n openPoints: epic.openPoints,\n donePoints: epic.donePoints,\n }))\n .filter((leaf) => leaf.size > 0);\n\n const noEpicSize = metricTotals(project.noEpic, metric).size;\n if (noEpicSize > 0) {\n epicLeaves.push({\n name: noEpicLabel,\n size: noEpicSize,\n doneRatio: doneRatioOf(project.noEpic, metric),\n epicId: null,\n isNoEpic: true,\n isHeader: false,\n projectId: project.projectId,\n projectName: project.projectName,\n openCount: project.noEpic.openCount,\n doneCount: project.noEpic.doneCount,\n openPoints: project.noEpic.openPoints,\n donePoints: project.noEpic.donePoints,\n });\n }\n\n const totalSize = epicLeaves.reduce((sum, leaf) => sum + leaf.size, 0);\n if (totalSize <= 0) continue;\n\n // A small leading child in a list of much-larger siblings reliably becomes its own single-item row under\n // squarify's row-packing (recharts' Treemap layout): adding the next, much bigger sibling to that same row\n // would immediately worsen the row's aspect ratio, so squarify closes the row after just this one item —\n // which a single-item row always stretches to the full width (or height) of its parent rect. That makes\n // this synthetic \"header\" leaf a reliable label strip across the top (or side) of each project's own area,\n // without needing a second, hand-rolled layout pass. See WorkBreakdownCell's `isHeader` branch for the\n // render side of this.\n const headerLeaf: BreakdownLeafDatum = {\n name: project.projectName,\n size: Math.max(Math.round(totalSize * HEADER_FRACTION), HEADER_MIN_SIZE),\n doneRatio: null,\n epicId: null,\n isNoEpic: false,\n isHeader: true,\n projectId: project.projectId,\n projectName: project.projectName,\n openCount: 0,\n doneCount: 0,\n openPoints: 0,\n donePoints: 0,\n };\n\n groups.push({\n name: project.projectName,\n projectId: project.projectId,\n children: [headerLeaf, ...epicLeaves],\n });\n }\n\n return groups;\n}\n\nfunction ratioToFillOpacity(doneRatio: number | null): number {\n // Sequential, single-hue encoding on DONE's own chart color (the same hue statusVisual.ts assigns to the DONE\n // status everywhere else in this app): lighter = mostly open, darker = mostly done. Never fully transparent,\n // so a 0%-done cell still reads as a populated cell rather than empty space — and the exact ratio is always\n // available in the tooltip and the table below, so this fill is a supplementary channel, never the only way\n // to read it.\n if (doneRatio === null) return 0.12;\n return 0.18 + doneRatio * 0.72;\n}\n\ninterface WorkBreakdownCellProps {\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n depth?: number;\n name?: string;\n isHeader?: boolean;\n isNoEpic?: boolean;\n epicId?: string | null;\n doneRatio?: number | null;\n onOpenEpic: (epicId: string) => void;\n noEpicLabel: string;\n}\n\nfunction WorkBreakdownCell(props: WorkBreakdownCellProps) {\n const {\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n depth = 0,\n name = '',\n isHeader = false,\n isNoEpic = false,\n epicId = null,\n doneRatio = null,\n onOpenEpic,\n noEpicLabel,\n } = props;\n\n if (width <= 0 || height <= 0) return null;\n\n if (depth === 0) {\n // recharts wraps the ENTIRE `data` array (every project combined) in one synthetic root at depth 0\n // (see `computeNode` / `getDerivedStateFromProps` in recharts' own Treemap source) — this fires exactly\n // once for the whole chart area, never once per project. It has no boundary of its own to draw (a rect\n // here would be one border around every project combined, not a per-project frame), so render nothing.\n return null;\n }\n\n if (depth === 1) {\n // The real per-project frame: recharts assigns depth 1 to each top-level entry of `data` — i.e. one\n // `BreakdownGroupDatum` per project — so this branch fires once per project, at that project's own\n // bounds. Drawn first (recharts renders a non-leaf node's own content, then its children, in that\n // order), so the header + epic leaves at depth 2 paint on top of it within the same bounds.\n // Never filled: fill is reserved for done-ratio below, so the frame reads purely as a boundary.\n return (\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={4}\n className=\"fill-none stroke-border\"\n strokeWidth={1}\n />\n );\n }\n\n const label = isNoEpic ? noEpicLabel : name;\n const showLabel = width > 28 && height > 14;\n const fitChars = Math.max(1, Math.floor((width - 8) / 6));\n const displayLabel = truncate(label, fitChars);\n\n if (isHeader) {\n return (\n <g>\n <rect x={x} y={y} width={width} height={height} className=\"fill-muted\" />\n {showLabel && (\n <text\n x={x + 4}\n y={y + height / 2}\n dy=\"0.32em\"\n className=\"fill-foreground text-[11px] font-medium\"\n >\n {displayLabel}\n </text>\n )}\n </g>\n );\n }\n\n const cellRect = (\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n className=\"stroke-background\"\n style={{ fill: 'var(--color-chart-2)', fillOpacity: ratioToFillOpacity(doneRatio) }}\n strokeWidth={2}\n />\n );\n const cellLabel = showLabel && (\n <text x={x + 4} y={y + height / 2} dy=\"0.32em\" className=\"fill-foreground text-[11px]\">\n {displayLabel}\n </text>\n );\n\n if (!epicId) {\n // \"No epic\" bucket — an aggregate of several work items, not one item, so there is nothing to navigate to.\n return (\n <g>\n {cellRect}\n {cellLabel}\n </g>\n );\n }\n\n const accessibleLabel = `${name} — ${Math.round((doneRatio ?? 0) * 100)}% done`;\n return (\n <g\n {...rowLinkProps<SVGGElement>(() => onOpenEpic(epicId), accessibleLabel)}\n className=\"cursor-pointer focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2\"\n >\n <title>{accessibleLabel}</title>\n {cellRect}\n {cellLabel}\n </g>\n );\n}\n\nexport function WorkBreakdownCard({\n workspaceId,\n projectId,\n}: {\n workspaceId?: string | null;\n /** ANR-09 project scope. Omitted/undefined = every project (up to 50 — see truncatedProjects below). */\n projectId?: string;\n}) {\n const { t } = useI18n();\n const navigate = usePlanMagnetNavigate();\n const [metric, setMetric] = useState<BreakdownMetric>('items');\n const { data, loading, error } = useQuery<WorkBreakdownData>(GET_WORK_BREAKDOWN, {\n variables: { workspaceId, projectIds: projectId ? [projectId] : undefined },\n skip: !workspaceId,\n });\n\n const noEpicLabel = t('planmagnet.analytics.workBreakdown.noEpic', 'No epic');\n const breakdown = data?.planmagnetWorkBreakdown;\n const projects = breakdown?.projects ?? [];\n const groups = buildBreakdownGroups(projects, metric, noEpicLabel);\n const truncatedProjectNames = projects.filter((p) => p.truncatedEpics).map((p) => p.projectName);\n\n function openEpic(epicId: string) {\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(epicId)}`);\n }\n\n return (\n <Card data-testid=\"work-breakdown-card\">\n <CardHeader>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <CardTitle className=\"flex items-center gap-2\">\n <LayoutGrid className=\"h-4 w-4\" />\n {t('planmagnet.analytics.workBreakdown.title', 'Work breakdown')}\n </CardTitle>\n <div\n className=\"flex items-center gap-1 rounded-md border p-0.5\"\n role=\"group\"\n aria-label={t('planmagnet.analytics.workBreakdown.metricLabel', 'Size by')}\n data-testid=\"viz-breakdown-metric\"\n >\n {(['items', 'points'] as const).map((m) => (\n <button\n key={m}\n type=\"button\"\n aria-pressed={metric === m}\n onClick={() => setMetric(m)}\n className={`px-2 py-1 text-xs rounded ${\n metric === m\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {m === 'items'\n ? t('planmagnet.analytics.workBreakdown.items', 'Items')\n : t('planmagnet.analytics.workBreakdown.points', 'Story points')}\n </button>\n ))}\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.workBreakdown.subtitle',\n 'Live open/done work, by project and epic — current state, not limited by the date range above.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {loading && !data ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {isForbidden(error)\n ? t(\n 'planmagnet.analytics.workBreakdown.forbidden',\n 'You do not have access to the work breakdown for this scope.'\n )\n : `${t('planmagnet.analytics.workBreakdown.error', 'Work breakdown could not be loaded')}: ${error.message}`}\n </p>\n ) : groups.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.workBreakdown.empty',\n 'No live work items found for this scope.'\n )}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {breakdown?.truncatedProjects && (\n <p className=\"text-xs text-status-warning-text\">\n {t(\n 'planmagnet.analytics.workBreakdown.truncatedProjects',\n 'Showing the first 50 projects with live work. Narrow by project above to see a specific one.'\n )}\n </p>\n )}\n {truncatedProjectNames.length > 0 && (\n <p className=\"text-xs text-status-warning-text\">\n {t(\n 'planmagnet.analytics.workBreakdown.truncatedEpics',\n 'Showing at most 200 epics for: {{projects}}.',\n { projects: truncatedProjectNames.join(', ') }\n )}\n </p>\n )}\n\n <div data-testid=\"viz-work-breakdown\">\n <ResponsiveContainer width=\"100%\" height={360}>\n <Treemap\n data={groups}\n dataKey=\"size\"\n type=\"flat\"\n aspectRatio={4 / 3}\n content={<WorkBreakdownCell onOpenEpic={openEpic} noEpicLabel={noEpicLabel} />}\n >\n <Tooltip\n content={({ active, payload }) => {\n if (!active || !payload || payload.length === 0) return null;\n // Treemap hands back our own leaf datum directly as `payload.payload` (recharts types it\n // as `any`); narrowed here, once.\n const node = payload[0]?.payload as BreakdownLeafDatum | undefined;\n if (!node || node.isHeader) return null;\n const label = node.isNoEpic ? noEpicLabel : node.name;\n return (\n <div className=\"rounded-md border border-border bg-popover px-2.5 py-1.5 text-xs shadow-md\">\n <p className=\"font-medium text-popover-foreground\">{label}</p>\n {!node.isNoEpic && (\n <p className=\"text-muted-foreground\">{node.projectName}</p>\n )}\n <p className=\"mt-1\">\n {t('planmagnet.analytics.workBreakdown.tooltipOpen', 'Open')}:{' '}\n {node.openCount} ({node.openPoints}{' '}\n {t('planmagnet.analytics.workBreakdown.pts', 'pts')}) ·{' '}\n {t('planmagnet.analytics.workBreakdown.tooltipDone', 'Done')}:{' '}\n {node.doneCount} ({node.donePoints}{' '}\n {t('planmagnet.analytics.workBreakdown.pts', 'pts')})\n </p>\n <p>\n {t('planmagnet.analytics.workBreakdown.doneRatio', 'Done ratio')}:{' '}\n {node.doneRatio === null ? '—' : `${Math.round(node.doneRatio * 100)}%`}\n </p>\n </div>\n );\n }}\n />\n </Treemap>\n </ResponsiveContainer>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.workBreakdown.legend',\n 'Shade = share done (lighter → more open, darker → more done). Click an epic to open it.'\n )}\n </p>\n\n {/* Accessible table alternative — every group/epic the treemap draws, in the same data. */}\n <table className=\"w-full text-sm\" data-testid=\"viz-work-breakdown-table\">\n <caption className=\"mb-2 text-left text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.workBreakdown.tableCaption',\n 'Work breakdown by project and epic'\n )}\n </caption>\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.workBreakdown.project', 'Project')}\n </th>\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.workBreakdown.epic', 'Epic')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.workBreakdown.tooltipOpen', 'Open')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.workBreakdown.tooltipDone', 'Done')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.workBreakdown.doneRatio', 'Done ratio')}\n </th>\n </tr>\n </thead>\n <tbody>\n {groups.flatMap((g) =>\n g.children\n .filter((leaf) => !leaf.isHeader)\n .map((leaf) => {\n const epicId = leaf.epicId;\n const open = metric === 'items' ? leaf.openCount : leaf.openPoints;\n const done = metric === 'items' ? leaf.doneCount : leaf.donePoints;\n return (\n <tr key={`${g.projectId}-${epicId ?? 'no-epic'}`} className=\"border-t\">\n <td className=\"py-1.5 pr-2\">{g.name}</td>\n <td className=\"py-1.5 pr-2\">\n {epicId ? (\n <button\n type=\"button\"\n className=\"text-left text-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary\"\n onClick={() => openEpic(epicId)}\n >\n {leaf.name}\n </button>\n ) : (\n noEpicLabel\n )}\n </td>\n <td className=\"py-1.5 text-right tabular-nums\">{open}</td>\n <td className=\"py-1.5 text-right tabular-nums\">{done}</td>\n <td className=\"py-1.5 text-right tabular-nums\">\n {leaf.doneRatio === null ? '—' : `${Math.round(leaf.doneRatio * 100)}%`}\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
|
2
2
|
import { LEAVES_PAGE_CONTEXT as t, throwOnError as n } from "../utils/mutationResult.js";
|
|
3
3
|
import { ConfirmButton as r, ErrorLine as i } from "../controls/ConfirmButton.js";
|
|
4
4
|
import { enumLabel as a } from "../controls/WorkItemControls.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { mutationErrorMessage as o } from "../utils/graphqlErrors.js";
|
|
6
|
+
import { detailPath as s } from "../utils/rowNavigation.js";
|
|
7
7
|
import { endBeforeStart as c, parseIntField as l } from "../utils/formValidation.js";
|
|
8
8
|
import { useState as u } from "react";
|
|
9
9
|
import { BarChart3 as d, Pencil as f, Plus as p, Trash2 as m } from "lucide-react";
|
|
@@ -66,7 +66,7 @@ var O = [
|
|
|
66
66
|
}
|
|
67
67
|
`, I = (e) => e ? e.slice(0, 10) : "";
|
|
68
68
|
function L({ roadmapId: e, item: t, lanes: r }) {
|
|
69
|
-
let
|
|
69
|
+
let s = {
|
|
70
70
|
title: t?.title ?? "",
|
|
71
71
|
description: t?.description ?? "",
|
|
72
72
|
status: t?.status ?? "PLANNED",
|
|
@@ -77,7 +77,7 @@ function L({ roadmapId: e, item: t, lanes: r }) {
|
|
|
77
77
|
confidence: t?.confidence == null ? "" : String(t.confidence),
|
|
78
78
|
riskLevel: t?.riskLevel ?? "",
|
|
79
79
|
riskNote: t?.riskNote ?? ""
|
|
80
|
-
}, [d, m] = u(!1), [h, P] = u(
|
|
80
|
+
}, [d, m] = u(!1), [h, P] = u(s), [F, L] = u(null), R = {
|
|
81
81
|
refetchQueries: j,
|
|
82
82
|
awaitRefetchQueries: !0
|
|
83
83
|
}, [z, B] = g(M, R), [V, H] = g(N, R), U = B.loading || H.loading, W = c(h.startDate, h.endDate), G = l(h.confidence, {
|
|
@@ -94,13 +94,13 @@ function L({ roadmapId: e, item: t, lanes: r }) {
|
|
|
94
94
|
try {
|
|
95
95
|
if (t) {
|
|
96
96
|
let e = {
|
|
97
|
-
...h.startDate !==
|
|
98
|
-
...h.endDate !==
|
|
97
|
+
...h.startDate !== s.startDate && { startDate: h.startDate || null },
|
|
98
|
+
...h.endDate !== s.endDate && { endDate: h.endDate || null }
|
|
99
99
|
}, i = {
|
|
100
|
-
...h.horizon !==
|
|
101
|
-
...h.confidence !==
|
|
102
|
-
...h.riskLevel !==
|
|
103
|
-
...h.riskNote.trim() !==
|
|
100
|
+
...h.horizon !== s.horizon && { horizon: h.horizon || null },
|
|
101
|
+
...h.confidence !== s.confidence && { confidence: G.value ?? null },
|
|
102
|
+
...h.riskLevel !== s.riskLevel && { riskLevel: h.riskLevel || null },
|
|
103
|
+
...h.riskNote.trim() !== s.riskNote.trim() && { riskNote: h.riskNote.trim() || null }
|
|
104
104
|
};
|
|
105
105
|
await n(V({ variables: {
|
|
106
106
|
id: t.id,
|
|
@@ -119,16 +119,16 @@ function L({ roadmapId: e, item: t, lanes: r }) {
|
|
|
119
119
|
confidence: G.value,
|
|
120
120
|
riskLevel: h.riskLevel || void 0,
|
|
121
121
|
riskNote: h.riskNote.trim() || void 0
|
|
122
|
-
} } })), P(
|
|
122
|
+
} } })), P(s);
|
|
123
123
|
m(!1);
|
|
124
124
|
} catch (e) {
|
|
125
|
-
L(
|
|
125
|
+
L(o(e, "The item could not be saved."));
|
|
126
126
|
}
|
|
127
127
|
}, Q = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm";
|
|
128
128
|
return /* @__PURE__ */ v(x, {
|
|
129
129
|
open: d,
|
|
130
130
|
onOpenChange: (e) => {
|
|
131
|
-
m(e), e ? P(
|
|
131
|
+
m(e), e ? P(s) : L(null);
|
|
132
132
|
},
|
|
133
133
|
children: [/* @__PURE__ */ _(D, {
|
|
134
134
|
asChild: !0,
|
|
@@ -375,7 +375,7 @@ function R({ id: e, title: t }) {
|
|
|
375
375
|
}), s && /* @__PURE__ */ _(i, { message: s })]
|
|
376
376
|
});
|
|
377
377
|
}
|
|
378
|
-
function z({ roadmapId: a, name:
|
|
378
|
+
function z({ roadmapId: a, name: o }) {
|
|
379
379
|
let c = e(), [l, p] = g(F, {
|
|
380
380
|
refetchQueries: ["ListRoadmapsPage", "ListRoadmaps"],
|
|
381
381
|
context: t
|
|
@@ -396,21 +396,21 @@ function z({ roadmapId: a, name: s }) {
|
|
|
396
396
|
size: "sm",
|
|
397
397
|
variant: "outline",
|
|
398
398
|
"data-testid": "roadmap-edit",
|
|
399
|
-
onClick: () => c(
|
|
399
|
+
onClick: () => c(s("/products/edit-roadmap-page", a)),
|
|
400
400
|
children: [/* @__PURE__ */ _(f, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
401
401
|
}),
|
|
402
402
|
/* @__PURE__ */ v(y, {
|
|
403
403
|
size: "sm",
|
|
404
404
|
variant: "outline",
|
|
405
405
|
"data-testid": "roadmap-analytics",
|
|
406
|
-
onClick: () => c(
|
|
406
|
+
onClick: () => c(s("/products/roadmap-analytics-page", a)),
|
|
407
407
|
children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Analytics"]
|
|
408
408
|
}),
|
|
409
409
|
/* @__PURE__ */ _(r, {
|
|
410
410
|
label: "Delete",
|
|
411
411
|
variant: "outline",
|
|
412
412
|
icon: /* @__PURE__ */ _(m, { className: "mr-1 h-4 w-4" }),
|
|
413
|
-
title: `Delete ${
|
|
413
|
+
title: `Delete ${o}?`,
|
|
414
414
|
description: "The roadmap and its items are deleted. This cannot be undone here.",
|
|
415
415
|
confirmLabel: "Delete roadmap",
|
|
416
416
|
destructive: !0,
|
|
@@ -4,21 +4,20 @@
|
|
|
4
4
|
* re-assigned from whichever statuses happen to appear in a given project/graph — a categorical channel keeps the
|
|
5
5
|
* same color for the same identity everywhere it shows up.
|
|
6
6
|
*
|
|
7
|
-
* Values are
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Values are CSS custom-property references to fe-libs' chart token ramp (`var(--color-chart-N)`), applied through
|
|
8
|
+
* inline `style` (`fill`, `stroke`, `backgroundColor`) — NOT Tailwind utilities. The host apps' Tailwind themes do
|
|
9
|
+
* not register `--color-chart-*`, so chart utility classes are never generated: measured in prod 2026-09-30, no
|
|
10
|
+
* such rule existed in the served CSS and every chart mark rendered black. The token variables themselves are
|
|
11
|
+
* always defined (fe-libs tokens.css), so referencing them directly works in every host.
|
|
10
12
|
*
|
|
11
13
|
* Colors come from fe-libs' chart token ramp (`chart-1`..`chart-12`), not the `status-*` tokens — those are
|
|
12
14
|
* reserved for good/warning/serious/critical severity (see `dev/skills` dataviz guidance) and workflow status is
|
|
13
15
|
* an identity dimension, not a severity one.
|
|
14
16
|
*/
|
|
15
17
|
export interface StatusVisual {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
stroke: string;
|
|
20
|
-
/** Solid swatch class for a small legend dot. */
|
|
21
|
-
swatch: string;
|
|
18
|
+
/** The status's chart color as a CSS value (`var(--color-chart-N)`) — use as an SVG `fill`/`stroke` or a legend
|
|
19
|
+
* swatch's `backgroundColor` through inline style. */
|
|
20
|
+
color: string;
|
|
22
21
|
}
|
|
23
22
|
export declare function statusVisual(status: string): StatusVisual;
|
|
24
23
|
/** Every status with an assigned visual, in the fixed legend order. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusColor.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/utils/statusColor.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"statusColor.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/utils/statusColor.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,YAAY;IAC3B;0DACsD;IACtD,KAAK,EAAE,MAAM,CAAC;CACf;AAgBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAEzD;AAED,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAuB,CAAC"}
|
|
@@ -1,46 +1,14 @@
|
|
|
1
1
|
import { WORK_ITEM_STATUSES as e } from "../controls/WorkItemControls.js";
|
|
2
2
|
//#region src/planmagnet/utils/statusColor.ts
|
|
3
3
|
var t = {
|
|
4
|
-
BACKLOG: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
swatch: "bg-chart-1"
|
|
13
|
-
},
|
|
14
|
-
IN_PROGRESS: {
|
|
15
|
-
fill: "fill-chart-4",
|
|
16
|
-
stroke: "stroke-chart-4",
|
|
17
|
-
swatch: "bg-chart-4"
|
|
18
|
-
},
|
|
19
|
-
IN_REVIEW: {
|
|
20
|
-
fill: "fill-chart-3",
|
|
21
|
-
stroke: "stroke-chart-3",
|
|
22
|
-
swatch: "bg-chart-3"
|
|
23
|
-
},
|
|
24
|
-
TESTING: {
|
|
25
|
-
fill: "fill-chart-9",
|
|
26
|
-
stroke: "stroke-chart-9",
|
|
27
|
-
swatch: "bg-chart-9"
|
|
28
|
-
},
|
|
29
|
-
DONE: {
|
|
30
|
-
fill: "fill-chart-2",
|
|
31
|
-
stroke: "stroke-chart-2",
|
|
32
|
-
swatch: "bg-chart-2"
|
|
33
|
-
},
|
|
34
|
-
CANCELLED: {
|
|
35
|
-
fill: "fill-chart-10",
|
|
36
|
-
stroke: "stroke-chart-10",
|
|
37
|
-
swatch: "bg-chart-10"
|
|
38
|
-
}
|
|
39
|
-
}, n = {
|
|
40
|
-
fill: "fill-chart-12",
|
|
41
|
-
stroke: "stroke-chart-12",
|
|
42
|
-
swatch: "bg-chart-12"
|
|
43
|
-
};
|
|
4
|
+
BACKLOG: { color: "var(--color-chart-6)" },
|
|
5
|
+
TODO: { color: "var(--color-chart-1)" },
|
|
6
|
+
IN_PROGRESS: { color: "var(--color-chart-4)" },
|
|
7
|
+
IN_REVIEW: { color: "var(--color-chart-3)" },
|
|
8
|
+
TESTING: { color: "var(--color-chart-9)" },
|
|
9
|
+
DONE: { color: "var(--color-chart-2)" },
|
|
10
|
+
CANCELLED: { color: "var(--color-chart-10)" }
|
|
11
|
+
}, n = { color: "var(--color-chart-12)" };
|
|
44
12
|
function r(e) {
|
|
45
13
|
return t[e] ?? n;
|
|
46
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusColor.js","names":[],"sources":["../../../src/planmagnet/utils/statusColor.ts"],"sourcesContent":["import { WORK_ITEM_STATUSES } from '../controls/WorkItemControls';\n\n/**\n * One fixed chart color per WorkItemStatus (PDM-27 dependency graph, ANR-24 Gantt): both visualizations color by\n * status, so they share this single mapping rather than each re-deriving one. The order is FIXED and never\n * re-assigned from whichever statuses happen to appear in a given project/graph — a categorical channel keeps the\n * same color for the same identity everywhere it shows up.\n *\n * Values are
|
|
1
|
+
{"version":3,"file":"statusColor.js","names":[],"sources":["../../../src/planmagnet/utils/statusColor.ts"],"sourcesContent":["import { WORK_ITEM_STATUSES } from '../controls/WorkItemControls';\n\n/**\n * One fixed chart color per WorkItemStatus (PDM-27 dependency graph, ANR-24 Gantt): both visualizations color by\n * status, so they share this single mapping rather than each re-deriving one. The order is FIXED and never\n * re-assigned from whichever statuses happen to appear in a given project/graph — a categorical channel keeps the\n * same color for the same identity everywhere it shows up.\n *\n * Values are CSS custom-property references to fe-libs' chart token ramp (`var(--color-chart-N)`), applied through\n * inline `style` (`fill`, `stroke`, `backgroundColor`) — NOT Tailwind utilities. The host apps' Tailwind themes do\n * not register `--color-chart-*`, so chart utility classes are never generated: measured in prod 2026-09-30, no\n * such rule existed in the served CSS and every chart mark rendered black. The token variables themselves are\n * always defined (fe-libs tokens.css), so referencing them directly works in every host.\n *\n * Colors come from fe-libs' chart token ramp (`chart-1`..`chart-12`), not the `status-*` tokens — those are\n * reserved for good/warning/serious/critical severity (see `dev/skills` dataviz guidance) and workflow status is\n * an identity dimension, not a severity one.\n */\nexport interface StatusVisual {\n /** The status's chart color as a CSS value (`var(--color-chart-N)`) — use as an SVG `fill`/`stroke` or a legend\n * swatch's `backgroundColor` through inline style. */\n color: string;\n}\n\nconst STATUS_VISUALS: Record<(typeof WORK_ITEM_STATUSES)[number], StatusVisual> = {\n BACKLOG: { color: 'var(--color-chart-6)' },\n TODO: { color: 'var(--color-chart-1)' },\n IN_PROGRESS: { color: 'var(--color-chart-4)' },\n IN_REVIEW: { color: 'var(--color-chart-3)' },\n TESTING: { color: 'var(--color-chart-9)' },\n DONE: { color: 'var(--color-chart-2)' },\n CANCELLED: { color: 'var(--color-chart-10)' },\n};\n\n/** A status the API returns that this map doesn't know yet (see WorkItemStatusCount's own doc comment on the\n * same risk) falls back to this rather than throwing or rendering unstyled. */\nconst FALLBACK_VISUAL: StatusVisual = { color: 'var(--color-chart-12)' };\n\nexport function statusVisual(status: string): StatusVisual {\n return (STATUS_VISUALS as Record<string, StatusVisual>)[status] ?? FALLBACK_VISUAL;\n}\n\n/** Every status with an assigned visual, in the fixed legend order. */\nexport const STATUS_LEGEND_ORDER: readonly string[] = WORK_ITEM_STATUSES;\n"],"mappings":";;AAwBA,IAAM,IAA4E;CAChF,SAAS,EAAE,OAAO,wBAAwB;CAC1C,MAAM,EAAE,OAAO,wBAAwB;CACvC,aAAa,EAAE,OAAO,wBAAwB;CAC9C,WAAW,EAAE,OAAO,wBAAwB;CAC5C,SAAS,EAAE,OAAO,wBAAwB;CAC1C,MAAM,EAAE,OAAO,wBAAwB;CACvC,WAAW,EAAE,OAAO,yBAAyB;CAC9C,EAIK,IAAgC,EAAE,OAAO,yBAAyB;AAExE,SAAgB,EAAa,GAA8B;AACzD,QAAQ,EAAgD,MAAW;;AAIrE,IAAa,IAAyC"}
|