@burdenoff/microfe-planmagnet 2026.1001.4 → 2026.1001.6

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 (62) hide show
  1. package/dist/planmagnet/controls/CompetitorControls.d.ts +71 -0
  2. package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -1
  3. package/dist/planmagnet/controls/CompetitorControls.js +346 -12
  4. package/dist/planmagnet/controls/CompetitorControls.js.map +1 -1
  5. package/dist/planmagnet/controls/FeatureControls.d.ts +5 -0
  6. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  7. package/dist/planmagnet/controls/FeatureControls.js +363 -218
  8. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  9. package/dist/planmagnet/controls/Pickers.d.ts +15 -0
  10. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  11. package/dist/planmagnet/controls/Pickers.js +61 -1
  12. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  13. package/dist/planmagnet/controls/PrioritizationControls.d.ts +36 -0
  14. package/dist/planmagnet/controls/PrioritizationControls.d.ts.map +1 -1
  15. package/dist/planmagnet/controls/PrioritizationControls.js +118 -1
  16. package/dist/planmagnet/controls/PrioritizationControls.js.map +1 -1
  17. package/dist/planmagnet/dashboards/DashboardUI.js +1 -1
  18. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
  19. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +67 -59
  20. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
  21. package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -1
  22. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +1 -1
  23. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -1
  24. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  25. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +289 -282
  26. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  27. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -1
  28. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +126 -63
  29. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -1
  30. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -1
  31. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +14 -0
  32. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -1
  33. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -1
  34. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +93 -57
  35. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -1
  36. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -1
  37. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +125 -83
  38. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -1
  39. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
  40. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +226 -126
  41. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
  42. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  43. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +162 -122
  44. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  45. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
  46. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +135 -109
  47. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  48. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  49. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +15 -3
  50. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  51. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  52. package/dist/planmagnet/pages/TeamTeamPagePage.js +59 -53
  53. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  54. package/dist/planmagnet/pages/analytics/MetricsCard.d.ts +8 -0
  55. package/dist/planmagnet/pages/analytics/MetricsCard.d.ts.map +1 -0
  56. package/dist/planmagnet/pages/analytics/MetricsCard.js +164 -0
  57. package/dist/planmagnet/pages/analytics/MetricsCard.js.map +1 -0
  58. package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts +2 -0
  59. package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts.map +1 -0
  60. package/dist/planmagnet/team/UnmatchedCrewIdentities.js +155 -0
  61. package/dist/planmagnet/team/UnmatchedCrewIdentities.js.map +1 -0
  62. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PortfoliosPortfolioDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge, Button } from '@burdenoff/fe-libs/ui';\nimport { Briefcase, Target, AlertTriangle, Clock, Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PortfolioHeaderActions } from '../controls/PortfolioControls';\n\nconst GET_PORTFOLIO = gql`\n query GetPortfolio($id: ID!) {\n planmagnetPortfolio(id: $id) {\n id\n workspaceId\n name\n description\n status\n alignment\n alignmentLabel\n ownerId\n budgetTotal\n currency\n fiscalPeriod\n tags\n createdAt\n updatedAt\n programs {\n id\n name\n status\n }\n projects {\n id\n name\n status\n }\n }\n }\n`;\n\nconst GET_PORTFOLIO_OVERVIEW = gql`\n query GetPortfolioOverview($portfolioId: ID!) {\n planmagnetPortfolioOverview(portfolioId: $portfolioId) {\n portfolioId\n totalPrograms\n totalProjects\n projectsByStatus\n totalBudget\n totalSpent\n budgetUtilization\n healthScore\n atRiskCount\n overdueMilestones\n }\n }\n`;\n\ninterface RelatedNode {\n id: string;\n name: string;\n status: string;\n}\n\ninterface OverviewNode {\n totalPrograms: number;\n totalProjects: number;\n projectsByStatus: Record<string, number>;\n totalBudget: number | null;\n totalSpent: number | null;\n budgetUtilization: number | null;\n healthScore: number | null;\n atRiskCount: number;\n overdueMilestones: number;\n}\n\nexport function PortfoliosPortfolioDetailPagePage() {\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const portfolioId = params.id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetPortfolio: {\n id: string;\n name: string;\n description: string | null;\n status: string;\n alignment: string | null;\n alignmentLabel: string | null;\n ownerId: string;\n budgetTotal: number | null;\n currency: string | null;\n fiscalPeriod: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n programs: RelatedNode[];\n projects: RelatedNode[];\n } | null;\n }>(GET_PORTFOLIO, { variables: { id: portfolioId }, skip: !portfolioId });\n\n const { data: overviewData } = useQuery<{ planmagnetPortfolioOverview: OverviewNode }>(\n GET_PORTFOLIO_OVERVIEW,\n { variables: { portfolioId }, skip: !portfolioId }\n );\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load portfolio: {error.message}</div>;\n }\n\n const portfolio = data?.planmagnetPortfolio;\n if (!portfolio) return <div className=\"p-6 text-muted-foreground\">Portfolio not found.</div>;\n\n const overview = overviewData?.planmagnetPortfolioOverview ?? null;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Briefcase className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{portfolio.name}</h1>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(portfolio.status)}\n </Badge>\n </div>\n {portfolio.alignment && (\n <p className=\"text-sm text-muted-foreground mt-1\">\n {portfolio.alignment === 'CUSTOM'\n ? (portfolio.alignmentLabel ?? 'Custom alignment')\n : enumLabel(portfolio.alignment)}\n </p>\n )}\n </div>\n <PortfolioHeaderActions\n portfolioId={portfolio.id}\n name={portfolio.name}\n status={portfolio.status}\n />\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Briefcase className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalPrograms ?? portfolio.programs.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Programs</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Target className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalProjects ?? portfolio.projects.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Projects</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <AlertTriangle className=\"h-5 w-5 mx-auto text-destructive\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.atRiskCount ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">At Risk</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-status-warning-text\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.overdueMilestones ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">Overdue Milestones</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm\">{portfolio.description ?? 'No description provided.'}</p>\n {overview ? (\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Budget Utilization</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.budgetUtilization != null\n ? `${overview.budgetUtilization.toFixed(0)}%`\n : 'No budget data'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Health Score</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.healthScore != null\n ? overview.healthScore.toFixed(0)\n : 'No projects yet'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Total Spent</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.totalSpent != null ? overview.totalSpent.toLocaleString() : 'N/A'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Projects by Status</p>\n <p className=\"text-sm mt-1\">\n {Object.keys(overview.projectsByStatus).length > 0\n ? Object.entries(overview.projectsByStatus)\n .map(([status, count]) => `${enumLabel(status)}: ${count}`)\n .join(', ')\n : 'No projects yet'}\n </p>\n </div>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">Loading overview…</p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Programs ({portfolio.programs.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.programs.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No programs are linked to this portfolio yet.\n </p>\n )}\n {portfolio.programs.map((program) => (\n <button\n key={program.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/products/program-detail-page', program.id))}\n >\n <span className=\"text-sm font-medium\">{program.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(program.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Projects ({portfolio.projects.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.projects.length === 0 && (\n <div className=\"space-y-1\">\n <p className=\"text-sm text-muted-foreground\">\n No projects are linked to this portfolio yet.\n </p>\n <p className=\"text-sm text-muted-foreground\">\n Open a project and set its Portfolio field to add it here.{' '}\n <Button\n variant=\"link\"\n className=\"h-auto p-0 text-sm\"\n onClick={() => navigate('/projects/projects-page')}\n >\n Go to Projects\n </Button>\n </p>\n </div>\n )}\n {portfolio.projects.map((project) => (\n <button\n key={project.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/projects/project-detail-page', project.id))}\n >\n <span className=\"text-sm font-medium\">{project.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(project.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <PersonName id={portfolio.ownerId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Budget</span>\n <span className=\"font-medium\">\n {portfolio.budgetTotal != null\n ? `${portfolio.currency ?? ''} ${portfolio.budgetTotal.toLocaleString()}`.trim()\n : 'Not set'}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Fiscal Period</span>\n <span>{portfolio.fiscalPeriod ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(portfolio.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(portfolio.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {portfolio.tags.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {portfolio.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PortfoliosPortfolioDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge, Button } from '@burdenoff/fe-libs/ui';\nimport { Briefcase, Target, AlertTriangle, Clock, Loader2, BarChart3 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PortfolioHeaderActions } from '../controls/PortfolioControls';\nimport { StatTile } from './analytics/DeliveryHealthCards';\n\nconst GET_PORTFOLIO = gql`\n query GetPortfolio($id: ID!) {\n planmagnetPortfolio(id: $id) {\n id\n workspaceId\n name\n description\n status\n alignment\n alignmentLabel\n ownerId\n budgetTotal\n currency\n fiscalPeriod\n tags\n createdAt\n updatedAt\n programs {\n id\n name\n status\n }\n projects {\n id\n name\n status\n }\n }\n }\n`;\n\nconst GET_PORTFOLIO_OVERVIEW = gql`\n query GetPortfolioOverview($portfolioId: ID!) {\n planmagnetPortfolioOverview(portfolioId: $portfolioId) {\n portfolioId\n totalPrograms\n totalProjects\n projectsByStatus\n totalBudget\n totalSpent\n budgetUtilization\n healthScore\n atRiskCount\n overdueMilestones\n }\n }\n`;\n\n// ANR-12 MVP: a second, server-computed view alongside planmagnetPortfolioOverview above — not a replacement\n// for it (see PlanmagnetPortfolioRollup's own doc comment in graphql/wspace.graphql for exactly where the two\n// agree and where they deliberately don't, e.g. budgetUtilization at a $0 budget: Overview says 0%, this says\n// null). Recomputed on every call, never a stored snapshot.\nconst GET_PORTFOLIO_ROLLUP = gql`\n query GetPortfolioRollup($portfolioId: ID!) {\n planmagnetPortfolioRollup(portfolioId: $portfolioId) {\n portfolioId\n totalProjects\n projectsByStatus\n totalBudget\n totalSpent\n budgetUtilization\n averageSprintOnTimeRate\n averageReleaseOnTimeRate\n averageMilestoneOnTimeRate\n calculatedAt\n }\n }\n`;\n\ninterface RollupNode {\n totalProjects: number;\n projectsByStatus: Record<string, number>;\n totalBudget: number | null;\n totalSpent: number | null;\n budgetUtilization: number | null;\n averageSprintOnTimeRate: number | null;\n averageReleaseOnTimeRate: number | null;\n averageMilestoneOnTimeRate: number | null;\n calculatedAt: string;\n}\n\n/** A rollup rate that is null for a real, documented reason — never a fabricated 0%. */\nfunction RollupRate({\n label,\n rate,\n emptyReason,\n}: {\n label: string;\n rate: number | null;\n emptyReason: string;\n}) {\n return (\n <StatTile\n label={label}\n value={rate == null ? '—' : `${Math.round(rate * 100)}%`}\n hint={rate == null ? emptyReason : undefined}\n />\n );\n}\n\ninterface RelatedNode {\n id: string;\n name: string;\n status: string;\n}\n\ninterface OverviewNode {\n totalPrograms: number;\n totalProjects: number;\n projectsByStatus: Record<string, number>;\n totalBudget: number | null;\n totalSpent: number | null;\n budgetUtilization: number | null;\n healthScore: number | null;\n atRiskCount: number;\n overdueMilestones: number;\n}\n\nexport function PortfoliosPortfolioDetailPagePage() {\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const portfolioId = params.id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetPortfolio: {\n id: string;\n name: string;\n description: string | null;\n status: string;\n alignment: string | null;\n alignmentLabel: string | null;\n ownerId: string;\n budgetTotal: number | null;\n currency: string | null;\n fiscalPeriod: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n programs: RelatedNode[];\n projects: RelatedNode[];\n } | null;\n }>(GET_PORTFOLIO, { variables: { id: portfolioId }, skip: !portfolioId });\n\n const { data: overviewData } = useQuery<{ planmagnetPortfolioOverview: OverviewNode }>(\n GET_PORTFOLIO_OVERVIEW,\n { variables: { portfolioId }, skip: !portfolioId }\n );\n\n const {\n data: rollupData,\n loading: rollupLoading,\n error: rollupError,\n } = useQuery<{\n planmagnetPortfolioRollup: RollupNode;\n }>(GET_PORTFOLIO_ROLLUP, { variables: { portfolioId }, skip: !portfolioId });\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load portfolio: {error.message}</div>;\n }\n\n const portfolio = data?.planmagnetPortfolio;\n if (!portfolio) return <div className=\"p-6 text-muted-foreground\">Portfolio not found.</div>;\n\n const overview = overviewData?.planmagnetPortfolioOverview ?? null;\n const rollup = rollupData?.planmagnetPortfolioRollup ?? null;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Briefcase className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{portfolio.name}</h1>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(portfolio.status)}\n </Badge>\n </div>\n {portfolio.alignment && (\n <p className=\"text-sm text-muted-foreground mt-1\">\n {portfolio.alignment === 'CUSTOM'\n ? (portfolio.alignmentLabel ?? 'Custom alignment')\n : enumLabel(portfolio.alignment)}\n </p>\n )}\n </div>\n <PortfolioHeaderActions\n portfolioId={portfolio.id}\n name={portfolio.name}\n status={portfolio.status}\n />\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Briefcase className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalPrograms ?? portfolio.programs.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Programs</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Target className=\"h-5 w-5 mx-auto text-primary\" />\n <p className=\"text-2xl font-bold mt-1\">\n {overview?.totalProjects ?? portfolio.projects.length}\n </p>\n <p className=\"text-xs text-muted-foreground\">Projects</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <AlertTriangle className=\"h-5 w-5 mx-auto text-destructive\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.atRiskCount ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">At Risk</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-status-warning-text\" />\n <p className=\"text-2xl font-bold mt-1\">{overview?.overdueMilestones ?? 0}</p>\n <p className=\"text-xs text-muted-foreground\">Overdue Milestones</p>\n </CardContent>\n </Card>\n </div>\n\n {/* ANR-12 MVP: server-computed rollup across this portfolio's live projects — total, status breakdown,\n planned vs. spent with utilisation %, and average on-time rates. A different, additive view from the\n Overview stats above (see GET_PORTFOLIO_ROLLUP's own doc comment for exactly where the two differ). */}\n <Card data-testid=\"portfolio-rollup-card\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n Portfolio Rollup\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n Recomputed on every load across every live project in this portfolio.\n </p>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {rollupLoading && !rollup ? (\n <p className=\"text-sm text-muted-foreground\">Loading…</p>\n ) : rollupError ? (\n <p className=\"text-sm text-status-error-text\">\n Rollup could not be loaded: {rollupError.message}\n </p>\n ) : !rollup ? null : (\n <>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <StatTile label=\"Total projects\" value={String(rollup.totalProjects)} />\n <StatTile\n label=\"Status breakdown\"\n value={\n Object.keys(rollup.projectsByStatus).length === 0\n ? '—'\n : Object.entries(rollup.projectsByStatus)\n .map(([status, count]) => `${enumLabel(status)}: ${count}`)\n .join(', ')\n }\n hint={\n rollup.totalProjects === 0 ? 'No live projects in this portfolio' : undefined\n }\n />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3\">\n <StatTile\n label=\"Planned\"\n value={rollup.totalBudget == null ? '—' : rollup.totalBudget.toLocaleString()}\n hint={rollup.totalBudget == null ? 'No budgets set on these projects' : undefined}\n />\n <StatTile\n label=\"Spent\"\n value={rollup.totalSpent == null ? '—' : rollup.totalSpent.toLocaleString()}\n hint={rollup.totalSpent == null ? 'No budgets set on these projects' : undefined}\n />\n <StatTile\n label=\"Utilisation\"\n value={\n rollup.budgetUtilization == null\n ? '—'\n : `${rollup.budgetUtilization.toFixed(0)}%`\n }\n hint={\n rollup.budgetUtilization == null\n ? 'No budget to compare spend against'\n : undefined\n }\n />\n </div>\n <div>\n <h3 className=\"text-sm font-medium mb-1\">\n Average on-time rate (trailing 30 days)\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3\">\n <RollupRate\n label=\"Sprints\"\n rate={rollup.averageSprintOnTimeRate}\n emptyReason=\"No completed sprint in this window across these projects\"\n />\n <RollupRate\n label=\"Releases\"\n rate={rollup.averageReleaseOnTimeRate}\n emptyReason=\"No completed release in this window across these projects\"\n />\n <RollupRate\n label=\"Milestones\"\n rate={rollup.averageMilestoneOnTimeRate}\n emptyReason=\"No completed milestone in this window across these projects\"\n />\n </div>\n </div>\n </>\n )}\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm\">{portfolio.description ?? 'No description provided.'}</p>\n {overview ? (\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Budget Utilization</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.budgetUtilization != null\n ? `${overview.budgetUtilization.toFixed(0)}%`\n : 'No budget data'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Health Score</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.healthScore != null\n ? overview.healthScore.toFixed(0)\n : 'No projects yet'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Total Spent</p>\n <p className=\"text-lg font-semibold mt-1\">\n {overview.totalSpent != null ? overview.totalSpent.toLocaleString() : 'N/A'}\n </p>\n </div>\n <div className=\"p-3 bg-muted rounded-md\">\n <p className=\"text-xs text-muted-foreground\">Projects by Status</p>\n <p className=\"text-sm mt-1\">\n {Object.keys(overview.projectsByStatus).length > 0\n ? Object.entries(overview.projectsByStatus)\n .map(([status, count]) => `${enumLabel(status)}: ${count}`)\n .join(', ')\n : 'No projects yet'}\n </p>\n </div>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">Loading overview…</p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Programs ({portfolio.programs.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.programs.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No programs are linked to this portfolio yet.\n </p>\n )}\n {portfolio.programs.map((program) => (\n <button\n key={program.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/products/program-detail-page', program.id))}\n >\n <span className=\"text-sm font-medium\">{program.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(program.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Projects ({portfolio.projects.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {portfolio.projects.length === 0 && (\n <div className=\"space-y-1\">\n <p className=\"text-sm text-muted-foreground\">\n No projects are linked to this portfolio yet.\n </p>\n <p className=\"text-sm text-muted-foreground\">\n Open a project and set its Portfolio field to add it here.{' '}\n <Button\n variant=\"link\"\n className=\"h-auto p-0 text-sm\"\n onClick={() => navigate('/projects/projects-page')}\n >\n Go to Projects\n </Button>\n </p>\n </div>\n )}\n {portfolio.projects.map((project) => (\n <button\n key={project.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/projects/project-detail-page', project.id))}\n >\n <span className=\"text-sm font-medium\">{project.name}</span>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(project.status)}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <PersonName id={portfolio.ownerId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Budget</span>\n <span className=\"font-medium\">\n {portfolio.budgetTotal != null\n ? `${portfolio.currency ?? ''} ${portfolio.budgetTotal.toLocaleString()}`.trim()\n : 'Not set'}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Fiscal Period</span>\n <span>{portfolio.fiscalPeriod ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(portfolio.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(portfolio.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {portfolio.tags.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {portfolio.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsFeaturesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsFeaturesPagePage.tsx"],"names":[],"mappings":"AA6FA,wBAAgB,wBAAwB,4CAyTvC"}
