feat: improve synthesis list cards with time, all categories, and uniform height

- Add generation time below date in synthesis cards
- Show all categories with article count in parentheses
- Use flex-col layout for uniform card height
- Add sections_summary to SynthesisListItem API response
- Add formatTime utility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
master
oabrivard 3 months ago
parent 03f2660163
commit 6b84c335d0

@ -84,9 +84,17 @@ pub struct SynthesisListItem {
pub created_at: DateTime<Utc>,
pub first_section_title: Option<String>,
pub first_section_item_count: usize,
pub sections_summary: Vec<SectionSummary>,
pub job_id: Option<Uuid>,
}
/// Summary of a section for the synthesis list view.
#[derive(Debug, Serialize)]
pub struct SectionSummary {
pub title: String,
pub count: usize,
}
impl TryFrom<Synthesis> for SynthesisListItem {
type Error = crate::errors::AppError;
@ -98,6 +106,14 @@ impl TryFrom<Synthesis> for SynthesisListItem {
let first_section_title = first.map(|sec| sec.title.clone());
let first_section_item_count = first.map(|sec| sec.items.len()).unwrap_or(0);
let sections_summary: Vec<SectionSummary> = sections
.iter()
.map(|sec| SectionSummary {
title: sec.title.clone(),
count: sec.items.len(),
})
.collect();
Ok(Self {
id: s.id,
week: s.week,
@ -105,6 +121,7 @@ impl TryFrom<Synthesis> for SynthesisListItem {
created_at: s.created_at,
first_section_title,
first_section_item_count,
sections_summary,
job_id: s.job_id,
})
}

@ -17,6 +17,10 @@ export const MOCK_SYNTHESIS_LIST_ITEM: SynthesisListItem = {
created_at: '2026-03-21T10:00:00Z',
first_section_title: 'Annonces majeures',
first_section_item_count: 3,
sections_summary: [
{ title: 'Annonces majeures', count: 3 },
{ title: 'Recherche', count: 2 },
],
job_id: 'job-test-1',
};

@ -12,7 +12,7 @@ import { useI18n } from '~/i18n';
import { synthesesApi } from '~/api/syntheses';
import { isApiError } from '~/types';
import type { SynthesisListItem } from '~/types';
import { extractWeekNumber, formatDate } from '~/utils/dates';
import { extractWeekNumber, formatDate, formatTime } from '~/utils/dates';
import LoadingSpinner from '~/components/ui/LoadingSpinner';
/**
@ -168,29 +168,34 @@ const Home: Component = () => {
{(synth) => (
<A
href={`/synthesis/${synth.id}`}
class="block bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md hover:border-indigo-300 transition-all duration-200 overflow-hidden"
class="flex flex-col bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md hover:border-indigo-300 transition-all duration-200 overflow-hidden"
>
<div class="p-6">
<div class="p-6 flex-1">
<div class="flex items-center justify-between mb-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
{t('home.weekLabel', { week: extractWeekNumber(synth.week) })}
</span>
<span class="text-sm text-gray-500">
{formatDate(synth.created_at)}
</span>
<div class="text-right">
<div class="text-sm text-gray-500">{formatDate(synth.created_at)}</div>
<div class="text-xs text-gray-400">{formatTime(synth.created_at)}</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">
<h3 class="text-lg font-semibold text-gray-900 mb-3">
{t('home.cardTitle')}
</h3>
<div class="text-sm text-gray-600 space-y-1.5">
<div class="space-y-1">
<Show
when={synth.first_section_title}
fallback={<p>{t('home.noPreview')}</p>}
when={synth.sections_summary && synth.sections_summary.length > 0}
fallback={<p class="text-sm text-gray-400">{t('home.noPreview')}</p>}
>
<p class="font-medium text-gray-700">{synth.first_section_title}</p>
<p class="text-gray-500">
{t('home.previewCount', { count: String(synth.first_section_item_count) })}
</p>
<For each={synth.sections_summary}>
{(section) => (
<div class="flex items-center justify-between text-sm">
<span class="text-gray-700 truncate mr-2">{section.title}</span>
<span class="text-gray-400 text-xs whitespace-nowrap">({section.count})</span>
</div>
)}
</For>
</Show>
</div>
</div>

@ -133,6 +133,11 @@ export interface Synthesis {
created_at: string;
}
export interface SectionSummary {
title: string;
count: number;
}
export interface SynthesisListItem {
id: string;
week: string;
@ -140,6 +145,7 @@ export interface SynthesisListItem {
created_at: string;
first_section_title: string | null;
first_section_item_count: number;
sections_summary: SectionSummary[];
job_id: string | null;
}

@ -27,6 +27,17 @@ export function formatDate(isoDate: string): string {
}
}
/**
* Format an ISO date string as "10:30".
*/
export function formatTime(isoDate: string): string {
try {
return format(parseISO(isoDate), 'HH:mm', { locale: fr });
} catch {
return '';
}
}
/**
* Format an ISO date string as "21 mars 2026" (full month name).
*/

Loading…
Cancel
Save