@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.
Files changed (127) hide show
  1. package/dist/node_modules/lodash/_arrayEach.js +14 -0
  2. package/dist/node_modules/lodash/_arrayEach.js.map +1 -0
  3. package/dist/node_modules/lodash/_assignValue.js +17 -0
  4. package/dist/node_modules/lodash/_assignValue.js.map +1 -0
  5. package/dist/node_modules/lodash/_baseAssign.js +16 -0
  6. package/dist/node_modules/lodash/_baseAssign.js.map +1 -0
  7. package/dist/node_modules/lodash/_baseAssignIn.js +16 -0
  8. package/dist/node_modules/lodash/_baseAssignIn.js.map +1 -0
  9. package/dist/node_modules/lodash/_baseClone.js +64 -0
  10. package/dist/node_modules/lodash/_baseClone.js.map +1 -0
  11. package/dist/node_modules/lodash/_baseCreate.js +21 -0
  12. package/dist/node_modules/lodash/_baseCreate.js.map +1 -0
  13. package/dist/node_modules/lodash/_baseIsMap.js +16 -0
  14. package/dist/node_modules/lodash/_baseIsMap.js.map +1 -0
  15. package/dist/node_modules/lodash/_baseIsSet.js +16 -0
  16. package/dist/node_modules/lodash/_baseIsSet.js.map +1 -0
  17. package/dist/node_modules/lodash/_baseKeysIn.js +20 -0
  18. package/dist/node_modules/lodash/_baseKeysIn.js.map +1 -0
  19. package/dist/node_modules/lodash/_baseSum.js +17 -0
  20. package/dist/node_modules/lodash/_baseSum.js.map +1 -0
  21. package/dist/node_modules/lodash/_baseUnset.js +26 -0
  22. package/dist/node_modules/lodash/_baseUnset.js.map +1 -0
  23. package/dist/node_modules/lodash/_cloneArrayBuffer.js +16 -0
  24. package/dist/node_modules/lodash/_cloneArrayBuffer.js.map +1 -0
  25. package/dist/node_modules/lodash/_cloneBuffer.js +17 -0
  26. package/dist/node_modules/lodash/_cloneBuffer.js.map +1 -0
  27. package/dist/node_modules/lodash/_cloneDataView.js +16 -0
  28. package/dist/node_modules/lodash/_cloneDataView.js.map +1 -0
  29. package/dist/node_modules/lodash/_cloneRegExp.js +15 -0
  30. package/dist/node_modules/lodash/_cloneRegExp.js.map +1 -0
  31. package/dist/node_modules/lodash/_cloneSymbol.js +15 -0
  32. package/dist/node_modules/lodash/_cloneSymbol.js.map +1 -0
  33. package/dist/node_modules/lodash/_cloneTypedArray.js +16 -0
  34. package/dist/node_modules/lodash/_cloneTypedArray.js.map +1 -0
  35. package/dist/node_modules/lodash/_copyArray.js +15 -0
  36. package/dist/node_modules/lodash/_copyArray.js.map +1 -0
  37. package/dist/node_modules/lodash/_copyObject.js +22 -0
  38. package/dist/node_modules/lodash/_copyObject.js.map +1 -0
  39. package/dist/node_modules/lodash/_copySymbols.js +16 -0
  40. package/dist/node_modules/lodash/_copySymbols.js.map +1 -0
  41. package/dist/node_modules/lodash/_copySymbolsIn.js +16 -0
  42. package/dist/node_modules/lodash/_copySymbolsIn.js.map +1 -0
  43. package/dist/node_modules/lodash/_customOmitClone.js +15 -0
  44. package/dist/node_modules/lodash/_customOmitClone.js.map +1 -0
  45. package/dist/node_modules/lodash/_flatRest.js +17 -0
  46. package/dist/node_modules/lodash/_flatRest.js.map +1 -0
  47. package/dist/node_modules/lodash/_getAllKeysIn.js +17 -0
  48. package/dist/node_modules/lodash/_getAllKeysIn.js.map +1 -0
  49. package/dist/node_modules/lodash/_getSymbolsIn.js +18 -0
  50. package/dist/node_modules/lodash/_getSymbolsIn.js.map +1 -0
  51. package/dist/node_modules/lodash/_initCloneArray.js +15 -0
  52. package/dist/node_modules/lodash/_initCloneArray.js.map +1 -0
  53. package/dist/node_modules/lodash/_initCloneByTag.js +40 -0
  54. package/dist/node_modules/lodash/_initCloneByTag.js.map +1 -0
  55. package/dist/node_modules/lodash/_initCloneObject.js +17 -0
  56. package/dist/node_modules/lodash/_initCloneObject.js.map +1 -0
  57. package/dist/node_modules/lodash/_nativeKeysIn.js +15 -0
  58. package/dist/node_modules/lodash/_nativeKeysIn.js.map +1 -0
  59. package/dist/node_modules/lodash/_parent.js +16 -0
  60. package/dist/node_modules/lodash/_parent.js.map +1 -0
  61. package/dist/node_modules/lodash/flatten.js +15 -0
  62. package/dist/node_modules/lodash/flatten.js.map +1 -0
  63. package/dist/node_modules/lodash/isMap.js +14 -0
  64. package/dist/node_modules/lodash/isMap.js.map +1 -0
  65. package/dist/node_modules/lodash/isSet.js +14 -0
  66. package/dist/node_modules/lodash/isSet.js.map +1 -0
  67. package/dist/node_modules/lodash/keysIn.js +17 -0
  68. package/dist/node_modules/lodash/keysIn.js.map +1 -0
  69. package/dist/node_modules/lodash/maxBy.js +17 -0
  70. package/dist/node_modules/lodash/maxBy.js.map +1 -0
  71. package/dist/node_modules/lodash/omit.js +28 -0
  72. package/dist/node_modules/lodash/omit.js.map +1 -0
  73. package/dist/node_modules/lodash/sumBy.js +16 -0
  74. package/dist/node_modules/lodash/sumBy.js.map +1 -0
  75. package/dist/node_modules/recharts/es6/chart/Sankey.js +556 -0
  76. package/dist/node_modules/recharts/es6/chart/Sankey.js.map +1 -0
  77. package/dist/node_modules/recharts/es6/chart/Treemap.js +545 -0
  78. package/dist/node_modules/recharts/es6/chart/Treemap.js.map +1 -0
  79. package/dist/node_modules/recharts/es6/shape/Polygon.js +112 -0
  80. package/dist/node_modules/recharts/es6/shape/Polygon.js.map +1 -0
  81. package/dist/node_modules/recharts/es6/util/Constants.js +31 -0
  82. package/dist/node_modules/recharts/es6/util/Constants.js.map +1 -0
  83. package/dist/node_modules/recharts/es6/util/ReactUtils.js +15 -10
  84. package/dist/node_modules/recharts/es6/util/ReactUtils.js.map +1 -1
  85. package/dist/planmagnet/controls/AutomationRuleControls.js +10 -10
  86. package/dist/planmagnet/controls/CompetitorControls.js +7 -7
  87. package/dist/planmagnet/controls/FeatureControls.js +38 -38
  88. package/dist/planmagnet/controls/ProgramControls.js +8 -8
  89. package/dist/planmagnet/dependency/DependencyGraph.d.ts.map +1 -1
  90. package/dist/planmagnet/dependency/DependencyGraph.js +13 -10
  91. package/dist/planmagnet/dependency/DependencyGraph.js.map +1 -1
  92. package/dist/planmagnet/dependency/DependencyPanel.js +13 -13
  93. package/dist/planmagnet/dependency/GanttChart.d.ts.map +1 -1
  94. package/dist/planmagnet/dependency/GanttChart.js +11 -5
  95. package/dist/planmagnet/dependency/GanttChart.js.map +1 -1
  96. package/dist/planmagnet/pages/ActivityActivityPagePage.js +16 -16
  97. package/dist/planmagnet/pages/ActivityActivityPagePage.js.map +1 -1
  98. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  99. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +91 -73
  100. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  101. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +6 -6
  102. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
  103. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -11
  104. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +5 -5
  105. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +5 -5
  106. package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +6 -6
  107. package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
  108. package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +8 -8
  109. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +10 -10
  110. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +6 -6
  111. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +5 -5
  112. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +8 -8
  113. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +4 -4
  114. package/dist/planmagnet/pages/analytics/StatusFlowCard.d.ts +8 -0
  115. package/dist/planmagnet/pages/analytics/StatusFlowCard.d.ts.map +1 -0
  116. package/dist/planmagnet/pages/analytics/StatusFlowCard.js +234 -0
  117. package/dist/planmagnet/pages/analytics/StatusFlowCard.js.map +1 -0
  118. package/dist/planmagnet/pages/analytics/WorkBreakdownCard.d.ts +6 -0
  119. package/dist/planmagnet/pages/analytics/WorkBreakdownCard.d.ts.map +1 -0
  120. package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +367 -0
  121. package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js.map +1 -0
  122. package/dist/planmagnet/roadmaps/RoadmapControls.js +17 -17
  123. package/dist/planmagnet/utils/statusColor.d.ts +8 -9
  124. package/dist/planmagnet/utils/statusColor.d.ts.map +1 -1
  125. package/dist/planmagnet/utils/statusColor.js +8 -40
  126. package/dist/planmagnet/utils/statusColor.js.map +1 -1
  127. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TrendingUp, Zap, FolderKanban, BarChart3, Target } from 'lucide-react';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport { countLabel, useProjectCounts, useSprintCounts } from '../hooks/useCounts';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { BudgetPerformanceCard, CycleTimeCard } from './analytics/DeliveryHealthCards';\nimport { QualityMetricsCard } from './analytics/QualityMetricsCard';\nimport { DeliveryKpisCard } from './analytics/DeliveryKpisCard';\nimport { ThroughputCard } from './analytics/ThroughputCard';\nimport {\n ALL_PROJECTS,\n activePreset,\n isValidRange,\n presetRange,\n scopeToProjectId,\n toRangeVariables,\n type DateRange,\n type RangePreset,\n} from './analytics/analyticsRange';\n\n// Server-side totals (QA H2). The work-item list returns at most 100 rows per\n// page, so every figure computed from the loaded page undercounted past 100\n// items — and \"Project Health\" did not filter by project at all.\n// The chosen project's sprints, queried by project (BOFF-7964: the picker was built from the 100 newest sprints\n// of the whole workspace, so an older project's list could be empty).\nconst GET_PROJECT_SPRINTS = gql`\n query AnalyticsProjectSprints($workspaceId: ID!, $projectId: String) {\n planmagnetSprints(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n name\n status\n completedAt\n endDate\n }\n }\n }\n`;\n\n// At risk = missed, or still open and past due — counted on the server over every milestone.\nconst GET_MILESTONE_RISK = gql`\n query AnalyticsMilestoneRisk($workspaceId: ID!, $yesterday: DateTime) {\n missed: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { status: MISSED }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n overdue: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { open: true, dueTo: $yesterday }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n }\n`;\n\nconst GET_ANALYTICS_COUNTS = gql`\n query GetAnalyticsWorkItemCounts($workspaceId: ID!) {\n planmagnetWorkItemCounts(workspaceId: $workspaceId) {\n total\n points\n blocked\n byStatus {\n status\n count\n points\n }\n byProject {\n projectId\n status\n count\n }\n }\n }\n`;\n\ninterface WorkItemCounts {\n total: number;\n points: number;\n blocked: number;\n byStatus: Array<{ status: string; count: number; points: number }>;\n byProject: Array<{ projectId: string; status: string; count: number }>;\n}\n\n// Only the project list (for the pickers and per-project health). Counts come from the server; the 500 work items,\n// 100 sprints and 100 milestones this used to load only fed page-derived counts (BOFF-7997).\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData($workspaceId: ID!, $projectPagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n startDate\n endDate\n tags\n }\n totalCount\n }\n }\n`;\n\n// BOFF-4447: real server-computed sprint delivery analytics — burndown,\n// velocity, and cumulative flow are all aggregated in wspace-planmagnet-svc\n// (src/modules/analytics) from the Sprint/WorkItem data model plus the\n// WorkItemStatusTransition audit trail, not derived client-side from raw\n// lists like the summary cards above.\nconst GET_SPRINT_BURNDOWN = gql`\n query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n startDate\n endDate\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n remainingCount\n completedPoints\n completedCount\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query GetVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {\n planmagnetVelocity(\n workspaceId: $workspaceId\n projectId: $projectId\n trailingSprintCount: $trailingSprintCount\n ) {\n projectId\n averageVelocity\n forecastNextSprintPoints\n sprints {\n sprintId\n sprintName\n sequenceNumber\n completedPoints\n completedCount\n plannedPoints\n }\n }\n }\n`;\n\nconst GET_CUMULATIVE_FLOW = gql`\n query GetCumulativeFlow(\n $workspaceId: ID!\n $projectId: ID!\n $startDate: DateTime\n $endDate: DateTime\n ) {\n planmagnetCumulativeFlow(\n workspaceId: $workspaceId\n projectId: $projectId\n startDate: $startDate\n endDate: $endDate\n ) {\n projectId\n startDate\n endDate\n points {\n date\n statuses {\n status\n count\n }\n }\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n tags: string[];\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n}\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n startDate: string | null;\n endDate: string | null;\n totalPoints: number;\n totalCount: number;\n points: {\n date: string;\n remainingPoints: number;\n remainingCount: number;\n completedPoints: number;\n completedCount: number;\n }[];\n } | null;\n}\n\ninterface VelocityData {\n planmagnetVelocity: {\n projectId: string;\n averageVelocity: number;\n forecastNextSprintPoints: number;\n sprints: {\n sprintId: string;\n sprintName: string;\n sequenceNumber: number;\n completedPoints: number;\n completedCount: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\ninterface CumulativeFlowData {\n planmagnetCumulativeFlow: {\n projectId: string;\n startDate: string;\n endDate: string;\n points: {\n date: string;\n statuses: { status: string; count: number }[];\n }[];\n };\n}\n\n// recharts' `stroke`/`fill` props render as literal SVG paint attributes, not\n// Tailwind classNames — they cannot consume the semantic `bg-*`/`text-*`\n// utility tokens the lint rule expects everywhere else on this page. This is\n// the one place on the page raw color values are load-bearing rather than\n// stylistic, matching the same constraint every other MFE chart primitive in\n// the fleet (e.g. microfe-assethandler's `ChartCard`/`DonutChart`) hits.\nconst CFD_STATUS_COLORS: Record<string, string> = {\n BACKLOG: '#9ca3af',\n TODO: '#60a5fa',\n IN_PROGRESS: '#fbbf24',\n IN_REVIEW: '#a78bfa',\n TESTING: '#f472b6',\n DONE: '#4ade80',\n CANCELLED: '#f87171',\n};\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const { t } = useI18n();\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n // ANR-09: date range + project scope live in the URL (?from=&to=&project=) so a view is linkable, and so a\n // Dashboard KPI tile can deep-link straight into a pre-filtered view (see DashboardDashboardPagePage.tsx).\n const [searchParams, setSearchParams] = useSearchParams();\n const urlFrom = searchParams.get('from');\n const urlTo = searchParams.get('to');\n const [range, setRange] = useState<DateRange>(() =>\n urlFrom && urlTo && isValidRange(urlFrom, urlTo)\n ? { from: urlFrom, to: urlTo }\n : presetRange('30')\n );\n // The last range that was actually valid (from <= to), kept separate from `range` itself so an in-progress,\n // still-invalid edit in the inputs below can never leak into the query variables. `range` is always a valid\n // pair on mount (see the initializer above), so seeding this with it is safe.\n const [lastValidRange, setLastValidRange] = useState<DateRange>(range);\n const [scope, setScope] = useState<string>(searchParams.get('project') || ALL_PROJECTS);\n const scopeProjectId = scopeToProjectId(scope);\n const preset = activePreset(range);\n // Derived straight from `range` on every render — never a separately-tracked flag — so it can never go stale\n // relative to what the queries below actually send, however `range` got into an invalid state.\n const rangeIsValid = isValidRange(range.from, range.to);\n const rangeError = rangeIsValid\n ? null\n : t('planmagnet.analytics.range.invalid', '\"From\" must be on or before \"To\".');\n\n function applyRange(next: DateRange, nextScope: string) {\n setRange(next);\n if (isValidRange(next.from, next.to)) setLastValidRange(next);\n const params = new URLSearchParams(searchParams);\n params.set('from', next.from);\n params.set('to', next.to);\n if (nextScope !== ALL_PROJECTS) params.set('project', nextScope);\n else params.delete('project');\n setSearchParams(params, { replace: true });\n }\n\n function onPreset(p: RangePreset) {\n applyRange(presetRange(p), scope);\n }\n\n function onFromChange(value: string) {\n if (value && range.to && isValidRange(value, range.to)) {\n applyRange({ from: value, to: range.to }, scope);\n } else {\n setRange((r) => ({ ...r, from: value }));\n }\n }\n\n function onToChange(value: string) {\n if (value && range.from && isValidRange(range.from, value)) {\n applyRange({ from: range.from, to: value }, scope);\n } else {\n setRange((r) => ({ ...r, to: value }));\n }\n }\n\n function onScopeChange(value: string) {\n setScope(value);\n // The sprint select below is always scoped to the current project — an old selection would otherwise\n // silently carry over into whichever project happens to load first for the new scope.\n setSelectedSprintId('');\n applyRange(range, value);\n }\n\n // ANR-09 fix: while the visible `range` is invalid (rangeError above), every query below keeps using the last\n // known-good range instead of re-firing with an inverted from/to — `applyRange` only ever advances\n // `lastValidRange` on a genuinely valid pair, so this can never itself go out of range.\n const { startDate, endDate } = toRangeVariables(rangeIsValid ? range : lastValidRange);\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: { workspaceId, projectPagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n const { data: countsData } = useQuery<{ planmagnetWorkItemCounts: WorkItemCounts }>(\n GET_ANALYTICS_COUNTS,\n { variables: { workspaceId }, skip: !workspaceId }\n );\n const counts = countsData?.planmagnetWorkItemCounts;\n const projectCounts = useProjectCounts();\n const sprintCounts = useSprintCounts();\n // Fixed for the life of the page (render must stay pure).\n const [yesterday] = useState(() => new Date(Date.now() - 86_400_000).toISOString().slice(0, 10));\n const { data: riskData } = useQuery<{\n missed: { totalCount: number };\n overdue: { totalCount: number };\n }>(GET_MILESTONE_RISK, { variables: { workspaceId, yesterday }, skip: !workspaceId });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n // Default the project/sprint selectors to the first available option once\n // the summary data loads, so the delivery-analytics charts render without\n // requiring a manual pick every time the page opens. Filtering a list of\n // at most a few hundred sprints is cheap enough to redo on every render —\n // memoizing it would only need its own dependency-array workaround since\n // the `?? []` fallback above hands back a fresh array identity per render\n // whenever the query hasn't resolved yet.\n // Burndown/velocity/CFD/cycle-time/throughput all require a concrete project (the server fields take\n // `projectId: ID!`, not optional): \"All projects\" scope still needs SOME project for this section, so it falls\n // back to the first loaded project — the same fallback this page always used before ANR-09 added a scope select.\n const effectiveProjectId = scopeProjectId || projects[0]?.id || '';\n const { data: projectSprintsData } = useQuery<{\n planmagnetSprints: {\n items: {\n id: string;\n name: string;\n status: string;\n completedAt: string | null;\n endDate: string | null;\n }[];\n };\n }>(GET_PROJECT_SPRINTS, {\n variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\n });\n const sprintsForProject = projectSprintsData?.planmagnetSprints.items ?? [];\n // Default to the running sprint (a PLANNING one has a future start and an empty burndown), else the latest\n // completed one, else whatever there is.\n const lastCompleted = sprintsForProject\n .filter((s) => s.status === 'COMPLETED')\n .sort((a, b) =>\n (b.completedAt ?? b.endDate ?? '').localeCompare(a.completedAt ?? a.endDate ?? '')\n )[0];\n const defaultSprint =\n sprintsForProject.find((s) => s.status === 'ACTIVE' || s.status === 'REVIEW') ??\n lastCompleted ??\n sprintsForProject[0];\n const effectiveSprintId = selectedSprintId || defaultSprint?.id || '';\n const effectiveProjectName = projects.find((p) => p.id === effectiveProjectId)?.name ?? '';\n\n const { data: burndownData, loading: burndownLoading } = useQuery<BurndownData>(\n GET_SPRINT_BURNDOWN,\n {\n variables: { workspaceId, sprintId: effectiveSprintId },\n skip: !workspaceId || !effectiveSprintId,\n }\n );\n\n const { data: velocityData, loading: velocityLoading } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId: effectiveProjectId, trailingSprintCount: 6 },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n const { data: cfdData, loading: cfdLoading } = useQuery<CumulativeFlowData>(GET_CUMULATIVE_FLOW, {\n // ANR-09: the page's shared date range, not the field's own unbounded default.\n variables: { workspaceId, projectId: effectiveProjectId, startDate, endDate },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"p-6 text-red-500\">\n {t('planmagnet.analytics.errorLoading', 'Error loading analytics')}: {error.message}\n </div>\n );\n }\n\n // Every figure comes from the server counts; while they load (or if they fail) the tiles say \"—\" instead of\n // quietly falling back to the loaded page (BOFF-7997).\n const statusCount = (status: string) => counts?.byStatus.find((c) => c.status === status);\n const cancelled = statusCount('CANCELLED')?.count ?? 0;\n const doneTasks = counts ? (statusCount('DONE')?.count ?? 0) : null;\n const totalTasks = counts ? counts.total : null;\n // Blocked = held by a dependency (the release readiness rule), not a status nobody can set (BOFF-7973).\n const blockedTasks = counts ? counts.blocked : null;\n const totalPoints = counts ? counts.points : null;\n const completedPoints = counts ? (statusCount('DONE')?.points ?? 0) : null;\n const projectProgress = (projectId: string) => {\n const rows = counts?.byProject.filter((r) => r.projectId === projectId) ?? [];\n // Cancelled work no longer needs doing (BOFF-7989).\n const live = rows.filter((r) => r.status !== 'CANCELLED').reduce((sum, r) => sum + r.count, 0);\n const done = rows.filter((r) => r.status === 'DONE').reduce((sum, r) => sum + r.count, 0);\n return live > 0 ? Math.round((done / live) * 100) : 0;\n };\n\n const activeSprints = sprintCounts?.running ?? null;\n const atRiskMs = riskData ? riskData.missed.totalCount + riskData.overdue.totalCount : null;\n const activeProjects = projectCounts?.active ?? null;\n const completionRate =\n totalTasks != null && doneTasks != null && totalTasks - cancelled > 0\n ? Math.round((doneTasks / (totalTasks - cancelled)) * 100)\n : null;\n\n const burndown = burndownData?.planmagnetSprintBurndown;\n const burndownChartData =\n burndown?.points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n })) ?? [];\n\n const velocity = velocityData?.planmagnetVelocity;\n const velocityChartData =\n velocity?.sprints.map((s) => ({\n name: s.sprintName,\n completed: s.completedPoints,\n planned: s.plannedPoints ?? undefined,\n })) ?? [];\n const avgVelocity = velocity ? Math.round(velocity.averageVelocity) : 0;\n const forecastNextSprint = velocity ? Math.round(velocity.forecastNextSprintPoints) : 0;\n\n const cfd = cfdData?.planmagnetCumulativeFlow;\n const cfdStatusKeys = Array.from(\n new Set(cfd?.points.flatMap((p) => p.statuses.map((s) => s.status)) ?? [])\n );\n const cfdChartData =\n cfd?.points.map((p) => {\n const row: Record<string, string | number> = { day: formatDay(p.date) };\n for (const s of p.statuses) row[s.status] = s.count;\n return row;\n }) ?? [];\n\n const metrics = [\n {\n label: t('planmagnet.analytics.metrics.totalWorkItems.label', 'Total Work Items'),\n value: countLabel(totalTasks),\n trend: `${countLabel(completionRate)}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: countLabel(totalPoints),\n trend: `${countLabel(completedPoints)} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: countLabel(activeProjects),\n trend: `${countLabel(projectCounts?.all)} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: countLabel(blockedTasks),\n trend:\n blockedTasks == null\n ? ''\n : blockedTasks > 0\n ? t('planmagnet.analytics.metrics.blockedItems.attention', 'Needs attention')\n : t('planmagnet.analytics.metrics.blockedItems.clear', 'All clear'),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {t('planmagnet.analytics.title', 'Analytics Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.subtitle', 'Overview of project health and team performance')}\n </p>\n </div>\n\n {/* ANR-09: date range + project scope. Both live in the URL, so this view is linkable and a Dashboard tile\n can deep-link straight into it pre-filtered. Everything below — CFD, cycle time, throughput, the new\n Quality/Delivery cards, and the budget card — reads this same range and scope. */}\n <div\n className=\"flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between\"\n data-testid=\"analytics-range\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <div\n className=\"flex items-center gap-1 rounded-md border p-0.5\"\n role=\"group\"\n aria-label={t('planmagnet.analytics.range.presetLabel', 'Date range preset')}\n data-testid=\"analytics-range-preset\"\n >\n {(['7', '30', '90'] as const).map((p) => (\n <button\n key={p}\n type=\"button\"\n aria-pressed={preset === p}\n onClick={() => onPreset(p)}\n className={`px-2.5 py-1 text-xs rounded ${\n preset === p\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {t('planmagnet.analytics.range.last', 'Last {{days}} days', { days: p })}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor=\"analytics-range-from\" className=\"sr-only\">\n {t('planmagnet.analytics.range.from', 'From')}\n </label>\n <Input\n id=\"analytics-range-from\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.from}\n max={range.to}\n onChange={(e) => onFromChange(e.target.value)}\n />\n <span className=\"text-muted-foreground text-sm\">–</span>\n <label htmlFor=\"analytics-range-to\" className=\"sr-only\">\n {t('planmagnet.analytics.range.to', 'To')}\n </label>\n <Input\n id=\"analytics-range-to\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.to}\n min={range.from}\n onChange={(e) => onToChange(e.target.value)}\n />\n </div>\n {rangeError && (\n <p role=\"alert\" className=\"text-xs text-status-error-text\">\n {rangeError}\n </p>\n )}\n </div>\n\n <Select value={scope} onValueChange={onScopeChange}>\n <SelectTrigger className=\"h-8 w-56\" data-testid=\"analytics-project\">\n <SelectValue\n placeholder={t('planmagnet.analytics.range.allProjects', 'All projects')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_PROJECTS}>\n {t('planmagnet.analytics.range.allProjects', 'All projects')}\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {metrics.map((m) => (\n <Card key={m.label}>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">{m.label}</p>\n <p className=\"text-xl font-bold mt-1\">{m.value}</p>\n <p className=\"text-xs text-green-600 mt-1\">{m.trend}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <FolderKanban className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeProjects', 'Active Projects')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeProjects)}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Zap className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeSprints', 'Active Sprints')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeSprints)}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.avgVelocity', 'Avg Velocity')}\n </div>\n {/* This one is per project — name it (BOFF-7987: it silently followed the picker below). */}\n <p className=\"text-xs text-muted-foreground truncate\">{effectiveProjectName || '—'}</p>\n <p className=\"text-xl font-bold mt-1\">\n {avgVelocity} {t('planmagnet.analytics.summary.pointsShort', 'pts')}\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.atRisk', 'At Risk')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {countLabel(atRiskMs)} {t('planmagnet.analytics.summary.milestones', 'milestones')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-start sm:items-center\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n {/* Project comes from the scope select above (ANR-09) — a sprint is always per-project, so burndown\n follows it here rather than repeating its own project picker. */}\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.delivery.forProject', 'For {{project}}', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </div>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select value={effectiveSprintId} onValueChange={setSelectedSprintId}>\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t('planmagnet.analytics.delivery.sprintPlaceholder', 'Select sprint')}\n />\n </SelectTrigger>\n <SelectContent>\n {sprintsForProject.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.burndown.title', 'Sprint Burndown')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {burndownLoading ? (\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 ) : burndownChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={burndownChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Line\n type=\"monotone\"\n dataKey=\"remaining\"\n name={t('planmagnet.analytics.burndown.remaining', 'Remaining points')}\n stroke=\"#f59e0b\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"completed\"\n name={t('planmagnet.analytics.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.burndown.empty',\n 'No sprint selected, or the sprint has no work items yet'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.velocity.title', 'Sprint Velocity')} (\n {velocityChartData.length} {t('planmagnet.analytics.velocity.sprints', 'Sprints')})\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityLoading ? (\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 ) : velocityChartData.length > 0 ? (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={velocityChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Bar\n dataKey=\"planned\"\n name={t('planmagnet.analytics.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.analytics.velocity.completed', 'Completed')}\n fill=\"#6366f1\"\n />\n <ReferenceLine\n y={avgVelocity}\n stroke=\"#22c55e\"\n strokeDasharray=\"4 4\"\n label={{\n value: t('planmagnet.analytics.velocity.avgLabel', 'avg'),\n fontSize: 10,\n position: 'right',\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n <p className=\"text-xs text-muted-foreground text-center mt-2\">\n {/* ANR-02: this is arithmetic (the mean of the completed sprints shown), so it is labelled as an\n average, not a forecast. A real delivery forecast is separate work (full-spec phase G). */}\n {velocityChartData.length === 1\n ? t(\n 'planmagnet.analytics.velocity.trailingAverageOne',\n 'Average of the last completed sprint: {{points}} pts per sprint',\n { points: forecastNextSprint }\n )\n : t(\n 'planmagnet.analytics.velocity.trailingAverage',\n 'Average of the last {{count}} completed sprints: {{points}} pts per sprint',\n { points: forecastNextSprint, count: velocityChartData.length }\n )}\n </p>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.velocity.empty',\n 'No completed sprints yet for this project'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cfd.title', 'Cumulative Flow Diagram')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cfd.subtitle', 'Selected date range, for {{project}}.', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </CardHeader>\n <CardContent>\n {cfdLoading ? (\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 ) : cfdChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={260}>\n <AreaChart data={cfdChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n {cfdStatusKeys.map((status) => (\n <Area\n key={status}\n type=\"monotone\"\n dataKey={status}\n name={status.toLowerCase().replace('_', ' ')}\n stackId=\"cfd\"\n stroke={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fill={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fillOpacity={0.7}\n />\n ))}\n </AreaChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t('planmagnet.analytics.cfd.empty', 'No work items found for this project')}\n </p>\n )}\n </CardContent>\n </Card>\n\n {/* ANR-09: throughput's own group-by (day/week bucket). */}\n <ThroughputCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n\n {/* ANR-20 / ANR-21: quality and delivery KPIs, scoped to the page's project select (undefined = every\n project) and the shared date range. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <QualityMetricsCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <DeliveryKpisCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n </div>\n\n {/* Cycle time follows the delivery selector above; budgets follow the project scope select. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <CycleTimeCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <BudgetPerformanceCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('planmagnet.analytics.projectHealth.title', 'Project Health')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {projects\n .filter((p) => p.status === 'ACTIVE')\n .map((p) => {\n // Per project, from the server. This used every ASSIGNED item in\n // the workspace for every project, so all bars were identical.\n const progress = projectProgress(p.id);\n const hasOverdue = p.endDate && new Date(p.endDate) < new Date() && progress < 100;\n const health = hasOverdue ? 'at_risk' : progress >= 75 ? 'on_track' : 'in_progress';\n return (\n <div key={p.id} className=\"flex items-center gap-4\">\n <span className=\"text-sm font-medium w-40 truncate\">{p.name}</span>\n <div className=\"flex-1\">\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${health === 'at_risk' ? 'bg-orange-400' : health === 'on_track' ? 'bg-green-400' : 'bg-blue-400'}`}\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n <span className=\"text-sm w-12 text-right\">{progress}%</span>\n <Badge variant=\"outline\" className=\"capitalize text-xs w-20 justify-center\">\n {health.replace('_', ' ')}\n </Badge>\n </div>\n );\n })}\n {projects.filter((p) => p.status === 'ACTIVE').length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n {t('planmagnet.analytics.projectHealth.empty', 'No active projects found')}\n </p>\n )}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,IAAM,KAAsB,CAAG;;;;;;;;;;;;;;;;GAmBzB,KAAqB,CAAG;;;;;;;;;;;;;;;;;GAmBxB,KAAuB,CAAG;;;;;;;;;;;;;;;;;;GA8B1B,KAAqB,CAAG;;;;;;;;;;;;;;;GAsBxB,KAAsB,CAAG;;;;;;;;;;;;;;;;;GAmBzB,KAAe,CAAG;;;;;;;;;;;;;;;;;;;;GAsBlB,KAAsB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;GA4FzB,IAA4C;CAChD,SAAS;CACT,MAAM;CACN,aAAa;CACb,WAAW;CACX,SAAS;CACT,MAAM;CACN,WAAW;CACZ;AAED,SAAS,EAAU,GAAqB;CACtC,IAAM,IAAI,IAAI,KAAK,EAAI;AACvB,QAAO,GAAG,EAAE,aAAa,GAAG,EAAE,GAAG,EAAE,YAAY;;AAGjD,SAAgB,IAA6B;CAC3C,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,IAAS,EACjB,CAAC,IAAkB,KAAuB,EAAiB,GAAG,EAI9D,CAAC,GAAc,MAAmB,IAAiB,EACnD,IAAU,EAAa,IAAI,OAAO,EAClC,IAAQ,EAAa,IAAI,KAAK,EAC9B,CAAC,GAAO,KAAY,QACxB,KAAW,KAAS,EAAa,GAAS,EAAM,GAC5C;EAAE,MAAM;EAAS,IAAI;EAAO,GAC5B,EAAY,KAAK,CACtB,EAIK,CAAC,IAAgB,MAAqB,EAAoB,EAAM,EAChE,CAAC,GAAO,MAAY,EAAiB,EAAa,IAAI,UAAU,IAAA,MAAiB,EACjF,IAAiB,GAAiB,EAAM,EACxC,IAAS,GAAa,EAAM,EAG5B,KAAe,EAAa,EAAM,MAAM,EAAM,GAAG,EACjD,KAAa,KACf,OACA,EAAE,sCAAsC,wCAAoC;CAEhF,SAAS,EAAW,GAAiB,GAAmB;AAEtD,EADA,EAAS,EAAK,EACV,EAAa,EAAK,MAAM,EAAK,GAAG,IAAE,GAAkB,EAAK;EAC7D,IAAM,IAAS,IAAI,gBAAgB,EAAa;AAKhD,EAJA,EAAO,IAAI,QAAQ,EAAK,KAAK,EAC7B,EAAO,IAAI,MAAM,EAAK,GAAG,EACrB,MAAA,QACC,EAAO,OAAO,UAAU,GADG,EAAO,IAAI,WAAW,EAAU,EAEhE,GAAgB,GAAQ,EAAE,SAAS,IAAM,CAAC;;CAG5C,SAAS,GAAS,GAAgB;AAChC,IAAW,EAAY,EAAE,EAAE,EAAM;;CAGnC,SAAS,GAAa,GAAe;AACnC,EAAI,KAAS,EAAM,MAAM,EAAa,GAAO,EAAM,GAAG,GACpD,EAAW;GAAE,MAAM;GAAO,IAAI,EAAM;GAAI,EAAE,EAAM,GAEhD,GAAU,OAAO;GAAE,GAAG;GAAG,MAAM;GAAO,EAAE;;CAI5C,SAAS,GAAW,GAAe;AACjC,EAAI,KAAS,EAAM,QAAQ,EAAa,EAAM,MAAM,EAAM,GACxD,EAAW;GAAE,MAAM,EAAM;GAAM,IAAI;GAAO,EAAE,EAAM,GAElD,GAAU,OAAO;GAAE,GAAG;GAAG,IAAI;GAAO,EAAE;;CAI1C,SAAS,GAAc,GAAe;AAKpC,EAJA,GAAS,EAAM,EAGf,EAAoB,GAAG,EACvB,EAAW,GAAO,EAAM;;CAM1B,IAAM,EAAE,cAAW,eAAY,GAAiB,KAAe,IAAQ,GAAe,EAEhF,EAAE,UAAM,aAAS,cAAU,EAAwB,IAAoB;EAC3E,WAAW;GAAE;GAAa,mBAAmB,EAAE,OAAO,KAAK;GAAE;EAC7D,MAAM,CAAC;EACR,CAAC,EAEI,EAAE,MAAM,OAAe,EAC3B,IACA;EAAE,WAAW,EAAE,gBAAa;EAAE,MAAM,CAAC;EAAa,CACnD,EACK,IAAS,IAAY,0BACrB,KAAgB,IAAkB,EAClC,KAAe,IAAiB,EAEhC,CAAC,MAAa,yBAAe,IAAI,KAAK,KAAK,KAAK,GAAG,MAAW,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,EAC1F,EAAE,MAAM,MAAa,EAGxB,IAAoB;EAAE,WAAW;GAAE;GAAa;GAAW;EAAE,MAAM,CAAC;EAAa,CAAC,EAE/E,IAAW,IAAM,oBAAoB,SAAS,EAAE,EAYhD,IAAqB,KAAkB,EAAS,IAAI,MAAM,IAC1D,EAAE,MAAM,OAAuB,EAUlC,IAAqB;EACtB,WAAW;GAAE;GAAa,WAAW;GAAoB;EACzD,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EACI,IAAoB,IAAoB,kBAAkB,SAAS,EAAE,EAGrE,KAAgB,EACnB,QAAQ,MAAM,EAAE,WAAW,YAAY,CACvC,MAAM,GAAG,OACP,EAAE,eAAe,EAAE,WAAW,IAAI,cAAc,EAAE,eAAe,EAAE,WAAW,GAAG,CACnF,CAAC,IACE,KACJ,EAAkB,MAAM,MAAM,EAAE,WAAW,YAAY,EAAE,WAAW,SAAS,IAC7E,MACA,EAAkB,IACd,IAAoB,MAAoB,IAAe,MAAM,IAC7D,IAAuB,EAAS,MAAM,MAAM,EAAE,OAAO,EAAmB,EAAE,QAAQ,IAElF,EAAE,MAAM,IAAc,SAAS,OAAoB,EACvD,IACA;EACE,WAAW;GAAE;GAAa,UAAU;GAAmB;EACvD,MAAM,CAAC,KAAe,CAAC;EACxB,CACF,EAEK,EAAE,MAAM,IAAc,SAAS,OAAoB,EAAuB,IAAc;EAC5F,WAAW;GAAE;GAAa,WAAW;GAAoB,qBAAqB;GAAG;EACjF,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EAEI,EAAE,MAAM,IAAS,SAAS,OAAe,EAA6B,IAAqB;EAE/F,WAAW;GAAE;GAAa,WAAW;GAAoB;GAAW;GAAS;EAC7E,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC;AAMF,KAAI,MAAW,CAAC,GACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;EAC9F,CAAA;AAIV,KAAI,GACF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,EAAE,qCAAqC,0BAA0B;GAAC;GAAG,GAAM;GACxE;;CAMV,IAAM,KAAe,MAAmB,GAAQ,SAAS,MAAM,MAAM,EAAE,WAAW,EAAO,EACnF,KAAY,EAAY,YAAY,EAAE,SAAS,GAC/C,KAAY,IAAU,EAAY,OAAO,EAAE,SAAS,IAAK,MACzD,IAAa,IAAS,EAAO,QAAQ,MAErC,IAAe,IAAS,EAAO,UAAU,MACzC,KAAc,IAAS,EAAO,SAAS,MACvC,KAAkB,IAAU,EAAY,OAAO,EAAE,UAAU,IAAK,MAChE,MAAmB,MAAsB;EAC7C,IAAM,IAAO,GAAQ,UAAU,QAAQ,MAAM,EAAE,cAAc,EAAU,IAAI,EAAE,EAEvE,IAAO,EAAK,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAAQ,GAAK,MAAM,IAAM,EAAE,OAAO,EAAE,EACxF,IAAO,EAAK,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC,QAAQ,GAAK,MAAM,IAAM,EAAE,OAAO,EAAE;AACzF,SAAO,IAAO,IAAI,KAAK,MAAO,IAAO,IAAQ,IAAI,GAAG;IAGhD,KAAgB,IAAc,WAAW,MACzC,KAAW,IAAW,EAAS,OAAO,aAAa,EAAS,QAAQ,aAAa,MACjF,KAAiB,IAAe,UAAU,MAC1C,KACJ,KAAc,QAAQ,MAAa,QAAQ,IAAa,KAAY,IAChE,KAAK,MAAO,MAAa,IAAa,MAAc,IAAI,GACxD,MAGA,MADW,IAAc,2BAEnB,OAAO,KAAK,OAAO;EAC3B,KAAK,EAAU,EAAE,KAAK;EACtB,WAAW,EAAE;EACb,WAAW,EAAE;EACd,EAAE,IAAI,EAAE,EAEL,IAAW,IAAc,oBACzB,IACJ,GAAU,QAAQ,KAAK,OAAO;EAC5B,MAAM,EAAE;EACR,WAAW,EAAE;EACb,SAAS,EAAE,iBAAiB,KAAA;EAC7B,EAAE,IAAI,EAAE,EACL,KAAc,IAAW,KAAK,MAAM,EAAS,gBAAgB,GAAG,GAChE,KAAqB,IAAW,KAAK,MAAM,EAAS,yBAAyB,GAAG,GAEhF,IAAM,IAAS,0BACf,KAAgB,MAAM,KAC1B,IAAI,IAAI,GAAK,OAAO,SAAS,MAAM,EAAE,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAC3E,EACK,IACJ,GAAK,OAAO,KAAK,MAAM;EACrB,IAAM,IAAuC,EAAE,KAAK,EAAU,EAAE,KAAK,EAAE;AACvE,OAAK,IAAM,KAAK,EAAE,SAAU,GAAI,EAAE,UAAU,EAAE;AAC9C,SAAO;GACP,IAAI,EAAE,EAEJ,KAAU;EACd;GACE,OAAO,EAAE,qDAAqD,mBAAmB;GACjF,OAAO,EAAW,EAAW;GAC7B,OAAO,GAAG,EAAW,GAAe,CAAC,IAAI,EAAE,qDAAqD,YAAY;GAC7G;EACD;GACE,OAAO,EAAE,kDAAkD,eAAe;GAC1E,OAAO,EAAW,GAAY;GAC9B,OAAO,GAAG,EAAW,GAAgB,CAAC,GAAG,EAAE,kDAAkD,YAAY;GAC1G;EACD;GACE,OAAO,EAAE,qDAAqD,kBAAkB;GAChF,OAAO,EAAW,GAAe;GACjC,OAAO,GAAG,EAAW,IAAe,IAAI,CAAC,GAAG,EAAE,qDAAqD,QAAQ;GAC5G;EACD;GACE,OAAO,EAAE,mDAAmD,gBAAgB;GAC5E,OAAO,EAAW,EAAa;GAC/B,OACE,KAAgB,OACZ,KACA,IAAe,IACb,EAAE,uDAAuD,kBAAkB,GAC3E,EAAE,mDAAmD,YAAY;GAC1E;EACF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAE,8BAA8B,sBAAsB;IACpD,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAE,iCAAiC,kDAAkD;IACpF,CAAA,CACA,EAAA,CAAA;GAKN,kBAAC,OAAD;IACE,WAAU;IACV,eAAY;cAFd,CAIE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,cAAY,EAAE,0CAA0C,oBAAoB;OAC5E,eAAY;iBAEV;QAAC;QAAK;QAAM;QAAK,CAAW,KAAK,MACjC,kBAAC,UAAD;QAEE,MAAK;QACL,gBAAc,MAAW;QACzB,eAAe,GAAS,EAAE;QAC1B,WAAW,+BACT,MAAW,IACP,uCACA;kBAGL,EAAE,mCAAmC,sBAAsB,EAAE,MAAM,GAAG,CAAC;QACjE,EAXF,EAWE,CACT;OACE,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,SAAD;SAAO,SAAQ;SAAuB,WAAU;mBAC7C,EAAE,mCAAmC,OAAO;SACvC,CAAA;QACR,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,WAAU;SACV,OAAO,EAAM;SACb,KAAK,EAAM;SACX,WAAW,MAAM,GAAa,EAAE,OAAO,MAAM;SAC7C,CAAA;QACF,kBAAC,QAAD;SAAM,WAAU;mBAAgC;SAAQ,CAAA;QACxD,kBAAC,SAAD;SAAO,SAAQ;SAAqB,WAAU;mBAC3C,EAAE,iCAAiC,KAAK;SACnC,CAAA;QACR,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,WAAU;SACV,OAAO,EAAM;SACb,KAAK,EAAM;SACX,WAAW,MAAM,GAAW,EAAE,OAAO,MAAM;SAC3C,CAAA;QACE;;MACL,MACC,kBAAC,KAAD;OAAG,MAAK;OAAQ,WAAU;iBACvB;OACC,CAAA;MAEF;QAEN,kBAAC,GAAD;KAAQ,OAAO;KAAO,eAAe;eAArC,CACE,kBAAC,GAAD;MAAe,WAAU;MAAW,eAAY;gBAC9C,kBAAC,GAAD,EACE,aAAa,EAAE,0CAA0C,eAAe,EACxE,CAAA;MACY,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAY,OAAA;gBACT,EAAE,0CAA0C,eAAe;MACjD,CAAA,EACZ,EAAS,KAAK,MACb,kBAAC,GAAD;MAAuB,OAAO,EAAE;gBAC7B,EAAE;MACQ,EAFI,EAAE,GAEN,CACb,CACY,EAAA,CAAA,CACT;OACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,GAAQ,KAAK,MACZ,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAa,WAAU;eAAvB;MACE,kBAAC,KAAD;OAAG,WAAU;iBAAiC,EAAE;OAAU,CAAA;MAC1D,kBAAC,KAAD;OAAG,WAAU;iBAA0B,EAAE;OAAU,CAAA;MACnD,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAE;OAAU,CAAA;MAC5C;QACT,EANI,EAAE,MAMN,CACP;IACE,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAc,WAAU,WAAY,CAAA,EACnC,EAAE,+CAA+C,kBAAkB,CAChE;UACN,kBAAC,KAAD;OAAG,WAAU;iBAA0B,EAAW,GAAe;OAAK,CAAA,CAC1D;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAK,WAAU,WAAY,CAAA,EAC1B,EAAE,8CAA8C,iBAAiB,CAC9D;UACN,kBAAC,KAAD;OAAG,WAAU;iBAA0B,EAAW,GAAc;OAAK,CAAA,CACzD;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,IAAD,EAAY,WAAU,WAAY,CAAA,EACjC,EAAE,4CAA4C,eAAe,CAC1D;;OAEN,kBAAC,KAAD;QAAG,WAAU;kBAA0C,KAAwB;QAAQ,CAAA;OACvF,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACG;SAAY;SAAE,EAAE,4CAA4C,MAAM;SACjE;;OACQ;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAQ,WAAU,WAAY,CAAA,EAC7B,EAAE,uCAAuC,UAAU,CAChD;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAW,GAAS;QAAC;QAAE,EAAE,2CAA2C,aAAa;QAChF;SACQ;SACT,CAAA;KACH;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,uCAAuC,4BAA4B;KACnE,CAAA,EAGL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAE,4CAA4C,mBAAmB,EAChE,SACE,KAAwB,EAAE,2CAA2C,aAAa,EACrF,CAAC;KACA,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAQ,OAAO;MAAmB,eAAe;gBAAjD,CACE,kBAAC,GAAD;OAAe,WAAU;iBACvB,kBAAC,GAAD,EACE,aAAa,EAAE,mDAAmD,gBAAgB,EAClF,CAAA;OACY,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAkB,KAAK,MACtB,kBAAC,GAAD;OAAuB,OAAO,EAAE;iBAC7B,EAAE;OACQ,EAFI,EAAE,GAEN,CACb,EACY,CAAA,CACT;;KACL,CAAA,CACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAW,WAAU;eAArB,CACE,kBAAC,IAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EAAE,uCAAuC,kBAAkB,CAClD;QACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;KAC9F,CAAA,GACJ,GAAkB,SAAS,IAC7B,kBAAC,GAAD;KAAqB,OAAM;KAAO,QAAQ;eACxC,kBAAC,IAAD;MAAW,MAAM;gBAAjB;OACE,kBAAC,GAAD;QAAe,iBAAgB;QAAM,SAAS;QAAO,CAAA;OACrD,kBAAC,GAAD;QAAO,SAAQ;QAAM,MAAM,EAAE,UAAU,IAAI;QAAI,CAAA;OAC/C,kBAAC,GAAD;QAAO,MAAM,EAAE,UAAU,IAAI;QAAE,eAAe;QAAS,CAAA;OACvD,kBAAC,GAAD,EAAW,CAAA;OACX,kBAAC,GAAD,EAAQ,cAAc,EAAE,UAAU,IAAI,EAAI,CAAA;OAC1C,kBAAC,IAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAM,EAAE,2CAA2C,mBAAmB;QACtE,QAAO;QACP,aAAa;QACb,KAAK,EAAE,GAAG,GAAG;QACb,CAAA;OACF,kBAAC,IAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAM,EAAE,2CAA2C,mBAAmB;QACtE,QAAO;QACP,aAAa;QACb,KAAK,EAAE,GAAG,GAAG;QACb,CAAA;OACQ;;KACQ,CAAA,GAEtB,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,uCACA,0DACD;KACC,CAAA,EAEM,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAW,WAAU;eAArB;MACE,kBAAC,IAAD,EAAY,WAAU,WAAY,CAAA;MACjC,EAAE,uCAAuC,kBAAkB;MAAC;MAC5D,EAAkB;MAAO;MAAE,EAAE,yCAAyC,UAAU;MAAC;MACxE;QACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;KAC9F,CAAA,GACJ,EAAkB,SAAS,IAC7B,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,GAAD;KAAqB,OAAM;KAAO,QAAQ;eACxC,kBAAC,IAAD;MAAU,MAAM;gBAAhB;OACE,kBAAC,GAAD;QAAe,iBAAgB;QAAM,SAAS;QAAO,CAAA;OACrD,kBAAC,GAAD;QAAO,SAAQ;QAAO,MAAM,EAAE,UAAU,IAAI;QAAI,CAAA;OAChD,kBAAC,GAAD;QAAO,MAAM,EAAE,UAAU,IAAI;QAAE,eAAe;QAAS,CAAA;OACvD,kBAAC,GAAD,EAAW,CAAA;OACX,kBAAC,GAAD,EAAQ,cAAc,EAAE,UAAU,IAAI,EAAI,CAAA;OAC1C,kBAAC,IAAD;QACE,SAAQ;QACR,MAAM,EAAE,yCAAyC,UAAU;QAC3D,MAAK;QACL,CAAA;OACF,kBAAC,IAAD;QACE,SAAQ;QACR,MAAM,EAAE,2CAA2C,YAAY;QAC/D,MAAK;QACL,CAAA;OACF,kBAAC,IAAD;QACE,GAAG;QACH,QAAO;QACP,iBAAgB;QAChB,OAAO;SACL,OAAO,EAAE,0CAA0C,MAAM;SACzD,UAAU;SACV,UAAU;SACX;QACD,CAAA;OACO;;KACS,CAAA,EACtB,kBAAC,KAAD;KAAG,WAAU;eAGV,EAAkB,WAAW,IAC1B,EACE,oDACA,mEACA,EAAE,QAAQ,IAAoB,CAC/B,GACD,EACE,iDACA,8EACA;MAAE,QAAQ;MAAoB,OAAO,EAAkB;MAAQ,CAChE;KACH,CAAA,CACH,EAAA,CAAA,GAEH,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,uCACA,4CACD;KACC,CAAA,EAEM,CAAA,CACT,EAAA,CAAA,CACH;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAW,WAAU;cAArB,CACE,kBAAC,IAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EAAE,kCAAkC,0BAA0B,CACrD;OACZ,kBAAC,KAAD;IAAG,WAAU;cACV,EAAE,qCAAqC,yCAAyC,EAC/E,SACE,KAAwB,EAAE,2CAA2C,aAAa,EACrF,CAAC;IACA,CAAA,CACO,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;IAC9F,CAAA,GACJ,EAAa,SAAS,IACxB,kBAAC,GAAD;IAAqB,OAAM;IAAO,QAAQ;cACxC,kBAAC,IAAD;KAAW,MAAM;eAAjB;MACE,kBAAC,GAAD;OAAe,iBAAgB;OAAM,SAAS;OAAO,CAAA;MACrD,kBAAC,GAAD;OAAO,SAAQ;OAAM,MAAM,EAAE,UAAU,IAAI;OAAI,CAAA;MAC/C,kBAAC,GAAD;OAAO,MAAM,EAAE,UAAU,IAAI;OAAE,eAAe;OAAS,CAAA;MACvD,kBAAC,GAAD,EAAW,CAAA;MACX,kBAAC,GAAD,EAAQ,cAAc,EAAE,UAAU,IAAI,EAAI,CAAA;MACzC,GAAc,KAAK,MAClB,kBAAC,IAAD;OAEE,MAAK;OACL,SAAS;OACT,MAAM,EAAO,aAAa,CAAC,QAAQ,KAAK,IAAI;OAC5C,SAAQ;OACR,QAAQ,EAAkB,MAAW;OACrC,MAAM,EAAkB,MAAW;OACnC,aAAa;OACb,EARK,EAQL,CACF;MACQ;;IACQ,CAAA,GAEtB,kBAAC,KAAD;IAAG,WAAU;cACV,EAAE,kCAAkC,uCAAuC;IAC1E,CAAA,EAEM,CAAA,CACT,EAAA,CAAA;GAGP,kBAAC,IAAD;IACe;IACb,WAAW;IACA;IACF;IACT,CAAA;GAIF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,IAAD;KACe;KACb,WAAW;KACA;KACF;KACT,CAAA,EACF,kBAAC,IAAD;KACe;KACb,WAAW;KACA;KACF;KACT,CAAA,CACE;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,IAAD;KACe;KACb,WAAW;KACA;KACF;KACT,CAAA,EACF,kBAAC,IAAD;KAAoC;KAAa,WAAW;KAAkB,CAAA,CAC1E;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAY,EAAE,4CAA4C,iBAAiB,EAAa,CAAA,EAC7E,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EACE,QAAQ,MAAM,EAAE,WAAW,SAAS,CACpC,KAAK,MAAM;KAGV,IAAM,IAAW,GAAgB,EAAE,GAAG,EAEhC,IADa,EAAE,WAAW,IAAI,KAAK,EAAE,QAAQ,mBAAG,IAAI,MAAM,IAAI,IAAW,MACnD,YAAY,KAAY,KAAK,aAAa;AACtE,YACE,kBAAC,OAAD;MAAgB,WAAU;gBAA1B;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAqC,EAAE;QAAY,CAAA;OACnE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UACE,WAAW,uBAAuB,MAAW,YAAY,kBAAkB,MAAW,aAAa,iBAAiB;UACpH,OAAO,EAAE,OAAO,GAAG,EAAS,IAAI;UAChC,CAAA;SACE,CAAA;QACF,CAAA;OACN,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA2C,GAAS,IAAQ;;OAC5D,kBAAC,IAAD;QAAO,SAAQ;QAAU,WAAU;kBAChC,EAAO,QAAQ,KAAK,IAAI;QACnB,CAAA;OACJ;QAdI,EAAE,GAcN;MAER,EACH,EAAS,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,WAAW,KACxD,kBAAC,KAAD;KAAG,WAAU;eACV,EAAE,4CAA4C,2BAA2B;KACxE,CAAA,CAEF;OACM,CAAA,CACT,EAAA,CAAA;GACH"}