1
+ {"version":3,"file":"ProductsFeaturesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsFeaturesPagePage.tsx"],"names":[],"mappings":"AAyGA,wBAAgB,wBAAwB,4CA2WvC"}
@@ -3,15 +3,15 @@ import { PersonName as n } from "../people/People.js";
3
3
  import { countLabel as r, useFeatureCounts as i } from "../hooks/useCounts.js";
4
4
  import { detailPath as a, rowLinkProps as o } from "../utils/rowNavigation.js";
5
5
  import { FeatureTriageActions as s, isSnoozed as c } from "../controls/FeatureTriage.js";
6
- import { KANO_META as l, KANO_VALUES as u, MOSCOW_META as d, MOSCOW_VALUES as f, QUADRANT_META as p, QUADRANT_VALUES as m, QuadrantBadge as h } from "../controls/PrioritizationControls.js";
6
+ import { KANO_META as l, KANO_VALUES as u, MOSCOW_META as ee, MOSCOW_VALUES as d, QUADRANT_META as f, QUADRANT_VALUES as p, QuadrantBadge as m, riceScoreMissingHint as h } from "../controls/PrioritizationControls.js";
7
7
  import { useState as g } from "react";
8
- import { ArrowUpDown as _, Filter as v, Loader2 as y, Plus as b, Star as x, Target as S, ThumbsUp as ee } from "lucide-react";
9
- import { gql as C } from "@apollo/client";
10
- import { useQuery as w } from "@apollo/client/react";
11
- import { jsx as T, jsxs as E } from "react/jsx-runtime";
12
- import { Badge as D, Button as O, Card as k, CardContent as A } from "@burdenoff/fe-libs/ui";
8
+ import { ArrowUpDown as te, Filter as ne, Gauge as _, Loader2 as v, Plus as y, Star as b, Target as re, ThumbsUp as ie } from "lucide-react";
9
+ import { gql as x } from "@apollo/client";
10
+ import { useQuery as S } from "@apollo/client/react";
11
+ import { jsx as C, jsxs as w } from "react/jsx-runtime";
12
+ import { Badge as T, Button as E, Card as D, CardContent as O } from "@burdenoff/fe-libs/ui";
13
13
  //#region src/planmagnet/pages/ProductsFeaturesPagePage.tsx
