@burdenoff/microfe-planmagnet 2026.924.2 → 2026.924.4

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 (75) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +93 -142
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
  5. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +150 -149
  6. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
  7. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  8. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +180 -172
  9. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  10. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  11. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +113 -116
  12. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  13. package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
  14. package/dist/planmagnet/pages/GoalsGoalsPagePage.js +59 -58
  15. package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
  16. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
  17. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +150 -149
  18. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
  19. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  20. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +108 -107
  21. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  22. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  23. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +93 -92
  24. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  25. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  26. package/dist/planmagnet/pages/ProductsProductsPagePage.js +89 -87
  27. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  28. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  29. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +94 -93
  30. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  31. package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
  32. package/dist/planmagnet/pages/ProductsReleasesPagePage.js +57 -56
  33. package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
  34. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
  35. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +82 -81
  36. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
  37. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
  38. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +72 -71
  39. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
  40. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
  41. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +38 -37
  42. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
  43. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +72 -71
  45. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +47 -46
  48. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
  49. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  50. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +120 -119
  51. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +131 -130
  54. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  56. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +77 -75
  57. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  58. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  59. package/dist/planmagnet/pages/ReportsReportsPagePage.js +71 -70
  60. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  61. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  62. package/dist/planmagnet/pages/TeamTeamPagePage.js +58 -57
  63. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  64. package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.d.ts.map +1 -1
  65. package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.js +81 -72
  66. package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.js.map +1 -1
  67. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +15 -0
  68. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -0
  69. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +302 -0
  70. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -0
  71. package/dist/planmagnet/people/People.d.ts +20 -0
  72. package/dist/planmagnet/people/People.d.ts.map +1 -0
  73. package/dist/planmagnet/people/People.js +42 -0
  74. package/dist/planmagnet/people/People.js.map +1 -0
  75. 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 { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\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 { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData(\n $workspaceId: ID!\n $projectPagination: PlanmagnetPaginationInput\n $workItemPagination: PlanmagnetPaginationInput\n $sprintPagination: PlanmagnetPaginationInput\n $milestonePagination: PlanmagnetPaginationInput\n ) {\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 planmagnetWorkItems(workspaceId: $workspaceId, pagination: $workItemPagination) {\n items {\n id\n status\n priority\n storyPoints\n assigneeId\n }\n totalCount\n }\n planmagnetSprints(workspaceId: $workspaceId, pagination: $sprintPagination) {\n items {\n id\n name\n status\n velocity\n capacity\n projectId\n startDate\n endDate\n }\n totalCount\n }\n planmagnetMilestones(workspaceId: $workspaceId, pagination: $milestonePagination) {\n items {\n id\n name\n status\n dueDate\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($workspaceId: ID!, $projectId: ID!) {\n planmagnetCumulativeFlow(workspaceId: $workspaceId, projectId: $projectId) {\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 WorkItemNode {\n id: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n}\n\ninterface SprintNode {\n id: string;\n name: string;\n status: string;\n velocity: number | null;\n capacity: number | null;\n projectId: string;\n startDate: string | null;\n endDate: string | null;\n}\n\ninterface MilestoneNode {\n id: string;\n name: string;\n status: string;\n dueDate: string | null;\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n planmagnetWorkItems: { items: WorkItemNode[]; totalCount: number };\n planmagnetSprints: { items: SprintNode[]; totalCount: number };\n planmagnetMilestones: { items: MilestoneNode[]; 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 [selectedProjectId, setSelectedProjectId] = useState<string>('');\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: {\n workspaceId,\n projectPagination: { first: 100 },\n workItemPagination: { first: 500 },\n sprintPagination: { first: 100 },\n milestonePagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n const tasks = data?.planmagnetWorkItems?.items ?? [];\n const sprints = data?.planmagnetSprints?.items ?? [];\n const milestones = data?.planmagnetMilestones?.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 const effectiveProjectId = selectedProjectId || projects[0]?.id || '';\n const sprintsForProject = sprints.filter((s) => s.projectId === effectiveProjectId);\n const effectiveSprintId = selectedSprintId || sprintsForProject[0]?.id || '';\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 variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n if (loading) {\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 const totalTasks = tasks.length;\n const doneTasks = tasks.filter((t) => t.status === 'DONE').length;\n const blockedTasks = tasks.filter((t) => t.status === 'BLOCKED').length;\n const totalPoints = tasks.reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n const completedPoints = tasks\n .filter((t) => t.status === 'DONE')\n .reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n\n const activeSprints = sprints.filter((s) => s.status === 'ACTIVE').length;\n const atRiskMs = milestones.filter(\n (m) => m.status === 'AT_RISK' || m.status === 'OVERDUE'\n ).length;\n const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;\n const completionRate = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;\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: String(totalTasks),\n trend: `${completionRate}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: String(totalPoints),\n trend: `${completedPoints} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: String(activeProjects),\n trend: `${projects.length} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: String(blockedTasks),\n trend:\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 <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\">{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\">{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 <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 {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 <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select\n value={effectiveProjectId}\n onValueChange={(v) => {\n setSelectedProjectId(v);\n setSelectedSprintId('');\n }}\n >\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t(\n 'planmagnet.analytics.delivery.projectPlaceholder',\n 'Select project'\n )}\n />\n </SelectTrigger>\n <SelectContent>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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 {t(\n 'planmagnet.analytics.velocity.forecast',\n 'Forecast for next sprint: {{points}} pts (trailing average)',\n { points: forecastNextSprint }\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 </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 <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 const projectTasks = tasks.filter((t) => t.assigneeId != null);\n const projectDone = projectTasks.filter((t) => t.status === 'DONE').length;\n const progress =\n projectTasks.length > 0\n ? Math.round((projectDone / projectTasks.length) * 100)\n : 0;\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
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\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 { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { BudgetPerformanceCard, CycleTimeCard } from './analytics/DeliveryHealthCards';\n\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData(\n $workspaceId: ID!\n $projectPagination: PlanmagnetPaginationInput\n $workItemPagination: PlanmagnetPaginationInput\n $sprintPagination: PlanmagnetPaginationInput\n $milestonePagination: PlanmagnetPaginationInput\n ) {\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 planmagnetWorkItems(workspaceId: $workspaceId, pagination: $workItemPagination) {\n items {\n id\n status\n priority\n storyPoints\n assigneeId\n }\n totalCount\n }\n planmagnetSprints(workspaceId: $workspaceId, pagination: $sprintPagination) {\n items {\n id\n name\n status\n velocity\n capacity\n projectId\n startDate\n endDate\n }\n totalCount\n }\n planmagnetMilestones(workspaceId: $workspaceId, pagination: $milestonePagination) {\n items {\n id\n name\n status\n dueDate\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($workspaceId: ID!, $projectId: ID!) {\n planmagnetCumulativeFlow(workspaceId: $workspaceId, projectId: $projectId) {\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 WorkItemNode {\n id: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n}\n\ninterface SprintNode {\n id: string;\n name: string;\n status: string;\n velocity: number | null;\n capacity: number | null;\n projectId: string;\n startDate: string | null;\n endDate: string | null;\n}\n\ninterface MilestoneNode {\n id: string;\n name: string;\n status: string;\n dueDate: string | null;\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n planmagnetWorkItems: { items: WorkItemNode[]; totalCount: number };\n planmagnetSprints: { items: SprintNode[]; totalCount: number };\n planmagnetMilestones: { items: MilestoneNode[]; 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 [selectedProjectId, setSelectedProjectId] = useState<string>('');\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: {\n workspaceId,\n projectPagination: { first: 100 },\n workItemPagination: { first: 500 },\n sprintPagination: { first: 100 },\n milestonePagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n const tasks = data?.planmagnetWorkItems?.items ?? [];\n const sprints = data?.planmagnetSprints?.items ?? [];\n const milestones = data?.planmagnetMilestones?.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 const effectiveProjectId = selectedProjectId || projects[0]?.id || '';\n const sprintsForProject = sprints.filter((s) => s.projectId === effectiveProjectId);\n const effectiveSprintId = selectedSprintId || sprintsForProject[0]?.id || '';\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 variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n if (loading) {\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 const totalTasks = tasks.length;\n const doneTasks = tasks.filter((t) => t.status === 'DONE').length;\n const blockedTasks = tasks.filter((t) => t.status === 'BLOCKED').length;\n const totalPoints = tasks.reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n const completedPoints = tasks\n .filter((t) => t.status === 'DONE')\n .reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n\n const activeSprints = sprints.filter((s) => s.status === 'ACTIVE').length;\n const atRiskMs = milestones.filter(\n (m) => m.status === 'AT_RISK' || m.status === 'OVERDUE'\n ).length;\n const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;\n const completionRate = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;\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: String(totalTasks),\n trend: `${completionRate}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: String(totalPoints),\n trend: `${completedPoints} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: String(activeProjects),\n trend: `${projects.length} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: String(blockedTasks),\n trend:\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 <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\">{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\">{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 <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 {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 <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select\n value={effectiveProjectId}\n onValueChange={(v) => {\n setSelectedProjectId(v);\n setSelectedSprintId('');\n }}\n >\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t(\n 'planmagnet.analytics.delivery.projectPlaceholder',\n 'Select project'\n )}\n />\n </SelectTrigger>\n <SelectContent>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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 {t(\n 'planmagnet.analytics.velocity.forecast',\n 'Forecast for next sprint: {{points}} pts (trailing average)',\n { points: forecastNextSprint }\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 </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 {/* Cycle time follows the delivery selector above; budgets are workspace-wide. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <CycleTimeCard workspaceId={workspaceId} projectId={effectiveProjectId} />\n <BudgetPerformanceCard workspaceId={workspaceId} />\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 const projectTasks = tasks.filter((t) => t.assigneeId != null);\n const projectDone = projectTasks.filter((t) => t.status === 'DONE').length;\n const progress =\n projectTasks.length > 0\n ? Math.round((projectDone / projectTasks.length) * 100)\n : 0;\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 +1 @@
1
- {"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"AAuFA,wBAAgB,0BAA0B,4CAwUzC"}
1
+ {"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"AAwFA,wBAAgB,0BAA0B,4CAyVzC"}
@@ -1,12 +1,13 @@
1
1
  import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
2
- import { AlertTriangle as t, ArrowUpRight as n, Calendar as r, CheckSquare as i, FolderKanban as a, LayoutDashboard as o, TrendingUp as s, Users as c, Zap as l } from "lucide-react";
3
- import { gql as u } from "@apollo/client";
4
- import { useQuery as d } from "@apollo/client/react";
5
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
6
- import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
- import { Badge as h, Card as g, CardContent as _, CardHeader as v, CardTitle as y } from "@burdenoff/fe-libs/ui";
2
+ import { PersonName as t } from "../people/People.js";
3
+ import { AlertTriangle as n, ArrowUpRight as r, Calendar as i, CheckSquare as a, FolderKanban as o, LayoutDashboard as s, TrendingUp as c, Users as l, Zap as u } from "lucide-react";
4
+ import { gql as d } from "@apollo/client";
5
+ import { useQuery as f } from "@apollo/client/react";
6
+ import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
7
+ import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
+ import { Badge as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x } from "@burdenoff/fe-libs/ui";
8
9
  //#region src/planmagnet/pages/DashboardDashboardPagePage.tsx
9
- var b = u`
10
+ var S = d`
10
11
  query GetDashboardData($workspaceId: ID!) {
11
12
  planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 50 }) {
12
13
  items {
@@ -73,126 +74,126 @@ var b = u`
73
74
  }
74
75
  }
75
76
  `;
76
- function x() {
77
- let { workspaceId: u } = e(), { t: x } = m(), { data: S, loading: C, error: w } = d(b, { variables: { workspaceId: u ?? "default" } });
78
- if (C) return /* @__PURE__ */ f("div", {
77
+ function C() {
78
+ let { workspaceId: d } = e(), { t: C } = g(), { data: w, loading: T, error: E } = f(S, { variables: { workspaceId: d ?? "default" } });
79
+ if (T) return /* @__PURE__ */ m("div", {
79
80
  className: "flex items-center justify-center p-12",
80
- children: /* @__PURE__ */ f("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
81
+ children: /* @__PURE__ */ m("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
81
82
  });
82
- if (w) return /* @__PURE__ */ p("div", {
83
+ if (E) return /* @__PURE__ */ h("div", {
83
84
  className: "p-6 text-red-500",
84
85
  children: [
85
- x("planmagnet.dashboard.errorLoading", "Error loading dashboard"),
86
+ C("planmagnet.dashboard.errorLoading", "Error loading dashboard"),
86
87
  ": ",
87
- w.message
88
+ E.message
88
89
  ]
89
90
  });
90
- let T = S?.planmagnetProjects?.items ?? [], E = S?.planmagnetSprints?.items ?? [], D = S?.planmagnetMilestones?.items ?? [], O = S?.planmagnetWorkItems?.items ?? [], k = T.filter((e) => e.status === "ACTIVE");
91
- T.filter((e) => e.status === "COMPLETED");
92
- let A = E.filter((e) => e.status === "ACTIVE"), j = D.filter((e) => e.status === "PENDING" || e.status === "IN_PROGRESS").slice(0, 4), M = O.slice(0, 5), N = [
91
+ let D = w?.planmagnetProjects?.items ?? [], O = w?.planmagnetSprints?.items ?? [], k = w?.planmagnetMilestones?.items ?? [], A = w?.planmagnetWorkItems?.items ?? [], j = D.filter((e) => e.status === "ACTIVE");
92
+ D.filter((e) => e.status === "COMPLETED");
93
+ let M = O.filter((e) => e.status === "ACTIVE"), N = k.filter((e) => e.status === "PENDING" || e.status === "IN_PROGRESS").slice(0, 4), P = A.slice(0, 5), F = new Map(D.map((e) => [e.id, e.name])), I = [
93
94
  {
94
- label: x("planmagnet.dashboard.metrics.totalProjects.label", "Total Projects"),
95
- value: String(T.length),
96
- trend: `${k.length} ${x("planmagnet.dashboard.metrics.totalProjects.trend", "active")}`
95
+ label: C("planmagnet.dashboard.metrics.totalProjects.label", "Total Projects"),
96
+ value: String(D.length),
97
+ trend: `${j.length} ${C("planmagnet.dashboard.metrics.totalProjects.trend", "active")}`
97
98
  },
98
99
  {
99
- label: x("planmagnet.dashboard.metrics.activeSprints.label", "Active Sprints"),
100
- value: String(A.length),
101
- trend: `${E.length} ${x("planmagnet.dashboard.metrics.activeSprints.trend", "total")}`
100
+ label: C("planmagnet.dashboard.metrics.activeSprints.label", "Active Sprints"),
101
+ value: String(M.length),
102
+ trend: `${O.length} ${C("planmagnet.dashboard.metrics.activeSprints.trend", "total")}`
102
103
  },
103
104
  {
104
- label: x("planmagnet.dashboard.metrics.workItems.label", "Work Items"),
105
- value: String(S?.planmagnetWorkItems?.totalCount ?? 0),
106
- trend: x("planmagnet.dashboard.metrics.workItems.trend", "across all projects")
105
+ label: C("planmagnet.dashboard.metrics.workItems.label", "Work Items"),
106
+ value: String(w?.planmagnetWorkItems?.totalCount ?? 0),
107
+ trend: C("planmagnet.dashboard.metrics.workItems.trend", "across all projects")
107
108
  },
108
109
  {
109
- label: x("planmagnet.dashboard.metrics.milestones.label", "Milestones"),
110
- value: String(D.length),
111
- trend: `${j.length} ${x("planmagnet.dashboard.metrics.milestones.trend", "upcoming")}`
110
+ label: C("planmagnet.dashboard.metrics.milestones.label", "Milestones"),
111
+ value: String(k.length),
112
+ trend: `${N.length} ${C("planmagnet.dashboard.metrics.milestones.trend", "upcoming")}`
112
113
  }
113
114
  ];
114
- return /* @__PURE__ */ p("div", {
115
+ return /* @__PURE__ */ h("div", {
115
116
  className: "space-y-6 p-6",
116
117
  children: [
117
- /* @__PURE__ */ f("div", {
118
+ /* @__PURE__ */ m("div", {
118
119
  className: "flex items-center justify-between",
119
- children: /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ p("h1", {
120
+ children: /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("h1", {
120
121
  className: "text-2xl font-semibold flex items-center gap-2",
121
- children: [/* @__PURE__ */ f(o, { className: "h-6 w-6" }), x("planmagnet.dashboard.title", "Dashboard")]
122
- }), /* @__PURE__ */ f("p", {
122
+ children: [/* @__PURE__ */ m(s, { className: "h-6 w-6" }), C("planmagnet.dashboard.title", "Dashboard")]
123
+ }), /* @__PURE__ */ m("p", {
123
124
  className: "text-sm text-muted-foreground mt-1",
124
- children: x("planmagnet.dashboard.subtitle", "Welcome back! Here is your project overview.")
125
+ children: C("planmagnet.dashboard.subtitle", "Welcome back! Here is your project overview.")
125
126
  })] })
126
127
  }),
127
- /* @__PURE__ */ f("div", {
128
+ /* @__PURE__ */ m("div", {
128
129
  className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4",
129
- children: N.map((e) => /* @__PURE__ */ p(g, { children: [/* @__PURE__ */ p(v, {
130
+ children: I.map((e) => /* @__PURE__ */ h(v, { children: [/* @__PURE__ */ h(b, {
130
131
  className: "flex flex-row items-center justify-between space-y-0 pb-2",
131
- children: [/* @__PURE__ */ f(y, {
132
+ children: [/* @__PURE__ */ m(x, {
132
133
  className: "text-sm font-medium",
133
134
  children: e.label
134
- }), /* @__PURE__ */ f(s, { className: "h-4 w-4 text-muted-foreground" })]
135
- }), /* @__PURE__ */ p(_, { children: [/* @__PURE__ */ f("div", {
135
+ }), /* @__PURE__ */ m(c, { className: "h-4 w-4 text-muted-foreground" })]
136
+ }), /* @__PURE__ */ h(y, { children: [/* @__PURE__ */ m("div", {
136
137
  className: "text-2xl font-bold",
137
138
  children: e.value
138
- }), /* @__PURE__ */ f("p", {
139
+ }), /* @__PURE__ */ m("p", {
139
140
  className: "text-xs text-muted-foreground",
140
141
  children: e.trend
141
142
  })] })] }, e.label))
142
143
  }),
143
- /* @__PURE__ */ p("div", {
144
+ /* @__PURE__ */ h("div", {
144
145
  className: "grid gap-6 lg:grid-cols-12",
145
- children: [/* @__PURE__ */ p(g, {
146
+ children: [/* @__PURE__ */ h(v, {
146
147
  className: "lg:col-span-8",
147
- children: [/* @__PURE__ */ f(v, { children: /* @__PURE__ */ p(y, {
148
+ children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ h(x, {
148
149
  className: "flex items-center gap-2 text-base",
149
- children: [/* @__PURE__ */ f(l, { className: "h-5 w-5 text-blue-500" }), x("planmagnet.dashboard.activeSprints.title", "Active Sprints")]
150
- }) }), /* @__PURE__ */ p(_, {
150
+ children: [/* @__PURE__ */ m(u, { className: "h-5 w-5 text-blue-500" }), C("planmagnet.dashboard.activeSprints.title", "Active Sprints")]
151
+ }) }), /* @__PURE__ */ h(y, {
151
152
  className: "space-y-4",
152
- children: [A.map((e) => {
153
+ children: [M.map((e) => {
153
154
  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 = t > 0 ? Math.round(n / t * 100) : 0;
154
- return /* @__PURE__ */ p("div", {
155
+ return /* @__PURE__ */ h("div", {
155
156
  className: "rounded-lg border p-4",
156
157
  children: [
157
- /* @__PURE__ */ p("div", {
158
+ /* @__PURE__ */ h("div", {
158
159
  className: "flex items-center justify-between mb-2",
159
- children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
160
+ children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
160
161
  className: "font-medium",
161
162
  children: e.name
162
- }), /* @__PURE__ */ p("p", {
163
+ }), /* @__PURE__ */ h("p", {
163
164
  className: "text-xs text-muted-foreground",
164
165
  children: [
165
- x("planmagnet.dashboard.shared.project", "Project"),
166
+ C("planmagnet.dashboard.shared.project", "Project"),
166
167
  ": ",
167
168
  e.projectId
168
169
  ]
169
- })] }), /* @__PURE__ */ p(h, {
170
+ })] }), /* @__PURE__ */ h(_, {
170
171
  variant: "secondary",
171
172
  children: [
172
173
  n,
173
174
  "/",
174
175
  t,
175
176
  " ",
176
- x("planmagnet.dashboard.shared.pointsShort", "pts")
177
+ C("planmagnet.dashboard.shared.pointsShort", "pts")
177
178
  ]
178
179
  })]
179
180
  }),
180
- /* @__PURE__ */ f("div", {
181
+ /* @__PURE__ */ m("div", {
181
182
  className: "w-full h-2 bg-muted rounded-full overflow-hidden",
182
- children: /* @__PURE__ */ f("div", {
183
+ children: /* @__PURE__ */ m("div", {
183
184
  className: "h-full bg-primary rounded-full transition-all",
184
185
  style: { width: `${a}%` }
185
186
  })
186
187
  }),
187
- /* @__PURE__ */ p("div", {
188
+ /* @__PURE__ */ h("div", {
188
189
  className: "flex items-center justify-between mt-2 text-xs text-muted-foreground",
189
- children: [/* @__PURE__ */ p("span", { children: [
190
+ children: [/* @__PURE__ */ h("span", { children: [
190
191
  i,
191
192
  "/",
192
193
  r,
193
194
  " ",
194
- x("planmagnet.dashboard.shared.tasks", "tasks")
195
- ] }), /* @__PURE__ */ p("span", { children: [
195
+ C("planmagnet.dashboard.shared.tasks", "tasks")
196
+ ] }), /* @__PURE__ */ h("span", { children: [
196
197
  e.startDate ? new Date(e.startDate).toLocaleDateString() : "—",
197
198
  " -",
198
199
  " ",
@@ -201,143 +202,139 @@ function x() {
201
202
  })
202
203
  ]
203
204
  }, e.id);
204
- }), A.length === 0 && /* @__PURE__ */ f("p", {
205
+ }), M.length === 0 && /* @__PURE__ */ m("p", {
205
206
  className: "text-sm text-muted-foreground text-center py-4",
206
- children: x("planmagnet.dashboard.activeSprints.empty", "No active sprints.")
207
+ children: C("planmagnet.dashboard.activeSprints.empty", "No active sprints.")
207
208
  })]
208
209
  })]
209
- }), /* @__PURE__ */ p("div", {
210
+ }), /* @__PURE__ */ h("div", {
210
211
  className: "lg:col-span-4 space-y-6",
211
- children: [T.filter((e) => e.status === "ON_HOLD").length > 0 && /* @__PURE__ */ p(g, { children: [/* @__PURE__ */ f(v, { children: /* @__PURE__ */ p(y, {
212
+ children: [D.filter((e) => e.status === "ON_HOLD").length > 0 && /* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ h(x, {
212
213
  className: "flex items-center gap-2 text-base",
213
- children: [/* @__PURE__ */ f(t, { className: "h-5 w-5 text-orange-500" }), x("planmagnet.dashboard.onHoldProjects.title", "On-Hold Projects")]
214
- }) }), /* @__PURE__ */ f(_, {
214
+ children: [/* @__PURE__ */ m(n, { className: "h-5 w-5 text-orange-500" }), C("planmagnet.dashboard.onHoldProjects.title", "On-Hold Projects")]
215
+ }) }), /* @__PURE__ */ m(y, {
215
216
  className: "space-y-3",
216
- children: T.filter((e) => e.status === "ON_HOLD").map((e) => /* @__PURE__ */ p("div", {
217
+ children: D.filter((e) => e.status === "ON_HOLD").map((e) => /* @__PURE__ */ h("div", {
217
218
  className: "flex items-center justify-between rounded border p-2",
218
- children: [/* @__PURE__ */ f("div", { children: /* @__PURE__ */ f("p", {
219
+ children: [/* @__PURE__ */ m("div", { children: /* @__PURE__ */ m("p", {
219
220
  className: "text-sm font-medium",
220
221
  children: e.name
221
- }) }), /* @__PURE__ */ f(h, {
222
+ }) }), /* @__PURE__ */ m(_, {
222
223
  variant: "destructive",
223
224
  children: e.status.toLowerCase().replace("_", " ")
224
225
  })]
225
226
  }, e.id))
226
- })] }), /* @__PURE__ */ p(g, { children: [/* @__PURE__ */ f(v, { children: /* @__PURE__ */ p(y, {
227
+ })] }), /* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ h(x, {
227
228
  className: "flex items-center gap-2 text-base",
228
- children: [/* @__PURE__ */ f(r, { className: "h-5 w-5 text-green-500" }), x("planmagnet.dashboard.upcomingMilestones.title", "Upcoming Milestones")]
229
- }) }), /* @__PURE__ */ p(_, {
229
+ children: [/* @__PURE__ */ m(i, { className: "h-5 w-5 text-green-500" }), C("planmagnet.dashboard.upcomingMilestones.title", "Upcoming Milestones")]
230
+ }) }), /* @__PURE__ */ h(y, {
230
231
  className: "space-y-3",
231
- children: [j.map((e) => /* @__PURE__ */ p("div", {
232
+ children: [N.map((e) => /* @__PURE__ */ h("div", {
232
233
  className: "flex items-start gap-3 rounded border p-2",
233
- children: [/* @__PURE__ */ p("div", {
234
+ children: [/* @__PURE__ */ h("div", {
234
235
  className: "flex-1 min-w-0",
235
236
  children: [
236
- /* @__PURE__ */ f("p", {
237
+ /* @__PURE__ */ m("p", {
237
238
  className: "text-sm font-medium truncate",
238
239
  children: e.name
239
240
  }),
240
- /* @__PURE__ */ p("p", {
241
+ /* @__PURE__ */ h("p", {
241
242
  className: "text-xs text-muted-foreground",
242
243
  children: [
243
- x("planmagnet.dashboard.shared.project", "Project"),
244
+ C("planmagnet.dashboard.shared.project", "Project"),
244
245
  ": ",
245
246
  e.projectId
246
247
  ]
247
248
  }),
248
- /* @__PURE__ */ p("p", {
249
+ /* @__PURE__ */ h("p", {
249
250
  className: "text-xs text-muted-foreground",
250
251
  children: [
251
- x("planmagnet.dashboard.shared.due", "Due"),
252
+ C("planmagnet.dashboard.shared.due", "Due"),
252
253
  " ",
253
254
  e.dueDate ? new Date(e.dueDate).toLocaleDateString() : "—"
254
255
  ]
255
256
  })
256
257
  ]
257
- }), /* @__PURE__ */ f(h, {
258
+ }), /* @__PURE__ */ m(_, {
258
259
  variant: "secondary",
259
260
  className: "text-xs flex-shrink-0 capitalize",
260
261
  children: e.status.toLowerCase().replace("_", " ")
261
262
  })]
262
- }, e.id)), j.length === 0 && /* @__PURE__ */ f("p", {
263
+ }, e.id)), N.length === 0 && /* @__PURE__ */ m("p", {
263
264
  className: "text-sm text-muted-foreground text-center py-2",
264
- children: x("planmagnet.dashboard.upcomingMilestones.empty", "No upcoming milestones.")
265
+ children: C("planmagnet.dashboard.upcomingMilestones.empty", "No upcoming milestones.")
265
266
  })]
266
267
  })] })]
267
268
  })]
268
269
  }),
269
- /* @__PURE__ */ p("div", {
270
+ /* @__PURE__ */ h("div", {
270
271
  className: "grid gap-6 lg:grid-cols-2",
271
- children: [/* @__PURE__ */ p(g, { children: [/* @__PURE__ */ f(v, { children: /* @__PURE__ */ p(y, {
272
+ children: [/* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ h(x, {
272
273
  className: "flex items-center gap-2 text-base",
273
- children: [/* @__PURE__ */ f(i, { className: "h-5 w-5 text-purple-500" }), x("planmagnet.dashboard.recentWorkItems.title", "Recent Work Items")]
274
- }) }), /* @__PURE__ */ p(_, {
274
+ children: [/* @__PURE__ */ m(a, { className: "h-5 w-5 text-purple-500" }), C("planmagnet.dashboard.recentWorkItems.title", "Recent Work Items")]
275
+ }) }), /* @__PURE__ */ h(y, {
275
276
  className: "space-y-3",
276
- children: [M.map((e) => /* @__PURE__ */ p("div", {
277
+ children: [P.map((e) => /* @__PURE__ */ h("div", {
277
278
  className: "flex items-center justify-between rounded border p-2",
278
- children: [/* @__PURE__ */ p("div", {
279
+ children: [/* @__PURE__ */ h("div", {
279
280
  className: "min-w-0 flex-1",
280
- children: [/* @__PURE__ */ f("p", {
281
+ children: [/* @__PURE__ */ m("p", {
281
282
  className: "text-sm font-medium truncate",
282
283
  children: e.key
283
- }), /* @__PURE__ */ p("p", {
284
+ }), /* @__PURE__ */ h("p", {
284
285
  className: "text-xs text-muted-foreground",
285
- children: [
286
- e.projectId,
287
- " ",
288
- e.assigneeId ? `\u00b7 ${e.assigneeId}` : ""
289
- ]
286
+ children: [F.get(e.projectId) ?? `Project ${e.projectId.slice(0, 6)}`, e.assigneeId ? /* @__PURE__ */ h(p, { children: [" · ", /* @__PURE__ */ m(t, { id: e.assigneeId })] }) : null]
290
287
  })]
291
- }), /* @__PURE__ */ p("div", {
288
+ }), /* @__PURE__ */ h("div", {
292
289
  className: "flex items-center gap-2 flex-shrink-0",
293
- children: [/* @__PURE__ */ p(h, {
290
+ children: [/* @__PURE__ */ h(_, {
294
291
  variant: "outline",
295
292
  className: "text-xs",
296
293
  children: [
297
294
  e.storyPoints ?? 0,
298
295
  " ",
299
- x("planmagnet.dashboard.shared.pointsShort", "pts")
296
+ C("planmagnet.dashboard.shared.pointsShort", "pts")
300
297
  ]
301
- }), /* @__PURE__ */ f(h, {
298
+ }), /* @__PURE__ */ m(_, {
302
299
  variant: "secondary",
303
300
  className: "text-xs capitalize",
304
301
  children: e.status.toLowerCase().replace("_", " ")
305
302
  })]
306
303
  })]
307
- }, e.id)), M.length === 0 && /* @__PURE__ */ f("p", {
304
+ }, e.id)), P.length === 0 && /* @__PURE__ */ m("p", {
308
305
  className: "text-sm text-muted-foreground text-center py-4",
309
- children: x("planmagnet.dashboard.recentWorkItems.empty", "No work items yet.")
306
+ children: C("planmagnet.dashboard.recentWorkItems.empty", "No work items yet.")
310
307
  })]
311
- })] }), /* @__PURE__ */ p(g, { children: [/* @__PURE__ */ f(v, { children: /* @__PURE__ */ p(y, {
308
+ })] }), /* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ h(x, {
312
309
  className: "flex items-center gap-2 text-base",
313
- children: [/* @__PURE__ */ f(a, { className: "h-5 w-5 text-cyan-500" }), x("planmagnet.dashboard.projectStatus.title", "Project Status")]
314
- }) }), /* @__PURE__ */ p(_, {
310
+ children: [/* @__PURE__ */ m(o, { className: "h-5 w-5 text-cyan-500" }), C("planmagnet.dashboard.projectStatus.title", "Project Status")]
311
+ }) }), /* @__PURE__ */ h(y, {
315
312
  className: "space-y-3",
316
- children: [k.map((e) => /* @__PURE__ */ p("div", {
313
+ children: [j.map((e) => /* @__PURE__ */ h("div", {
317
314
  className: "flex items-center justify-between rounded border p-2",
318
- children: [/* @__PURE__ */ p("div", {
315
+ children: [/* @__PURE__ */ h("div", {
319
316
  className: "min-w-0 flex-1",
320
- children: [/* @__PURE__ */ f("p", {
317
+ children: [/* @__PURE__ */ m("p", {
321
318
  className: "text-sm font-medium truncate",
322
319
  children: e.name
323
- }), /* @__PURE__ */ f("p", {
320
+ }), /* @__PURE__ */ m("p", {
324
321
  className: "text-xs text-muted-foreground capitalize",
325
322
  children: e.status.toLowerCase().replace("_", " ")
326
323
  })]
327
- }), /* @__PURE__ */ p("div", {
324
+ }), /* @__PURE__ */ h("div", {
328
325
  className: "flex items-center gap-2 ml-3 flex-shrink-0",
329
326
  children: [
330
- /* @__PURE__ */ f(c, { className: "h-3 w-3 text-muted-foreground" }),
331
- /* @__PURE__ */ f("span", {
327
+ /* @__PURE__ */ m(l, { className: "h-3 w-3 text-muted-foreground" }),
328
+ /* @__PURE__ */ m("span", {
332
329
  className: "text-xs text-muted-foreground",
333
- children: e.ownerId
330
+ children: /* @__PURE__ */ m(t, { id: e.ownerId })
334
331
  }),
335
- /* @__PURE__ */ f(n, { className: "h-3 w-3 text-muted-foreground" })
332
+ /* @__PURE__ */ m(r, { className: "h-3 w-3 text-muted-foreground" })
336
333
  ]
337
334
  })]
338
- }, e.id)), k.length === 0 && /* @__PURE__ */ f("p", {
335
+ }, e.id)), j.length === 0 && /* @__PURE__ */ m("p", {
339
336
  className: "text-sm text-muted-foreground text-center py-4",
340
- children: x("planmagnet.dashboard.projectStatus.empty", "No active projects.")
337
+ children: C("planmagnet.dashboard.projectStatus.empty", "No active projects.")
341
338
  })]
342
339
  })] })]
343
340
  })
@@ -345,6 +342,6 @@ function x() {
345
342
  });
346
343
  }
347
344
  //#endregion
348
- export { x as DashboardDashboardPagePage };
345
+ export { C as DashboardDashboardPagePage };
349
346
 
350
347
  //# sourceMappingURL=DashboardDashboardPagePage.js.map