/** * EffortDisplay - Shared component for displaying FTE effort calculations * * Used in: * - ApplicationInfo.tsx (detail page) * - GovernanceModelHelper.tsx (governance helper page) */ import type { EffortCalculationBreakdown } from '../types'; export interface EffortDisplayProps { /** The effective FTE value (after override if applicable) */ effectiveFte: number | null; /** The calculated FTE value (before override) */ calculatedFte?: number | null; /** Override FTE value if set */ overrideFte?: number | null; /** Full breakdown from effort calculation */ breakdown?: EffortCalculationBreakdown | null; /** Whether this is a preview (unsaved changes) */ isPreview?: boolean; /** Show full details (expanded view) */ showDetails?: boolean; /** Show override input field */ showOverrideInput?: boolean; /** Callback when override value changes */ onOverrideChange?: (value: number | null) => void; } // Display-only constants (for showing the formula, NOT for calculation) // The actual calculation happens in backend/src/services/effortCalculation.ts const HOURS_PER_WEEK_DISPLAY = 36; const WORK_WEEKS_PER_YEAR_DISPLAY = 46; const DECLARABLE_PERCENTAGE_DISPLAY = 0.75; export function EffortDisplay({ effectiveFte, calculatedFte, overrideFte, breakdown, isPreview = false, showDetails = true, showOverrideInput = false, onOverrideChange, }: EffortDisplayProps) { const hasOverride = overrideFte !== null && overrideFte !== undefined; const hasBreakdown = breakdown !== null && breakdown !== undefined; // Extract breakdown values const baseEffort = breakdown?.baseEffort ?? null; const baseEffortMin = breakdown?.baseEffortMin ?? null; const baseEffortMax = breakdown?.baseEffortMax ?? null; const numberOfUsersFactor = breakdown?.numberOfUsersFactor ?? { value: 1.0, name: null }; const dynamicsFactor = breakdown?.dynamicsFactor ?? { value: 1.0, name: null }; const complexityFactor = breakdown?.complexityFactor ?? { value: 1.0, name: null }; // Calculate final min/max FTE by applying factors to base min/max const factorMultiplier = numberOfUsersFactor.value * dynamicsFactor.value * complexityFactor.value; const finalMinFTE = baseEffortMin !== null ? baseEffortMin * factorMultiplier : null; const finalMaxFTE = baseEffortMax !== null ? baseEffortMax * factorMultiplier : null; const governanceModelName = breakdown?.governanceModelName ?? breakdown?.governanceModel ?? null; const applicationTypeName = breakdown?.applicationType ?? null; const businessImpactAnalyse = breakdown?.businessImpactAnalyse ?? null; const applicationManagementHosting = breakdown?.applicationManagementHosting ?? null; const warnings = breakdown?.warnings ?? []; const errors = breakdown?.errors ?? []; const usedDefaults = breakdown?.usedDefaults ?? []; const requiresManualAssessment = breakdown?.requiresManualAssessment ?? false; const isFixedFte = breakdown?.isFixedFte ?? false; // Use hours from backend breakdown (calculated in effortCalculation.ts) // Only fall back to local calculation if breakdown is not available const declarableHoursPerYear = breakdown?.hoursPerYear ?? (effectiveFte !== null ? HOURS_PER_WEEK_DISPLAY * WORK_WEEKS_PER_YEAR_DISPLAY * effectiveFte * DECLARABLE_PERCENTAGE_DISPLAY : 0); const hoursPerMonth = breakdown?.hoursPerMonth ?? declarableHoursPerYear / 12; const hoursPerWeekCalculated = breakdown?.hoursPerWeek ?? declarableHoursPerYear / WORK_WEEKS_PER_YEAR_DISPLAY; const minutesPerWeek = hoursPerWeekCalculated * 60; // For display of netto hours (before declarable percentage) const netHoursPerYear = effectiveFte !== null ? HOURS_PER_WEEK_DISPLAY * WORK_WEEKS_PER_YEAR_DISPLAY * effectiveFte : 0; // No effort calculated if (effectiveFte === null || effectiveFte === undefined) { if (errors.length > 0) { return (