14
- var j = C`
14
+ var k = x`
15
15
  query ListFeatureRequests(
16
16
  $workspaceId: ID!
17
17
  $filter: PlanmagnetFeatureRequestFilter
@@ -40,13 +40,18 @@ var j = C`
40
40
  moscow
41
41
  kano
42
42
  valueEffortQuadrant
43
+ reach
44
+ impact
45
+ confidence
46
+ effortScore
47
+ riceScore
43
48
  createdAt
44
49
  updatedAt
45
50
  }
46
51
  totalCount
47
52
  }
48
53
  }
49
- `, M = {
54
+ `, A = {
50
55
  SUBMITTED: "bg-gray-100 text-gray-800",
51
56
  UNDER_REVIEW: "bg-yellow-100 text-yellow-800",
52
57
  PLANNED: "bg-blue-100 text-blue-800",
@@ -55,30 +60,30 @@ var j = C`
55
60
  DECLINED: "bg-red-100 text-red-800",
56
61
  DUPLICATE: "bg-orange-100 text-orange-800"
57
62
  };
58
- function N() {
59
- let C = t(), { workspaceId: N } = e(), [P, F] = g("all"), [I] = g(() => Date.now()), [L, te] = g("NEWEST"), [R, z] = g(""), [B, V] = g(""), [H, U] = g(""), W = i(), G = {
60
- ...P === "TRIAGE" ? { triage: !0 } : P === "SNOOZED" ? { snoozed: !0 } : P === "all" ? {} : { status: P },
61
- ...R ? { moscow: R } : {},
62
- ...B ? { kano: B } : {},
63
- ...H ? { quadrant: H } : {}
64
- }, { data: K, loading: q, error: J } = w(j, {
63
+ function j() {
64
+ let x = t(), { workspaceId: j } = e(), [M, N] = g("all"), [P] = g(() => Date.now()), [F, I] = g("NEWEST"), [L, R] = g(!1), [z, B] = g(""), [V, H] = g(""), [U, W] = g(""), G = i(), K = {
65
+ ...M === "TRIAGE" ? { triage: !0 } : M === "SNOOZED" ? { snoozed: !0 } : M === "all" ? {} : { status: M },
66
+ ...z ? { moscow: z } : {},
67
+ ...V ? { kano: V } : {},
68
+ ...U ? { quadrant: U } : {}
69
+ }, { data: q, loading: J, error: Y } = S(k, {
65
70
  variables: {
66
- workspaceId: N,
67
- filter: Object.keys(G).length > 0 ? G : void 0,
71
+ workspaceId: j,
72
+ filter: Object.keys(K).length > 0 ? K : void 0,
68
73
  pagination: { first: 50 },
69
- orderBy: L
74
+ orderBy: F
70
75
  },
71
- skip: !N
76
+ skip: !j
72
77
  });
73
- if (q && !K) return /* @__PURE__ */ T("div", {
78
+ if (J && !q) return /* @__PURE__ */ C("div", {
74
79
  className: "flex items-center justify-center p-12",
75
- children: /* @__PURE__ */ T(y, { className: "h-8 w-8 animate-spin text-muted-foreground" })
80
+ children: /* @__PURE__ */ C(v, { className: "h-8 w-8 animate-spin text-muted-foreground" })
76
81
  });
77
- if (J) return /* @__PURE__ */ E("div", {
82
+ if (Y) return /* @__PURE__ */ w("div", {
78
83
  className: "p-6 text-destructive",
79
- children: ["Failed to load features: ", J.message]
84
+ children: ["Failed to load features: ", Y.message]
80
85
  });
81
- let Y = K?.planmagnetFeatureRequests?.items ?? [], X = K?.planmagnetFeatureRequests?.totalCount ?? 0, Z = Y, Q = [
86
+ let X = q?.planmagnetFeatureRequests?.items ?? [], Z = q?.planmagnetFeatureRequests?.totalCount ?? 0, Q = L ? [...X].sort((e, t) => e.riceScore == null && t.riceScore == null ? 0 : e.riceScore == null ? 1 : t.riceScore == null ? -1 : t.riceScore - e.riceScore) : X, ae = [
82
87
  "all",
83
88
  "TRIAGE",
84
89
  "SNOOZED",
@@ -90,257 +95,292 @@ function N() {
90
95
  "DECLINED",
91
96
  "DUPLICATE"
92
97
  ], $ = {
93
- total: r(W?.all),
94
- open: r(W?.open),
95
- inProgress: r(W?.byStatus.IN_PROGRESS),
96
- completed: r(W?.byStatus.COMPLETED)
98
+ total: r(G?.all),
99
+ open: r(G?.open),
100
+ inProgress: r(G?.byStatus.IN_PROGRESS),
101
+ completed: r(G?.byStatus.COMPLETED)
97
102
  };
98
- return /* @__PURE__ */ E("div", {
103
+ return /* @__PURE__ */ w("div", {
99
104
  className: "space-y-6 p-6",
100
105
  children: [
101
- /* @__PURE__ */ E("div", {
106
+ /* @__PURE__ */ w("div", {
102
107
  className: "flex items-center justify-between",
103
- children: [/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("h1", {
108
+ children: [/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("h1", {
104
109
  className: "text-2xl font-semibold",
105
110
  children: "Features"
106
- }), /* @__PURE__ */ T("p", {
111
+ }), /* @__PURE__ */ C("p", {
107
112
  className: "text-sm text-muted-foreground",
108
- children: P === "all" ? `${$.total} feature requests` : P === "TRIAGE" ? `${X} waiting for triage` : `${X} ${P.toLowerCase().replace("_", " ")} match`
109
- })] }), /* @__PURE__ */ E(O, {
113
+ children: M === "all" ? `${$.total} feature requests` : M === "TRIAGE" ? `${Z} waiting for triage` : `${Z} ${M.toLowerCase().replace("_", " ")} match`
114
+ })] }), /* @__PURE__ */ w(E, {
110
115
  size: "sm",
111
- onClick: () => C("/products/new-feature-page"),
112
- children: [/* @__PURE__ */ T(b, { className: "mr-1 h-4 w-4" }), "New Feature"]
116
+ onClick: () => x("/products/new-feature-page"),
117
+ children: [/* @__PURE__ */ C(y, { className: "mr-1 h-4 w-4" }), "New Feature"]
113
118
  })]
114
119
  }),
115
- /* @__PURE__ */ E("div", {
120
+ /* @__PURE__ */ w("div", {
116
121
  className: "grid grid-cols-2 md:grid-cols-4 gap-4",
117
122
  children: [
118
- /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
123
+ /* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
119
124
  className: "pt-4",
120
- children: [/* @__PURE__ */ T("p", {
125
+ children: [/* @__PURE__ */ C("p", {
121
126
  className: "text-xs text-muted-foreground",
122
127
  children: "Total Features"
123
- }), /* @__PURE__ */ T("p", {
128
+ }), /* @__PURE__ */ C("p", {
124
129
  className: "text-xl font-bold",
125
130
  children: $.total
126
131
  })]
127
132
  }) }),
128
- /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
133
+ /* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
129
134
  className: "pt-4",
130
- children: [/* @__PURE__ */ T("p", {
135
+ children: [/* @__PURE__ */ C("p", {
131
136
  className: "text-xs text-muted-foreground",
132
137
  children: "In Progress"
133
- }), /* @__PURE__ */ T("p", {
138
+ }), /* @__PURE__ */ C("p", {
134
139
  className: "text-xl font-bold",
135
140
  children: $.inProgress
136
141
  })]
137
142
  }) }),
138
- /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
143
+ /* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
139
144
  className: "pt-4",
140
- children: [/* @__PURE__ */ T("p", {
145
+ children: [/* @__PURE__ */ C("p", {
141
146
  className: "text-xs text-muted-foreground",
142
147
  children: "Completed"
143
- }), /* @__PURE__ */ T("p", {
148
+ }), /* @__PURE__ */ C("p", {
144
149
  className: "text-xl font-bold",
145
150
  children: $.completed
146
151
  })]
147
152
  }) }),
148
- /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
153
+ /* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
149
154
  className: "pt-4",
150
- children: [/* @__PURE__ */ T("p", {
155
+ children: [/* @__PURE__ */ C("p", {
151
156
  className: "text-xs text-muted-foreground",
152
157
  children: "Open"
153
- }), /* @__PURE__ */ T("p", {
158
+ }), /* @__PURE__ */ C("p", {
154
159
  className: "text-xl font-bold",
155
160
  children: $.open
156
161
  })]
157
162
  }) })
158
163
  ]
159
164
  }),
160
- /* @__PURE__ */ E("div", {
165
+ /* @__PURE__ */ w("div", {
161
166
  className: "flex items-center justify-between flex-wrap gap-2",
162
- children: [/* @__PURE__ */ E("div", {
167
+ children: [/* @__PURE__ */ w("div", {
163
168
  className: "flex items-center gap-2",
164
- children: [/* @__PURE__ */ T(v, { className: "h-4 w-4 text-muted-foreground" }), Q.map((e) => /* @__PURE__ */ T("button", {
165
- onClick: () => F(e),
166
- className: `px-2 py-1 text-xs rounded-md capitalize ${P === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
167
- children: e === "all" ? "All" : e === "TRIAGE" ? `Triage (${r(W?.triage)})` : e === "SNOOZED" ? `Snoozed (${r(W?.snoozed)})` : e.toLowerCase().replace("_", " ")
169
+ children: [/* @__PURE__ */ C(ne, { className: "h-4 w-4 text-muted-foreground" }), ae.map((e) => /* @__PURE__ */ C("button", {
170
+ onClick: () => N(e),
171
+ className: `px-2 py-1 text-xs rounded-md capitalize ${M === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
172
+ children: e === "all" ? "All" : e === "TRIAGE" ? `Triage (${r(G?.triage)})` : e === "SNOOZED" ? `Snoozed (${r(G?.snoozed)})` : e.toLowerCase().replace("_", " ")
168
173
  }, e))]
169
- }), /* @__PURE__ */ E("div", {
174
+ }), /* @__PURE__ */ w("div", {
170
175
  className: "flex items-center gap-2",
171
- children: [/* @__PURE__ */ T(_, { className: "h-4 w-4 text-muted-foreground" }), ["NEWEST", "MOST_VOTED"].map((e) => /* @__PURE__ */ T("button", {
172
- onClick: () => te(e),
173
- className: `px-2 py-1 text-xs rounded-md ${L === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
174
- children: e === "NEWEST" ? "Newest" : "Most voted"
175
- }, e))]
176
+ children: [
177
+ /* @__PURE__ */ C(te, { className: "h-4 w-4 text-muted-foreground" }),
178
+ ["NEWEST", "MOST_VOTED"].map((e) => /* @__PURE__ */ C("button", {
179
+ onClick: () => I(e),
180
+ className: `px-2 py-1 text-xs rounded-md ${!L && F === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
181
+ children: e === "NEWEST" ? "Newest" : "Most voted"
182
+ }, e)),
183
+ /* @__PURE__ */ C("button", {
184
+ type: "button",
185
+ "aria-pressed": L,
186
+ "data-testid": "feature-sort-rice",
187
+ onClick: () => R((e) => !e),
188
+ className: `px-2 py-1 text-xs rounded-md ${L ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
189
+ children: "RICE score"
190
+ })
191
+ ]
176
192
  })]
177
193
  }),
178
- /* @__PURE__ */ E("div", {
194
+ L && /* @__PURE__ */ w("p", {
195
+ className: "text-xs text-muted-foreground",
196
+ "data-testid": "feature-sort-rice-note",
197
+ children: [
198
+ "Sorted by RICE score within the ",
199
+ Q.length,
200
+ " loaded results above — not a server-wide sort."
201
+ ]
202
+ }),
203
+ /* @__PURE__ */ w("div", {
179
204
  className: "flex flex-wrap items-center gap-3",
180
205
  children: [
181
- /* @__PURE__ */ E("div", {
206
+ /* @__PURE__ */ w("div", {
182
207
  className: "flex items-center gap-1",
183
- children: [/* @__PURE__ */ T("label", {
208
+ children: [/* @__PURE__ */ C("label", {
184
209
  htmlFor: "feature-filter-moscow",
185
210
  className: "text-xs text-muted-foreground",
186
211
  children: "MoSCoW"
187
- }), /* @__PURE__ */ E("select", {
212
+ }), /* @__PURE__ */ w("select", {
188
213
  id: "feature-filter-moscow",
189
214
  "data-testid": "feature-filter-moscow",
190
215
  className: "rounded-md border border-input bg-background px-2 py-1 text-xs",
191
- value: R,
192
- onChange: (e) => z(e.target.value),
193
- children: [/* @__PURE__ */ T("option", {
216
+ value: z,
217
+ onChange: (e) => B(e.target.value),
218
+ children: [/* @__PURE__ */ C("option", {
194
219
  value: "",
195
220
  children: "All"
196
- }), f.map((e) => /* @__PURE__ */ T("option", {
221
+ }), d.map((e) => /* @__PURE__ */ C("option", {
197
222
  value: e,
198
- children: d[e].label
223
+ children: ee[e].label
199
224
  }, e))]
200
225
  })]
201
226
  }),
202
- /* @__PURE__ */ E("div", {
227
+ /* @__PURE__ */ w("div", {
203
228
  className: "flex items-center gap-1",
204
- children: [/* @__PURE__ */ T("label", {
229
+ children: [/* @__PURE__ */ C("label", {
205
230
  htmlFor: "feature-filter-kano",
206
231
  className: "text-xs text-muted-foreground",
207
232
  children: "Kano"
208
- }), /* @__PURE__ */ E("select", {
233
+ }), /* @__PURE__ */ w("select", {
209
234
  id: "feature-filter-kano",
210
235
  "data-testid": "feature-filter-kano",
211
236
  className: "rounded-md border border-input bg-background px-2 py-1 text-xs",
212
- value: B,
213
- onChange: (e) => V(e.target.value),
214
- children: [/* @__PURE__ */ T("option", {
237
+ value: V,
238
+ onChange: (e) => H(e.target.value),
239
+ children: [/* @__PURE__ */ C("option", {
215
240
  value: "",
216
241
  children: "All"
217
- }), u.map((e) => /* @__PURE__ */ T("option", {
242
+ }), u.map((e) => /* @__PURE__ */ C("option", {
218
243
  value: e,
219
244
  children: l[e].label
220
245
  }, e))]
221
246
  })]
222
247
  }),
223
- /* @__PURE__ */ E("div", {
248
+ /* @__PURE__ */ w("div", {
224
249
  className: "flex items-center gap-1",
225
- children: [/* @__PURE__ */ T("label", {
250
+ children: [/* @__PURE__ */ C("label", {
226
251
  htmlFor: "feature-filter-quadrant",
227
252
  className: "text-xs text-muted-foreground",
228
253
  children: "Quadrant"
229
- }), /* @__PURE__ */ E("select", {
254
+ }), /* @__PURE__ */ w("select", {
230
255
  id: "feature-filter-quadrant",
231
256
  "data-testid": "feature-filter-quadrant",
232
257
  className: "rounded-md border border-input bg-background px-2 py-1 text-xs",
233
- value: H,
234
- onChange: (e) => U(e.target.value),
235
- children: [/* @__PURE__ */ T("option", {
258
+ value: U,
259
+ onChange: (e) => W(e.target.value),
260
+ children: [/* @__PURE__ */ C("option", {
236
261
  value: "",
237
262
  children: "All"
238
- }), m.map((e) => /* @__PURE__ */ T("option", {
263
+ }), p.map((e) => /* @__PURE__ */ C("option", {
239
264
  value: e,
240
- children: p[e].label
265
+ children: f[e].label
241
266
  }, e))]
242
267
  })]
243
268
  })
244
269
  ]
245
270
  }),
246
- /* @__PURE__ */ T("div", {
271
+ /* @__PURE__ */ C("div", {
247
272
  className: "space-y-3",
248
- children: Z.map((e) => /* @__PURE__ */ T(k, {
249
- ...o(() => C(a("/products/feature-detail-page", e.id)), e.title),
250
- children: /* @__PURE__ */ T(A, {
273
+ children: Q.map((e) => /* @__PURE__ */ C(D, {
274
+ ...o(() => x(a("/products/feature-detail-page", e.id)), e.title),
275
+ children: /* @__PURE__ */ C(O, {
251
276
  className: "py-4",
252
- children: /* @__PURE__ */ E("div", {
277
+ children: /* @__PURE__ */ w("div", {
253
278
  className: "flex items-start justify-between gap-4",
254
- children: [/* @__PURE__ */ E("div", {
279
+ children: [/* @__PURE__ */ w("div", {
255
280
  className: "flex-1 space-y-2",
256
281
  children: [
257
- /* @__PURE__ */ E("div", {
282
+ /* @__PURE__ */ w("div", {
258
283
  className: "flex items-center gap-2",
259
284
  children: [
260
- /* @__PURE__ */ T("span", {
285
+ /* @__PURE__ */ C("span", {
261
286
  className: "text-sm font-medium",
262
287
  children: e.title
263
288
  }),
264
- /* @__PURE__ */ T("span", {
265
- className: `text-[10px] px-1.5 py-0.5 rounded-full capitalize ${M[e.status] ?? "bg-gray-100"}`,
289
+ /* @__PURE__ */ C("span", {
290
+ className: `text-[10px] px-1.5 py-0.5 rounded-full capitalize ${A[e.status] ?? "bg-gray-100"}`,
266
291
  children: e.status.toLowerCase().replace("_", " ")
267
292
  }),
268
- /* @__PURE__ */ T(h, {
293
+ /* @__PURE__ */ C(m, {
269
294
  quadrant: e.valueEffortQuadrant,
270
295
  hideWhenUnscored: !0
271
296
  })
272
297
  ]
273
298
  }),
274
- /* @__PURE__ */ T("p", {
299
+ /* @__PURE__ */ C("p", {
275
300
  className: "text-xs text-muted-foreground",
276
301
  children: e.description
277
302
  }),
278
- /* @__PURE__ */ E("div", {
303
+ /* @__PURE__ */ w("div", {
279
304
  className: "flex items-center gap-4 text-xs text-muted-foreground",
280
305
  children: [
281
- /* @__PURE__ */ E("span", { children: ["Effort: ", e.effortEstimate ?? "N/A"] }),
282
- /* @__PURE__ */ E("span", { children: ["Submitted by: ", /* @__PURE__ */ T(n, { id: e.submittedBy })] }),
283
- /* @__PURE__ */ T("span", { children: new Date(e.updatedAt).toLocaleDateString() })
306
+ /* @__PURE__ */ w("span", { children: ["Effort: ", e.effortEstimate ?? "N/A"] }),
307
+ /* @__PURE__ */ w("span", { children: ["Submitted by: ", /* @__PURE__ */ C(n, { id: e.submittedBy })] }),
308
+ /* @__PURE__ */ C("span", { children: new Date(e.updatedAt).toLocaleDateString() })
284
309
  ]
285
310
  }),
286
- c(e.snoozedUntil, I) && /* @__PURE__ */ E("p", {
311
+ c(e.snoozedUntil, P) && /* @__PURE__ */ w("p", {
287
312
  className: "text-xs text-muted-foreground",
288
313
  children: ["Snoozed until ", new Date(e.snoozedUntil).toLocaleDateString()]
289
314
  }),
290
- /* @__PURE__ */ T(s, {
315
+ /* @__PURE__ */ C(s, {
291
316
  feature: e,
292
- now: I
317
+ now: P
293
318
  }),
294
- e.tags.length > 0 && /* @__PURE__ */ T("div", {
319
+ e.tags.length > 0 && /* @__PURE__ */ C("div", {
295
320
  className: "flex gap-1",
296
- children: e.tags.map((e) => /* @__PURE__ */ T(D, {
321
+ children: e.tags.map((e) => /* @__PURE__ */ C(T, {
297
322
  variant: "outline",
298
323
  className: "text-[10px]",
299
324
  children: e
300
325
  }, e))
301
326
  })
302
327
  ]
303
- }), /* @__PURE__ */ E("div", {
328
+ }), /* @__PURE__ */ w("div", {
304
329
  className: "flex items-center gap-4 shrink-0",
305
330
  children: [
306
- /* @__PURE__ */ E("div", {
331
+ /* @__PURE__ */ w("div", {
307
332
  className: "text-center",
308
- children: [/* @__PURE__ */ E("div", {
333
+ children: [/* @__PURE__ */ w("div", {
309
334
  className: "flex items-center gap-1",
310
- children: [/* @__PURE__ */ T(S, { className: "h-3 w-3 text-primary" }), /* @__PURE__ */ T("span", {
335
+ children: [/* @__PURE__ */ C(re, { className: "h-3 w-3 text-primary" }), /* @__PURE__ */ C("span", {
311
336
  className: "text-sm font-bold",
312
337
  children: e.priorityScore ?? 0
313
338
  })]
314
- }), /* @__PURE__ */ T("p", {
339
+ }), /* @__PURE__ */ C("p", {
315
340
  className: "text-[10px] text-muted-foreground",
316
341
  children: "Priority"
317
342
  })]
318
343
  }),
319
- /* @__PURE__ */ E("div", {
344
+ /* @__PURE__ */ w("div", {
320
345
  className: "text-center",
321
- children: [/* @__PURE__ */ E("div", {
346
+ children: [/* @__PURE__ */ w("div", {
322
347
  className: "flex items-center gap-1",
323
- children: [/* @__PURE__ */ T(ee, { className: "h-3 w-3 text-green-500" }), /* @__PURE__ */ T("span", {
348
+ children: [/* @__PURE__ */ C(ie, { className: "h-3 w-3 text-green-500" }), /* @__PURE__ */ C("span", {
324
349
  className: "text-sm font-bold",
325
350
  children: e.voteCount
326
351
  })]
327
- }), /* @__PURE__ */ T("p", {
352
+ }), /* @__PURE__ */ C("p", {
328
353
  className: "text-[10px] text-muted-foreground",
329
354
  children: "Votes"
330
355
  })]
331
356
  }),
332
- /* @__PURE__ */ E("div", {
357
+ /* @__PURE__ */ w("div", {
333
358
  className: "text-center",
334
- children: [/* @__PURE__ */ E("div", {
359
+ children: [/* @__PURE__ */ w("div", {
335
360
  className: "flex items-center gap-1",
336
- children: [/* @__PURE__ */ T(x, { className: "h-3 w-3 text-yellow-500" }), /* @__PURE__ */ T("span", {
361
+ children: [/* @__PURE__ */ C(b, { className: "h-3 w-3 text-yellow-500" }), /* @__PURE__ */ C("span", {
337
362
  className: "text-sm font-bold",
338
363
  children: e.businessValue ?? 0
339
364
  })]
340
- }), /* @__PURE__ */ T("p", {
365
+ }), /* @__PURE__ */ C("p", {
341
366
  className: "text-[10px] text-muted-foreground",
342
367
  children: "Value"
343
368
  })]
369
+ }),
370
+ /* @__PURE__ */ w("div", {
371
+ className: "text-center",
372
+ title: e.riceScore == null ? h(e) ?? void 0 : void 0,
373
+ children: [/* @__PURE__ */ w("div", {
374
+ className: "flex items-center gap-1",
375
+ children: [/* @__PURE__ */ C(_, { className: "h-3 w-3 text-primary" }), /* @__PURE__ */ C("span", {
376
+ className: "text-sm font-bold",
377
+ "data-testid": "feature-rice-score-cell",
378
+ children: e.riceScore == null ? "—" : e.riceScore.toFixed(1)
379
+ })]
380
+ }), /* @__PURE__ */ C("p", {
381
+ className: "text-[10px] text-muted-foreground",
382
+ children: "RICE"
383
+ })]
344
384
  })
345
385
  ]
346
386
  })]
@@ -348,14 +388,14 @@ function N() {
348
388
  })
349
389
  }, e.id))
350
390
  }),
351
- Z.length === 0 && /* @__PURE__ */ T("p", {
391
+ Q.length === 0 && /* @__PURE__ */ C("p", {
352
392
  className: "text-center text-muted-foreground py-8",
353
- children: P === "TRIAGE" ? "Nothing waits for triage." : "No features match the selected filters."
393
+ children: M === "TRIAGE" ? "Nothing waits for triage." : "No features match the selected filters."
354
394
  })
355
395
  ]
356
396
  });
357
397
  }
358
398
  //#endregion
359
- export { N as ProductsFeaturesPagePage };
399
+ export { j as ProductsFeaturesPagePage };
360
400
 
361
401
  //# sourceMappingURL=ProductsFeaturesPagePage.js.map