@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.
- package/dist/planmagnet/controls/CompetitorControls.d.ts +71 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/CompetitorControls.js +346 -12
- package/dist/planmagnet/controls/CompetitorControls.js.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts +5 -0
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +363 -218
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +15 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +61 -1
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PrioritizationControls.d.ts +36 -0
- package/dist/planmagnet/controls/PrioritizationControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/PrioritizationControls.js +118 -1
- package/dist/planmagnet/controls/PrioritizationControls.js.map +1 -1
- package/dist/planmagnet/dashboards/DashboardUI.js +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +67 -59
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -1
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +1 -1
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +289 -282
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +126 -63
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +14 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +93 -57
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +125 -83
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +226 -126
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +162 -122
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +135 -109
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +15 -3
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +59 -53
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/MetricsCard.d.ts +8 -0
- package/dist/planmagnet/pages/analytics/MetricsCard.d.ts.map +1 -0
- package/dist/planmagnet/pages/analytics/MetricsCard.js +164 -0
- package/dist/planmagnet/pages/analytics/MetricsCard.js.map +1 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts +2 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts.map +1 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.js +155 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.js.map +1 -0
- 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":"
|
|
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
|
|
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
|
|
9
|
-
import { gql as
|
|
10
|
-
import { useQuery as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { Badge as
|
|
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
|
|
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
|
-
`,
|
|
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
|
|
59
|
-
let
|
|
60
|
-
...
|
|
61
|
-
...
|
|
62
|
-
...
|
|
63
|
-
...
|
|
64
|
-
}, { data:
|
|
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:
|
|
67
|
-
filter: Object.keys(
|
|
71
|
+
workspaceId: j,
|
|
72
|
+
filter: Object.keys(K).length > 0 ? K : void 0,
|
|
68
73
|
pagination: { first: 50 },
|
|
69
|
-
orderBy:
|
|
74
|
+
orderBy: F
|
|
70
75
|
},
|
|
71
|
-
skip: !
|
|
76
|
+
skip: !j
|
|
72
77
|
});
|
|
73
|
-
if (
|
|
78
|
+
if (J && !q) return /* @__PURE__ */ C("div", {
|
|
74
79
|
className: "flex items-center justify-center p-12",
|
|
75
|
-
children: /* @__PURE__ */
|
|
80
|
+
children: /* @__PURE__ */ C(v, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
76
81
|
});
|
|
77
|
-
if (
|
|
82
|
+
if (Y) return /* @__PURE__ */ w("div", {
|
|
78
83
|
className: "p-6 text-destructive",
|
|
79
|
-
children: ["Failed to load features: ",
|
|
84
|
+
children: ["Failed to load features: ", Y.message]
|
|
80
85
|
});
|
|
81
|
-
let
|
|
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(
|
|
94
|
-
open: r(
|
|
95
|
-
inProgress: r(
|
|
96
|
-
completed: r(
|
|
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__ */
|
|
103
|
+
return /* @__PURE__ */ w("div", {
|
|
99
104
|
className: "space-y-6 p-6",
|
|
100
105
|
children: [
|
|
101
|
-
/* @__PURE__ */
|
|
106
|
+
/* @__PURE__ */ w("div", {
|
|
102
107
|
className: "flex items-center justify-between",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
108
|
+
children: [/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("h1", {
|
|
104
109
|
className: "text-2xl font-semibold",
|
|
105
110
|
children: "Features"
|
|
106
|
-
}), /* @__PURE__ */
|
|
111
|
+
}), /* @__PURE__ */ C("p", {
|
|
107
112
|
className: "text-sm text-muted-foreground",
|
|
108
|
-
children:
|
|
109
|
-
})] }), /* @__PURE__ */ E
|
|
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: () =>
|
|
112
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
120
|
+
/* @__PURE__ */ w("div", {
|
|
116
121
|
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
117
122
|
children: [
|
|
118
|
-
/* @__PURE__ */
|
|
123
|
+
/* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
|
|
119
124
|
className: "pt-4",
|
|
120
|
-
children: [/* @__PURE__ */
|
|
125
|
+
children: [/* @__PURE__ */ C("p", {
|
|
121
126
|
className: "text-xs text-muted-foreground",
|
|
122
127
|
children: "Total Features"
|
|
123
|
-
}), /* @__PURE__ */
|
|
128
|
+
}), /* @__PURE__ */ C("p", {
|
|
124
129
|
className: "text-xl font-bold",
|
|
125
130
|
children: $.total
|
|
126
131
|
})]
|
|
127
132
|
}) }),
|
|
128
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
|
|
129
134
|
className: "pt-4",
|
|
130
|
-
children: [/* @__PURE__ */
|
|
135
|
+
children: [/* @__PURE__ */ C("p", {
|
|
131
136
|
className: "text-xs text-muted-foreground",
|
|
132
137
|
children: "In Progress"
|
|
133
|
-
}), /* @__PURE__ */
|
|
138
|
+
}), /* @__PURE__ */ C("p", {
|
|
134
139
|
className: "text-xl font-bold",
|
|
135
140
|
children: $.inProgress
|
|
136
141
|
})]
|
|
137
142
|
}) }),
|
|
138
|
-
/* @__PURE__ */
|
|
143
|
+
/* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
|
|
139
144
|
className: "pt-4",
|
|
140
|
-
children: [/* @__PURE__ */
|
|
145
|
+
children: [/* @__PURE__ */ C("p", {
|
|
141
146
|
className: "text-xs text-muted-foreground",
|
|
142
147
|
children: "Completed"
|
|
143
|
-
}), /* @__PURE__ */
|
|
148
|
+
}), /* @__PURE__ */ C("p", {
|
|
144
149
|
className: "text-xl font-bold",
|
|
145
150
|
children: $.completed
|
|
146
151
|
})]
|
|
147
152
|
}) }),
|
|
148
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ C(D, { children: /* @__PURE__ */ w(O, {
|
|
149
154
|
className: "pt-4",
|
|
150
|
-
children: [/* @__PURE__ */
|
|
155
|
+
children: [/* @__PURE__ */ C("p", {
|
|
151
156
|
className: "text-xs text-muted-foreground",
|
|
152
157
|
children: "Open"
|
|
153
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
165
|
+
/* @__PURE__ */ w("div", {
|
|
161
166
|
className: "flex items-center justify-between flex-wrap gap-2",
|
|
162
|
-
children: [/* @__PURE__ */
|
|
167
|
+
children: [/* @__PURE__ */ w("div", {
|
|
163
168
|
className: "flex items-center gap-2",
|
|
164
|
-
children: [/* @__PURE__ */
|
|
165
|
-
onClick: () =>
|
|
166
|
-
className: `px-2 py-1 text-xs rounded-md capitalize ${
|
|
167
|
-
children: e === "all" ? "All" : e === "TRIAGE" ? `Triage (${r(
|
|
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__ */
|
|
174
|
+
}), /* @__PURE__ */ w("div", {
|
|
170
175
|
className: "flex items-center gap-2",
|
|
171
|
-
children: [
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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__ */
|
|
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__ */
|
|
206
|
+
/* @__PURE__ */ w("div", {
|
|
182
207
|
className: "flex items-center gap-1",
|
|
183
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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:
|
|
192
|
-
onChange: (e) =>
|
|
193
|
-
children: [/* @__PURE__ */
|
|
216
|
+
value: z,
|
|
217
|
+
onChange: (e) => B(e.target.value),
|
|
218
|
+
children: [/* @__PURE__ */ C("option", {
|
|
194
219
|
value: "",
|
|
195
220
|
children: "All"
|
|
196
|
-
}),
|
|
221
|
+
}), d.map((e) => /* @__PURE__ */ C("option", {
|
|
197
222
|
value: e,
|
|
198
|
-
children:
|
|
223
|
+
children: ee[e].label
|
|
199
224
|
}, e))]
|
|
200
225
|
})]
|
|
201
226
|
}),
|
|
202
|
-
/* @__PURE__ */
|
|
227
|
+
/* @__PURE__ */ w("div", {
|
|
203
228
|
className: "flex items-center gap-1",
|
|
204
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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:
|
|
213
|
-
onChange: (e) =>
|
|
214
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
248
|
+
/* @__PURE__ */ w("div", {
|
|
224
249
|
className: "flex items-center gap-1",
|
|
225
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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:
|
|
234
|
-
onChange: (e) =>
|
|
235
|
-
children: [/* @__PURE__ */
|
|
258
|
+
value: U,
|
|
259
|
+
onChange: (e) => W(e.target.value),
|
|
260
|
+
children: [/* @__PURE__ */ C("option", {
|
|
236
261
|
value: "",
|
|
237
262
|
children: "All"
|
|
238
|
-
}),
|
|
263
|
+
}), p.map((e) => /* @__PURE__ */ C("option", {
|
|
239
264
|
value: e,
|
|
240
|
-
children:
|
|
265
|
+
children: f[e].label
|
|
241
266
|
}, e))]
|
|
242
267
|
})]
|
|
243
268
|
})
|
|
244
269
|
]
|
|
245
270
|
}),
|
|
246
|
-
/* @__PURE__ */
|
|
271
|
+
/* @__PURE__ */ C("div", {
|
|
247
272
|
className: "space-y-3",
|
|
248
|
-
children:
|
|
249
|
-
...o(() =>
|
|
250
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
277
|
+
children: /* @__PURE__ */ w("div", {
|
|
253
278
|
className: "flex items-start justify-between gap-4",
|
|
254
|
-
children: [/* @__PURE__ */
|
|
279
|
+
children: [/* @__PURE__ */ w("div", {
|
|
255
280
|
className: "flex-1 space-y-2",
|
|
256
281
|
children: [
|
|
257
|
-
/* @__PURE__ */
|
|
282
|
+
/* @__PURE__ */ w("div", {
|
|
258
283
|
className: "flex items-center gap-2",
|
|
259
284
|
children: [
|
|
260
|
-
/* @__PURE__ */
|
|
285
|
+
/* @__PURE__ */ C("span", {
|
|
261
286
|
className: "text-sm font-medium",
|
|
262
287
|
children: e.title
|
|
263
288
|
}),
|
|
264
|
-
/* @__PURE__ */
|
|
265
|
-
className: `text-[10px] px-1.5 py-0.5 rounded-full capitalize ${
|
|
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__ */
|
|
293
|
+
/* @__PURE__ */ C(m, {
|
|
269
294
|
quadrant: e.valueEffortQuadrant,
|
|
270
295
|
hideWhenUnscored: !0
|
|
271
296
|
})
|
|
272
297
|
]
|
|
273
298
|
}),
|
|
274
|
-
/* @__PURE__ */
|
|
299
|
+
/* @__PURE__ */ C("p", {
|
|
275
300
|
className: "text-xs text-muted-foreground",
|
|
276
301
|
children: e.description
|
|
277
302
|
}),
|
|
278
|
-
/* @__PURE__ */
|
|
303
|
+
/* @__PURE__ */ w("div", {
|
|
279
304
|
className: "flex items-center gap-4 text-xs text-muted-foreground",
|
|
280
305
|
children: [
|
|
281
|
-
/* @__PURE__ */
|
|
282
|
-
/* @__PURE__ */
|
|
283
|
-
/* @__PURE__ */
|
|
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,
|
|
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__ */
|
|
315
|
+
/* @__PURE__ */ C(s, {
|
|
291
316
|
feature: e,
|
|
292
|
-
now:
|
|
317
|
+
now: P
|
|
293
318
|
}),
|
|
294
|
-
e.tags.length > 0 && /* @__PURE__ */
|
|
319
|
+
e.tags.length > 0 && /* @__PURE__ */ C("div", {
|
|
295
320
|
className: "flex gap-1",
|
|
296
|
-
children: e.tags.map((e) => /* @__PURE__ */ T
|
|
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__ */
|
|
328
|
+
}), /* @__PURE__ */ w("div", {
|
|
304
329
|
className: "flex items-center gap-4 shrink-0",
|
|
305
330
|
children: [
|
|
306
|
-
/* @__PURE__ */
|
|
331
|
+
/* @__PURE__ */ w("div", {
|
|
307
332
|
className: "text-center",
|
|
308
|
-
children: [/* @__PURE__ */
|
|
333
|
+
children: [/* @__PURE__ */ w("div", {
|
|
309
334
|
className: "flex items-center gap-1",
|
|
310
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
339
|
+
}), /* @__PURE__ */ C("p", {
|
|
315
340
|
className: "text-[10px] text-muted-foreground",
|
|
316
341
|
children: "Priority"
|
|
317
342
|
})]
|
|
318
343
|
}),
|
|
319
|
-
/* @__PURE__ */
|
|
344
|
+
/* @__PURE__ */ w("div", {
|
|
320
345
|
className: "text-center",
|
|
321
|
-
children: [/* @__PURE__ */
|
|
346
|
+
children: [/* @__PURE__ */ w("div", {
|
|
322
347
|
className: "flex items-center gap-1",
|
|
323
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
352
|
+
}), /* @__PURE__ */ C("p", {
|
|
328
353
|
className: "text-[10px] text-muted-foreground",
|
|
329
354
|
children: "Votes"
|
|
330
355
|
})]
|
|
331
356
|
}),
|
|
332
|
-
/* @__PURE__ */
|
|
357
|
+
/* @__PURE__ */ w("div", {
|
|
333
358
|
className: "text-center",
|
|
334
|
-
children: [/* @__PURE__ */
|
|
359
|
+
children: [/* @__PURE__ */ w("div", {
|
|
335
360
|
className: "flex items-center gap-1",
|
|
336
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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
|
-
|
|
391
|
+
Q.length === 0 && /* @__PURE__ */ C("p", {
|
|
352
392
|
className: "text-center text-muted-foreground py-8",
|
|
353
|
-
children:
|
|
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 {
|
|
399
|
+
export { j as ProductsFeaturesPagePage };
|
|
360
400
|
|
|
361
401
|
//# sourceMappingURL=ProductsFeaturesPagePage.js.map
|