Fil d'informations dans l'espace adhérent

L'association pouvait publier des promotions et des rencontres, mais rien
à destination de ses seuls adhérents connectés. Un troisième onglet, placé
en tête de l'espace, porte désormais ce que publie le bureau : annonces,
convocations, rappels.

Côté rédaction, `/backend/informations` (capacité `manageInformations`,
administrateur et modérateur) : titre, texte balisé avec barre de mise en
forme et aperçu, image de couverture, brouillon puis publication,
épinglage — une seule information en tête à la fois, appliqué dans
l'action plutôt que par un index conditionnel.

Côté lecture, `information_reads` porte une ligne par lecture plutôt
qu'une date « vu jusqu'ici » sur l'utilisateur : c'est ce qui permet à la
fois la pastille « Nouveau » par information et le « lue par 12 / 40 » du
back-office. Le marquage passe par une action appelée après affichage :
la pastille de l'onglet et celle de la barre latérale sont calculées par
deux composants serveur distincts, dont l'ordre de rendu n'est pas
garanti, et se seraient contredites sur la même page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QsLjRAnuLwivqxCbM4WeP
This commit is contained in:
Claude committed 2026-09-14 15:49:42 +00:00
1 parent 1701fc2834
commit d57e544a18
19 files changed
+2709 -7

No files matched your search

+8 -2
View File
@@ -19,6 +19,7 @@ const TITLES: Record<string, [string, string]> = {
"/backend/rencontres": ["Rencontres", "Gérer les prochaines dates"],
"/backend/inscriptions": ["Inscriptions", "Gérer les participants aux rencontres"],
"/backend/rencontres-passees": ["Rencontres passées", "Publier les archives et leurs photos"],
"/backend/informations": ["Informations", "Ce que l'association publie dans l'espace adhérent"],
"/backend/emails": ["Création d'e-mails", "Composer des messages aux couleurs de Plein R"],
"/backend/reseaux": ["Réseaux sociaux", "Connecter les pages Facebook et LinkedIn"],
"/backend/administrateurs": ["Administrateurs", "Gérer les accès à l'administration"],
@@ -26,11 +27,12 @@ const TITLES: Record<string, [string, string]> = {
"/backend/parametres": ["Paramètres du site", "Configurer l'association et les mentions légales"],
"/backend/espace": ["Mon profil", "Votre fiche publique, vos inscriptions et votre droit à l'image"],
"/backend/espace/promotions": ["Mes promotions", "Proposez une offre et suivez celles en ligne"],
"/backend/espace/informations": ["Informations", "Les annonces de l'association"],
"/backend/changer-mot-de-passe": ["Mot de passe", "Sécurisez votre compte"],
};
type Capability = Parameters<typeof can>[1];
type BadgeKey = "inbox" | "promos";
type BadgeKey = "inbox" | "promos" | "infos";
type NavItem = {
href: string;
@@ -72,6 +74,7 @@ const SECTIONS: NavSection[] = [
{
label: "Communication",
items: [
{ href: "/backend/informations", label: "Informations", icon: "news", capability: "manageInformations" },
{ href: "/backend/emails", label: "E-mails", icon: "emails", capability: "manageEmails" },
{ href: "/backend/reseaux", label: "Réseaux sociaux", icon: "social", capability: "manageSettings" },
],
@@ -87,6 +90,7 @@ const SECTIONS: NavSection[] = [
{
label: "Côté adhérent",
items: [
{ href: "/backend/espace/informations", label: "Informations", icon: "news", badge: "infos" },
{ href: "/backend/espace", label: "Mon profil", icon: "space" },
{ href: "/backend/espace/promotions", label: "Mes promotions", icon: "promos" },
],
@@ -124,11 +128,13 @@ export function BackendShell({
user,
pendingCount,
inboxCount = 0,
infoCount = 0,
children,
}: {
user: { name: string; role: AppRole };
pendingCount: number;
inboxCount?: number;
infoCount?: number;
children: ReactNode;
}) {
const pathname = usePathname();
@@ -164,7 +170,7 @@ export function BackendShell({
return () => window.removeEventListener("keydown", onKey);
}, [menuOpen]);
const badges: Record<BadgeKey, number> = { inbox: inboxCount, promos: pendingCount };
const badges: Record<BadgeKey, number> = { inbox: inboxCount, promos: pendingCount, infos: infoCount };
const sections = SECTIONS.map((section) => ({
...section,