@php use App\Services\AnalysisComparisonService; $totals = $diff['totals']; $sections = $diff['sections']; $gapDays = $diff['gap_days']; $autoSum = $diff['auto_summary'] ?? ''; $anomalies = $diff['anomalies'] ?? []; $trends = $diff['trends'] ?? ['positive' => [], 'negative' => []]; $leftDate = $left->finished_at?->format('Y-m-d') ?? '—'; $rightDate = $right->finished_at?->format('Y-m-d') ?? '—'; $totalCompared = $totals['improved'] + $totals['declined'] + $totals['flat']; $totalAll = $totalCompared + $totals['missing']; $netScore = $totals['improved'] - $totals['declined']; $improvedPct = $totalCompared > 0 ? round($totals['improved'] / $totalCompared * 100) : 0; $declinedPct = $totalCompared > 0 ? round($totals['declined'] / $totalCompared * 100) : 0; $flatPct = max(0, 100 - $improvedPct - $declinedPct); $isBudget = $mode === AnalysisComparisonService::MODE_BUDGET; $isAnnual = $mode === AnalysisComparisonService::MODE_ANNUAL; // Column captions depend on the mode. [$leftCaption, $rightCaption] = $isBudget ? [__('Budget'), __('Actual')] : ($isAnnual ? [__('Last year'), __('This year')] : [__('Earlier'), __('Now')]); $headline = match (true) { $totalCompared === 0 => ['copy' => __('Nothing comparable yet'), 'tone' => 'neutral'], $netScore >= 3 => ['copy' => __('Most things got better'), 'tone' => 'good'], $netScore > 0 => ['copy' => __('Slightly better overall'), 'tone' => 'good'], $netScore === 0 => ['copy' => __('Mixed picture'), 'tone' => 'neutral'], $netScore < -3 => ['copy' => __('Most things got worse'), 'tone' => 'bad'], default => ['copy' => __('Slightly worse overall'), 'tone' => 'bad'], }; @endphp {{-- ── DATE STRIP / PERIOD CHIPS / PICKER ──────────────────────────── --}}
{{ $leftDate }}
@if($gapDays !== null) {{ trans_choice('{1} :n day|[2,*] :n days', $gapDays, ['n' => $gapDays]) }} @endif
{{ $rightDate }}
@if($mode === AnalysisComparisonService::MODE_PERIOD)
@foreach($periodChips as $key => $label) @php $active = $period === $key; @endphp {{ $label }} @endforeach
@elseif($mode === AnalysisComparisonService::MODE_BUDGET)
@else
@endif {{-- ── HEADLINE SUMMARY CARD ───────────────────────────────────────── --}}
@if($headline['tone'] === 'good') @elseif($headline['tone'] === 'bad') @else @endif
{{ __('Overall trend') }}

{{ $headline['copy'] }}

@if($autoSum)

{{ $autoSum }}

@endif

{{ __(':compared of :total indicators could be compared.', ['compared' => $totalCompared, 'total' => $totalAll]) }}

@if($totalCompared > 0)
{{ $totals['improved'] }}
{{ __('Better') }}
{{ $totals['flat'] }}
{{ __('Flat') }}
{{ $totals['declined'] }}
{{ __('Worse') }}
{{ $totals['anomalies'] ?? 0 }}
{{ __('Outliers') }}
@endif
{{-- ── ANOMALIES & TRENDS CALLOUT ──────────────────────────────────── --}} @if(!empty($anomalies) || !empty($trends['positive']) || !empty($trends['negative']))
@if(!empty($trends['positive']))

{{ __('Positive trends') }}

@endif @if(!empty($trends['negative']))

{{ __('Negative trends') }}

@endif @if(!empty($anomalies))

{{ __('Anomalies') }}

@endif
@endif {{-- ── AI NARRATIVE ────────────────────────────────────────────────── --}}
{{ __('Smart commentary') }}

{{ __('Automatic explanation') }}

@if($narrative) {{ __('Updated :when.', ['when' => $narrative->updated_at?->diffForHumans()]) }} @else {{ __('Click Generate to produce a plain-language explanation.') }} @endif

@csrf @if($isBudget)@endif
@if($narrative)
{!! $narrative->narrative_html !!}
@if($narrative->model){{ __('Model') }}: {{ $narrative->model }}@endif @if($narrative->tokens_used)· {{ number_format($narrative->tokens_used) }} {{ __('tokens') }}@endif @if($narrative->language)· {{ strtoupper($narrative->language) }}@endif @if($narrative->provider)· {{ strtoupper($narrative->provider) }}@endif
@else

