auditmysite 0.21.0

Resource-efficient WCAG 2.1 Accessibility Checker in Rust
Documentation
//! Module overview components for PDF reports.

use renderreport::components::advanced::{Grid, List, SectionHeaderSplit};
use renderreport::components::{AuditTable, MetricCard, ScoreCard, TableColumn};
use renderreport::prelude::*;

use super::findings::first_sentence;
use crate::output::report_model::*;

pub(super) fn build_summary_overview(summary: &SummaryBlock) -> Grid {
    let score_card = serde_json::json!({
        "type": "score-card",
        "data": ScoreCard::new("Accessibility-Score", summary.score)
            .with_description(format!("Grade {} — {}", summary.grade, summary.maturity_label))
            .with_thresholds(70, 50)
            .with_height("100%")
            .inverted()
            .to_data()
    });
    let mut grid = Grid::new(2)
        .with_item_min_height("132pt")
        .add_item(score_card);

    for metric in summary.metrics.iter().take(3) {
        let mut card = MetricCard::new(&metric.title, &metric.value).with_height("100%");
        if let Some(ref color) = metric.accent_color {
            card = card.with_accent_color(color);
        }
        grid = grid.add_item(serde_json::json!({
            "type": "metric-card",
            "data": card.to_data()
        }));
    }

    grid
}

pub(super) enum WasJetztTunContent {
    Table(AuditTable),
    Empty(Callout),
}

/// Build the "Was jetzt tun?" task table (max 5 actions)
pub(super) fn build_was_jetzt_tun_table(vm: &ReportViewModel) -> WasJetztTunContent {
    // Collect top items from action roadmap, prioritize by execution priority
    let all_items: Vec<&RoadmapItemData> = vm
        .actions
        .roadmap_columns
        .iter()
        .flat_map(|col| col.items.iter())
        .collect();

    // Sort: Sofort beheben / Direkt angehen first
    let mut sorted: Vec<&RoadmapItemData> = all_items;
    sorted.sort_by_key(|i| {
        let ep = i.execution_priority.as_str();
        if ep.contains("Direkt") || ep.contains("Sofort") {
            0u8
        } else if ep.contains("Nächstes") || ep.contains("Wichtig") {
            1
        } else {
            2
        }
    });

    let selected: Vec<&RoadmapItemData> = sorted.into_iter().take(5).collect();

    if selected.is_empty() {
        return WasJetztTunContent::Empty(
            Callout::success(
                "Keine priorisierten Maßnahmen identifiziert — Qualität sichern und regelmäßige Audits einplanen.",
            )
            .with_title("Aktuell keine offenen Maßnahmen"),
        );
    }

    let table_title = match selected.len() {
        1 => "Maßnahmenplan (1 Maßnahme) — Executive View".to_string(),
        n => format!("Maßnahmenplan ({n} Maßnahmen) — Executive View"),
    };

    let mut table = AuditTable::new(vec![
        TableColumn::new("Maßnahme"),
        TableColumn::new("Nutzer-Effekt"),
        TableColumn::new("Risiko"),
        TableColumn::new("Rolle / Aufwand"),
    ])
    .with_title(&table_title);

    for item in selected {
        table = table.add_row(vec![
            item.action.clone(),
            item.user_effect.clone(),
            item.risk_effect.clone(),
            format!("{} / {}", item.role, item.effort),
        ]);
    }

    WasJetztTunContent::Table(table)
}

/// Closing section: recommended next steps
pub(super) fn render_next_steps_single(
    mut builder: renderreport::engine::ReportBuilder,
    vm: &ReportViewModel,
) -> renderreport::engine::ReportBuilder {
    builder = builder.add_component(
        SectionHeaderSplit::new(
            &vm.executive.next_steps_title,
            &vm.executive.next_steps_intro,
        )
        .with_level(1),
    );

    let mut steps: Vec<String> = Vec::new();

    // Highest priority from quick wins
    for col in &vm.actions.roadmap_columns {
        for item in &col.items {
            if steps.len() >= 3 {
                break;
            }
            steps.push(item.action.clone());
        }
    }

    // Fallback from findings
    if steps.is_empty() {
        for group in vm.findings.top_findings.iter().take(3) {
            steps.push(first_sentence(&group.recommendation).to_string());
        }
    }

    if !steps.is_empty() {
        let mut list = List::new();
        for action in &steps {
            list = list.add_item(action);
        }
        builder = builder.add_component(list);
    }

    builder = builder.add_component(
        Callout::info(&vm.executive.next_steps_callout_body)
            .with_title(&vm.executive.next_steps_callout_title),
    );

    builder
}