Implementing report profiles and disclosure policy as per spec 357. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #428
672 lines
32 KiB
PHP
672 lines
32 KiB
PHP
@php
|
|
/** @var array<string, mixed> $report */
|
|
$badgeClasses = [
|
|
'success' => 'background:#ecfdf3;color:#166534;border-color:#bbf7d0;',
|
|
'warning' => 'background:#fffbeb;color:#92400e;border-color:#fde68a;',
|
|
'danger' => 'background:#fef2f2;color:#991b1b;border-color:#fecaca;',
|
|
'gray' => 'background:#f3f4f6;color:#374151;border-color:#d1d5db;',
|
|
];
|
|
$hero = is_array($report['hero'] ?? null) ? $report['hero'] : [];
|
|
$branding = is_array($report['branding'] ?? null) ? $report['branding'] : [];
|
|
$managementSummary = is_array($report['management_summary'] ?? null) ? $report['management_summary'] : [];
|
|
$evidenceBasis = is_array($report['evidence_basis'] ?? null) ? $report['evidence_basis'] : [];
|
|
$profile = is_array($report['profile'] ?? null) ? $report['profile'] : [];
|
|
$disclosurePolicy = is_array($report['disclosure_policy'] ?? null) ? $report['disclosure_policy'] : [];
|
|
$sourceMetadata = is_array($report['source_metadata'] ?? null) ? $report['source_metadata'] : [];
|
|
$heroBadgeStyle = $badgeClasses[$hero['color'] ?? 'gray'] ?? $badgeClasses['gray'];
|
|
$boundaryBadgeStyle = $badgeClasses[$report['guidance']['boundary_color'] ?? 'gray'] ?? $badgeClasses['gray'];
|
|
$proofBadgeStyles = [
|
|
'verified' => $badgeClasses['success'],
|
|
'assumed' => $badgeClasses['warning'],
|
|
'missing' => $badgeClasses['danger'],
|
|
'unknown' => $badgeClasses['gray'],
|
|
'not_applicable' => $badgeClasses['gray'],
|
|
];
|
|
$requestedProfile = $sourceMetadata['requested_profile'] ?? ($profile['requested_key'] ?? null);
|
|
$generatedAt = $report['generated_at'] ?? null;
|
|
$publishedAt = $report['published_at'] ?? null;
|
|
@endphp
|
|
<!DOCTYPE html>
|
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{{ $report['title'] }} · {{ $report['tenant_name'] }}</title>
|
|
<style>
|
|
:root {
|
|
color-scheme: light;
|
|
--ink: #111827;
|
|
--muted: #4b5563;
|
|
--soft: #64748b;
|
|
--line: #d9e2ec;
|
|
--paper: #fffdf8;
|
|
--panel: #f8fafc;
|
|
--accent: #0f766e;
|
|
--accent-soft: #ccfbf1;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
margin: 0;
|
|
color: var(--ink);
|
|
background:
|
|
radial-gradient(circle at 8% 8%, rgba(20, 83, 45, 0.08), transparent 34%),
|
|
radial-gradient(circle at 90% 4%, rgba(15, 118, 110, 0.12), transparent 28%),
|
|
linear-gradient(180deg, #eef7f4 0%, #f8fafc 24%, #e5e7eb 100%);
|
|
font-family: Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
}
|
|
a { color: inherit; }
|
|
.page {
|
|
width: min(1080px, calc(100% - 32px));
|
|
margin: 0 auto;
|
|
padding: 28px 0 64px;
|
|
}
|
|
.report-toolbar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 18px;
|
|
color: var(--soft);
|
|
font-size: 13px;
|
|
}
|
|
.toolbar-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
.app-action {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid rgba(148, 163, 184, 0.5);
|
|
border-radius: 999px;
|
|
padding: 8px 12px;
|
|
background: rgba(255, 255, 255, 0.82);
|
|
color: #334155;
|
|
text-decoration: none;
|
|
font: 700 12px/1.2 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
cursor: pointer;
|
|
}
|
|
.app-action--primary {
|
|
background: #0f172a;
|
|
border-color: #0f172a;
|
|
color: #fff;
|
|
}
|
|
.report-canvas {
|
|
overflow: hidden;
|
|
border: 1px solid rgba(148, 163, 184, 0.42);
|
|
border-radius: 30px;
|
|
background: var(--paper);
|
|
box-shadow: 0 28px 70px rgba(15, 23, 42, 0.14);
|
|
}
|
|
.cover {
|
|
padding: clamp(28px, 5vw, 56px);
|
|
background:
|
|
linear-gradient(135deg, rgba(15, 118, 110, 0.14), transparent 46%),
|
|
linear-gradient(180deg, #ffffff 0%, #fffdf8 100%);
|
|
border-bottom: 1px solid rgba(148, 163, 184, 0.35);
|
|
}
|
|
.co-brand {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px 18px;
|
|
justify-content: space-between;
|
|
color: var(--soft);
|
|
font: 700 12px/1.4 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
}
|
|
.eyebrow {
|
|
margin-top: 46px;
|
|
color: var(--accent);
|
|
font: 800 12px/1.4 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
}
|
|
h1 {
|
|
max-width: 12ch;
|
|
margin: 12px 0 14px;
|
|
font-family: Georgia, Cambria, "Times New Roman", serif;
|
|
font-size: clamp(2.6rem, 6vw, 5.2rem);
|
|
line-height: 0.93;
|
|
letter-spacing: -0.05em;
|
|
}
|
|
.hero-summary {
|
|
max-width: 76ch;
|
|
margin: 0;
|
|
color: var(--muted);
|
|
font: 500 17px/1.7 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
}
|
|
.badges {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin: 22px 0 0;
|
|
}
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 8px 12px;
|
|
border: 1px solid;
|
|
border-radius: 999px;
|
|
font: 800 11px/1.2 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
.share-warning {
|
|
margin-top: 22px;
|
|
border: 1px solid #fbbf24;
|
|
border-left: 5px solid #d97706;
|
|
border-radius: 18px;
|
|
background: #fffbeb;
|
|
padding: 15px 18px;
|
|
color: #78350f;
|
|
font: 800 14px/1.55 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
}
|
|
.meta-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
gap: 12px;
|
|
margin-top: 26px;
|
|
}
|
|
.meta-item {
|
|
border-top: 1px solid rgba(148, 163, 184, 0.5);
|
|
padding-top: 12px;
|
|
}
|
|
.meta-label, .field-label {
|
|
margin: 0;
|
|
color: var(--soft);
|
|
font: 800 10px/1.2 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
.meta-value, .field-value {
|
|
margin: 7px 0 0;
|
|
color: var(--ink);
|
|
font: 700 14px/1.45 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
}
|
|
.content {
|
|
display: grid;
|
|
gap: 24px;
|
|
padding: clamp(24px, 4vw, 44px);
|
|
}
|
|
.section {
|
|
padding-bottom: 24px;
|
|
border-bottom: 1px solid rgba(148, 163, 184, 0.32);
|
|
}
|
|
.section:last-child {
|
|
border-bottom: 0;
|
|
padding-bottom: 0;
|
|
}
|
|
h2 {
|
|
margin: 0 0 12px;
|
|
font-family: Georgia, Cambria, "Times New Roman", serif;
|
|
font-size: clamp(1.55rem, 3vw, 2.15rem);
|
|
line-height: 1.05;
|
|
letter-spacing: -0.025em;
|
|
}
|
|
h3 {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
line-height: 1.35;
|
|
}
|
|
.copy, .empty-state, .list {
|
|
color: var(--ink);
|
|
font: 500 15px/1.75 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
}
|
|
.empty-state {
|
|
margin: 0;
|
|
color: var(--muted);
|
|
}
|
|
.summary-grid, .risk-grid, .appendix-grid {
|
|
display: grid;
|
|
gap: 14px;
|
|
}
|
|
.policy-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
gap: 14px;
|
|
margin-top: 14px;
|
|
}
|
|
.summary-grid {
|
|
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
|
margin-top: 14px;
|
|
}
|
|
.summary-field, .risk-card, .appendix-card, .technical-card {
|
|
border: 1px solid rgba(148, 163, 184, 0.32);
|
|
border-radius: 18px;
|
|
background: rgba(248, 250, 252, 0.72);
|
|
padding: 16px;
|
|
}
|
|
.limitations {
|
|
display: grid;
|
|
gap: 12px;
|
|
margin-top: 14px;
|
|
}
|
|
.limitation {
|
|
border: 1px solid #fde68a;
|
|
border-left: 5px solid #d97706;
|
|
border-radius: 18px;
|
|
background: #fffbeb;
|
|
padding: 16px 18px;
|
|
}
|
|
.limitation p, .risk-card p, .appendix-card p {
|
|
margin: 8px 0 0;
|
|
}
|
|
.note {
|
|
color: var(--muted);
|
|
font: 500 13px/1.65 Avenir, "Avenir Next", "Segoe UI", sans-serif;
|
|
}
|
|
.list {
|
|
margin: 0;
|
|
padding-left: 20px;
|
|
}
|
|
.list li + li { margin-top: 10px; }
|
|
.risk-grid {
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
}
|
|
.supporting {
|
|
background: #f8fafc;
|
|
border-radius: 24px;
|
|
padding: 22px;
|
|
}
|
|
.appendix-grid {
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
margin-top: 14px;
|
|
}
|
|
.appendix-card + .appendix-card { margin-top: 14px; }
|
|
.technical-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 10px;
|
|
margin-top: 14px;
|
|
}
|
|
.screen-only { display: block; }
|
|
body.print-preview-smoke .report-toolbar,
|
|
body.print-preview-smoke .screen-only {
|
|
display: none !important;
|
|
}
|
|
@media print {
|
|
body { background: #fff; }
|
|
.page { width: 100%; padding: 0; }
|
|
.report-toolbar, .screen-only { display: none !important; }
|
|
.report-canvas {
|
|
border: 0;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
}
|
|
.cover, .section, .appendix-card, .risk-card, .summary-field, .technical-card {
|
|
break-inside: avoid;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
<div class="report-toolbar screen-only" data-testid="rendered-report-toolbar">
|
|
<div>{{ __('localization.review.rendered_report_html_only') }}</div>
|
|
|
|
<div class="toolbar-actions">
|
|
@if (filled($report['review_url'] ?? null))
|
|
<a class="app-action" href="{{ $report['review_url'] }}" target="_blank" rel="noreferrer">
|
|
{{ __('localization.review.return_to_review_detail') }}
|
|
</a>
|
|
@endif
|
|
|
|
@if (filled($report['review_pack_url'] ?? null))
|
|
<a class="app-action" href="{{ $report['review_pack_url'] }}" target="_blank" rel="noreferrer">
|
|
{{ __('localization.review.return_to_review_pack_detail') }}
|
|
</a>
|
|
@endif
|
|
|
|
@if (filled($report['download_url'] ?? null))
|
|
<a class="app-action" href="{{ $report['download_url'] }}" target="_blank" rel="noreferrer">
|
|
{{ $report['download_label'] ?? __('localization.review.download_review_pack_with_limitations') }}
|
|
</a>
|
|
@endif
|
|
|
|
<button class="app-action app-action--primary" type="button" onclick="window.print()">
|
|
{{ __('localization.review.print_rendered_report') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<main class="report-canvas" data-testid="rendered-report-canvas">
|
|
<section class="cover" data-testid="rendered-report-hero">
|
|
<div class="co-brand">
|
|
<span>{{ __('localization.review.prepared_by_for', [
|
|
'prepared_by' => $branding['prepared_by'] ?? 'TenantPilot',
|
|
'prepared_for' => $branding['prepared_for'] ?? $report['tenant_name'],
|
|
]) }}</span>
|
|
<span>{{ __('localization.review.generated_by', ['generated_by' => $branding['generated_by'] ?? 'TenantPilot']) }}</span>
|
|
</div>
|
|
|
|
<div class="eyebrow">{{ __('localization.review.governance_review_report') }}</div>
|
|
<h1>{{ $hero['title'] ?? $report['title'] }}</h1>
|
|
<p class="hero-summary">{{ $hero['summary'] ?? $report['executive_summary'] }}</p>
|
|
|
|
<div class="badges">
|
|
<span class="badge" style="{{ $heroBadgeStyle }}">{{ $hero['badge'] ?? __('localization.review.requires_review') }}</span>
|
|
<span class="badge" style="{{ $boundaryBadgeStyle }}">{{ $report['guidance']['boundary_label'] ?? __('localization.review.requires_review') }}</span>
|
|
</div>
|
|
|
|
@if (filled($hero['warning'] ?? null))
|
|
<div class="share-warning" data-testid="rendered-report-sharing-warning">{{ $hero['warning'] }}</div>
|
|
@endif
|
|
|
|
<div class="meta-row">
|
|
<div class="meta-item">
|
|
<p class="meta-label">{{ __('localization.review.review_status') }}</p>
|
|
<p class="meta-value">#{{ $report['review_id'] }} · {{ $report['review_status_label'] }}</p>
|
|
</div>
|
|
<div class="meta-item">
|
|
<p class="meta-label">{{ __('localization.review.evidence_basis') }}</p>
|
|
<p class="meta-value">{{ $evidenceBasis['label'] ?? __('localization.review.unavailable') }}</p>
|
|
</div>
|
|
<div class="meta-item">
|
|
<p class="meta-label">{{ __('localization.review.generated_at') }}</p>
|
|
<p class="meta-value">{{ $generatedAt?->format('Y-m-d H:i') ?? '—' }}</p>
|
|
</div>
|
|
<div class="meta-item">
|
|
<p class="meta-label">{{ __('localization.review.published_at') }}</p>
|
|
<p class="meta-value">{{ $publishedAt?->format('Y-m-d H:i') ?? '—' }}</p>
|
|
</div>
|
|
<div class="meta-item">
|
|
<p class="meta-label">{{ __('localization.review.report_effective_profile') }}</p>
|
|
<p class="meta-value">{{ $profile['label'] ?? __('localization.review.unavailable') }}</p>
|
|
</div>
|
|
<div class="meta-item">
|
|
<p class="meta-label">{{ __('localization.review.report_audience') }}</p>
|
|
<p class="meta-value">{{ $profile['audience_label'] ?? __('localization.review.unavailable') }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="content">
|
|
<section class="section" data-testid="rendered-report-executive-summary">
|
|
<h2>{{ __('localization.review.executive_summary') }}</h2>
|
|
<p class="copy">{{ $report['executive_summary'] }}</p>
|
|
|
|
<div class="summary-grid">
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.overall_state') }}</p>
|
|
<p class="field-value">{{ $managementSummary['overall_state'] ?? ($hero['title'] ?? '') }}</p>
|
|
</div>
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.reason') }}</p>
|
|
<p class="field-value">{{ $managementSummary['reason'] ?? '' }}</p>
|
|
</div>
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.impact') }}</p>
|
|
<p class="field-value">{{ $managementSummary['impact'] ?? '' }}</p>
|
|
</div>
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.recommended_next_action') }}</p>
|
|
<p class="field-value">{{ $managementSummary['next_action'] ?? '' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if (($managementSummary['top_limitations'] ?? []) !== [])
|
|
<ul class="list" style="margin-top:16px;">
|
|
@foreach (($managementSummary['top_limitations'] ?? []) as $limitation)
|
|
<li>{{ $limitation }}</li>
|
|
@endforeach
|
|
</ul>
|
|
@endif
|
|
</section>
|
|
|
|
<section class="section" data-testid="rendered-report-profile">
|
|
<h2>{{ __('localization.review.report_profile') }}</h2>
|
|
|
|
<div class="summary-grid">
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.report_effective_profile') }}</p>
|
|
<p class="field-value">{{ $profile['label'] ?? __('localization.review.unavailable') }}</p>
|
|
<p class="note">{{ $profile['effective_key'] ?? '—' }}</p>
|
|
</div>
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.report_audience') }}</p>
|
|
<p class="field-value">{{ $profile['audience_label'] ?? __('localization.review.unavailable') }}</p>
|
|
</div>
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.report_requested_profile') }}</p>
|
|
<p class="field-value">{{ filled($requestedProfile) ? $requestedProfile : '—' }}</p>
|
|
</div>
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ __('localization.review.report_source_surface') }}</p>
|
|
<p class="field-value">{{ $sourceMetadata['source_surface'] ?? 'review_pack' }}</p>
|
|
<p class="note">{{ __('localization.review.interpretation_version') }}: {{ $sourceMetadata['interpretation_version'] ?? '—' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if (($profile['is_fallback'] ?? false) === true)
|
|
<div class="share-warning" style="margin-top:16px;" data-testid="rendered-report-profile-fallback">
|
|
{{ __('localization.review.report_profile_fallback_notice') }}
|
|
{{ __('localization.review.report_profile_fallback_summary') }}
|
|
</div>
|
|
@endif
|
|
</section>
|
|
|
|
@if (($report['limitations'] ?? []) !== [])
|
|
<section class="section" data-testid="rendered-report-output-limitations">
|
|
<h2>{{ __('localization.review.output_limitations') }}</h2>
|
|
<div class="limitations">
|
|
@foreach (($report['limitations'] ?? []) as $limitation)
|
|
<article class="limitation">
|
|
<h3>{{ $limitation['title'] }}</h3>
|
|
<p class="copy">{{ $limitation['summary'] }}</p>
|
|
<p class="note">{{ $limitation['next_action'] }}</p>
|
|
</article>
|
|
@endforeach
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
<section class="section" data-testid="rendered-report-findings">
|
|
<h2>{{ __('localization.review.findings_and_open_risks') }}</h2>
|
|
@if (($report['top_findings'] ?? []) === [])
|
|
<p class="empty-state">{{ __('localization.review.no_open_risks_listed') }}</p>
|
|
@else
|
|
<ul class="list">
|
|
@foreach (($report['top_findings'] ?? []) as $finding)
|
|
<li>
|
|
<strong>{{ $finding['title'] ?? __('localization.review.control') }}</strong>
|
|
@if (filled($finding['summary'] ?? null))
|
|
: {{ $finding['summary'] }}
|
|
@endif
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
@endif
|
|
</section>
|
|
|
|
<section class="section" data-testid="rendered-report-accepted-risks">
|
|
<h2>{{ __('localization.review.accepted_risks') }}</h2>
|
|
@if (($report['accepted_risks'] ?? []) === [])
|
|
<p class="empty-state">{{ __('localization.review.no_accepted_risks_listed_for_review') }}</p>
|
|
@else
|
|
<div class="risk-grid">
|
|
@foreach (($report['accepted_risks'] ?? []) as $risk)
|
|
<article class="risk-card">
|
|
<h3>{{ $risk['title'] }}</h3>
|
|
<p class="note">{{ __('localization.review.status') }}: {{ $risk['status'] }} · {{ $risk['review_state'] }}</p>
|
|
@if (filled($risk['owner'] ?? null))
|
|
<p class="note">{{ __('localization.review.accepted_risk_owner') }}: {{ $risk['owner'] }}</p>
|
|
@endif
|
|
@if (filled($risk['summary'] ?? null))
|
|
<p class="copy">{{ $risk['summary'] }}</p>
|
|
@elseif (filled($risk['limitation'] ?? null))
|
|
<p class="copy">{{ $risk['limitation'] }}</p>
|
|
@endif
|
|
</article>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</section>
|
|
|
|
<section class="section" data-testid="rendered-report-governance-decisions">
|
|
<h2>{{ __('localization.review.governance_decisions_requiring_awareness') }}</h2>
|
|
|
|
@if (filled($report['decision_summary']['summary'] ?? null))
|
|
<p class="copy">{{ $report['decision_summary']['summary'] }}</p>
|
|
@endif
|
|
|
|
@if (($report['governance_decisions'] ?? []) === [])
|
|
<p class="empty-state">{{ __('localization.review.no_decisions_require_awareness_description') }}</p>
|
|
@else
|
|
<ul class="list">
|
|
@foreach (($report['governance_decisions'] ?? []) as $decision)
|
|
<li>
|
|
<strong>{{ $decision['title'] ?? __('localization.review.governance_decisions') }}</strong>
|
|
@if (filled($decision['summary'] ?? null))
|
|
: {{ $decision['summary'] }}
|
|
@endif
|
|
@if (filled($decision['next_action'] ?? null))
|
|
<div class="note">{{ $decision['next_action'] }}</div>
|
|
@endif
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
@endif
|
|
</section>
|
|
|
|
<section class="section" data-testid="rendered-report-evidence-basis">
|
|
<h2>{{ __('localization.review.evidence_basis') }}</h2>
|
|
<p class="copy">{{ $evidenceBasis['description'] ?? $report['evidence_basis_summary'] }}</p>
|
|
<p class="note">{{ $evidenceBasis['operator_action'] ?? '' }}</p>
|
|
</section>
|
|
|
|
<section class="section">
|
|
<h2>{{ __('localization.review.next_actions') }}</h2>
|
|
@if (($report['next_actions'] ?? []) === [])
|
|
<p class="empty-state">{{ __('localization.review.no_next_action_listed') }}</p>
|
|
@else
|
|
<ul class="list">
|
|
@foreach (($report['next_actions'] ?? []) as $nextAction)
|
|
<li>{{ $nextAction }}</li>
|
|
@endforeach
|
|
</ul>
|
|
@endif
|
|
</section>
|
|
|
|
<section class="section" data-testid="rendered-report-disclosure">
|
|
<h2>{{ __('localization.review.disclosure_policy') }}</h2>
|
|
|
|
@if (($disclosurePolicy['blocking_reasons'] ?? []) !== [])
|
|
<div class="limitations">
|
|
@foreach (($disclosurePolicy['blocking_reasons'] ?? []) as $reason)
|
|
<article class="limitation">
|
|
<h3>{{ $reason['label'] ?? __('localization.review.blocked') }}</h3>
|
|
<p class="copy">{{ $reason['summary'] ?? '' }}</p>
|
|
</article>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (($disclosurePolicy['warnings'] ?? []) !== [])
|
|
<div class="summary-grid" style="margin-top:14px;">
|
|
@foreach (($disclosurePolicy['warnings'] ?? []) as $warning)
|
|
<div class="summary-field">
|
|
<p class="field-label">{{ $warning['label'] ?? __('localization.review.requires_review') }}</p>
|
|
<p class="field-value">{{ $warning['summary'] ?? '' }}</p>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
<div class="policy-grid">
|
|
@foreach (($disclosurePolicy['mandatory_disclosures'] ?? []) as $disclosure)
|
|
<article class="technical-card">
|
|
<div style="display:flex;flex-wrap:wrap;justify-content:space-between;gap:10px;align-items:center;">
|
|
<p class="field-label">{{ $disclosure['label'] ?? __('localization.review.non_certification_disclosure') }}</p>
|
|
<span class="badge" style="{{ $proofBadgeStyles[$disclosure['proof_state'] ?? 'unknown'] ?? $proofBadgeStyles['unknown'] }}">
|
|
{{ __('localization.review.proof_state_'.($disclosure['proof_state'] ?? 'unknown')) }}
|
|
</span>
|
|
</div>
|
|
<p class="copy">{{ $disclosure['summary'] ?? '' }}</p>
|
|
</article>
|
|
@endforeach
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section supporting" data-testid="rendered-report-supporting-appendix">
|
|
<h2>{{ __('localization.review.supporting_appendix') }}</h2>
|
|
<p class="copy">{{ __('localization.review.rendered_report_appendix_note') }}</p>
|
|
|
|
@if (($disclosurePolicy['show_section_appendix'] ?? false) !== true)
|
|
<p class="copy">{{ __('localization.review.report_appendix_hidden_for_profile') }}</p>
|
|
@else
|
|
<div class="technical-grid" data-testid="rendered-report-technical-details">
|
|
<div class="technical-card">
|
|
<p class="field-label">{{ __('localization.review.executive_entrypoint') }}</p>
|
|
<p class="field-value">{{ $report['entrypoint_file'] }}</p>
|
|
</div>
|
|
<div class="technical-card">
|
|
<p class="field-label">{{ __('localization.review.auditor_appendix') }}</p>
|
|
<p class="field-value">{{ implode(', ', $report['appendix_files'] ?? []) ?: '—' }}</p>
|
|
</div>
|
|
@foreach (($report['technical_details'] ?? []) as $label => $value)
|
|
<div class="technical-card">
|
|
<p class="field-label">{{ $label }}</p>
|
|
<p class="field-value">{{ $value }}</p>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
@foreach (($report['section_appendix'] ?? []) as $section)
|
|
<article class="appendix-card">
|
|
<div style="display:flex;flex-wrap:wrap;justify-content:space-between;gap:10px;align-items:baseline;">
|
|
<h3>{{ $section['title'] }}</h3>
|
|
<span class="badge" style="{{ $badgeClasses['gray'] }}">{{ $section['completeness_label'] }}</span>
|
|
</div>
|
|
|
|
@if (($section['highlights'] ?? []) !== [])
|
|
<p class="copy">{{ implode(' ', $section['highlights']) }}</p>
|
|
@endif
|
|
|
|
@if (($section['entries'] ?? []) !== [])
|
|
<div class="appendix-grid">
|
|
@foreach (($section['entries'] ?? []) as $entry)
|
|
<div class="technical-card">
|
|
<p class="field-value">{{ $entry['title'] }}</p>
|
|
@if (filled($entry['summary'] ?? null))
|
|
<p class="note">{{ $entry['summary'] }}</p>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (($section['summary_items'] ?? []) !== [])
|
|
<div class="appendix-grid">
|
|
@foreach (($section['summary_items'] ?? []) as $item)
|
|
<div class="technical-card">
|
|
<p class="field-label">{{ $item['label'] }}</p>
|
|
<p class="field-value">{{ $item['value'] }}</p>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (($section['next_actions'] ?? []) !== [])
|
|
<ul class="list" style="margin-top:16px;">
|
|
@foreach (($section['next_actions'] ?? []) as $nextAction)
|
|
<li>{{ $nextAction }}</li>
|
|
@endforeach
|
|
</ul>
|
|
@endif
|
|
|
|
@if (filled($section['disclosure'] ?? null))
|
|
<p class="note">{{ $section['disclosure'] }}</p>
|
|
@endif
|
|
</article>
|
|
@endforeach
|
|
@endif
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</body>
|
|
</html>
|