@burdenoff/microfe-planmagnet 2026.928.3 → 2026.928.5
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/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +126 -84
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +192 -64
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts +5 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts.map +1 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.js +107 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.js.map +1 -0
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts +29 -0
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -0
- package/dist/planmagnet/automation/AutomationRuleFields.js +308 -0
- package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -0
- package/dist/planmagnet/automation/automationRuleTypes.d.ts +60 -0
- package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -0
- package/dist/planmagnet/automation/automationRuleTypes.js +113 -0
- package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -0
- package/dist/planmagnet/board/BulkActions.d.ts +11 -0
- package/dist/planmagnet/board/BulkActions.d.ts.map +1 -0
- package/dist/planmagnet/board/BulkActions.js +182 -0
- package/dist/planmagnet/board/BulkActions.js.map +1 -0
- package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.js +289 -254
- package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
- package/dist/planmagnet/budgets/BudgetControls.d.ts +30 -0
- package/dist/planmagnet/budgets/BudgetControls.d.ts.map +1 -0
- package/dist/planmagnet/budgets/BudgetControls.js +470 -0
- package/dist/planmagnet/budgets/BudgetControls.js.map +1 -0
- package/dist/planmagnet/budgets/ExpenseControls.d.ts +27 -0
- package/dist/planmagnet/budgets/ExpenseControls.d.ts.map +1 -0
- package/dist/planmagnet/budgets/ExpenseControls.js +366 -0
- package/dist/planmagnet/budgets/ExpenseControls.js.map +1 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts +5 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts.map +1 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.js +112 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.js.map +1 -0
- package/dist/planmagnet/budgets/budgetMath.d.ts +26 -0
- package/dist/planmagnet/budgets/budgetMath.d.ts.map +1 -0
- package/dist/planmagnet/budgets/budgetMath.js +49 -0
- package/dist/planmagnet/budgets/budgetMath.js.map +1 -0
- package/dist/planmagnet/controls/AutomationRuleControls.d.ts +23 -0
- package/dist/planmagnet/controls/AutomationRuleControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/AutomationRuleControls.js +172 -0
- package/dist/planmagnet/controls/AutomationRuleControls.js.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts +20 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.js +172 -0
- package/dist/planmagnet/controls/CompetitorControls.js.map +1 -0
- package/dist/planmagnet/controls/CustomFieldControls.d.ts +42 -0
- package/dist/planmagnet/controls/CustomFieldControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CustomFieldControls.js +84 -0
- package/dist/planmagnet/controls/CustomFieldControls.js.map +1 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts +8 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts.map +1 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.js +315 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.js.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.js +53 -53
- package/dist/planmagnet/controls/FeatureTriage.js +11 -11
- package/dist/planmagnet/controls/ObjectiveControls.js +37 -37
- package/dist/planmagnet/controls/Pickers.d.ts +17 -2
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +100 -9
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PortfolioControls.d.ts +13 -0
- package/dist/planmagnet/controls/PortfolioControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/PortfolioControls.js +114 -0
- package/dist/planmagnet/controls/PortfolioControls.js.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts +25 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.js +187 -0
- package/dist/planmagnet/controls/ProgramControls.js.map +1 -0
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +54 -47
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.js +7 -7
- package/dist/planmagnet/controls/RiskControls.d.ts +27 -0
- package/dist/planmagnet/controls/RiskControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/RiskControls.js +346 -0
- package/dist/planmagnet/controls/RiskControls.js.map +1 -0
- package/dist/planmagnet/controls/TemplateControls.d.ts +15 -0
- package/dist/planmagnet/controls/TemplateControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/TemplateControls.js +55 -0
- package/dist/planmagnet/controls/TemplateControls.js.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.js +1 -1
- package/dist/planmagnet/create/CreateDialogs.d.ts +20 -0
- package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
- package/dist/planmagnet/create/CreateDialogs.js +455 -116
- package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
- package/dist/planmagnet/dependency/DependencyPanel.d.ts +6 -0
- package/dist/planmagnet/dependency/DependencyPanel.d.ts.map +1 -0
- package/dist/planmagnet/dependency/DependencyPanel.js +310 -0
- package/dist/planmagnet/dependency/DependencyPanel.js.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +171 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +232 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +93 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +127 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +129 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +132 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +181 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js +242 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +302 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +335 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +154 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +124 -138
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +221 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +279 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js +178 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +157 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js +315 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +186 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +164 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js +348 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +130 -103
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +325 -189
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +81 -72
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.js +278 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js +191 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +5 -5
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
- package/dist/planmagnet/pages/TasksImportPagePage.js +5 -5
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +161 -137
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +124 -113
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +263 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js +254 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.js +165 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js +316 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js.map +1 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +208 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +0 -5
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +47 -89
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +24 -24
- package/dist/planmagnet/time/TaskTimePanel.d.ts +7 -0
- package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
- package/dist/planmagnet/time/TaskTimePanel.js +104 -0
- package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.js +338 -0
- package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.js +244 -0
- package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
- package/dist/planmagnet/time/timeShared.d.ts +29 -0
- package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
- package/dist/planmagnet/time/timeShared.js +34 -0
- package/dist/planmagnet/time/timeShared.js.map +1 -0
- package/dist/planmagnet/utils/rowNavigation.js +5 -2
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortfoliosEditPortfolioPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/PortfoliosEditPortfolioPagePage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft, Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PORTFOLIO_STATUSES, STRATEGIC_ALIGNMENTS } from '../controls/PortfolioControls';\n\nconst GET_PORTFOLIO = gql`\n query GetPortfolioForEdit($id: ID!) {\n planmagnetPortfolio(id: $id) {\n id\n name\n description\n status\n alignment\n alignmentLabel\n budgetTotal\n currency\n fiscalPeriod\n tags\n }\n }\n`;\n\nconst UPDATE_PORTFOLIO = gql`\n mutation UpdatePortfolio($id: ID!, $input: PlanmagnetUpdatePortfolioInput!) {\n planmagnetUpdatePortfolio(id: $id, input: $input) {\n id\n name\n description\n status\n alignment\n alignmentLabel\n budgetTotal\n currency\n fiscalPeriod\n tags\n updatedAt\n }\n }\n`;\n\nexport function PortfoliosEditPortfolioPagePage() {\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 budgetTotal: number | null;\n currency: string | null;\n fiscalPeriod: string | null;\n tags: string[];\n } | null;\n }>(GET_PORTFOLIO, { variables: { id: portfolioId }, skip: !portfolioId });\n\n const [updatePortfolio, { loading: saving }] = useMutation(UPDATE_PORTFOLIO, {\n refetchQueries: [\n 'ListPortfolios',\n 'GetPortfolioForEdit',\n 'GetPortfolio',\n 'GetPortfolioOverview',\n ],\n awaitRefetchQueries: true,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const portfolio = data?.planmagnetPortfolio;\n\n const defaultForm = useMemo(\n () => ({\n name: portfolio?.name ?? '',\n description: portfolio?.description ?? '',\n status: portfolio?.status ?? 'ACTIVE',\n alignment: portfolio?.alignment ?? '',\n alignmentLabel: portfolio?.alignmentLabel ?? '',\n budgetTotal: portfolio?.budgetTotal != null ? String(portfolio.budgetTotal) : '',\n currency: portfolio?.currency ?? '',\n fiscalPeriod: portfolio?.fiscalPeriod ?? '',\n tags: portfolio?.tags?.join(', ') ?? '',\n }),\n [portfolio]\n );\n\n const [formOverrides, setFormOverrides] = useState<Record<string, string>>({});\n const form = { ...defaultForm, ...formOverrides };\n const setForm = (val: Record<string, string>) => setFormOverrides(val);\n\n const statuses = (PORTFOLIO_STATUSES as readonly string[]).includes(form.status)\n ? PORTFOLIO_STATUSES\n : [form.status, ...PORTFOLIO_STATUSES];\n const needsLabel = form.alignment === 'CUSTOM' && !form.alignmentLabel.trim();\n const invalidBudget = form.budgetTotal !== '' && Number(form.budgetTotal) < 0;\n\n const handleSave = async () => {\n if (!portfolio || !form.name.trim() || needsLabel || invalidBudget) return;\n const tagList = (v: string) =>\n v\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const next: Record<string, unknown> = {\n name: form.name.trim(),\n description: form.description.trim() || null,\n status: form.status,\n alignment: form.alignment || null,\n alignmentLabel: form.alignment === 'CUSTOM' ? form.alignmentLabel.trim() : null,\n budgetTotal: form.budgetTotal !== '' ? Number(form.budgetTotal) : null,\n currency: form.currency.trim() || null,\n fiscalPeriod: form.fiscalPeriod.trim() || null,\n tags: tagList(form.tags),\n };\n const before: Record<string, unknown> = {\n name: defaultForm.name,\n description: defaultForm.description.trim() || null,\n status: defaultForm.status,\n alignment: defaultForm.alignment || null,\n alignmentLabel: defaultForm.alignment === 'CUSTOM' ? defaultForm.alignmentLabel.trim() : null,\n budgetTotal: defaultForm.budgetTotal !== '' ? Number(defaultForm.budgetTotal) : null,\n currency: defaultForm.currency.trim() || null,\n fiscalPeriod: defaultForm.fiscalPeriod.trim() || null,\n tags: tagList(defaultForm.tags),\n };\n const input = Object.fromEntries(\n Object.entries(next).filter(([k, v]) => JSON.stringify(v) !== JSON.stringify(before[k]))\n );\n setSaveError(null);\n setSaved(false);\n if (Object.keys(input).length === 0) {\n setSaved(true);\n return;\n }\n try {\n await throwOnError(updatePortfolio({ variables: { id: portfolioId, input } }));\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The portfolio could not be saved.');\n }\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 if (!portfolio) return <div className=\"p-6 text-muted-foreground\">Portfolio not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Back to the portfolio\"\n onClick={() => navigate(detailPath('/products/portfolio-detail-page', portfolioId))}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Portfolio</h1>\n <p className=\"text-sm text-muted-foreground\">{portfolio.name}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving || needsLabel || invalidBudget}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Save Changes\n </Button>\n </div>\n\n {saved && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n Changes saved.\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Portfolio Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"portfolio-edit-status\" className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id=\"portfolio-edit-status\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"portfolio-edit-alignment\" className=\"text-sm font-medium\">\n Strategic Alignment\n </label>\n <select\n id=\"portfolio-edit-alignment\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.alignment}\n onChange={(e) => setForm({ ...form, alignment: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {STRATEGIC_ALIGNMENTS.map((a) => (\n <option key={a} value={a}>\n {a === 'CUSTOM' ? 'Custom…' : enumLabel(a)}\n </option>\n ))}\n </select>\n </div>\n </div>\n {form.alignment === 'CUSTOM' && (\n <div>\n <label className=\"text-sm font-medium\">Custom Alignment Label</label>\n <Input\n value={form.alignmentLabel}\n onChange={(e) => setForm({ ...form, alignmentLabel: e.target.value })}\n className=\"mt-1\"\n aria-invalid={needsLabel}\n />\n {needsLabel && (\n <p role=\"alert\" className=\"text-sm text-destructive mt-1\">\n A custom alignment needs a label.\n </p>\n )}\n </div>\n )}\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Total Budget</label>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.budgetTotal}\n onChange={(e) => setForm({ ...form, budgetTotal: e.target.value })}\n className=\"mt-1\"\n aria-invalid={invalidBudget}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Currency</label>\n <Input\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Fiscal Period</label>\n <Input\n value={form.fiscalPeriod}\n onChange={(e) => setForm({ ...form, fiscalPeriod: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </div>\n {invalidBudget && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The budget cannot be negative.\n </p>\n )}\n <div>\n <label className=\"text-sm font-medium\">Tags (comma-separated)</label>\n <Input\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortfoliosPortfolioDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,iCAAiC,4CAuQhD"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { PersonName as t } from "../people/People.js";
|
|
3
|
+
import { enumLabel as n } from "../controls/WorkItemControls.js";
|
|
4
|
+
import { detailPath as r } from "../utils/rowNavigation.js";
|
|
5
|
+
import { PortfolioHeaderActions as i } from "../controls/PortfolioControls.js";
|
|
6
|
+
import { AlertTriangle as a, Briefcase as o, Clock as s, Loader2 as c, Target as l } from "lucide-react";
|
|
7
|
+
import { useParams as u } from "react-router";
|
|
8
|
+
import { gql as d } from "@apollo/client";
|
|
9
|
+
import { useQuery as f } from "@apollo/client/react";
|
|
10
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
11
|
+
import { Badge as h, Card as g, CardContent as _, CardHeader as v, CardTitle as y } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx
|
|
13
|
+
var b = d`
|
|
14
|
+
query GetPortfolio($id: ID!) {
|
|
15
|
+
planmagnetPortfolio(id: $id) {
|
|
16
|
+
id
|
|
17
|
+
workspaceId
|
|
18
|
+
name
|
|
19
|
+
description
|
|
20
|
+
status
|
|
21
|
+
alignment
|
|
22
|
+
alignmentLabel
|
|
23
|
+
ownerId
|
|
24
|
+
budgetTotal
|
|
25
|
+
currency
|
|
26
|
+
fiscalPeriod
|
|
27
|
+
tags
|
|
28
|
+
createdAt
|
|
29
|
+
updatedAt
|
|
30
|
+
programs {
|
|
31
|
+
id
|
|
32
|
+
name
|
|
33
|
+
status
|
|
34
|
+
}
|
|
35
|
+
projects {
|
|
36
|
+
id
|
|
37
|
+
name
|
|
38
|
+
status
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`, x = d`
|
|
43
|
+
query GetPortfolioOverview($portfolioId: ID!) {
|
|
44
|
+
planmagnetPortfolioOverview(portfolioId: $portfolioId) {
|
|
45
|
+
portfolioId
|
|
46
|
+
totalPrograms
|
|
47
|
+
totalProjects
|
|
48
|
+
projectsByStatus
|
|
49
|
+
totalBudget
|
|
50
|
+
totalSpent
|
|
51
|
+
budgetUtilization
|
|
52
|
+
healthScore
|
|
53
|
+
atRiskCount
|
|
54
|
+
overdueMilestones
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
function S() {
|
|
59
|
+
let d = e(), S = u().id ?? "", { data: C, loading: w, error: T } = f(b, {
|
|
60
|
+
variables: { id: S },
|
|
61
|
+
skip: !S
|
|
62
|
+
}), { data: E } = f(x, {
|
|
63
|
+
variables: { portfolioId: S },
|
|
64
|
+
skip: !S
|
|
65
|
+
});
|
|
66
|
+
if (w && !C) return /* @__PURE__ */ p("div", {
|
|
67
|
+
className: "flex items-center justify-center p-12",
|
|
68
|
+
children: /* @__PURE__ */ p(c, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
69
|
+
});
|
|
70
|
+
if (T) return /* @__PURE__ */ m("div", {
|
|
71
|
+
className: "p-6 text-destructive",
|
|
72
|
+
children: ["Failed to load portfolio: ", T.message]
|
|
73
|
+
});
|
|
74
|
+
let D = C?.planmagnetPortfolio;
|
|
75
|
+
if (!D) return /* @__PURE__ */ p("div", {
|
|
76
|
+
className: "p-6 text-muted-foreground",
|
|
77
|
+
children: "Portfolio not found."
|
|
78
|
+
});
|
|
79
|
+
let O = E?.planmagnetPortfolioOverview ?? null;
|
|
80
|
+
return /* @__PURE__ */ m("div", {
|
|
81
|
+
className: "space-y-6 p-6",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ m("div", {
|
|
84
|
+
className: "flex items-center justify-between",
|
|
85
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ m("div", {
|
|
86
|
+
className: "flex items-center gap-2",
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ p(o, { className: "h-5 w-5 text-primary" }),
|
|
89
|
+
/* @__PURE__ */ p("h1", {
|
|
90
|
+
className: "text-2xl font-semibold",
|
|
91
|
+
children: D.name
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ p(h, {
|
|
94
|
+
variant: "outline",
|
|
95
|
+
className: "text-xs",
|
|
96
|
+
children: n(D.status)
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
}), D.alignment && /* @__PURE__ */ p("p", {
|
|
100
|
+
className: "text-sm text-muted-foreground mt-1",
|
|
101
|
+
children: D.alignment === "CUSTOM" ? D.alignmentLabel ?? "Custom alignment" : n(D.alignment)
|
|
102
|
+
})] }), /* @__PURE__ */ p(i, {
|
|
103
|
+
portfolioId: D.id,
|
|
104
|
+
name: D.name,
|
|
105
|
+
status: D.status
|
|
106
|
+
})]
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ m("div", {
|
|
109
|
+
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ p(g, { children: /* @__PURE__ */ m(_, {
|
|
112
|
+
className: "pt-4 text-center",
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ p(o, { className: "h-5 w-5 mx-auto text-primary" }),
|
|
115
|
+
/* @__PURE__ */ p("p", {
|
|
116
|
+
className: "text-2xl font-bold mt-1",
|
|
117
|
+
children: O?.totalPrograms ?? D.programs.length
|
|
118
|
+
}),
|
|
119
|
+
/* @__PURE__ */ p("p", {
|
|
120
|
+
className: "text-xs text-muted-foreground",
|
|
121
|
+
children: "Programs"
|
|
122
|
+
})
|
|
123
|
+
]
|
|
124
|
+
}) }),
|
|
125
|
+
/* @__PURE__ */ p(g, { children: /* @__PURE__ */ m(_, {
|
|
126
|
+
className: "pt-4 text-center",
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ p(l, { className: "h-5 w-5 mx-auto text-primary" }),
|
|
129
|
+
/* @__PURE__ */ p("p", {
|
|
130
|
+
className: "text-2xl font-bold mt-1",
|
|
131
|
+
children: O?.totalProjects ?? D.projects.length
|
|
132
|
+
}),
|
|
133
|
+
/* @__PURE__ */ p("p", {
|
|
134
|
+
className: "text-xs text-muted-foreground",
|
|
135
|
+
children: "Projects"
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
}) }),
|
|
139
|
+
/* @__PURE__ */ p(g, { children: /* @__PURE__ */ m(_, {
|
|
140
|
+
className: "pt-4 text-center",
|
|
141
|
+
children: [
|
|
142
|
+
/* @__PURE__ */ p(a, { className: "h-5 w-5 mx-auto text-destructive" }),
|
|
143
|
+
/* @__PURE__ */ p("p", {
|
|
144
|
+
className: "text-2xl font-bold mt-1",
|
|
145
|
+
children: O?.atRiskCount ?? 0
|
|
146
|
+
}),
|
|
147
|
+
/* @__PURE__ */ p("p", {
|
|
148
|
+
className: "text-xs text-muted-foreground",
|
|
149
|
+
children: "At Risk"
|
|
150
|
+
})
|
|
151
|
+
]
|
|
152
|
+
}) }),
|
|
153
|
+
/* @__PURE__ */ p(g, { children: /* @__PURE__ */ m(_, {
|
|
154
|
+
className: "pt-4 text-center",
|
|
155
|
+
children: [
|
|
156
|
+
/* @__PURE__ */ p(s, { className: "h-5 w-5 mx-auto text-status-warning-text" }),
|
|
157
|
+
/* @__PURE__ */ p("p", {
|
|
158
|
+
className: "text-2xl font-bold mt-1",
|
|
159
|
+
children: O?.overdueMilestones ?? 0
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ p("p", {
|
|
162
|
+
className: "text-xs text-muted-foreground",
|
|
163
|
+
children: "Overdue Milestones"
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
}) })
|
|
167
|
+
]
|
|
168
|
+
}),
|
|
169
|
+
/* @__PURE__ */ m("div", {
|
|
170
|
+
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
171
|
+
children: [/* @__PURE__ */ m("div", {
|
|
172
|
+
className: "lg:col-span-2 space-y-6",
|
|
173
|
+
children: [
|
|
174
|
+
/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Overview" }) }), /* @__PURE__ */ m(_, {
|
|
175
|
+
className: "space-y-4",
|
|
176
|
+
children: [/* @__PURE__ */ p("p", {
|
|
177
|
+
className: "text-sm",
|
|
178
|
+
children: D.description ?? "No description provided."
|
|
179
|
+
}), O ? /* @__PURE__ */ m("div", {
|
|
180
|
+
className: "grid grid-cols-2 gap-4",
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ m("div", {
|
|
183
|
+
className: "p-3 bg-muted rounded-md",
|
|
184
|
+
children: [/* @__PURE__ */ p("p", {
|
|
185
|
+
className: "text-xs text-muted-foreground",
|
|
186
|
+
children: "Budget Utilization"
|
|
187
|
+
}), /* @__PURE__ */ p("p", {
|
|
188
|
+
className: "text-lg font-semibold mt-1",
|
|
189
|
+
children: O.budgetUtilization == null ? "No budget data" : `${O.budgetUtilization.toFixed(0)}%`
|
|
190
|
+
})]
|
|
191
|
+
}),
|
|
192
|
+
/* @__PURE__ */ m("div", {
|
|
193
|
+
className: "p-3 bg-muted rounded-md",
|
|
194
|
+
children: [/* @__PURE__ */ p("p", {
|
|
195
|
+
className: "text-xs text-muted-foreground",
|
|
196
|
+
children: "Health Score"
|
|
197
|
+
}), /* @__PURE__ */ p("p", {
|
|
198
|
+
className: "text-lg font-semibold mt-1",
|
|
199
|
+
children: O.healthScore == null ? "No projects yet" : O.healthScore.toFixed(0)
|
|
200
|
+
})]
|
|
201
|
+
}),
|
|
202
|
+
/* @__PURE__ */ m("div", {
|
|
203
|
+
className: "p-3 bg-muted rounded-md",
|
|
204
|
+
children: [/* @__PURE__ */ p("p", {
|
|
205
|
+
className: "text-xs text-muted-foreground",
|
|
206
|
+
children: "Total Spent"
|
|
207
|
+
}), /* @__PURE__ */ p("p", {
|
|
208
|
+
className: "text-lg font-semibold mt-1",
|
|
209
|
+
children: O.totalSpent == null ? "N/A" : O.totalSpent.toLocaleString()
|
|
210
|
+
})]
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ m("div", {
|
|
213
|
+
className: "p-3 bg-muted rounded-md",
|
|
214
|
+
children: [/* @__PURE__ */ p("p", {
|
|
215
|
+
className: "text-xs text-muted-foreground",
|
|
216
|
+
children: "Projects by Status"
|
|
217
|
+
}), /* @__PURE__ */ p("p", {
|
|
218
|
+
className: "text-sm mt-1",
|
|
219
|
+
children: Object.keys(O.projectsByStatus).length > 0 ? Object.entries(O.projectsByStatus).map(([e, t]) => `${n(e)}: ${t}`).join(", ") : "No projects yet"
|
|
220
|
+
})]
|
|
221
|
+
})
|
|
222
|
+
]
|
|
223
|
+
}) : /* @__PURE__ */ p("p", {
|
|
224
|
+
className: "text-sm text-muted-foreground",
|
|
225
|
+
children: "Loading overview…"
|
|
226
|
+
})]
|
|
227
|
+
})] }),
|
|
228
|
+
/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
|
|
229
|
+
"Programs (",
|
|
230
|
+
D.programs.length,
|
|
231
|
+
")"
|
|
232
|
+
] }) }), /* @__PURE__ */ m(_, {
|
|
233
|
+
className: "space-y-2",
|
|
234
|
+
children: [D.programs.length === 0 && /* @__PURE__ */ p("p", {
|
|
235
|
+
className: "text-sm text-muted-foreground",
|
|
236
|
+
children: "No programs are linked to this portfolio yet."
|
|
237
|
+
}), D.programs.map((e) => /* @__PURE__ */ m("button", {
|
|
238
|
+
type: "button",
|
|
239
|
+
className: "flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted",
|
|
240
|
+
onClick: () => d(r("/products/program-detail-page", e.id)),
|
|
241
|
+
children: [/* @__PURE__ */ p("span", {
|
|
242
|
+
className: "text-sm font-medium",
|
|
243
|
+
children: e.name
|
|
244
|
+
}), /* @__PURE__ */ p(h, {
|
|
245
|
+
variant: "outline",
|
|
246
|
+
className: "text-xs",
|
|
247
|
+
children: n(e.status)
|
|
248
|
+
})]
|
|
249
|
+
}, e.id))]
|
|
250
|
+
})] }),
|
|
251
|
+
/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
|
|
252
|
+
"Projects (",
|
|
253
|
+
D.projects.length,
|
|
254
|
+
")"
|
|
255
|
+
] }) }), /* @__PURE__ */ m(_, {
|
|
256
|
+
className: "space-y-2",
|
|
257
|
+
children: [D.projects.length === 0 && /* @__PURE__ */ p("p", {
|
|
258
|
+
className: "text-sm text-muted-foreground",
|
|
259
|
+
children: "No projects are linked to this portfolio yet."
|
|
260
|
+
}), D.projects.map((e) => /* @__PURE__ */ m("button", {
|
|
261
|
+
type: "button",
|
|
262
|
+
className: "flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted",
|
|
263
|
+
onClick: () => d(r("/projects/project-detail-page", e.id)),
|
|
264
|
+
children: [/* @__PURE__ */ p("span", {
|
|
265
|
+
className: "text-sm font-medium",
|
|
266
|
+
children: e.name
|
|
267
|
+
}), /* @__PURE__ */ p(h, {
|
|
268
|
+
variant: "outline",
|
|
269
|
+
className: "text-xs",
|
|
270
|
+
children: n(e.status)
|
|
271
|
+
})]
|
|
272
|
+
}, e.id))]
|
|
273
|
+
})] })
|
|
274
|
+
]
|
|
275
|
+
}), /* @__PURE__ */ m("div", {
|
|
276
|
+
className: "space-y-6",
|
|
277
|
+
children: [/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Details" }) }), /* @__PURE__ */ m(_, {
|
|
278
|
+
className: "space-y-2 text-sm",
|
|
279
|
+
children: [
|
|
280
|
+
/* @__PURE__ */ m("div", {
|
|
281
|
+
className: "flex justify-between",
|
|
282
|
+
children: [/* @__PURE__ */ p("span", {
|
|
283
|
+
className: "text-muted-foreground",
|
|
284
|
+
children: "Owner"
|
|
285
|
+
}), /* @__PURE__ */ p(t, { id: D.ownerId })]
|
|
286
|
+
}),
|
|
287
|
+
/* @__PURE__ */ m("div", {
|
|
288
|
+
className: "flex justify-between",
|
|
289
|
+
children: [/* @__PURE__ */ p("span", {
|
|
290
|
+
className: "text-muted-foreground",
|
|
291
|
+
children: "Budget"
|
|
292
|
+
}), /* @__PURE__ */ p("span", {
|
|
293
|
+
className: "font-medium",
|
|
294
|
+
children: D.budgetTotal == null ? "Not set" : `${D.currency ?? ""} ${D.budgetTotal.toLocaleString()}`.trim()
|
|
295
|
+
})]
|
|
296
|
+
}),
|
|
297
|
+
/* @__PURE__ */ m("div", {
|
|
298
|
+
className: "flex justify-between",
|
|
299
|
+
children: [/* @__PURE__ */ p("span", {
|
|
300
|
+
className: "text-muted-foreground",
|
|
301
|
+
children: "Fiscal Period"
|
|
302
|
+
}), /* @__PURE__ */ p("span", { children: D.fiscalPeriod ?? "Not set" })]
|
|
303
|
+
}),
|
|
304
|
+
/* @__PURE__ */ m("div", {
|
|
305
|
+
className: "flex justify-between",
|
|
306
|
+
children: [/* @__PURE__ */ p("span", {
|
|
307
|
+
className: "text-muted-foreground",
|
|
308
|
+
children: "Created"
|
|
309
|
+
}), /* @__PURE__ */ p("span", { children: new Date(D.createdAt).toLocaleDateString() })]
|
|
310
|
+
}),
|
|
311
|
+
/* @__PURE__ */ m("div", {
|
|
312
|
+
className: "flex justify-between",
|
|
313
|
+
children: [/* @__PURE__ */ p("span", {
|
|
314
|
+
className: "text-muted-foreground",
|
|
315
|
+
children: "Updated"
|
|
316
|
+
}), /* @__PURE__ */ p("span", { children: new Date(D.updatedAt).toLocaleDateString() })]
|
|
317
|
+
})
|
|
318
|
+
]
|
|
319
|
+
})] }), D.tags.length > 0 && /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Tags" }) }), /* @__PURE__ */ p(_, {
|
|
320
|
+
className: "flex flex-wrap gap-1",
|
|
321
|
+
children: D.tags.map((e) => /* @__PURE__ */ p(h, {
|
|
322
|
+
variant: "outline",
|
|
323
|
+
className: "text-xs",
|
|
324
|
+
children: e
|
|
325
|
+
}, e))
|
|
326
|
+
})] })]
|
|
327
|
+
})]
|
|
328
|
+
})
|
|
329
|
+
]
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
export { S as PortfoliosPortfolioDetailPagePage };
|
|
334
|
+
|
|
335
|
+
//# sourceMappingURL=PortfoliosPortfolioDetailPagePage.js.map
|
|
@@ -0,0 +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 } 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 <p className=\"text-sm text-muted-foreground\">\n No projects are linked to this portfolio yet.\n </p>\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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortfoliosPortfoliosPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/PortfoliosPortfoliosPagePage.tsx"],"names":[],"mappings":"AA4DA,wBAAgB,4BAA4B,4CAuI3C"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { PersonName as n } from "../people/People.js";
|
|
3
|
+
import { enumLabel as r } from "../controls/WorkItemControls.js";
|
|
4
|
+
import { detailPath as i, rowLinkProps as a } from "../utils/rowNavigation.js";
|
|
5
|
+
import { PORTFOLIO_STATUSES as o } from "../controls/PortfolioControls.js";
|
|
6
|
+
import { useState as s } from "react";
|
|
7
|
+
import { Briefcase as c, Loader2 as l, Plus as u } from "lucide-react";
|
|
8
|
+
import { gql as d } from "@apollo/client";
|
|
9
|
+
import { useQuery as f } from "@apollo/client/react";
|
|
10
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
11
|
+
import { Badge as h, Button as g, Card as _, CardContent as v, CardHeader as y, CardTitle as b, Input as x } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/pages/PortfoliosPortfoliosPagePage.tsx
|
|
13
|
+
var S = d`
|
|
14
|
+
query ListPortfolios(
|
|
15
|
+
$workspaceId: ID!
|
|
16
|
+
$filter: PlanmagnetPortfolioFilter
|
|
17
|
+
$pagination: PlanmagnetPaginationInput
|
|
18
|
+
) {
|
|
19
|
+
planmagnetPortfolios(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {
|
|
20
|
+
items {
|
|
21
|
+
id
|
|
22
|
+
name
|
|
23
|
+
description
|
|
24
|
+
status
|
|
25
|
+
alignment
|
|
26
|
+
alignmentLabel
|
|
27
|
+
ownerId
|
|
28
|
+
budgetTotal
|
|
29
|
+
currency
|
|
30
|
+
fiscalPeriod
|
|
31
|
+
tags
|
|
32
|
+
updatedAt
|
|
33
|
+
}
|
|
34
|
+
totalCount
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
function C() {
|
|
39
|
+
let d = t(), { workspaceId: C } = e(), [w, T] = s("all"), [E, D] = s(""), { data: O, loading: k, error: A } = f(S, {
|
|
40
|
+
variables: {
|
|
41
|
+
workspaceId: C,
|
|
42
|
+
pagination: { first: 50 },
|
|
43
|
+
filter: {
|
|
44
|
+
...w === "all" ? {} : { status: w },
|
|
45
|
+
...E.trim() ? { search: E.trim() } : {}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
skip: !C
|
|
49
|
+
});
|
|
50
|
+
if (k && !O) return /* @__PURE__ */ p("div", {
|
|
51
|
+
className: "flex items-center justify-center p-12",
|
|
52
|
+
children: /* @__PURE__ */ p(l, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
53
|
+
});
|
|
54
|
+
if (A) return /* @__PURE__ */ m("div", {
|
|
55
|
+
className: "p-6 text-destructive",
|
|
56
|
+
children: ["Failed to load portfolios: ", A.message]
|
|
57
|
+
});
|
|
58
|
+
let j = O?.planmagnetPortfolios.items ?? [], M = O?.planmagnetPortfolios.totalCount ?? 0, N = ["all", ...o];
|
|
59
|
+
return /* @__PURE__ */ m("div", {
|
|
60
|
+
className: "space-y-6 p-6",
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ m("div", {
|
|
63
|
+
className: "flex items-center justify-between",
|
|
64
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h1", {
|
|
65
|
+
className: "text-2xl font-semibold",
|
|
66
|
+
children: "Portfolios"
|
|
67
|
+
}), /* @__PURE__ */ m("p", {
|
|
68
|
+
className: "text-sm text-muted-foreground",
|
|
69
|
+
children: [
|
|
70
|
+
M,
|
|
71
|
+
" portfolio",
|
|
72
|
+
M === 1 ? "" : "s",
|
|
73
|
+
" across the workspace"
|
|
74
|
+
]
|
|
75
|
+
})] }), /* @__PURE__ */ m(g, {
|
|
76
|
+
size: "sm",
|
|
77
|
+
onClick: () => d("/products/create-portfolio-page"),
|
|
78
|
+
children: [/* @__PURE__ */ p(u, { className: "mr-1 h-4 w-4" }), "New Portfolio"]
|
|
79
|
+
})]
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ m("div", {
|
|
82
|
+
className: "flex flex-wrap items-center gap-4",
|
|
83
|
+
children: [/* @__PURE__ */ p(x, {
|
|
84
|
+
"aria-label": "Search portfolios",
|
|
85
|
+
placeholder: "Search by name…",
|
|
86
|
+
value: E,
|
|
87
|
+
onChange: (e) => D(e.target.value),
|
|
88
|
+
className: "max-w-xs"
|
|
89
|
+
}), /* @__PURE__ */ p("div", {
|
|
90
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
91
|
+
children: N.map((e) => /* @__PURE__ */ p("button", {
|
|
92
|
+
type: "button",
|
|
93
|
+
"aria-pressed": w === e,
|
|
94
|
+
onClick: () => T(e),
|
|
95
|
+
className: `px-2 py-1 text-xs rounded-md capitalize ${w === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
|
|
96
|
+
children: e === "all" ? "All" : r(e)
|
|
97
|
+
}, e))
|
|
98
|
+
})]
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ p("div", {
|
|
101
|
+
className: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
|
102
|
+
children: j.map((e) => /* @__PURE__ */ m(_, {
|
|
103
|
+
...a(() => d(i("/products/portfolio-detail-page", e.id)), e.name),
|
|
104
|
+
children: [/* @__PURE__ */ p(y, {
|
|
105
|
+
className: "pb-2",
|
|
106
|
+
children: /* @__PURE__ */ m("div", {
|
|
107
|
+
className: "flex items-center justify-between gap-2",
|
|
108
|
+
children: [/* @__PURE__ */ m(b, {
|
|
109
|
+
className: "text-base flex items-center gap-2",
|
|
110
|
+
children: [/* @__PURE__ */ p(c, { className: "h-4 w-4 text-primary" }), e.name]
|
|
111
|
+
}), /* @__PURE__ */ p(h, {
|
|
112
|
+
variant: "outline",
|
|
113
|
+
className: "text-xs",
|
|
114
|
+
children: r(e.status)
|
|
115
|
+
})]
|
|
116
|
+
})
|
|
117
|
+
}), /* @__PURE__ */ m(v, {
|
|
118
|
+
className: "space-y-3",
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ p("p", {
|
|
121
|
+
className: "text-sm text-muted-foreground",
|
|
122
|
+
children: e.description ?? "No description"
|
|
123
|
+
}),
|
|
124
|
+
/* @__PURE__ */ m("div", {
|
|
125
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
126
|
+
children: [e.alignment && /* @__PURE__ */ p(h, {
|
|
127
|
+
variant: "secondary",
|
|
128
|
+
className: "text-xs",
|
|
129
|
+
children: e.alignment === "CUSTOM" ? e.alignmentLabel ?? "Custom" : r(e.alignment)
|
|
130
|
+
}), e.fiscalPeriod && /* @__PURE__ */ p(h, {
|
|
131
|
+
variant: "outline",
|
|
132
|
+
className: "text-xs",
|
|
133
|
+
children: e.fiscalPeriod
|
|
134
|
+
})]
|
|
135
|
+
}),
|
|
136
|
+
/* @__PURE__ */ m("div", {
|
|
137
|
+
className: "flex items-center justify-between text-xs text-muted-foreground border-t pt-2",
|
|
138
|
+
children: [/* @__PURE__ */ m("span", { children: ["Owner: ", /* @__PURE__ */ p(n, { id: e.ownerId })] }), /* @__PURE__ */ p("span", { children: e.budgetTotal == null ? "No budget set" : `${e.currency ?? ""} ${e.budgetTotal.toLocaleString()}`.trim() })]
|
|
139
|
+
})
|
|
140
|
+
]
|
|
141
|
+
})]
|
|
142
|
+
}, e.id))
|
|
143
|
+
}),
|
|
144
|
+
j.length === 0 && /* @__PURE__ */ p("p", {
|
|
145
|
+
className: "text-center text-muted-foreground py-8",
|
|
146
|
+
children: w !== "all" || E ? "No portfolios match the selected filters." : "No portfolios yet. Create one to group programs and projects for executive oversight."
|
|
147
|
+
})
|
|
148
|
+
]
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { C as PortfoliosPortfoliosPagePage };
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=PortfoliosPortfoliosPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortfoliosPortfoliosPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/PortfoliosPortfoliosPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Briefcase, Plus, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PORTFOLIO_STATUSES } from '../controls/PortfolioControls';\n\nconst LIST_PORTFOLIOS = gql`\n query ListPortfolios(\n $workspaceId: ID!\n $filter: PlanmagnetPortfolioFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetPortfolios(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n name\n description\n status\n alignment\n alignmentLabel\n ownerId\n budgetTotal\n currency\n fiscalPeriod\n tags\n updatedAt\n }\n totalCount\n }\n }\n`;\n\ninterface PortfolioNode {\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 updatedAt: string;\n}\n\nexport function PortfoliosPortfoliosPagePage() {\n const navigate = usePlanMagnetNavigate();\n const { workspaceId } = usePlanMagnet();\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const [search, setSearch] = useState('');\n\n const { data, loading, error } = useQuery<{\n planmagnetPortfolios: { items: PortfolioNode[]; totalCount: number };\n }>(LIST_PORTFOLIOS, {\n variables: {\n workspaceId,\n pagination: { first: 50 },\n filter: {\n ...(statusFilter !== 'all' ? { status: statusFilter } : {}),\n ...(search.trim() ? { search: search.trim() } : {}),\n },\n },\n skip: !workspaceId,\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 portfolios: {error.message}</div>;\n }\n\n const portfolios = data?.planmagnetPortfolios.items ?? [];\n const totalCount = data?.planmagnetPortfolios.totalCount ?? 0;\n const statuses = ['all', ...PORTFOLIO_STATUSES];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Portfolios</h1>\n <p className=\"text-sm text-muted-foreground\">\n {totalCount} portfolio{totalCount === 1 ? '' : 's'} across the workspace\n </p>\n </div>\n <Button size=\"sm\" onClick={() => navigate('/products/create-portfolio-page')}>\n <Plus className=\"mr-1 h-4 w-4\" />\n New Portfolio\n </Button>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-4\">\n <Input\n aria-label=\"Search portfolios\"\n placeholder=\"Search by name…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"max-w-xs\"\n />\n <div className=\"flex items-center gap-2 flex-wrap\">\n {statuses.map((s) => (\n <button\n key={s}\n type=\"button\"\n aria-pressed={statusFilter === s}\n onClick={() => setStatusFilter(s)}\n className={`px-2 py-1 text-xs rounded-md capitalize ${statusFilter === s ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {s === 'all' ? 'All' : enumLabel(s)}\n </button>\n ))}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {portfolios.map((pf) => (\n <Card\n key={pf.id}\n {...rowLinkProps(\n () => navigate(detailPath('/products/portfolio-detail-page', pf.id)),\n pf.name\n )}\n >\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Briefcase className=\"h-4 w-4 text-primary\" />\n {pf.name}\n </CardTitle>\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(pf.status)}\n </Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">{pf.description ?? 'No description'}</p>\n <div className=\"flex items-center gap-2 flex-wrap\">\n {pf.alignment && (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {pf.alignment === 'CUSTOM'\n ? (pf.alignmentLabel ?? 'Custom')\n : enumLabel(pf.alignment)}\n </Badge>\n )}\n {pf.fiscalPeriod && (\n <Badge variant=\"outline\" className=\"text-xs\">\n {pf.fiscalPeriod}\n </Badge>\n )}\n </div>\n <div className=\"flex items-center justify-between text-xs text-muted-foreground border-t pt-2\">\n <span>\n Owner: <PersonName id={pf.ownerId} />\n </span>\n <span>\n {pf.budgetTotal != null\n ? `${pf.currency ?? ''} ${pf.budgetTotal.toLocaleString()}`.trim()\n : 'No budget set'}\n </span>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n {portfolios.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n {statusFilter !== 'all' || search\n ? 'No portfolios match the selected filters.'\n : 'No portfolios yet. Create one to group programs and projects for executive oversight.'}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -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":"AA0EA,wBAAgB,6BAA6B,4CAmQ5C"}
|