{{-- Stakeholder Report — Page 4 (Programs & Narrative) §5 page 4: Two columns — program rows with RAG + completed count on the left, status narrative from statusUpdates on the right. "Also this period" (secondary closures) at the bottom. Vars in: $scope 'strategy' | 'program' $programRows object[] (strategy scope; empty at program scope) $programUpdates array (byProject at strategy scope) --}}
{{-- ── Programs column (strategy scope only) ─────────────────── --}}
{{ $scope === 'strategy' ? __('stakeholder.programs.label_programs') : __('stakeholder.programs.label_child_projects') }}
@if (count($programRows) === 0)
{{ $scope === 'strategy' ? __('stakeholder.programs.none') : __('stakeholder.programs.none_projects') }}
@else @foreach ($programRows as $row) @php $row = (array) $row; // Status → dot color. programRows carries a status field from Marcel's // buildProgramRows (worst-of-children rollup); values: green/yellow/red/null. $status = (string) ($row['status'] ?? ''); $dotColor = match ($status) { 'green' => 'var(--rd-ok)', 'yellow' => 'var(--rd-warn)', 'red' => 'var(--rd-danger)', default => 'var(--rd-text-4)', }; $statusLabel = match ($status) { 'green' => __('stakeholder.programs.status_ontrack'), 'yellow' => __('stakeholder.programs.status_atrisk'), 'red' => __('stakeholder.programs.status_off'), default => __('stakeholder.programs.status_none'), }; $completedCt = (int) ($row['completedCount'] ?? 0); @endphp
{{ $row['name'] ?? '' }} {{ $statusLabel }} · {{ sprintf(__('stakeholder.programs.done_count'), $completedCt) }}
@endforeach @endif
{{-- ── Status narrative column ───────────────────────────────── --}}
{{ __('stakeholder.programs.label_narrative') }}
@php // programUpdates is keyed by projectId. Flatten with a name lookup from // programRows for the bold label per line. $namesByProject = []; foreach ($programRows as $row) { $row = (array) $row; $namesByProject[(int) ($row['id'] ?? 0)] = $row['name'] ?? ''; } $flatUpdates = []; foreach ($programUpdates as $pid => $updates) { foreach ($updates as $update) { $update = (object) $update; $update->_projectName = $namesByProject[(int) $pid] ?? ''; $flatUpdates[] = $update; } } // Sort newest first — Marcel returns in the same order per project; // for cross-project flat display we re-sort by date desc. usort($flatUpdates, fn ($a, $b) => strcmp((string) ($b->date ?? ''), (string) ($a->date ?? ''))); $flatUpdates = array_slice($flatUpdates, 0, 5); // top 5 for the packet view @endphp @if (count($flatUpdates) === 0)
{{ __('stakeholder.programs.no_updates') }}
@else @foreach ($flatUpdates as $u) @php $date = ! empty($u->dateParsed) ? $u->dateParsed->setToUserTimezone()->format('M j') : ''; $text = trim(strip_tags((string) ($u->text ?? ''))); // Truncate long updates — board views want the executive summary. if (mb_strlen($text) > 220) $text = mb_substr($text, 0, 217).'…'; @endphp
@if (! empty($u->_projectName)) {{ $u->_projectName }} — @endif {{ $text }} @if ($date !== '') {{ $date }} @endif
@endforeach @endif
{{-- "Also this period" — secondary closures beyond the peak-this-period hero. Nomination surface for closures that could be attached to an outcome. Requires the nomination pass that also feeds the p1 hero; render coming-soon until that lands, to avoid pretending an empty state is a full one. --}}
{{ __('stakeholder.programs.also_label') }} — {{ __('stakeholder.programs.also_coming') }}