@burdenoff/microfe-planmagnet 2026.1001.5 → 2026.1001.7
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/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +10 -6
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/automation/automationRuleTypes.d.ts +31 -19
- package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -1
- package/dist/planmagnet/automation/automationRuleTypes.js +4 -8
- package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -1
- 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 +23 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +431 -229
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/FeedbackControls.d.ts +70 -0
- package/dist/planmagnet/controls/FeedbackControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeedbackControls.js +153 -0
- package/dist/planmagnet/controls/FeedbackControls.js.map +1 -0
- 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/controls/SyncControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/SyncControls.js +1 -0
- package/dist/planmagnet/controls/SyncControls.js.map +1 -1
- package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts +47 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.js +542 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.js.map +1 -0
- package/dist/planmagnet/dashboards/DashboardUI.js +8 -8
- package/dist/planmagnet/dashboards/cards/RiskSeverityCard.js +4 -4
- 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/forecast/DeliveryForecastCard.js +8 -8
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +10 -10
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +9 -9
- package/dist/planmagnet/forecast/ResourceForecastCard.js +6 -6
- package/dist/planmagnet/integrations/syncTypes.d.ts +2 -1
- package/dist/planmagnet/integrations/syncTypes.d.ts.map +1 -1
- package/dist/planmagnet/integrations/syncTypes.js +19 -9
- package/dist/planmagnet/integrations/syncTypes.js.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +290 -283
- 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/DashboardDashboardPagePage.js +23 -23
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +340 -248
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js +172 -167
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +92 -83
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js +175 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js.map +1 -0
- 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/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +151 -126
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +169 -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 +165 -109
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +13 -13
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +9 -9
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +8 -8
- 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/ProjectsProjectsPagePage.js +12 -12
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +23 -23
- package/dist/planmagnet/pages/TasksMyDashboardPagePage.js +18 -18
- package/dist/planmagnet/pages/TasksMyWorkPagePage.js +10 -10
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +5 -5
- 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/DeliveryHealthCards.js +8 -8
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +6 -6
- 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/pages/analytics/QualityMetricsCard.js +6 -6
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +8 -8
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +7 -7
- package/dist/planmagnet/pages/analytics/ThroughputCard.js +7 -7
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +7 -7
- package/dist/planmagnet/people/People.d.ts +14 -0
- package/dist/planmagnet/people/People.d.ts.map +1 -1
- package/dist/planmagnet/people/People.js +36 -22
- package/dist/planmagnet/people/People.js.map +1 -1
- 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/dist/planmagnet/webhooks/webhookTypes.d.ts +54 -0
- package/dist/planmagnet/webhooks/webhookTypes.d.ts.map +1 -0
- package/dist/planmagnet/webhooks/webhookTypes.js +17 -0
- package/dist/planmagnet/webhooks/webhookTypes.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":";;;;;;;;;;;;;AAYA,IAAM,IAAgB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BnB,IAAyB,CAAG;;;;;;;;;;;;;;;GAqB5B,IAAuB,CAAG;;;;;;;;;;;;;;;;AA8BhC,SAAS,EAAW,EAClB,UACA,SACA,kBAKC;AACD,QACE,kBAAC,GAAD;EACS;EACP,OAAO,KAAQ,OAAO,MAAM,GAAG,KAAK,MAAM,IAAO,IAAI,CAAC;EACtD,MAAM,KAAQ,OAAO,IAAc,KAAA;EACnC,CAAA;;AAsBN,SAAgB,IAAoC;CAClD,IAAM,IAAW,GAAuB,EAElC,IADS,GAA2B,CACf,MAAM,IAE3B,EAAE,SAAM,YAAS,aAAU,EAkB9B,GAAe;EAAE,WAAW,EAAE,IAAI,GAAa;EAAE,MAAM,CAAC;EAAa,CAAC,EAEnE,EAAE,MAAM,MAAiB,EAC7B,GACA;EAAE,WAAW,EAAE,gBAAa;EAAE,MAAM,CAAC;EAAa,CACnD,EAEK,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAED,GAAsB;EAAE,WAAW,EAAE,gBAAa;EAAE,MAAM,CAAC;EAAa,CAAC;AAI5E,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAIV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAsC,8BAA2B,EAAM,QAAc;;CAG9F,IAAM,IAAY,GAAM;AACxB,KAAI,CAAC,EAAW,QAAO,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAA0B,CAAA;CAE5F,IAAM,IAAW,GAAc,+BAA+B,MACxD,IAAS,GAAY,6BAA6B;AAExD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA;MAC9C,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAU;OAAU,CAAA;MAC5D,kBAAC,GAAD;OAAO,SAAQ;OAAU,WAAU;iBAChC,EAAU,EAAU,OAAO;OACtB,CAAA;MACJ;QACL,EAAU,aACT,kBAAC,KAAD;KAAG,WAAU;eACV,EAAU,cAAc,WACpB,EAAU,kBAAkB,qBAC7B,EAAU,EAAU,UAAU;KAChC,CAAA,CAEF,EAAA,CAAA,EACN,kBAAC,GAAD;KACE,aAAa,EAAU;KACvB,MAAM,EAAU;KAChB,QAAQ,EAAU;KAClB,CAAA,CACE;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAW,WAAU,gCAAiC,CAAA;OACtD,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAU,iBAAiB,EAAU,SAAS;QAC7C,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAY,CAAA;OAC7C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA;OACnD,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAU,iBAAiB,EAAU,SAAS;QAC7C,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAY,CAAA;OAC7C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAe,WAAU,oCAAqC,CAAA;OAC9D,kBAAC,KAAD;QAAG,WAAU;kBAA2B,GAAU,eAAe;QAAM,CAAA;OACvE,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAW,CAAA;OAC5C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAO,WAAU,4CAA6C,CAAA;OAC9D,kBAAC,KAAD;QAAG,WAAU;kBAA2B,GAAU,qBAAqB;QAAM,CAAA;OAC7E,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAsB,CAAA;OACvD;SACT,CAAA;KACH;;GAKN,kBAAC,GAAD;IAAM,eAAY;cAAlB,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAW,WAAU;eAArB,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,mBAEvB;QACZ,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAEzC,CAAA,CACO,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eACpB,KAAiB,CAAC,IACjB,kBAAC,KAAD;MAAG,WAAU;gBAAgC;MAAY,CAAA,GACvD,IACF,kBAAC,KAAD;MAAG,WAAU;gBAAb,CAA8C,gCACf,EAAY,QACvC;UACD,IACH,kBAAA,GAAA,EAAA,UAAA;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAU,OAAM;QAAiB,OAAO,OAAO,EAAO,cAAc;QAAI,CAAA,EACxE,kBAAC,GAAD;QACE,OAAM;QACN,OACE,OAAO,KAAK,EAAO,iBAAiB,CAAC,WAAW,IAC5C,MACA,OAAO,QAAQ,EAAO,iBAAiB,CACpC,KAAK,CAAC,GAAQ,OAAW,GAAG,EAAU,EAAO,CAAC,IAAI,IAAQ,CAC1D,KAAK,KAAK;QAEnB,MACE,EAAO,kBAAkB,IAAI,uCAAuC,KAAA;QAEtE,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SACE,OAAM;SACN,OAAO,EAAO,eAAe,OAAO,MAAM,EAAO,YAAY,gBAAgB;SAC7E,MAAM,EAAO,eAAe,OAAO,qCAAqC,KAAA;SACxE,CAAA;QACF,kBAAC,GAAD;SACE,OAAM;SACN,OAAO,EAAO,cAAc,OAAO,MAAM,EAAO,WAAW,gBAAgB;SAC3E,MAAM,EAAO,cAAc,OAAO,qCAAqC,KAAA;SACvE,CAAA;QACF,kBAAC,GAAD;SACE,OAAM;SACN,OACE,EAAO,qBAAqB,OACxB,MACA,GAAG,EAAO,kBAAkB,QAAQ,EAAE,CAAC;SAE7C,MACE,EAAO,qBAAqB,OACxB,uCACA,KAAA;SAEN,CAAA;QACE;;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA2B;OAEpC,CAAA,EACL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SACE,OAAM;SACN,MAAM,EAAO;SACb,aAAY;SACZ,CAAA;QACF,kBAAC,GAAD;SACE,OAAM;SACN,MAAM,EAAO;SACb,aAAY;SACZ,CAAA;QACF,kBAAC,GAAD;SACE,OAAM;SACN,MAAM,EAAO;SACb,aAAY;SACZ,CAAA;QACE;SACF,EAAA,CAAA;MACL,EAAA,CAAA,GAjES;KAmEF,CAAA,CACT;;GAEP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAW,EAAU,eAAe;QAA+B,CAAA,EAC/E,IACC,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAsB,CAAA,EACnE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAS,qBAAqB,OAE3B,mBADA,GAAG,EAAS,kBAAkB,QAAQ,EAAE,CAAC;WAE3C,CAAA,CACA;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAgB,CAAA,EAC7D,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAS,eAAe,OAErB,oBADA,EAAS,YAAY,QAAQ,EAAE;WAEjC,CAAA,CACA;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAe,CAAA,EAC5D,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAS,cAAc,OAA8C,QAAvC,EAAS,WAAW,gBAAgB;WACjE,CAAA,CACA;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAgC;WAAsB,CAAA,EACnE,kBAAC,KAAD;WAAG,WAAU;qBACV,OAAO,KAAK,EAAS,iBAAiB,CAAC,SAAS,IAC7C,OAAO,QAAQ,EAAS,iBAAiB,CACtC,KAAK,CAAC,GAAQ,OAAW,GAAG,EAAU,EAAO,CAAC,IAAI,IAAQ,CAC1D,KAAK,KAAK,GACb;WACF,CAAA,CACA;;SACF;YAEN,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAAqB,CAAA,CAExD;SACT,EAAA,CAAA;MAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;OAAW;OAAW,EAAU,SAAS;OAAO;OAAa,EAAA,CAAA,EAClD,CAAA,EACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACG,EAAU,SAAS,WAAW,KAC7B,kBAAC,KAAD;QAAG,WAAU;kBAAgC;QAEzC,CAAA,EAEL,EAAU,SAAS,KAAK,MACvB,kBAAC,UAAD;QAEE,MAAK;QACL,WAAU;QACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;kBAJlF,CAME,kBAAC,QAAD;SAAM,WAAU;mBAAuB,EAAQ;SAAY,CAAA,EAC3D,kBAAC,GAAD;SAAO,SAAQ;SAAU,WAAU;mBAChC,EAAU,EAAQ,OAAO;SACpB,CAAA,CACD;UATF,EAAQ,GASN,CACT,CACU;SACT,EAAA,CAAA;MAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;OAAW;OAAW,EAAU,SAAS;OAAO;OAAa,EAAA,CAAA,EAClD,CAAA,EACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACG,EAAU,SAAS,WAAW,KAC7B,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAEzC,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAA6C;UACgB;UAC3D,kBAAC,GAAD;WACE,SAAQ;WACR,WAAU;WACV,eAAe,EAAS,0BAA0B;qBACnD;WAEQ,CAAA;UACP;WACA;WAEP,EAAU,SAAS,KAAK,MACvB,kBAAC,UAAD;QAEE,MAAK;QACL,WAAU;QACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;kBAJlF,CAME,kBAAC,QAAD;SAAM,WAAU;mBAAuB,EAAQ;SAAY,CAAA,EAC3D,kBAAC,GAAD;SAAO,SAAQ;SAAU,WAAU;mBAChC,EAAU,EAAQ,OAAO;SACpB,CAAA,CACD;UATF,EAAQ,GASN,CACT,CACU;SACT,EAAA,CAAA;MACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA,EACnB,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAY,CAAA,EACpD,kBAAC,GAAD,EAAY,IAAI,EAAU,SAAW,CAAA,CACjC;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAa,CAAA,EACrD,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAU,eAAe,OAEtB,YADA,GAAG,EAAU,YAAY,GAAG,GAAG,EAAU,YAAY,gBAAgB,GAAG,MAAM;SAE7E,CAAA,CACH;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAoB,CAAA,EAC5D,kBAAC,QAAD,EAAA,UAAO,EAAU,gBAAgB,WAAiB,CAAA,CAC9C;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB,EAAQ,CAAA,CAC7D;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwB;SAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB,EAAQ,CAAA,CAC7D;;OACM;QACT,EAAA,CAAA,EAEN,EAAU,KAAK,SAAS,KACvB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,QAAgB,CAAA,EAChB,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBACpB,EAAU,KAAK,KAAK,MACnB,kBAAC,GAAD;OAAiB,SAAQ;OAAU,WAAU;iBAC1C;OACK,EAFI,EAEJ,CACR;MACU,CAAA,CACT,EAAA,CAAA,CAEL;OACF;;GACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsFeatureDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsFeatureDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductsFeatureDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsFeatureDetailPagePage.tsx"],"names":[],"mappings":"AAqGA,wBAAgB,6BAA6B,4CA0R5C"}
|
|
@@ -3,17 +3,18 @@ import { PersonName as n } from "../people/People.js";
|
|
|
3
3
|
import { detailPath as r } from "../utils/rowNavigation.js";
|
|
4
4
|
import { CompetitorLinkCard as i } from "../controls/CompetitorControls.js";
|
|
5
5
|
import { FeatureTriageActions as a, isOpenRequest as o, isSnoozed as s } from "../controls/FeatureTriage.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
import { CategoryBadge as c, RatingDisplay as l } from "../controls/FeedbackControls.js";
|
|
7
|
+
import { FeatureHeaderActions as u, FeaturePrioritizationCard as d, FeatureVoteButton as f } from "../controls/FeatureControls.js";
|
|
8
|
+
import { FeatureInitiativeCard as p } from "../roadmaps/FeatureInitiativeCard.js";
|
|
9
|
+
import { useState as m } from "react";
|
|
10
|
+
import { Loader2 as h, MessageSquare as g, Star as _, Target as v, ThumbsUp as y } from "lucide-react";
|
|
11
|
+
import { useParams as b } from "react-router";
|
|
12
|
+
import { gql as x } from "@apollo/client";
|
|
13
|
+
import { useQuery as S } from "@apollo/client/react";
|
|
14
|
+
import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
15
|
+
import { Badge as E, Card as D, CardContent as O, CardHeader as k, CardTitle as A } from "@burdenoff/fe-libs/ui";
|
|
15
16
|
//#region src/planmagnet/pages/ProductsFeatureDetailPagePage.tsx
|
|
16
|
-
var
|
|
17
|
+
var j = x`
|
|
17
18
|
query GetFeatureRequest($id: ID!) {
|
|
18
19
|
planmagnetFeatureRequest(id: $id) {
|
|
19
20
|
id
|
|
@@ -30,11 +31,18 @@ var k = y`
|
|
|
30
31
|
workItemId
|
|
31
32
|
tags
|
|
32
33
|
snoozedUntil
|
|
34
|
+
rating
|
|
35
|
+
category
|
|
36
|
+
isAnonymous
|
|
33
37
|
moscow
|
|
34
38
|
kano
|
|
35
39
|
valueScore
|
|
36
40
|
effortScore
|
|
37
41
|
valueEffortQuadrant
|
|
42
|
+
reach
|
|
43
|
+
impact
|
|
44
|
+
confidence
|
|
45
|
+
riceScore
|
|
38
46
|
declineReason
|
|
39
47
|
duplicateOfId
|
|
40
48
|
duplicateOf {
|
|
@@ -70,88 +78,88 @@ var k = y`
|
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
`;
|
|
73
|
-
function
|
|
74
|
-
let { workspaceId:
|
|
75
|
-
variables: { id:
|
|
76
|
-
skip: !
|
|
81
|
+
function M() {
|
|
82
|
+
let { workspaceId: x, currentUser: M } = e(), N = t(), [P] = m(() => Date.now()), F = b().id ?? "", { data: I, loading: L, error: R } = S(j, {
|
|
83
|
+
variables: { id: F },
|
|
84
|
+
skip: !F
|
|
77
85
|
});
|
|
78
|
-
if (
|
|
86
|
+
if (L && !I) return /* @__PURE__ */ w("div", {
|
|
79
87
|
className: "flex items-center justify-center p-12",
|
|
80
|
-
children: /* @__PURE__ */
|
|
88
|
+
children: /* @__PURE__ */ w(h, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
81
89
|
});
|
|
82
|
-
if (
|
|
90
|
+
if (R) return /* @__PURE__ */ T("div", {
|
|
83
91
|
className: "p-6 text-destructive",
|
|
84
|
-
children: ["Failed to load feature: ",
|
|
92
|
+
children: ["Failed to load feature: ", R.message]
|
|
85
93
|
});
|
|
86
|
-
let
|
|
87
|
-
if (!
|
|
94
|
+
let z = I?.planmagnetFeatureRequest;
|
|
95
|
+
if (!z) return /* @__PURE__ */ w("div", {
|
|
88
96
|
className: "p-6 text-muted-foreground",
|
|
89
97
|
children: "Feature not found."
|
|
90
98
|
});
|
|
91
|
-
let
|
|
92
|
-
return /* @__PURE__ */
|
|
99
|
+
let B = z.votes ?? [], V = z.competitors ?? [];
|
|
100
|
+
return /* @__PURE__ */ T("div", {
|
|
93
101
|
className: "space-y-6 p-6",
|
|
94
102
|
children: [
|
|
95
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ T("div", {
|
|
96
104
|
className: "flex items-center justify-between",
|
|
97
|
-
children: [/* @__PURE__ */
|
|
105
|
+
children: [/* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("h1", {
|
|
98
106
|
className: "text-2xl font-semibold",
|
|
99
|
-
children:
|
|
100
|
-
}), /* @__PURE__ */
|
|
107
|
+
children: z.title
|
|
108
|
+
}), /* @__PURE__ */ T("p", {
|
|
101
109
|
className: "text-sm text-muted-foreground",
|
|
102
110
|
children: [
|
|
103
111
|
"Submitted by: ",
|
|
104
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ w(n, { id: z.submittedBy }),
|
|
105
113
|
" · Effort:",
|
|
106
114
|
" ",
|
|
107
|
-
|
|
115
|
+
z.effortEstimate ?? "N/A"
|
|
108
116
|
]
|
|
109
|
-
})] }), /* @__PURE__ */
|
|
117
|
+
})] }), /* @__PURE__ */ T("div", {
|
|
110
118
|
className: "flex flex-col items-end gap-2",
|
|
111
|
-
children: [/* @__PURE__ */
|
|
112
|
-
featureId:
|
|
113
|
-
voted:
|
|
114
|
-
}), /* @__PURE__ */
|
|
119
|
+
children: [/* @__PURE__ */ w(f, {
|
|
120
|
+
featureId: z.id,
|
|
121
|
+
voted: B.some((e) => e.voterId === M?.id)
|
|
122
|
+
}, z.id), /* @__PURE__ */ w(u, { feature: z })]
|
|
115
123
|
})]
|
|
116
124
|
}),
|
|
117
|
-
o(
|
|
125
|
+
o(z.status) && /* @__PURE__ */ T(D, {
|
|
118
126
|
"data-testid": "triage-card",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
127
|
+
children: [/* @__PURE__ */ w(k, { children: /* @__PURE__ */ w(A, { children: "Triage" }) }), /* @__PURE__ */ T(O, {
|
|
120
128
|
className: "space-y-2",
|
|
121
|
-
children: [/* @__PURE__ */
|
|
129
|
+
children: [/* @__PURE__ */ w("p", {
|
|
122
130
|
className: "text-sm text-muted-foreground",
|
|
123
|
-
children: s(
|
|
124
|
-
}), /* @__PURE__ */
|
|
125
|
-
feature:
|
|
126
|
-
now:
|
|
131
|
+
children: s(z.snoozedUntil, P) ? `Snoozed until ${new Date(z.snoozedUntil).toLocaleString()} — it returns to the triage queue then.` : z.status === "SUBMITTED" ? "Waiting for a decision." : "Under review: accept it as planned, decline it, or merge it into its original."
|
|
132
|
+
}), /* @__PURE__ */ w(a, {
|
|
133
|
+
feature: z,
|
|
134
|
+
now: P
|
|
127
135
|
})]
|
|
128
136
|
})]
|
|
129
137
|
}),
|
|
130
|
-
|
|
138
|
+
z.status === "DECLINED" && z.declineReason && /* @__PURE__ */ w(D, {
|
|
131
139
|
"data-testid": "decline-reason",
|
|
132
|
-
children: /* @__PURE__ */
|
|
140
|
+
children: /* @__PURE__ */ T(O, {
|
|
133
141
|
className: "pt-4 text-sm",
|
|
134
|
-
children: [/* @__PURE__ */
|
|
142
|
+
children: [/* @__PURE__ */ w("span", {
|
|
135
143
|
className: "font-medium",
|
|
136
144
|
children: "Declined: "
|
|
137
|
-
}),
|
|
145
|
+
}), z.declineReason]
|
|
138
146
|
})
|
|
139
147
|
}),
|
|
140
|
-
|
|
148
|
+
z.status === "DUPLICATE" && z.duplicateOfId && /* @__PURE__ */ w(D, {
|
|
141
149
|
"data-testid": "duplicate-of",
|
|
142
|
-
children: /* @__PURE__ */
|
|
150
|
+
children: /* @__PURE__ */ T(O, {
|
|
143
151
|
className: "pt-4 text-sm",
|
|
144
152
|
children: [
|
|
145
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ w("span", {
|
|
146
154
|
className: "font-medium",
|
|
147
155
|
children: "Duplicate of "
|
|
148
156
|
}),
|
|
149
|
-
|
|
157
|
+
z.duplicateOf ? /* @__PURE__ */ w("button", {
|
|
150
158
|
type: "button",
|
|
151
159
|
className: "text-primary underline-offset-2 hover:underline",
|
|
152
|
-
onClick: () =>
|
|
153
|
-
children:
|
|
154
|
-
}) : /* @__PURE__ */
|
|
160
|
+
onClick: () => N(r("/products/feature-detail-page", z.duplicateOf.id)),
|
|
161
|
+
children: z.duplicateOf.title
|
|
162
|
+
}) : /* @__PURE__ */ w("span", {
|
|
155
163
|
className: "text-muted-foreground",
|
|
156
164
|
children: "a request that has since been deleted"
|
|
157
165
|
}),
|
|
@@ -160,60 +168,60 @@ function A() {
|
|
|
160
168
|
]
|
|
161
169
|
})
|
|
162
170
|
}),
|
|
163
|
-
/* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ T("div", {
|
|
164
172
|
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
165
173
|
children: [
|
|
166
|
-
/* @__PURE__ */
|
|
174
|
+
/* @__PURE__ */ w(D, { children: /* @__PURE__ */ T(O, {
|
|
167
175
|
className: "pt-4 text-center",
|
|
168
176
|
children: [
|
|
169
|
-
/* @__PURE__ */
|
|
170
|
-
/* @__PURE__ */
|
|
177
|
+
/* @__PURE__ */ w(v, { className: "h-5 w-5 mx-auto text-primary" }),
|
|
178
|
+
/* @__PURE__ */ w("p", {
|
|
171
179
|
className: "text-2xl font-bold mt-1",
|
|
172
|
-
children:
|
|
180
|
+
children: z.priorityScore ?? 0
|
|
173
181
|
}),
|
|
174
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ w("p", {
|
|
175
183
|
className: "text-xs text-muted-foreground",
|
|
176
184
|
children: "Priority Score"
|
|
177
185
|
})
|
|
178
186
|
]
|
|
179
187
|
}) }),
|
|
180
|
-
/* @__PURE__ */
|
|
188
|
+
/* @__PURE__ */ w(D, { children: /* @__PURE__ */ T(O, {
|
|
181
189
|
className: "pt-4 text-center",
|
|
182
190
|
children: [
|
|
183
|
-
/* @__PURE__ */
|
|
184
|
-
/* @__PURE__ */
|
|
191
|
+
/* @__PURE__ */ w(y, { className: "h-5 w-5 mx-auto text-green-500" }),
|
|
192
|
+
/* @__PURE__ */ w("p", {
|
|
185
193
|
className: "text-2xl font-bold mt-1",
|
|
186
|
-
children:
|
|
194
|
+
children: z.voteCount
|
|
187
195
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
196
|
+
/* @__PURE__ */ w("p", {
|
|
189
197
|
className: "text-xs text-muted-foreground",
|
|
190
198
|
children: "Votes"
|
|
191
199
|
})
|
|
192
200
|
]
|
|
193
201
|
}) }),
|
|
194
|
-
/* @__PURE__ */
|
|
202
|
+
/* @__PURE__ */ w(D, { children: /* @__PURE__ */ T(O, {
|
|
195
203
|
className: "pt-4 text-center",
|
|
196
204
|
children: [
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ w(_, { className: "h-5 w-5 mx-auto text-yellow-500" }),
|
|
206
|
+
/* @__PURE__ */ w("p", {
|
|
199
207
|
className: "text-2xl font-bold mt-1",
|
|
200
|
-
children:
|
|
208
|
+
children: z.businessValue ?? 0
|
|
201
209
|
}),
|
|
202
|
-
/* @__PURE__ */
|
|
210
|
+
/* @__PURE__ */ w("p", {
|
|
203
211
|
className: "text-xs text-muted-foreground",
|
|
204
212
|
children: "Business Value"
|
|
205
213
|
})
|
|
206
214
|
]
|
|
207
215
|
}) }),
|
|
208
|
-
/* @__PURE__ */
|
|
216
|
+
/* @__PURE__ */ w(D, { children: /* @__PURE__ */ T(O, {
|
|
209
217
|
className: "pt-4 text-center",
|
|
210
218
|
children: [
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
/* @__PURE__ */
|
|
219
|
+
/* @__PURE__ */ w(g, { className: "h-5 w-5 mx-auto text-blue-500" }),
|
|
220
|
+
/* @__PURE__ */ w("p", {
|
|
213
221
|
className: "text-2xl font-bold mt-1",
|
|
214
|
-
children:
|
|
222
|
+
children: V.length
|
|
215
223
|
}),
|
|
216
|
-
/* @__PURE__ */
|
|
224
|
+
/* @__PURE__ */ w("p", {
|
|
217
225
|
className: "text-xs text-muted-foreground",
|
|
218
226
|
children: "Competitors"
|
|
219
227
|
})
|
|
@@ -221,130 +229,147 @@ function A() {
|
|
|
221
229
|
}) })
|
|
222
230
|
]
|
|
223
231
|
}),
|
|
224
|
-
/* @__PURE__ */
|
|
232
|
+
/* @__PURE__ */ T("div", {
|
|
225
233
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
226
|
-
children: [/* @__PURE__ */
|
|
234
|
+
children: [/* @__PURE__ */ T("div", {
|
|
227
235
|
className: "lg:col-span-2 space-y-6",
|
|
228
236
|
children: [
|
|
229
|
-
/* @__PURE__ */
|
|
237
|
+
/* @__PURE__ */ T(D, { children: [/* @__PURE__ */ w(k, { children: /* @__PURE__ */ w(A, { children: "Description" }) }), /* @__PURE__ */ w(O, { children: /* @__PURE__ */ w("p", {
|
|
230
238
|
className: "text-sm",
|
|
231
|
-
children:
|
|
239
|
+
children: z.description ?? "No description provided."
|
|
232
240
|
}) })] }),
|
|
233
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ T(D, { children: [/* @__PURE__ */ w(k, { children: /* @__PURE__ */ T(A, { children: [
|
|
234
242
|
"Votes (",
|
|
235
|
-
|
|
243
|
+
B.length,
|
|
236
244
|
")"
|
|
237
|
-
] }) }), /* @__PURE__ */
|
|
245
|
+
] }) }), /* @__PURE__ */ T(O, {
|
|
238
246
|
className: "space-y-3",
|
|
239
|
-
children: [
|
|
247
|
+
children: [B.length === 0 && /* @__PURE__ */ w("p", {
|
|
240
248
|
className: "text-sm text-muted-foreground",
|
|
241
249
|
children: "No votes yet."
|
|
242
|
-
}),
|
|
250
|
+
}), B.map((e) => /* @__PURE__ */ T("div", {
|
|
243
251
|
className: "flex items-start justify-between border rounded-md p-3",
|
|
244
|
-
children: [/* @__PURE__ */
|
|
252
|
+
children: [/* @__PURE__ */ T("div", { children: [/* @__PURE__ */ T("p", {
|
|
245
253
|
className: "text-sm font-medium",
|
|
246
|
-
children: ["Voter: ", /* @__PURE__ */
|
|
247
|
-
}), e.comment && /* @__PURE__ */
|
|
254
|
+
children: ["Voter: ", /* @__PURE__ */ w(n, { id: e.voterId })]
|
|
255
|
+
}), e.comment && /* @__PURE__ */ w("p", {
|
|
248
256
|
className: "text-xs text-muted-foreground mt-1",
|
|
249
257
|
children: e.comment
|
|
250
|
-
})] }), /* @__PURE__ */
|
|
258
|
+
})] }), /* @__PURE__ */ T("div", {
|
|
251
259
|
className: "flex items-center gap-1",
|
|
252
|
-
children: [/* @__PURE__ */
|
|
260
|
+
children: [/* @__PURE__ */ w(y, { className: "h-3 w-3 text-green-500" }), /* @__PURE__ */ w("span", {
|
|
253
261
|
className: "text-sm font-bold",
|
|
254
262
|
children: e.weight
|
|
255
263
|
})]
|
|
256
264
|
})]
|
|
257
265
|
}, e.id))]
|
|
258
266
|
})] }),
|
|
259
|
-
/* @__PURE__ */
|
|
267
|
+
/* @__PURE__ */ T(D, { children: [/* @__PURE__ */ w(k, { children: /* @__PURE__ */ T(A, { children: [
|
|
260
268
|
"Competitors (",
|
|
261
|
-
|
|
269
|
+
V.length,
|
|
262
270
|
")"
|
|
263
|
-
] }) }), /* @__PURE__ */
|
|
264
|
-
featureRequestId:
|
|
265
|
-
competitors:
|
|
271
|
+
] }) }), /* @__PURE__ */ w(O, { children: /* @__PURE__ */ w(i, {
|
|
272
|
+
featureRequestId: z.id,
|
|
273
|
+
competitors: V
|
|
266
274
|
}) })] })
|
|
267
275
|
]
|
|
268
|
-
}), /* @__PURE__ */
|
|
276
|
+
}), /* @__PURE__ */ T("div", {
|
|
269
277
|
className: "space-y-6",
|
|
270
278
|
children: [
|
|
271
|
-
/* @__PURE__ */
|
|
272
|
-
featureId:
|
|
273
|
-
productId:
|
|
274
|
-
initiative:
|
|
279
|
+
/* @__PURE__ */ w(p, {
|
|
280
|
+
featureId: z.id,
|
|
281
|
+
productId: z.productId ?? null,
|
|
282
|
+
initiative: z.roadmapItem ?? null
|
|
275
283
|
}),
|
|
276
|
-
/* @__PURE__ */
|
|
277
|
-
/* @__PURE__ */
|
|
284
|
+
/* @__PURE__ */ w(d, { feature: z }),
|
|
285
|
+
/* @__PURE__ */ T(D, { children: [/* @__PURE__ */ w(k, { children: /* @__PURE__ */ w(A, { children: "Details" }) }), /* @__PURE__ */ T(O, {
|
|
278
286
|
className: "space-y-3 text-sm",
|
|
279
287
|
children: [
|
|
280
|
-
/* @__PURE__ */
|
|
288
|
+
/* @__PURE__ */ T("div", {
|
|
281
289
|
className: "flex justify-between",
|
|
282
|
-
children: [/* @__PURE__ */
|
|
290
|
+
children: [/* @__PURE__ */ w("span", {
|
|
283
291
|
className: "text-muted-foreground",
|
|
284
292
|
children: "Status"
|
|
285
|
-
}), /* @__PURE__ */
|
|
293
|
+
}), /* @__PURE__ */ w(E, {
|
|
286
294
|
variant: "secondary",
|
|
287
295
|
className: "capitalize",
|
|
288
|
-
children:
|
|
296
|
+
children: z.status.toLowerCase().replace("_", " ")
|
|
289
297
|
})]
|
|
290
298
|
}),
|
|
291
|
-
/* @__PURE__ */
|
|
299
|
+
/* @__PURE__ */ T("div", {
|
|
292
300
|
className: "flex justify-between",
|
|
293
|
-
children: [/* @__PURE__ */
|
|
301
|
+
children: [/* @__PURE__ */ w("span", {
|
|
294
302
|
className: "text-muted-foreground",
|
|
295
303
|
children: "Priority Score"
|
|
296
|
-
}), /* @__PURE__ */
|
|
304
|
+
}), /* @__PURE__ */ w(E, {
|
|
297
305
|
variant: "outline",
|
|
298
|
-
children:
|
|
306
|
+
children: z.priorityScore ?? "N/A"
|
|
299
307
|
})]
|
|
300
308
|
}),
|
|
301
|
-
/* @__PURE__ */
|
|
309
|
+
/* @__PURE__ */ T("div", {
|
|
302
310
|
className: "flex justify-between",
|
|
303
|
-
children: [/* @__PURE__ */
|
|
311
|
+
children: [/* @__PURE__ */ w("span", {
|
|
304
312
|
className: "text-muted-foreground",
|
|
305
313
|
children: "Effort"
|
|
306
|
-
}), /* @__PURE__ */
|
|
314
|
+
}), /* @__PURE__ */ w(E, {
|
|
307
315
|
variant: "outline",
|
|
308
316
|
className: "capitalize",
|
|
309
|
-
children:
|
|
317
|
+
children: z.effortEstimate ?? "N/A"
|
|
310
318
|
})]
|
|
311
319
|
}),
|
|
312
|
-
/* @__PURE__ */
|
|
320
|
+
/* @__PURE__ */ T("div", {
|
|
313
321
|
className: "flex justify-between",
|
|
314
|
-
children: [/* @__PURE__ */
|
|
322
|
+
children: [/* @__PURE__ */ w("span", {
|
|
315
323
|
className: "text-muted-foreground",
|
|
316
324
|
children: "Business Value"
|
|
317
|
-
}), /* @__PURE__ */
|
|
325
|
+
}), /* @__PURE__ */ w("span", {
|
|
318
326
|
className: "font-medium",
|
|
319
|
-
children:
|
|
327
|
+
children: z.businessValue ?? "N/A"
|
|
320
328
|
})]
|
|
321
329
|
}),
|
|
322
|
-
/* @__PURE__ */
|
|
330
|
+
/* @__PURE__ */ T("div", {
|
|
331
|
+
className: "flex justify-between items-center",
|
|
332
|
+
children: [/* @__PURE__ */ w("span", {
|
|
333
|
+
className: "text-muted-foreground",
|
|
334
|
+
children: "Rating"
|
|
335
|
+
}), /* @__PURE__ */ w(l, { rating: z.rating ?? null })]
|
|
336
|
+
}),
|
|
337
|
+
/* @__PURE__ */ T("div", {
|
|
338
|
+
className: "flex justify-between items-center",
|
|
339
|
+
children: [/* @__PURE__ */ w("span", {
|
|
340
|
+
className: "text-muted-foreground",
|
|
341
|
+
children: "Category"
|
|
342
|
+
}), z.category ? /* @__PURE__ */ w(c, { category: z.category }) : /* @__PURE__ */ w("span", {
|
|
343
|
+
className: "text-sm text-muted-foreground",
|
|
344
|
+
children: "Not set"
|
|
345
|
+
})]
|
|
346
|
+
}),
|
|
347
|
+
/* @__PURE__ */ T("div", {
|
|
323
348
|
className: "flex justify-between",
|
|
324
|
-
children: [/* @__PURE__ */
|
|
349
|
+
children: [/* @__PURE__ */ w("span", {
|
|
325
350
|
className: "text-muted-foreground",
|
|
326
351
|
children: "Submitted By"
|
|
327
|
-
}), /* @__PURE__ */
|
|
352
|
+
}), /* @__PURE__ */ w(n, { id: z.submittedBy })]
|
|
328
353
|
}),
|
|
329
|
-
|
|
354
|
+
z.triagedAt && /* @__PURE__ */ T("div", {
|
|
330
355
|
className: "flex justify-between",
|
|
331
|
-
children: [/* @__PURE__ */
|
|
356
|
+
children: [/* @__PURE__ */ w("span", {
|
|
332
357
|
className: "text-muted-foreground",
|
|
333
358
|
children: "Decided"
|
|
334
|
-
}), /* @__PURE__ */
|
|
359
|
+
}), /* @__PURE__ */ T("span", { children: [new Date(z.triagedAt).toLocaleDateString(), z.triagedBy && /* @__PURE__ */ T(C, { children: [" by ", /* @__PURE__ */ w(n, { id: z.triagedBy })] })] })]
|
|
335
360
|
}),
|
|
336
|
-
/* @__PURE__ */
|
|
361
|
+
/* @__PURE__ */ T("div", {
|
|
337
362
|
className: "flex justify-between",
|
|
338
|
-
children: [/* @__PURE__ */
|
|
363
|
+
children: [/* @__PURE__ */ w("span", {
|
|
339
364
|
className: "text-muted-foreground",
|
|
340
365
|
children: "Updated"
|
|
341
|
-
}), /* @__PURE__ */
|
|
366
|
+
}), /* @__PURE__ */ w("span", { children: new Date(z.updatedAt).toLocaleDateString() })]
|
|
342
367
|
})
|
|
343
368
|
]
|
|
344
369
|
})] }),
|
|
345
|
-
|
|
370
|
+
z.tags?.length > 0 && /* @__PURE__ */ T(D, { children: [/* @__PURE__ */ w(k, { children: /* @__PURE__ */ w(A, { children: "Tags" }) }), /* @__PURE__ */ w(O, {
|
|
346
371
|
className: "flex flex-wrap gap-1",
|
|
347
|
-
children:
|
|
372
|
+
children: z.tags.map((e) => /* @__PURE__ */ w(E, {
|
|
348
373
|
variant: "outline",
|
|
349
374
|
className: "text-xs",
|
|
350
375
|
children: e
|
|
@@ -357,6 +382,6 @@ function A() {
|
|
|
357
382
|
});
|
|
358
383
|
}
|
|
359
384
|
//#endregion
|
|
360
|
-
export {
|
|
385
|
+
export { M as ProductsFeatureDetailPagePage };
|
|
361
386
|
|
|
362
387
|
//# sourceMappingURL=ProductsFeatureDetailPagePage.js.map
|