Blog: numero di articoli accanto a ogni tematica

I filtri non dicevano dove ci fosse materiale: si sceglieva una categoria
per scoprirla vuota. Ora ognuna porta il proprio conteggio, cestino e
bozze esclusi, e il numero si aggiorna insieme ai filtri quando si cambia
tematica senza ricaricare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 17:16:39 +02:00
parent b7f4a7948c
commit d8862ba8ec
3 changed files with 29 additions and 3 deletions
+8
View File
@@ -69,6 +69,14 @@ export function listPublished(
return { items, total };
}
/** Quanti articoli pubblicati ha ogni categoria, cestino escluso. */
export function countPublishedByCategory(db: Database.Database): Record<string, number> {
const righe = db.prepare(
'SELECT category, COUNT(*) AS n FROM posts WHERE draft = 0 AND deleted_at IS NULL GROUP BY category'
).all() as { category: string; n: number }[];
return Object.fromEntries(righe.map((r) => [r.category, r.n]));
}
export function listAllPosts(db: Database.Database): Post[] {
return db.prepare('SELECT * FROM posts WHERE deleted_at IS NULL ORDER BY updated_at DESC').all() as Post[];
}
+12 -3
View File
@@ -2,7 +2,7 @@
import Base from '../../layouts/Base.astro';
import PageHero from '../../components/PageHero.astro';
import { getDb } from '../../lib/db';
import { listPublished } from '../../lib/posts';
import { listPublished, countPublishedByCategory } from '../../lib/posts';
import { CATEGORIES } from '../../lib/blog-const';
export const prerender = false;
@@ -15,6 +15,9 @@ const { items, total } = listPublished(getDb(), {
page, perPage: PER_PAGE,
});
const pages = Math.max(1, Math.ceil(total / PER_PAGE));
// Conteggi per categoria: mostrati accanto ai filtri, così si vede subito dove c'è materiale.
const perCategoria = countPublishedByCategory(getDb());
const totalePubblicati = Object.values(perCategoria).reduce((n, v) => n + v, 0);
const fmt = (d: string | null) => d ? new Intl.DateTimeFormat('it-IT', { dateStyle: 'long' }).format(new Date(d)) : '';
const linkFor = (cat?: string, p = 1) => {
const q = new URLSearchParams();
@@ -33,9 +36,13 @@ const linkFor = (cat?: string, p = 1) => {
<p>Edugo è lo spazio di InsanityLab dedicato alla conoscenza: articoli, guide e approfondimenti su allenamento, nutrizione e salute per capire il corpo e migliorare il proprio benessere. Perché la conoscenza è il primo passo del cambiamento.</p>
</div>
<nav class="bfilters" aria-label="categorie">
<a class:list={['bfilters__item', { 'is-active': !category }]} href={linkFor()}>Mostra tutto</a>
<a class:list={['bfilters__item', { 'is-active': !category }]} href={linkFor()}>
Mostra tutto <span class="bfilters__n">{totalePubblicati}</span>
</a>
{CATEGORIES.map((c) => (
<a class:list={['bfilters__item', { 'is-active': category === c }]} href={linkFor(c)}>{c}</a>
<a class:list={['bfilters__item', { 'is-active': category === c }]} href={linkFor(c)}>
{c} <span class="bfilters__n">{perCategoria[c] ?? 0}</span>
</a>
))}
</nav>
<div id="blog-results">
@@ -122,6 +129,8 @@ const linkFor = (cat?: string, p = 1) => {
.bfilters { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-bottom: 50px; }
.bfilters__item { font-family: var(--font-heading); font-size: .7rem; letter-spacing: .16em; text-transform: uppercase; text-decoration: none; color: var(--c-text); padding: 10px 16px; }
.bfilters__item.is-active { background: var(--c-accent); color: #fff; }
.bfilters__n { color: var(--c-text-light); }
.bfilters__item.is-active .bfilters__n { color: rgba(255,255,255,.75); }
.bfilters__item { transition: background .2s ease, color .2s ease; }
#blog-results { transition: opacity .25s ease; }
#blog-results.is-loading { opacity: .35; }
+9
View File
@@ -4,6 +4,7 @@ import { createDb } from '../src/lib/db';
import {
createPost, trashPost, restorePost, deletePost, getPostById, listAllPosts, listByAuthor,
listTrashed, listPublished, listRecent, listArchiveMonths, getPublishedBySlug,
countPublishedByCategory,
} from '../src/lib/posts';
let db: Database.Database;
@@ -60,6 +61,14 @@ describe('cestino', () => {
expect(getPublishedBySlug(db, 'uno')).toBeDefined();
});
it('i conteggi per categoria escludono cestino e bozze', () => {
expect(countPublishedByCategory(db)).toEqual({ training: 2 });
trashPost(db, id);
expect(countPublishedByCategory(db)).toEqual({ training: 1 });
restorePost(db, id);
expect(countPublishedByCategory(db)).toEqual({ training: 2 });
});
it('la cancellazione definitiva rimuove davvero la riga', () => {
trashPost(db, id);
deletePost(db, id);