{{-- Stakeholder Report — 4-page deck shell. Reused by both StrategyPro (strategy scope) and PgmPro (program scope). Data passed in via @include vars; this partial owns: - persistent header (subject, period, updated, status verdict) - global controls (period picker, print) - deck navigation (4 tabs + swipe + arrow keys + arrow buttons) - the 4 page containers (Overview / Logic Model / Resources & Coverage / Programs) - scoped CSS with `minmax(0,1fr)` discipline (§2 layout constraint) - print stylesheet expanding the deck (§7) Vars in: $scope 'strategy' | 'program' $subject string — displayed in the header $period ReportPeriod $updatedAt string $verdict 'ontrack' | 'atrisk' | 'off' | 'unknown' $verdictLabel string — the visible verdict $verdictSource string — provenance line (never hidden, per §3) $report ReportEngine::buildReport() output $stats $report['stats'] $deltas $report['deltas'] $needsAttn $report['needsAttention'] $logicModel null | {canvasId, narrative, stageProgress, healthBadges, coverageMatrix} $goalsGroup {goals, byProject, counts} — strategy: strategyGoals; program: programGoals $programRows array — strategy only, empty at program scope $programUpdates array — strategy only, empty at program scope --}} @php use Leantime\Domain\Reports\Models\ReportPeriod; $verdictDotColor = match ($verdict) { 'ontrack' => '#3E937A', 'inprogress' => '#3F72B0', 'atrisk' => '#C09035', 'off' => '#C2295B', default => '#9CA3AF', }; $completedCount = (int) ($stats['completed'] ?? 0); $overdueCount = (int) ($stats['overdue'] ?? 0); $goalsOnTrack = (int) ($stats['goalsOnTrack'] ?? 0); $goalsTotal = (int) ($stats['goalsTotal'] ?? 0); $hoursLogged = (float) ($stats['hoursLogged'] ?? 0); $completedDelta = (int) ($deltas['completedDelta'] ?? 0); $hasLM = $logicModel !== null; // Semantic period label — the "why this period" chip in the header sub-line. // Board audiences care WHY the report is showing this range (because it's // last closed) more than the raw dates, which appear separately in the picker. $periodMeaning = match ($period->preset) { ReportPeriod::PRESET_LAST_QUARTER => __('stakeholder.period.last_closed'), ReportPeriod::PRESET_THIS_QUARTER => __('stakeholder.period.in_progress'), ReportPeriod::PRESET_NEXT_QUARTER => __('stakeholder.period.upcoming'), ReportPeriod::PRESET_CUSTOM => __('stakeholder.period.custom'), default => '', }; // Preset name for the picker button — matches what the user selects in the // dropdown ("Last quarter" / "This quarter" / "Next quarter"). Deliberately // NOT "Q2 2026" — Leantime doesn't let companies define fiscal quarters, so // a calendar Q# label would be a lie for anyone whose fiscal year isn't // calendar-aligned. The literal date range is shown next to it. $presetName = match ($period->preset) { ReportPeriod::PRESET_LAST_QUARTER => __('label.period_last_quarter'), ReportPeriod::PRESET_THIS_QUARTER => __('label.period_this_quarter'), ReportPeriod::PRESET_NEXT_QUARTER => __('label.period_next_quarter'), ReportPeriod::PRESET_CUSTOM => __('label.period_custom'), default => __('label.period_this_quarter'), }; // Reload URL bases for the period picker preset links. $reportUrl = BASE_URL.'/'.($scope === 'strategy' ? 'strategyPro' : 'pgmPro').'/report'; @endphp @php // Prefer a $rdDark boolean passed by the caller/composer; fall back to the // Theme service only when it isn't supplied, so the view isn't required to // do a container lookup. $rdDark = $rdDark ?? (app()->make(\Leantime\Core\UI\Theme::class)->getColorMode() === 'dark'); @endphp
{{-- ── Persistent document header (doc-shell) ─────────────────────── breadcrumb + subject switcher + status verdict + actions live in the card; the plugin collapses the shared teal pageheader (body.report-doc) so the report reads as one document. Switcher + actions degrade safely when the caller doesn't pass switchableSubjects / projectId. --}}
{{-- One report = ONE subject; Task-view breadcrumb flow ("Report // {subject}", like "To-Dos // All To-Dos"). The scope label and period meaning are redundant here — the breadcrumb says Report, the period picker below owns the period context — so the under-text keeps only freshness. --}}

{{ __('stakeholder.header.crumb_report') }} {{ $subject }}

{{-- RIGHT = what about it: verdict with freshness stacked under it, centered as one group against the title row, then ⋮. --}}
{{-- Provenance ("set 1 month ago · overrides metrics") is secondary: it lives in the tooltip so the right side stays one balanced, vertically-centered row with the actions menu. --}}
{{ $verdictLabel }}
{{ __('stakeholder.header.updated') }} {{ $updatedAt }}
@if (! empty($projectId ?? null))
@include('reports::partials.stakeholder.actionsMenu', [ 'scope' => $scope, 'projectId' => $projectId, 'verdictOverride' => $verdictOverride ?? null, ])
@endif
{{-- ── Tab bar + period picker on ONE row (saves a full row of vertical space; picker sits with the view-mode controls it belongs with) ── --}}
{{-- Framed segmented tab group (mirrors the global .tabs nav): the tabs sit in one outlined container so they read as a connected control, the active one a white segment inside it. --}} {{--
{{-- ── Deck ─────────────────────────────────────────────────────── --}}
{{-- ═══ Page 1 — Overview ═════════════════════════════ --}}
@include('reports::partials.stakeholder.page-overview', compact( 'completedCount', 'completedDelta', 'goalsOnTrack', 'goalsTotal', 'overdueCount', 'hoursLogged', 'needsAttn', 'logicModel', 'hasLM', 'goalsGroup', 'report', 'strategyUpdates', 'programUpdates', 'programRows' ))
{{-- ═══ Page 2 — Logic Model read-out ═════════════════ --}}
@include('reports::partials.stakeholder.page-lm', compact('logicModel', 'hasLM', 'report'))
{{-- ═══ Page 3 — Resources & Coverage ═════════════════ --}}
@include('reports::partials.stakeholder.page-resources', compact('logicModel', 'hasLM', 'resourceSummary', 'report', 'scope', 'capacityAnalysis', 'programMeta', 'programChildMap', 'capacityByProgram') + ['projectId' => $projectId ?? null])
{{-- ═══ Page 4 — Impact Journey ═══════════════════════ --}}
@include('reports::partials.stakeholder.page-impact-journey', compact('scope', 'logicModel', 'hasLM'))