Leçon 15 / 15

Assembler et relire le tableau de bord complet

25 min React — composants et état Gratuit

Objectif : Finaliser une version cohérente du tableau de bord en contrôlant les types, le rendu conditionnel et le récapitulatif mensuel.

Un tableau de bord peut sembler correct tout en affichant un total qui ne correspond pas à la période choisie, ou en laissant passer une valeur invalide. Pour finaliser le projet, vous allez relire les échanges entre composants et vérifier que les données, les filtres et le récapitulatif restent cohérents.

Les composants fonctionnels, les props et l’état centralisé ont déjà été étudiés dans les leçons précédentes. Ici, vous vérifiez leur assemblage : App détient les dépenses et les filtres, tandis que les composants enfants affichent les données ou transmettent une action à leur parent.

Vérifier le flux des données

Commencez par vérifier les contrats des composants. Une dépense possède un identifiant, une description, un montant numérique, une catégorie et une date au format AAAA-MM-JJ. Le même type sert à l’ajout, à l’affichage et au calcul du total ; vous évitez ainsi qu’un composant traite le montant comme du texte.

src/composants/LigneDepense.tsxTexte
export type Depense = {
  id: string;
  description: string;
  montant: number;
  categorie: string;
  date: string;
};

type Props = { depense: Depense };

export default function LigneDepense({ depense }: Props) {
  return (
    <li className="depenses-liste__element">
      <span>{depense.description}</span>
      <span className="depenses-categorie">{depense.categorie}</span>
      <span>{depense.date}</span>
      <span>{depense.montant.toFixed(2)} €</span>
    </li>
  );
}

Relisez ensuite la donnée transmise à chaque prop. La liste reçoit les dépenses déjà filtrées et une clé issue de l’identifiant ; le récapitulatif reçoit le mois sélectionné et calcule son total à partir des dépenses complètes. Ce choix signifie que le filtre de catégorie limite les lignes affichées, mais ne modifie pas le total mensuel.

Contrôler les composants de sortie

src/composants/ListeDepenses.tsxTexte
import type { Depense } from "./LigneDepense";
import LigneDepense from "./LigneDepense";

type Props = { depenses: Depense[] };

export default function ListeDepenses({ depenses }: Props) {
  return (
    <section aria-label="Liste des dépenses">
      <h2>Dépenses</h2>
      {depenses.length > 0 ? (
        <ul className="depenses-liste">
          {depenses.map((depense) => (
            <LigneDepense key={depense.id} depense={depense} />
          ))}
        </ul>
      ) : (
        <p className="depenses-message--vide">Aucune dépense pour cette période.</p>
      )}
    </section>
  );
}
src/composants/RecapitulatifMensuel.tsxTexte
import type { Depense } from "./LigneDepense";

type Props = { depenses: Depense[]; mois: string };

export default function RecapitulatifMensuel({ depenses, mois }: Props) {
  const total = depenses
    .filter((depense) => depense.date.slice(0, 7) === mois)
    .reduce((somme, depense) => somme + depense.montant, 0);

  return (
    <section className="depenses-recapitulatif" aria-label="Récapitulatif mensuel">
      <h2>Total du mois</h2>
      <p className="depenses-recapitulatif__montant">{total.toFixed(2)} €</p>
    </section>
  );
}

Assembler App et relire les filtres

Dans App, contrôlez que la liste affichée est dérivée des dépenses et des deux critères, sans remplacer le tableau source. Le total mensuel doit, lui, être dérivé du mois et du tableau source. Les composants de formulaire et de filtres conservent leurs responsabilités : ils affichent les champs et appellent les fonctions fournies par App.

src/App.tsx — logique centrale à vérifierTexte
import { useState } from "react";
import EnteteDepenses from "./composants/EnteteDepenses";
import FormulaireDepense from "./composants/FormulaireDepense";
import FiltresDepenses from "./composants/FiltresDepenses";
import RecapitulatifMensuel from "./composants/RecapitulatifMensuel";
import ListeDepenses from "./composants/ListeDepenses";
import type { Depense } from "./composants/LigneDepense";

const categories = ["Alimentation", "Transport", "Logement", "Loisirs"];
const depensesInitiales: Depense[] = [
  { id: "dep-1", description: "Courses", montant: 42.5, categorie: "Alimentation", date: "2025-03-05" },
  { id: "dep-2", description: "Bus", montant: 18, categorie: "Transport", date: "2025-03-08" },
];

export default function App() {
  const [depenses, setDepenses] = useState<Depense[]>(depensesInitiales);
  const [categorie, setCategorie] = useState("toutes");
  const [mois, setMois] = useState("2025-03");
  const depensesFiltrees = depenses.filter((depense) =>
    (categorie === "toutes" || depense.categorie === categorie) &&
    depense.date.slice(0, 7) === mois
  );

  function ajouterDepense(nouvelleDepense: Depense) {
    setDepenses((courantes) => [...courantes, nouvelleDepense]);
  }

  return (
    <div className="depenses-app">
      <EnteteDepenses />
      <main className="depenses-principale">
        <FormulaireDepense categories={categories} onAjouter={ajouterDepense} />
        <RecapitulatifMensuel depenses={depenses} mois={mois} />
        <FiltresDepenses categories={categories} categorie={categorie} mois={mois}
          onCategorieChange={setCategorie} onMoisChange={setMois} />
        <ListeDepenses depenses={depensesFiltrees} />
      </main>
    </div>
  );
}

Ce bloc montre les contrôles centraux à effectuer dans votre App existant, pas un remplacement isolé : gardez les props et les composants déjà définis dans les leçons précédentes. Vérifiez en particulier que FormulaireDepense transmet une dépense dont le montant est bien converti en nombre, et que FiltresDepenses transmet la valeur courante des champs contrôlés. Le code suppose que ces composants utilisent les noms de props indiqués ici ; alignez-les sur leur définition déjà présente dans votre projet.

Valider le rendu de bout en bout

Lancez le projet et vérifiez d’abord l’état initial : pour mars 2025, le récapitulatif affiche 60.50 € et les deux dépenses sont visibles. Choisissez « Alimentation » : la liste ne montre plus que « Courses », tandis que le total reste à 60.50 €. Enfin, sélectionnez une période sans dépense : le total passe à 0.00 € et le message prévu pour une liste vide apparaît.

Terminez par une relecture TypeScript stricte : les imports correspondent aux fichiers du contrat, les props exigées sont fournies et les montants restent numériques jusqu’à leur formatage à l’écran. Le fichier src/main.tsx doit continuer à monter App, et les styles restent dans src/style.css avec les classes autorisées ; aucun style inline n’est nécessaire.

À retenir

  • App reste la source unique des dépenses et des filtres ; les composants enfants reçoivent des props typées.
  • Le total mensuel est calculé depuis toutes les dépenses du mois, indépendamment du filtre de catégorie.
  • La liste affichée combine les filtres et conserve une clé stable fondée sur l’identifiant.
  • Vérifiez l’état initial, l’ajout, les filtres combinés et le cas où aucun résultat ne correspond.

Vous disposez maintenant d’une grille de relecture pour maintenir le tableau de bord lorsque vous ajouterez une nouvelle fonctionnalité.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.