1
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TrendingUp, Zap, FolderKanban, BarChart3, Target } from 'lucide-react';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport { countLabel, useProjectCounts, useSprintCounts } from '../hooks/useCounts';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { BudgetPerformanceCard, CycleTimeCard } from './analytics/DeliveryHealthCards';\nimport { QualityMetricsCard } from './analytics/QualityMetricsCard';\nimport { DeliveryKpisCard } from './analytics/DeliveryKpisCard';\nimport { ThroughputCard } from './analytics/ThroughputCard';\nimport { StatusFlowCard } from './analytics/StatusFlowCard';\nimport { WorkBreakdownCard } from './analytics/WorkBreakdownCard';\nimport {\n ALL_PROJECTS,\n activePreset,\n isValidRange,\n presetRange,\n scopeToProjectId,\n toRangeVariables,\n type DateRange,\n type RangePreset,\n} from './analytics/analyticsRange';\n\n// Server-side totals (QA H2). The work-item list returns at most 100 rows per\n// page, so every figure computed from the loaded page undercounted past 100\n// items — and \"Project Health\" did not filter by project at all.\n// The chosen project's sprints, queried by project (BOFF-7964: the picker was built from the 100 newest sprints\n// of the whole workspace, so an older project's list could be empty).\nconst GET_PROJECT_SPRINTS = gql`\n query AnalyticsProjectSprints($workspaceId: ID!, $projectId: String) {\n planmagnetSprints(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n name\n status\n completedAt\n endDate\n }\n }\n }\n`;\n\n// At risk = missed, or still open and past due — counted on the server over every milestone.\nconst GET_MILESTONE_RISK = gql`\n query AnalyticsMilestoneRisk($workspaceId: ID!, $yesterday: DateTime) {\n missed: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { status: MISSED }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n overdue: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { open: true, dueTo: $yesterday }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n }\n`;\n\nconst GET_ANALYTICS_COUNTS = gql`\n query GetAnalyticsWorkItemCounts($workspaceId: ID!) {\n planmagnetWorkItemCounts(workspaceId: $workspaceId) {\n total\n points\n blocked\n byStatus {\n status\n count\n points\n }\n byProject {\n projectId\n status\n count\n }\n }\n }\n`;\n\ninterface WorkItemCounts {\n total: number;\n points: number;\n blocked: number;\n byStatus: Array<{ status: string; count: number; points: number }>;\n byProject: Array<{ projectId: string; status: string; count: number }>;\n}\n\n// Only the project list (for the pickers and per-project health). Counts come from the server; the 500 work items,\n// 100 sprints and 100 milestones this used to load only fed page-derived counts (BOFF-7997).\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData($workspaceId: ID!, $projectPagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n startDate\n endDate\n tags\n }\n totalCount\n }\n }\n`;\n\n// BOFF-4447: real server-computed sprint delivery analytics — burndown,\n// velocity, and cumulative flow are all aggregated in wspace-planmagnet-svc\n// (src/modules/analytics) from the Sprint/WorkItem data model plus the\n// WorkItemStatusTransition audit trail, not derived client-side from raw\n// lists like the summary cards above.\nconst GET_SPRINT_BURNDOWN = gql`\n query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n startDate\n endDate\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n remainingCount\n completedPoints\n completedCount\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query GetVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {\n planmagnetVelocity(\n workspaceId: $workspaceId\n projectId: $projectId\n trailingSprintCount: $trailingSprintCount\n ) {\n projectId\n averageVelocity\n forecastNextSprintPoints\n sprints {\n sprintId\n sprintName\n sequenceNumber\n completedPoints\n completedCount\n plannedPoints\n }\n }\n }\n`;\n\nconst GET_CUMULATIVE_FLOW = gql`\n query GetCumulativeFlow(\n $workspaceId: ID!\n $projectId: ID!\n $startDate: DateTime\n $endDate: DateTime\n ) {\n planmagnetCumulativeFlow(\n workspaceId: $workspaceId\n projectId: $projectId\n startDate: $startDate\n endDate: $endDate\n ) {\n projectId\n startDate\n endDate\n points {\n date\n statuses {\n status\n count\n }\n }\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n tags: string[];\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n}\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n startDate: string | null;\n endDate: string | null;\n totalPoints: number;\n totalCount: number;\n points: {\n date: string;\n remainingPoints: number;\n remainingCount: number;\n completedPoints: number;\n completedCount: number;\n }[];\n } | null;\n}\n\ninterface VelocityData {\n planmagnetVelocity: {\n projectId: string;\n averageVelocity: number;\n forecastNextSprintPoints: number;\n sprints: {\n sprintId: string;\n sprintName: string;\n sequenceNumber: number;\n completedPoints: number;\n completedCount: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\ninterface CumulativeFlowData {\n planmagnetCumulativeFlow: {\n projectId: string;\n startDate: string;\n endDate: string;\n points: {\n date: string;\n statuses: { status: string; count: number }[];\n }[];\n };\n}\n\n// recharts' `stroke`/`fill` props render as literal SVG paint attributes, not\n// Tailwind classNames — they cannot consume the semantic `bg-*`/`text-*`\n// utility tokens the lint rule expects everywhere else on this page. This is\n// the one place on the page raw color values are load-bearing rather than\n// stylistic, matching the same constraint every other MFE chart primitive in\n// the fleet (e.g. microfe-assethandler's `ChartCard`/`DonutChart`) hits.\nconst CFD_STATUS_COLORS: Record<string, string> = {\n BACKLOG: '#9ca3af',\n TODO: '#60a5fa',\n IN_PROGRESS: '#fbbf24',\n IN_REVIEW: '#a78bfa',\n TESTING: '#f472b6',\n DONE: '#4ade80',\n CANCELLED: '#f87171',\n};\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const { t } = useI18n();\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n // ANR-09: date range + project scope live in the URL (?from=&to=&project=) so a view is linkable, and so a\n // Dashboard KPI tile can deep-link straight into a pre-filtered view (see DashboardDashboardPagePage.tsx).\n const [searchParams, setSearchParams] = useSearchParams();\n const urlFrom = searchParams.get('from');\n const urlTo = searchParams.get('to');\n const [range, setRange] = useState<DateRange>(() =>\n urlFrom && urlTo && isValidRange(urlFrom, urlTo)\n ? { from: urlFrom, to: urlTo }\n : presetRange('30')\n );\n // The last range that was actually valid (from <= to), kept separate from `range` itself so an in-progress,\n // still-invalid edit in the inputs below can never leak into the query variables. `range` is always a valid\n // pair on mount (see the initializer above), so seeding this with it is safe.\n const [lastValidRange, setLastValidRange] = useState<DateRange>(range);\n const [scope, setScope] = useState<string>(searchParams.get('project') || ALL_PROJECTS);\n const scopeProjectId = scopeToProjectId(scope);\n const preset = activePreset(range);\n // Derived straight from `range` on every render — never a separately-tracked flag — so it can never go stale\n // relative to what the queries below actually send, however `range` got into an invalid state.\n const rangeIsValid = isValidRange(range.from, range.to);\n const rangeError = rangeIsValid\n ? null\n : t('planmagnet.analytics.range.invalid', '\"From\" must be on or before \"To\".');\n\n function applyRange(next: DateRange, nextScope: string) {\n setRange(next);\n if (isValidRange(next.from, next.to)) setLastValidRange(next);\n const params = new URLSearchParams(searchParams);\n params.set('from', next.from);\n params.set('to', next.to);\n if (nextScope !== ALL_PROJECTS) params.set('project', nextScope);\n else params.delete('project');\n setSearchParams(params, { replace: true });\n }\n\n function onPreset(p: RangePreset) {\n applyRange(presetRange(p), scope);\n }\n\n function onFromChange(value: string) {\n if (value && range.to && isValidRange(value, range.to)) {\n applyRange({ from: value, to: range.to }, scope);\n } else {\n setRange((r) => ({ ...r, from: value }));\n }\n }\n\n function onToChange(value: string) {\n if (value && range.from && isValidRange(range.from, value)) {\n applyRange({ from: range.from, to: value }, scope);\n } else {\n setRange((r) => ({ ...r, to: value }));\n }\n }\n\n function onScopeChange(value: string) {\n setScope(value);\n // The sprint select below is always scoped to the current project — an old selection would otherwise\n // silently carry over into whichever project happens to load first for the new scope.\n setSelectedSprintId('');\n applyRange(range, value);\n }\n\n // ANR-09 fix: while the visible `range` is invalid (rangeError above), every query below keeps using the last\n // known-good range instead of re-firing with an inverted from/to — `applyRange` only ever advances\n // `lastValidRange` on a genuinely valid pair, so this can never itself go out of range.\n const { startDate, endDate } = toRangeVariables(rangeIsValid ? range : lastValidRange);\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: { workspaceId, projectPagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n const { data: countsData } = useQuery<{ planmagnetWorkItemCounts: WorkItemCounts }>(\n GET_ANALYTICS_COUNTS,\n { variables: { workspaceId }, skip: !workspaceId }\n );\n const counts = countsData?.planmagnetWorkItemCounts;\n const projectCounts = useProjectCounts();\n const sprintCounts = useSprintCounts();\n // Fixed for the life of the page (render must stay pure).\n const [yesterday] = useState(() => new Date(Date.now() - 86_400_000).toISOString().slice(0, 10));\n const { data: riskData } = useQuery<{\n missed: { totalCount: number };\n overdue: { totalCount: number };\n }>(GET_MILESTONE_RISK, { variables: { workspaceId, yesterday }, skip: !workspaceId });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n // Default the project/sprint selectors to the first available option once\n // the summary data loads, so the delivery-analytics charts render without\n // requiring a manual pick every time the page opens. Filtering a list of\n // at most a few hundred sprints is cheap enough to redo on every render —\n // memoizing it would only need its own dependency-array workaround since\n // the `?? []` fallback above hands back a fresh array identity per render\n // whenever the query hasn't resolved yet.\n // Burndown/velocity/CFD/cycle-time/throughput all require a concrete project (the server fields take\n // `projectId: ID!`, not optional): \"All projects\" scope still needs SOME project for this section, so it falls\n // back to the first loaded project — the same fallback this page always used before ANR-09 added a scope select.\n const effectiveProjectId = scopeProjectId || projects[0]?.id || '';\n const { data: projectSprintsData } = useQuery<{\n planmagnetSprints: {\n items: {\n id: string;\n name: string;\n status: string;\n completedAt: string | null;\n endDate: string | null;\n }[];\n };\n }>(GET_PROJECT_SPRINTS, {\n variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\n });\n const sprintsForProject = projectSprintsData?.planmagnetSprints.items ?? [];\n // Default to the running sprint (a PLANNING one has a future start and an empty burndown), else the latest\n // completed one, else whatever there is.\n const lastCompleted = sprintsForProject\n .filter((s) => s.status === 'COMPLETED')\n .sort((a, b) =>\n (b.completedAt ?? b.endDate ?? '').localeCompare(a.completedAt ?? a.endDate ?? '')\n )[0];\n const defaultSprint =\n sprintsForProject.find((s) => s.status === 'ACTIVE' || s.status === 'REVIEW') ??\n lastCompleted ??\n sprintsForProject[0];\n const effectiveSprintId = selectedSprintId || defaultSprint?.id || '';\n const effectiveProjectName = projects.find((p) => p.id === effectiveProjectId)?.name ?? '';\n\n const { data: burndownData, loading: burndownLoading } = useQuery<BurndownData>(\n GET_SPRINT_BURNDOWN,\n {\n variables: { workspaceId, sprintId: effectiveSprintId },\n skip: !workspaceId || !effectiveSprintId,\n }\n );\n\n const { data: velocityData, loading: velocityLoading } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId: effectiveProjectId, trailingSprintCount: 6 },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n const { data: cfdData, loading: cfdLoading } = useQuery<CumulativeFlowData>(GET_CUMULATIVE_FLOW, {\n // ANR-09: the page's shared date range, not the field's own unbounded default.\n variables: { workspaceId, projectId: effectiveProjectId, startDate, endDate },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"p-6 text-red-500\">\n {t('planmagnet.analytics.errorLoading', 'Error loading analytics')}: {error.message}\n </div>\n );\n }\n\n // Every figure comes from the server counts; while they load (or if they fail) the tiles say \"—\" instead of\n // quietly falling back to the loaded page (BOFF-7997).\n const statusCount = (status: string) => counts?.byStatus.find((c) => c.status === status);\n const cancelled = statusCount('CANCELLED')?.count ?? 0;\n const doneTasks = counts ? (statusCount('DONE')?.count ?? 0) : null;\n const totalTasks = counts ? counts.total : null;\n // Blocked = held by a dependency (the release readiness rule), not a status nobody can set (BOFF-7973).\n const blockedTasks = counts ? counts.blocked : null;\n const totalPoints = counts ? counts.points : null;\n const completedPoints = counts ? (statusCount('DONE')?.points ?? 0) : null;\n const projectProgress = (projectId: string) => {\n const rows = counts?.byProject.filter((r) => r.projectId === projectId) ?? [];\n // Cancelled work no longer needs doing (BOFF-7989).\n const live = rows.filter((r) => r.status !== 'CANCELLED').reduce((sum, r) => sum + r.count, 0);\n const done = rows.filter((r) => r.status === 'DONE').reduce((sum, r) => sum + r.count, 0);\n return live > 0 ? Math.round((done / live) * 100) : 0;\n };\n\n const activeSprints = sprintCounts?.running ?? null;\n const atRiskMs = riskData ? riskData.missed.totalCount + riskData.overdue.totalCount : null;\n const activeProjects = projectCounts?.active ?? null;\n const completionRate =\n totalTasks != null && doneTasks != null && totalTasks - cancelled > 0\n ? Math.round((doneTasks / (totalTasks - cancelled)) * 100)\n : null;\n\n const burndown = burndownData?.planmagnetSprintBurndown;\n const burndownChartData =\n burndown?.points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n })) ?? [];\n\n const velocity = velocityData?.planmagnetVelocity;\n const velocityChartData =\n velocity?.sprints.map((s) => ({\n name: s.sprintName,\n completed: s.completedPoints,\n planned: s.plannedPoints ?? undefined,\n })) ?? [];\n const avgVelocity = velocity ? Math.round(velocity.averageVelocity) : 0;\n const forecastNextSprint = velocity ? Math.round(velocity.forecastNextSprintPoints) : 0;\n\n const cfd = cfdData?.planmagnetCumulativeFlow;\n const cfdStatusKeys = Array.from(\n new Set(cfd?.points.flatMap((p) => p.statuses.map((s) => s.status)) ?? [])\n );\n const cfdChartData =\n cfd?.points.map((p) => {\n const row: Record<string, string | number> = { day: formatDay(p.date) };\n for (const s of p.statuses) row[s.status] = s.count;\n return row;\n }) ?? [];\n\n const metrics = [\n {\n label: t('planmagnet.analytics.metrics.totalWorkItems.label', 'Total Work Items'),\n value: countLabel(totalTasks),\n trend: `${countLabel(completionRate)}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: countLabel(totalPoints),\n trend: `${countLabel(completedPoints)} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: countLabel(activeProjects),\n trend: `${countLabel(projectCounts?.all)} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: countLabel(blockedTasks),\n trend:\n blockedTasks == null\n ? ''\n : blockedTasks > 0\n ? t('planmagnet.analytics.metrics.blockedItems.attention', 'Needs attention')\n : t('planmagnet.analytics.metrics.blockedItems.clear', 'All clear'),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {t('planmagnet.analytics.title', 'Analytics Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.subtitle', 'Overview of project health and team performance')}\n </p>\n </div>\n\n {/* ANR-09: date range + project scope. Both live in the URL, so this view is linkable and a Dashboard tile\n can deep-link straight into it pre-filtered. Everything below — CFD, cycle time, throughput, the new\n Quality/Delivery cards, and the budget card — reads this same range and scope. */}\n <div\n className=\"flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between\"\n data-testid=\"analytics-range\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <div\n className=\"flex items-center gap-1 rounded-md border p-0.5\"\n role=\"group\"\n aria-label={t('planmagnet.analytics.range.presetLabel', 'Date range preset')}\n data-testid=\"analytics-range-preset\"\n >\n {(['7', '30', '90'] as const).map((p) => (\n <button\n key={p}\n type=\"button\"\n aria-pressed={preset === p}\n onClick={() => onPreset(p)}\n className={`px-2.5 py-1 text-xs rounded ${\n preset === p\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {t('planmagnet.analytics.range.last', 'Last {{days}} days', { days: p })}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor=\"analytics-range-from\" className=\"sr-only\">\n {t('planmagnet.analytics.range.from', 'From')}\n </label>\n <Input\n id=\"analytics-range-from\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.from}\n max={range.to}\n onChange={(e) => onFromChange(e.target.value)}\n />\n <span className=\"text-muted-foreground text-sm\">–</span>\n <label htmlFor=\"analytics-range-to\" className=\"sr-only\">\n {t('planmagnet.analytics.range.to', 'To')}\n </label>\n <Input\n id=\"analytics-range-to\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.to}\n min={range.from}\n onChange={(e) => onToChange(e.target.value)}\n />\n </div>\n {rangeError && (\n <p role=\"alert\" className=\"text-xs text-status-error-text\">\n {rangeError}\n </p>\n )}\n </div>\n\n <Select value={scope} onValueChange={onScopeChange}>\n <SelectTrigger className=\"h-8 w-56\" data-testid=\"analytics-project\">\n <SelectValue\n placeholder={t('planmagnet.analytics.range.allProjects', 'All projects')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_PROJECTS}>\n {t('planmagnet.analytics.range.allProjects', 'All projects')}\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {metrics.map((m) => (\n <Card key={m.label}>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">{m.label}</p>\n <p className=\"text-xl font-bold mt-1\">{m.value}</p>\n <p className=\"text-xs text-green-600 mt-1\">{m.trend}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <FolderKanban className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeProjects', 'Active Projects')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeProjects)}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Zap className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeSprints', 'Active Sprints')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeSprints)}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.avgVelocity', 'Avg Velocity')}\n </div>\n {/* This one is per project — name it (BOFF-7987: it silently followed the picker below). */}\n <p className=\"text-xs text-muted-foreground truncate\">{effectiveProjectName || '—'}</p>\n <p className=\"text-xl font-bold mt-1\">\n {avgVelocity} {t('planmagnet.analytics.summary.pointsShort', 'pts')}\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.atRisk', 'At Risk')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {countLabel(atRiskMs)} {t('planmagnet.analytics.summary.milestones', 'milestones')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-start sm:items-center\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n {/* Project comes from the scope select above (ANR-09) — a sprint is always per-project, so burndown\n follows it here rather than repeating its own project picker. */}\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.delivery.forProject', 'For {{project}}', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </div>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select value={effectiveSprintId} onValueChange={setSelectedSprintId}>\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t('planmagnet.analytics.delivery.sprintPlaceholder', 'Select sprint')}\n />\n </SelectTrigger>\n <SelectContent>\n {sprintsForProject.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.burndown.title', 'Sprint Burndown')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {burndownLoading ? (\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 ) : burndownChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={burndownChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Line\n type=\"monotone\"\n dataKey=\"remaining\"\n name={t('planmagnet.analytics.burndown.remaining', 'Remaining points')}\n stroke=\"#f59e0b\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"completed\"\n name={t('planmagnet.analytics.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.burndown.empty',\n 'No sprint selected, or the sprint has no work items yet'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.velocity.title', 'Sprint Velocity')} (\n {velocityChartData.length} {t('planmagnet.analytics.velocity.sprints', 'Sprints')})\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityLoading ? (\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 ) : velocityChartData.length > 0 ? (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={velocityChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Bar\n dataKey=\"planned\"\n name={t('planmagnet.analytics.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.analytics.velocity.completed', 'Completed')}\n fill=\"#6366f1\"\n />\n <ReferenceLine\n y={avgVelocity}\n stroke=\"#22c55e\"\n strokeDasharray=\"4 4\"\n label={{\n value: t('planmagnet.analytics.velocity.avgLabel', 'avg'),\n fontSize: 10,\n position: 'right',\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n <p className=\"text-xs text-muted-foreground text-center mt-2\">\n {/* ANR-02: this is arithmetic (the mean of the completed sprints shown), so it is labelled as an\n average, not a forecast. A real delivery forecast is separate work (full-spec phase G). */}\n {velocityChartData.length === 1\n ? t(\n 'planmagnet.analytics.velocity.trailingAverageOne',\n 'Average of the last completed sprint: {{points}} pts per sprint',\n { points: forecastNextSprint }\n )\n : t(\n 'planmagnet.analytics.velocity.trailingAverage',\n 'Average of the last {{count}} completed sprints: {{points}} pts per sprint',\n { points: forecastNextSprint, count: velocityChartData.length }\n )}\n </p>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.velocity.empty',\n 'No completed sprints yet for this project'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cfd.title', 'Cumulative Flow Diagram')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cfd.subtitle', 'Selected date range, for {{project}}.', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </CardHeader>\n <CardContent>\n {cfdLoading ? (\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 ) : cfdChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={260}>\n <AreaChart data={cfdChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n {cfdStatusKeys.map((status) => (\n <Area\n key={status}\n type=\"monotone\"\n dataKey={status}\n name={status.toLowerCase().replace('_', ' ')}\n stackId=\"cfd\"\n stroke={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fill={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fillOpacity={0.7}\n />\n ))}\n </AreaChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t('planmagnet.analytics.cfd.empty', 'No work items found for this project')}\n </p>\n )}\n </CardContent>\n </Card>\n\n {/* ANR-09: throughput's own group-by (day/week bucket). */}\n <ThroughputCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n\n {/* ANR-20 / ANR-21: quality and delivery KPIs, scoped to the page's project select (undefined = every\n project) and the shared date range. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <QualityMetricsCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <DeliveryKpisCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n </div>\n\n {/* Cycle time follows the delivery selector above; budgets follow the project scope select. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <CycleTimeCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <BudgetPerformanceCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n {/* ANR-24: advanced visualizations — status-flow Sankey (one project, the delivery selector above) and the\n work-breakdown Treemap (project scope select; live counts, not date-ranged). */}\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.advanced.title', 'Advanced visualizations')}\n </h2>\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <StatusFlowCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <WorkBreakdownCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('planmagnet.analytics.projectHealth.title', 'Project Health')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {projects\n .filter((p) => p.status === 'ACTIVE')\n .map((p) => {\n // Per project, from the server. This used every ASSIGNED item in\n // the workspace for every project, so all bars were identical.\n const progress = projectProgress(p.id);\n const hasOverdue = p.endDate && new Date(p.endDate) < new Date() && progress < 100;\n const health = hasOverdue ? 'at_risk' : progress >= 75 ? 'on_track' : 'in_progress';\n return (\n <div key={p.id} className=\"flex items-center gap-4\">\n <span className=\"text-sm font-medium w-40 truncate\">{p.name}</span>\n <div className=\"flex-1\">\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${health === 'at_risk' ? 'bg-orange-400' : health === 'on_track' ? 'bg-green-400' : 'bg-blue-400'}`}\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n <span className=\"text-sm w-12 text-right\">{progress}%</span>\n <Badge variant=\"outline\" className=\"capitalize text-xs w-20 justify-center\">\n {health.replace('_', ' ')}\n </Badge>\n </div>\n );\n })}\n {projects.filter((p) => p.status === 'ACTIVE').length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n {t('planmagnet.analytics.projectHealth.empty', 'No active projects found')}\n </p>\n )}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
- import { ProjectSelect as n } from "../controls/Pickers.js";
3
- import { ROW_LINK_CLASS as r, detailPath as i, rowLinkProps as a, stopRowActivation as o } from "../utils/rowNavigation.js";
2
+ import { ROW_LINK_CLASS as n, detailPath as r, rowLinkProps as i, stopRowActivation as a } from "../utils/rowNavigation.js";
3
+ import { ProjectSelect as o } from "../controls/Pickers.js";
4
4
  import { RULE_STATUSES as s, TRIGGER_TYPES as c, TRIGGER_TYPE_LABEL as l, triggerTypeLabel as u } from "../automation/automationRuleTypes.js";