{{ __('No explanation yet') }}

{{ __('Click Generate above to produce a plain-language explanation. Takes ~30 seconds.') }}

@endif
{{-- ── INDICATOR LIST (redesigned) ────────────────────────────────────── Dense table-style layout: each row shows label · earlier · delta bar · now · % chip. The delta bar visualizes the magnitude of change (width relative to the strongest move on screen) and is colour-coded by direction. Anomalies float to the top of each section and get a subtle amber tint. Section headers stay sticky while scrolling. --}} @if(count($sections) === 0)
{{ __('Nothing comparable between these two reports') }}
@else @php // Reference scale for the delta bars: widest absolute pct across the // entire visible diff. Capped at 100 so a 300% outlier still leaves // a readable bar for the 15% movers next to it. $maxAbsPct = 0.0; foreach ($sections as $s) { foreach ($s['rows'] as $r) { if ($r['pct'] !== null) $maxAbsPct = max($maxAbsPct, abs((float) $r['pct'])); } } $barScale = max(15.0, min(100.0, $maxAbsPct ?: 1.0)); // Per-row bar width as a CSS percentage. Tiny floor (3%) so flat-but- // non-zero rows still render a visible nub. $barWidth = function ($pct) use ($barScale) { if ($pct === null) return 0; $rel = min(100, (abs((float) $pct) / $barScale) * 100); return $pct == 0 ? 0 : max(3, $rel); }; @endphp {{-- Toolbar: title + counter + filter pills --}}

{{ __('Indicator-by-indicator') }}

{{ __(':compared of :total indicators could be compared.', ['compared' => $totalCompared, 'total' => $totalAll]) }}

@foreach($sections as $section) @php $catTotals = $section['totals']; $catGood = $catTotals['improved']; $catBad = $catTotals['declined']; $catAnom = $catTotals['anomalies'] ?? 0; @endphp
{{-- Section header — sticky band with title + auto-comment + counts --}}

{{ __($section['label']) }}

@if(!empty($section['auto_comment'])) @endif
@if($catGood) {{ $catGood }} @endif @if($catBad) {{ $catBad }} @endif @if($catAnom) {{ $catAnom }} @endif
{{-- Compact header row (desktop only) --}}
    @foreach($section['rows'] as $row) @php $dir = $row['direction']; $isAnomaly = $row['anomaly'] ?? false; $pct = $row['pct']; $bw = $barWidth($pct); $barColor = match($dir) { 'improved' => 'bg-emerald-500/70 dark:bg-emerald-400/70', 'declined' => 'bg-rose-500/70 dark:bg-rose-400/70', 'flat' => 'bg-slate-300 dark:bg-slate-600', default => 'bg-amber-400/70', }; $chipCls = match($dir) { 'improved' => 'bg-emerald-50 text-emerald-700 ring-emerald-200/60 dark:bg-emerald-900/30 dark:text-emerald-200 dark:ring-emerald-800/50', 'declined' => 'bg-rose-50 text-rose-700 ring-rose-200/60 dark:bg-rose-900/30 dark:text-rose-200 dark:ring-rose-800/50', 'flat' => 'bg-slate-100 text-slate-600 ring-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:ring-slate-700', default => 'bg-amber-50 text-amber-700 ring-amber-200 dark:bg-amber-900/30 dark:text-amber-200 dark:ring-amber-800/50', }; $dotColor = match($dir) { 'improved' => 'bg-emerald-500', 'declined' => 'bg-rose-500', 'flat' => 'bg-slate-400 dark:bg-slate-600', default => 'bg-amber-500', }; $rowTint = $isAnomaly ? 'bg-amber-50/40 dark:bg-amber-900/10' : ''; @endphp
  • {{-- Label + anomaly badge --}}
    {{ $row['label'] }} @if($isAnomaly) {{ __('Outlier') }} @endif
    @if(! empty($row['comment']))
    {{ $row['comment'] }}
    @endif
    {{-- Earlier value --}}
    {{ $leftCaption }}: {{ $fmtVal($row['left'], $row['unit']) }}
    {{-- Delta bar (the visual centerpiece) --}} {{-- Now value --}}
    {{ $rightCaption }}: {{ $fmtVal($row['right'], $row['unit']) }}
    {{-- Status chip with % --}}
    @if($dir === 'improved') @elseif($dir === 'declined') @elseif($dir === 'flat') @endif @if($pct !== null) {{ $pct > 0 ? '+' : '' }}{{ number_format((float) $pct, abs((float) $pct) >= 10 ? 0 : 1) }}% @else — @endif
  • @endforeach
@endforeach
@endif