Iniziamo a valorizzare la pagina delle statistiche. La prima cosa che voglio aggiungere è per ogni set quante carte ho e in percentuale quante me ne mancano.

in main.py aggiungere la funzione:
@app.get("/stats/sets")
def get_set_stats(user_id: int):

    with engine.connect() as conn:

        result = conn.execute(
            text("""
                SELECT
                    s.id,
                    s.name,
                    s.release_date,
                    s.total_cards,

                    COUNT(
                        DISTINCT CASE
                            WHEN col.id IS NOT NULL
                            THEN c.id
                        END
                    ) AS owned_cards,

                    ROUND(
                        COUNT(
                            DISTINCT CASE
                                WHEN col.id IS NOT NULL
                                THEN c.id
                            END
                        ) * 100.0 / s.total_cards,
                        1
                    ) AS completion

                FROM sets s

                LEFT JOIN cards c
                    ON c.set_id = s.id

                LEFT JOIN collection col
                    ON col.card_id = c.id
                    AND col.user_id = :user_id

                GROUP BY
                    s.id,
                    s.name,
                    s.release_date,
                    s.total_cards

                ORDER BY
                    completion DESC,
                    owned_cards DESC,
                    s.release_date DESC,
                    s.name
            """),
            {
                "user_id": user_id
            }
        )

        return result.mappings().all()

In index.html sostituisci <button>Statistiche</button> con:
<button onclick="window.location='/static/dashboard.html'">
    Statistiche
</button>

Creare da zero o sostiture completamente il contenuto del file dashboard.html con:
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="utf-8">
    <title>Statistiche Collezione</title>

    <link rel="stylesheet" href="/static/style.css">
</head>

<body>

<div class="header">
    <div class="logo">Pokemon DB</div>
    <div class="user">Paolo ▼</div>
</div>

<div class="container">

    <div class="toolbar">
        /
            ← Torna alla collezione
        </a>
    </div>

    <div class="stats-title-card">

        <h1>Statistiche</h1>

        <p>
            Stato della collezione Pokémon
        </p>

    </div>

    <!-- RIEPILOGO GENERALE -->

    <div class="stats-summary">

        <h2>Totale collezione</h2>

        <div class="summary-line">

            <span id="globalOwned">
                3
            </span>

            /

            <span id="globalTotal">
                378
            </span>

            carte

            <span class="summary-percent"
                  id="globalPercent">

                0,8%

            </span>

        </div>

        <div class="progress">

            <div
                id="globalBar"
                class="progress-fill"
                style="width:0.8%">
            </div>

        </div>

    </div>

    <!-- ELENCO SET -->

    <div id="statsContainer">

        <!-- Generato da JavaScript -->

    </div>

    <div class="toolbar">

        /
            ← Torna alla collezione
        </a>

    </div>

</div>

<script>

async function loadStats() {

    const response =
        await fetch(
            '/stats/sets?user_id=1'
        );

    const stats =
        await response.json();

    const container =
        document.getElementById(
            'statsContainer'
        );

    container.innerHTML = '';

    let globalOwned = 0;
    let globalTotal = 0;

    stats.forEach(set => {

        globalOwned +=
            set.owned_cards;

        globalTotal +=
            set.total_cards;

        const card =
            document.createElement('div');

        card.className =
            'set-stat-card';

        card.innerHTML = `

            <div class="set-stat-header">

                <div class="set-name">

                    ${set.name}

                </div>

                <div class="set-stats">

                    ${set.owned_cards}
                    /
                    ${set.total_cards}

                    |

                    ${set.completion}%

                </div>

            </div>

            <div class="progress">

                <div
                    class="progress-fill"
                    style="width:${set.completion}%">
                </div>

            </div>

        `;

        container.appendChild(card);

    });

    const globalPercent =
        (
            globalOwned * 100
            / globalTotal
        ).toFixed(1);

    document.getElementById(
        'globalOwned'
    ).textContent =
        globalOwned;

    document.getElementById(
        'globalTotal'
    ).textContent =
        globalTotal;

    document.getElementById(
        'globalPercent'
    ).textContent =
        globalPercent + '%';

    document.getElementById(
        'globalBar'
    ).style.width =
        globalPercent + '%';
}

loadStats();

</script>

</body>
</html>

Aggiungere in calce al css:
.back-link {

    text-decoration: none;

    font-weight: bold;

    color: var(--pokemon-blue);
}

.stats-title-card,
.stats-summary,
.set-stat-card {

    background: white;

    padding: 20px;

    margin-bottom: 20px;

    border-radius: 18px;

    box-shadow:
        0 4px 12px rgba(0,0,0,0.10);
}

.summary-line {

    font-size: 28px;

    font-weight: bold;

    margin-bottom: 15px;
}

.summary-percent {

    float: right;
}

.set-stat-header {

    display: flex;

    justify-content: space-between;

    align-items: center;

    margin-bottom: 12px;
}

.set-name {

    font-size: 26px;

    font-weight: bold;
}

.set-stats {

    font-size: 24px;

    font-weight: bold;
}

.progress {

    height: 20px;

    background: #dcdfe6;

    border-radius: 20px;

    overflow: hidden;
}

.progress-fill {

    height: 100%;

    background:
        linear-gradient(
            90deg,
            #d40000,
            #ff0000
        );

    border-radius: 20px;
}

Migliorerei anche un paio di cose:

  • Invertire la posizione della barra dei pulsanti con la scelta del set
  • Il set che si carica in automatico non è più de default Pitch Black ma il più recente
Editare index.html e sostituire il blocco <div class="container">
interamente con questo:
<div class="container">
    <div class="toolbar">
        <button>Ricerca</button>
        <button onclick="window.location='/static/dashboard.html'">Statistiche</button>
    </div>
    <div class="set-info"><label>Set: </label><select id="setSelect"></select></div>
    <div class="set-info"><h2 id="setTitle"></h2></div>
    <div id="card-grid" class="card-grid"></div>
</div>

Sostiture poi tutta la funzione async function loadSets() con questa:
async function loadSets() {

    const response = await fetch('/sets');
    const sets = await response.json();

    const select =
        document.getElementById('setSelect');

    select.innerHTML = '';

    sets.forEach((set, index) => {

        const option =
            document.createElement('option');

        option.value = set.id;

        const year =
            set.release_date.substring(0, 4);

        option.textContent =
            `${year} - ${set.name}`;

        if (index === 0) {

            option.selected = true;

            document.getElementById(
                'setTitle'
            ).textContent =
                `${year} - ${set.name}`;
        }

        select.appendChild(option);
    });

    select.addEventListener('change', () => {

        document.getElementById(
            'setTitle'
        ).textContent =
            select.options[
                select.selectedIndex
            ].text;

        loadCards(select.value);
    });

    if (sets.length > 0) {

        loadCards(sets[0].id);
    }
}

<—– Articolo Parte 10 | Articolo Parte 11 —–>