5
5
  import { AutomationRuleDeleteButton as d, AutomationRuleStatusSwitch as f } from "../controls/AutomationRuleControls.js";
6
6
  import { useDeferredValue as p, useState as m } from "react";
@@ -124,7 +124,7 @@ function A() {
124
124
  }),
125
125
  /* @__PURE__ */ b("div", {
126
126
  className: "w-48",
127
- children: /* @__PURE__ */ b(n, {
127
+ children: /* @__PURE__ */ b(o, {
128
128
  id: "automation-filter-project",
129
129
  value: I,
130
130
  onChange: L,
@@ -167,9 +167,9 @@ function A() {
167
167
  /* @__PURE__ */ b("ul", {
168
168
  className: "space-y-2",
169
169
  children: q.map((e) => /* @__PURE__ */ b("li", { children: /* @__PURE__ */ x("div", {
170
- ...a(() => v(i("/automation/automation-rule-detail-page", e.id)), e.name),
170
+ ...i(() => v(r("/automation/automation-rule-detail-page", e.id)), e.name),
171
171
  "data-testid": "automation-rule-row",
172
- className: `flex flex-wrap items-center justify-between gap-3 rounded-md border p-3 ${r}`,
172
+ className: `flex flex-wrap items-center justify-between gap-3 rounded-md border p-3 ${n}`,
173
173
  children: [/* @__PURE__ */ x("div", {
174
174
  className: "min-w-0 flex-1",
175
175
  children: [
@@ -205,7 +205,7 @@ function A() {
205
205
  ]
206
206
  }), /* @__PURE__ */ x("div", {
207
207
  className: "flex items-center gap-3",
208
- onClick: o,
208
+ onClick: a,
209
209
  children: [/* @__PURE__ */ b(f, {
210
210
  ruleId: e.id,
211
211
  status: e.status
@@ -1,7 +1,7 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
2
  import { throwOnError as n } from "../utils/mutationResult.js";
3
- import { detailPath as r } from "../utils/rowNavigation.js";
4
- import { isForbidden as i, mutationErrorMessage as a } from "../utils/graphqlErrors.js";
3
+ import { isForbidden as r, mutationErrorMessage as i } from "../utils/graphqlErrors.js";
4
+ import { detailPath as a } from "../utils/rowNavigation.js";
5
5
  import { actionsToApi as o, conditionsToApi as s, saveBlockReason as c, scheduleConditionsToApi as l } from "../automation/automationRuleTypes.js";
6
6
  import { AUTOMATION_RULE_QUERIES as u } from "../controls/AutomationRuleControls.js";
7
7
  import { AutomationRuleFields as d } from "../automation/AutomationRuleFields.js";
@@ -42,9 +42,9 @@ function D() {
42
42
  conditions: O.triggerType === "SCHEDULE" ? l(O.scheduleStatuses, O.scheduleNotUpdatedForDays) : s(O.conditions),
43
43
  actions: o(O.actions)
44
44
  } } }))).data?.planmagnetCreateAutomationRule.id;
45
- D(e ? r("/automation/automation-rule-detail-page", e) : "/automation/automation-rules-page");
45
+ D(e ? a("/automation/automation-rule-detail-page", e) : "/automation/automation-rules-page");
46
46
  } catch (e) {
47
- j(i(e) ? "Your role can view automation rules but not change them." : a(e, "The rule could not be created."));
47
+ j(r(e) ? "Your role can view automation rules but not change them." : i(e, "The rule could not be created."));
48
48
  }
49
49
  }
50
50
  };
@@ -1,8 +1,8 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
2
  import { PersonName as n } from "../people/People.js";
3
3
  import { countLabel as r, useProjectCounts as i, useSprintCounts as a, workProgress as o } from "../hooks/useCounts.js";
4
- import { analyticsSearch as s, presetRange as c } from "./analytics/analyticsRange.js";
5
- import { ROW_LINK_CLASS as l, detailPath as u, rowLinkProps as d } from "../utils/rowNavigation.js";
4
+ import { ROW_LINK_CLASS as s, detailPath as c, rowLinkProps as l } from "../utils/rowNavigation.js";
5
+ import { analyticsSearch as u, presetRange as d } from "./analytics/analyticsRange.js";
6
6
  import { useLiveRefresh as f } from "../hooks/useLiveRefresh.js";
7
7
  import { LiveStatus as p } from "../controls/LiveStatus.js";
8
8
  import { AlertTriangle as m, ArrowUpRight as h, Calendar as g, CheckSquare as _, FolderKanban as v, LayoutDashboard as y, TrendingUp as b, Users as x, Zap as S } from "lucide-react";
@@ -183,7 +183,7 @@ function I() {
183
183
  className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4",
184
184
  children: Y.map((e) => /* @__PURE__ */ D(A, {
185
185
  "data-testid": "dashboard-tile-link",
186
- ...d(() => H(`/analytics/analytics-page${s(c("30"))}`), `${e.label}: ${e.value}. ${I("planmagnet.dashboard.metrics.openAnalytics", "Open in Analytics")}`),
186
+ ...l(() => H(`/analytics/analytics-page${u(d("30"))}`), `${e.label}: ${e.value}. ${I("planmagnet.dashboard.metrics.openAnalytics", "Open in Analytics")}`),
187
187
  children: [/* @__PURE__ */ D(M, {
188
188
  className: "flex flex-row items-center justify-between space-y-0 pb-2",
189
189
  children: [/* @__PURE__ */ E(N, {
@@ -211,8 +211,8 @@ function I() {
211
211
  children: [K.map((e) => {
212
212
  let t = e.workItems.reduce((e, t) => e + (t.storyPoints ?? 0), 0), n = e.workItems.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0), r = e.workItems.length, i = e.workItems.filter((e) => e.status === "DONE").length, a = o(e.workItems) ?? 0;
213
213
  return /* @__PURE__ */ D("div", {
214
- ...d(() => H(u("/projects/planning-detail-page", e.id)), e.name),
215
- className: `rounded-lg border p-4 ${l}`,
214
+ ...l(() => H(c("/projects/planning-detail-page", e.id)), e.name),
215
+ className: `rounded-lg border p-4 ${s}`,
216
216
  children: [
217
217
  /* @__PURE__ */ D("div", {
218
218
  className: "flex items-center justify-between mb-2",
@@ -294,8 +294,8 @@ function I() {
294
294
  }) }), /* @__PURE__ */ D(j, {
295
295
  className: "space-y-3",
296
296
  children: [q.map((e) => /* @__PURE__ */ D("div", {
297
- ...d(() => H(u("/projects/milestone-detail-page", e.id)), e.name),
298
- className: `flex items-start gap-3 rounded border p-2 ${l}`,
297
+ ...l(() => H(c("/projects/milestone-detail-page", e.id)), e.name),
298
+ className: `flex items-start gap-3 rounded border p-2 ${s}`,
299
299
  children: [/* @__PURE__ */ D("div", {
300
300
  className: "flex-1 min-w-0",
301
301
  children: [
@@ -341,8 +341,8 @@ function I() {
341
341
  }) }), /* @__PURE__ */ D(j, {
342
342
  className: "space-y-3",
343
343
  children: [J.map((e) => /* @__PURE__ */ D("div", {
344
- ...d(() => H(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`), `${e.key} ${e.title}`),
345
- className: `flex items-center justify-between rounded border p-2 ${l}`,
344
+ ...l(() => H(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`), `${e.key} ${e.title}`),
345
+ className: `flex items-center justify-between rounded border p-2 ${s}`,
346
346
  children: [/* @__PURE__ */ D("div", {
347
347
  className: "min-w-0 flex-1",
348
348
  children: [/* @__PURE__ */ D("p", {
@@ -385,8 +385,8 @@ function I() {
385
385
  }) }), /* @__PURE__ */ D(j, {
386
386
  className: "space-y-3",
387
387
  children: [W.map((e) => /* @__PURE__ */ D("div", {
388
- ...d(() => H(u("/projects/project-detail-page", e.id)), e.name),
389
- className: `flex items-center justify-between rounded border p-2 ${l}`,
388
+ ...l(() => H(c("/projects/project-detail-page", e.id)), e.name),
389
+ className: `flex items-center justify-between rounded border p-2 ${s}`,
390
390
  children: [/* @__PURE__ */ D("div", {
391
391
  className: "min-w-0 flex-1",
392
392
  children: [/* @__PURE__ */ E("p", {
@@ -1,6 +1,6 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
- import { ProjectSelect as n } from "../controls/Pickers.js";
3
- import { detailPath as r } from "../utils/rowNavigation.js";
2
+ import { detailPath as n } from "../utils/rowNavigation.js";
3
+ import { ProjectSelect as r } from "../controls/Pickers.js";
4
4
  import { readUrlParam as i, writeUrlParam as a } from "../utils/urlParam.js";
5
5
  import { combineLiveRefreshModes as o, useLiveRefresh as s } from "../hooks/useLiveRefresh.js";
6
6
  import { LiveStatus as c } from "../controls/LiveStatus.js";
@@ -77,7 +77,7 @@ function D() {
77
77
  children: O("planmagnet.dashboards.pm.subtitle", "Velocity, burndown, task completion, quality, risk and capacity for one project.")
78
78
  })] }), /* @__PURE__ */ x("div", {
79
79
  className: "w-full max-w-xs",
80
- children: /* @__PURE__ */ x(n, {
80
+ children: /* @__PURE__ */ x(r, {
81
81
  id: "pm-dashboard-project",
82
82
  value: k,
83
83
  onChange: j,
@@ -111,7 +111,7 @@ function D() {
111
111
  /* @__PURE__ */ x(d, {
112
112
  workspaceId: y,
113
113
  projectId: k,
114
- onDrillDown: () => D(r("/projects/project-detail-page", k))
114
+ onDrillDown: () => D(n("/projects/project-detail-page", k))
115
115
  }),
116
116
  /* @__PURE__ */ x(f, {
117
117
  workspaceId: y,
@@ -129,7 +129,7 @@ function D() {
129
129
  className: "space-y-2",
130
130
  children: [/* @__PURE__ */ x(m, {
131
131
  sprintId: I,
132
- onDrillDown: () => D(r("/projects/project-detail-page", k))
132
+ onDrillDown: () => D(n("/projects/project-detail-page", k))
133
133
  }), /* @__PURE__ */ x(u, { message: O("planmagnet.dashboards.pm.noSkillView", "There is no skill concept in PlanMagnet — capacity above is per person, never per skill.") })]
134
134
  })
135
135
  ]
@@ -1,6 +1,6 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
- import { TeamSelect as n } from "../controls/Pickers.js";
3
- import { detailPath as r } from "../utils/rowNavigation.js";
2
+ import { detailPath as n } from "../utils/rowNavigation.js";
3
+ import { TeamSelect as r } from "../controls/Pickers.js";
4
4
  import { readUrlParam as i, writeUrlParam as a } from "../utils/urlParam.js";
5
5
  import { useLiveRefresh as o } from "../hooks/useLiveRefresh.js";
6
6
  import { LiveStatus as s } from "../controls/LiveStatus.js";
@@ -58,7 +58,7 @@ function S() {
58
58
  children: C("planmagnet.dashboards.team.subtitle", "Capacity, workload, individual delivery and time logged for one team.")
59
59
  })] }), /* @__PURE__ */ g("div", {
60
60
  className: "w-full max-w-xs",
61
- children: /* @__PURE__ */ g(n, {
61
+ children: /* @__PURE__ */ g(r, {
62
62
  id: "team-dashboard-team",
63
63
  value: w,
64
64
  onChange: E,
@@ -70,7 +70,7 @@ function S() {
70
70
  children: [
71
71
  /* @__PURE__ */ g(l, {
72
72
  teamId: w,
73
- onDrillDown: () => S(r("/team/team-detail-page", w))
73
+ onDrillDown: () => S(n("/team/team-detail-page", w))
74
74
  }),
75
75
  /* @__PURE__ */ _("div", {
76
76
  className: "space-y-2",
@@ -79,7 +79,7 @@ function S() {
79
79
  memberActorIds: A,
80
80
  sinceIso: D,
81
81
  weeks: 4,
82
- onDrillDown: () => S(r("/team/team-detail-page", w))
82
+ onDrillDown: () => S(n("/team/team-detail-page", w))
83
83
  }), /* @__PURE__ */ g(c, { message: C("planmagnet.dashboards.team.noSkillUtilization", "Skill utilization is not modelled in PlanMagnet — workload above is by person only.") })]
84
84
  }),
85
85
  /* @__PURE__ */ g(d, {
@@ -2,9 +2,9 @@ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
2
  import { throwOnError as t } from "../utils/mutationResult.js";
3
3
  import { ErrorLine as n } from "../controls/ConfirmButton.js";
4
4
  import { enumLabel as r } from "../controls/WorkItemControls.js";
5
- import { ProjectSelect as i } from "../controls/Pickers.js";
6
- import { detailPath as a } from "../utils/rowNavigation.js";
7
- import { isForbidden as ee, mutationErrorMessage as te } from "../utils/graphqlErrors.js";
5
+ import { isForbidden as i, mutationErrorMessage as a } from "../utils/graphqlErrors.js";
6
+ import { detailPath as ee } from "../utils/rowNavigation.js";
7
+ import { ProjectSelect as te } from "../controls/Pickers.js";
8
8
  import { useSyncProviderCapabilities as o } from "../integrations/useSyncProviderCapabilities.js";
9
9
  import { CONFLICT_RESOLUTIONS as s, GITHUB_FIELD_MAPPING_DEFAULTS as c, SYNC_DIRECTIONS as ne, directionLabel as l, directionSupported as re, isValidGithubOwner as ie, isValidGithubRepo as ae, providerIsLive as u, providerLabel as d } from "../integrations/syncTypes.js";
10
10
  import { CONFLICT_STRATEGY_EXPLANATION as oe, RunNowButton as se, SyncRunResultSummary as ce, TestConnectionButton as f } from "../controls/SyncControls.js";
@@ -91,7 +91,7 @@ function D() {
91
91
  let e = await t(G({ variables: { input: we() } }));
92
92
  e.data && Se(e.data.planmagnetCreateExternalSync);
93
93
  } catch (e) {
94
- H(ee(e) ? "Your role can view integrations but not set them up." : te(e, "The sync could not be created."));
94
+ H(i(e) ? "Your role can view integrations but not set them up." : a(e, "The sync could not be created."));
95
95
  }
96
96
  };
97
97
  return U ? /* @__PURE__ */ v("div", {
@@ -122,7 +122,7 @@ function D() {
122
122
  }),
123
123
  W && /* @__PURE__ */ v(ce, { result: W }),
124
124
  /* @__PURE__ */ v(b, {
125
- onClick: () => u(a("/integrations/sync-detail-page", U.id)),
125
+ onClick: () => u(ee("/integrations/sync-detail-page", U.id)),
126
126
  "data-testid": "sync-wizard-view",
127
127
  children: "View sync"
128
128
  })
@@ -167,7 +167,7 @@ function D() {
167
167
  children: "PlanMagnet project"
168
168
  }), /* @__PURE__ */ v("div", {
169
169
  className: "mt-1",
170
- children: /* @__PURE__ */ v(i, {
170
+ children: /* @__PURE__ */ v(te, {
171
171
  id: "sync-wizard-project",
172
172
  value: O,
173
173
  noneLabel: "Choose a project",