{{-- Renders a single capacity vs. demand card. Used at two levels: - top-level: one card per program (strategy scope) or per project (program scope) - nested: inside an expanded program card, one compact card per child project Vars in: $c The capacity analysis row (project or program-rolled). $verdictLabels Map of verdict key → translated label. $compactOnly When true, always render the one-liner regardless of verdict. --}} @php $compactOnly = $compactOnly ?? false; $vLabel = $verdictLabels[$c['verdict']] ?? $c['verdict']; $showFull = ! $compactOnly && in_array($c['verdict'], ['critical', 'tight', 'no_capacity'], true); @endphp @if ($showFull) @php // Balance bar geometry — carry the same reading the text states. // Fill 0 → available in the "supply" (green) segment. // Then a distinct "deficit" segment from available → needed, in // the verdict color. Marker sits AT the available position so a // reader instantly sees "we have this much, we need this much". $barMax = max($c['availableHours'], $c['referenceDemand'], 1); $availableMark = min(100, ($c['availableHours'] / $barMax) * 100); $demandWidth = min(100, ($c['referenceDemand'] / $barMax) * 100); $deficitWidth = max(0, $demandWidth - $availableMark); $gapHrs = abs($c['gap']); $gapPct = $c['availableHours'] > 0 ? abs($c['gap'] / $c['availableHours']) * 100 : 0; $isShort = $c['gap'] > 0; // Run-rate framing. Supply is a current weekly rate; projecting it over // a (past) period to a total is the fiction we avoid. Demand is a real // total of estimated work — expressed here as the weekly rate needed to // clear it within the period. The gap % (verdict) is unchanged: rate // ratios equal total ratios, so the bar geometry above still holds. $weeks = max(1, (int) $c['weeksInWindow']); $supplyPerWk = $c['weeklyHoursToProject']; $demandPerWk = $c['referenceDemand'] / $weeks; $gapPerWk = $gapHrs / $weeks; @endphp
{{ $vLabel }}
{{ $c['name'] }}
@if ($c['trustSignal'] === 'budgeted' && $c['effortHours'] > 0) {{ __('stakeholder.rc.cap.trust_high') }} @elseif ($c['trustSignal'] === 'effort') {{ __('stakeholder.rc.cap.trust_effort_short') }} @elseif ($c['trustSignal'] === 'mixed') {{ __('stakeholder.rc.cap.trust_mixed_short') }} @endif @if ($c['referenceDemand'] > 0 && $c['availableHours'] > 0)
@if ($isShort) {{ round($gapPerWk) }}h/wk {{ sprintf(__('stakeholder.rc.cap.short_suffix'), (int) $gapPct) }} @else {{ round($gapPerWk) }}h/wk {{ __('stakeholder.rc.cap.buffer_suffix') }} @endif
@endif
{{ __('stakeholder.rc.cap.scope') }}
@if ($c['openTicketCount'] === 0) {{ __('stakeholder.rc.cap.no_tickets') }} @else @if ($c['budgetedHours'] > 0) {{ round($c['budgetedHours']) }}h {{ __('stakeholder.rc.cap.budgeted') }} ({{ $c['ticketsWithBudget'] }}/{{ $c['openTicketCount'] }} {{ __('stakeholder.rc.cap.tickets_with_hours') }} — {{ (int) ($c['coverage'] * 100) }}% {{ __('stakeholder.rc.cap.coverage') }}) @else {{ __('stakeholder.rc.cap.no_budgeted') }} ({{ $c['openTicketCount'] }} {{ __('stakeholder.rc.cap.open_tickets') }}) @endif
@if ($c['effortPoints'] > 0) {{ round($c['effortHours']) }}h {{ __('stakeholder.rc.cap.effort') }} ({{ $c['effortPoints'] }} pts × {{ $c['hoursPerPoint'] }}h/pt) @else {{ __('stakeholder.rc.cap.no_effort') }} @endif @endif
{{ __('stakeholder.rc.cap.capacity') }}
@if ($c['peopleCount'] === 0) {{ __('stakeholder.rc.cap.no_people') }} @else {{ round($supplyPerWk) }}h/wk {{ __('stakeholder.rc.cap.supply') }} ({{ $c['peopleCount'] }} {{ __($c['peopleCount'] === 1 ? 'stakeholder.rc.cap.person' : 'stakeholder.rc.cap.people') }} × {{ round($supplyPerWk / max(1, $c['peopleCount']), 1) }}h/wk) @endif
@if ($c['referenceDemand'] > 0 && $c['availableHours'] > 0)
{{ __('stakeholder.rc.cap.balance') }}
{{ round($demandPerWk) }}h/wk {{ __('stakeholder.rc.cap.demand') }} · {{ round($supplyPerWk) }}h/wk {{ __('stakeholder.rc.cap.supply') }}
{{-- Supply segment: green fill 0 → available. --}}
{{-- Deficit segment: verdict-color band from available → needed, showing the shortfall. --}} @if ($isShort && $deficitWidth > 0)
@endif
{{ __('stakeholder.rc.cap.marker_label') }}
0h {{ round($barMax) }}h
@endif @if ($isShort && $c['recommendations']['extendWeeks'] > 0) @php $r = $c['recommendations']; @endphp
{{ __('stakeholder.rc.cap.rebalance_hd') }}
{{ __('stakeholder.rc.cap.lever_extend_pre') }} {{ $r['extendWeeks'] }} {{ __('stakeholder.rc.cap.lever_extend_post') }}
{{ sprintf(__('stakeholder.rc.cap.lever_extend_detail'), round($c['weeklyHoursToProject'])) }}
{{ __('stakeholder.rc.cap.lever_add_pre') }} {{ $r['addPeople'] }} {{ $r['addPeople'] === 1 ? __('stakeholder.rc.cap.lever_add_post_one') : __('stakeholder.rc.cap.lever_add_post_many') }}
{{ sprintf(__('stakeholder.rc.cap.lever_add_detail'), round($c['weeklyHoursToProject'] / max(1, $c['peopleCount']), 1), $c['weeksInWindow']) }}
{{ __('stakeholder.rc.cap.lever_cut_pre') }} {{ round($r['cutPoints']) }} {{ __('stakeholder.rc.cap.lever_cut_post') }}
{{ sprintf(__('stakeholder.rc.cap.lever_cut_detail'), round($r['cutHours']), $c['hoursPerPoint']) }}
@endif
@else {{-- Compact one-liner --}}
@if ($c['verdict'] === 'buffer')@else@endif {{ $vLabel }}
{{ $c['name'] }}
@if ($c['verdict'] === 'no_work') {{ __('stakeholder.rc.cap.summary_no_work') }} @else {{ round($c['referenceDemand']) }}h {{ __('stakeholder.rc.cap.summary_needed') }} · {{ round($c['availableHours']) }}h {{ __('stakeholder.rc.cap.summary_available') }} @endif
@endif