Leçon 11 / 15

Choisir le mois du récapitulatif

25 min React — composants et état Gratuit

Objectif : Permettre à l’utilisateur de sélectionner un mois et limiter les dépenses affichées à cette période.

Une dépense peut correspondre à la catégorie sélectionnée tout en appartenant à un autre mois. Sans filtre de période, votre liste mélange des opérations qui ne devraient pas apparaître ensemble dans le tableau de bord. Vous allez ajouter un sélecteur de mois et appliquer son choix à la liste, sans modifier les dépenses enregistrées.

Le composant App conserve déjà les dépenses et la catégorie sélectionnée. Vous allez aussi y conserver le mois choisi, puis le transmettre à FiltresDepenses et utiliser cette valeur pour calculer les éléments affichés. Le récapitulatif n’affiche pas encore de total calculé : ce calcul sera traité dans la prochaine leçon.

Ajouter un mois sélectionné dans App

Un champ HTML de type month fournit une valeur au format AAAA-MM, par exemple 2025-03. Ce format se compare directement au début d’une date au format ISO AAAA-MM-JJ. Vous pouvez donc extraire le mois d’une dépense avec slice(0, 7), sans construire une date ni dépendre du fuseau horaire du navigateur.

Remplacez le contenu de src/App.tsx par le fichier complet ci-dessous. Il conserve les données de démonstration et l’ajout de dépenses, puis combine le choix de catégorie existant avec le nouveau choix du mois. Les valeurs "" pour le mois et "toutes" pour la catégorie signifient qu’aucun critère correspondant n’est appliqué.

Texte
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";

export type Depense = {
  id: string;
  description: string;
  montant: number;
  categorie: string;
  date: string;
};

const depenseDemonstration: Depense = {
  id: "depense-1",
  description: "Courses alimentaires",
  montant: 42.5,
  categorie: "Alimentation",
  date: "2025-03-12",
};

export default function App() {
  const [depenses, setDepenses] = useState<Depense[]>([depenseDemonstration]);
  const [categorieSelectionnee, setCategorieSelectionnee] = useState("toutes");
  const [moisSelectionne, setMoisSelectionne] = useState("2025-03");

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

  const depensesFiltrees = depenses.filter((depense) => {
    const correspondCategorie =
      categorieSelectionnee === "toutes" ||
      depense.categorie === categorieSelectionnee;
    const correspondMois =
      moisSelectionne === "" || depense.date.slice(0, 7) === moisSelectionne;

    return correspondCategorie && correspondMois;
  });

  return (
    <div className="depenses-app">
      <EnteteDepenses />
      <main className="depenses-principale">
        <FormulaireDepense onAjouterDepense={ajouterDepense} />
        <RecapitulatifMensuel />
        <FiltresDepenses
          categorieSelectionnee={categorieSelectionnee}
          onCategorieChange={setCategorieSelectionnee}
          moisSelectionne={moisSelectionne}
          onMoisChange={setMoisSelectionne}
        />
        <ListeDepenses depenses={depensesFiltrees} />
      </main>
    </div>
  );
}

La comparaison combine les deux conditions avec && : une dépense doit correspondre à la catégorie et au mois pour rester dans la liste. Le tableau source depenses n’est pas modifié. Le filtre par catégorie reprend le fonctionnement présenté dans la leçon précédente ; ici, vous ajoutez seulement le second critère.

Relier le champ mois au parent

Dans src/composants/FiltresDepenses.tsx, le champ mois reçoit sa valeur depuis les props et signale chaque changement à App. Le composant affiche aussi les catégories présentes dans les dépenses ; il reçoit ces dernières afin de générer les options, sans créer un second état local.

Texte
import type { Depense } from "../App";

type FiltresDepensesProps = {
  depenses: Depense[];
  categorieSelectionnee: string;
  onCategorieChange: (categorie: string) => void;
  moisSelectionne: string;
  onMoisChange: (mois: string) => void;
};

export default function FiltresDepenses({
  depenses,
  categorieSelectionnee,
  onCategorieChange,
  moisSelectionne,
  onMoisChange,
}: FiltresDepensesProps) {
  const categories = Array.from(new Set(depenses.map((depense) => depense.categorie)));

  return (
    <section className="depenses-filtres" aria-label="Filtres des dépenses">
      <label>
        Filtrer par catégorie
        <select
          className="depenses-filtres__select"
          name="filtre-categorie"
          value={categorieSelectionnee}
          onChange={(event) => onCategorieChange(event.target.value)}
        >
          <option value="toutes">Toutes les catégories</option>
          {categories.map((categorie) => (
            <option key={categorie} value={categorie}>
              {categorie}
            </option>
          ))}
        </select>
      </label>
      <label>
        Mois
        <input
          className="depenses-filtres__select"
          type="month"
          name="filtre-mois"
          value={moisSelectionne}
          onChange={(event) => onMoisChange(event.target.value)}
        />
      </label>
    </section>
  );
}

Vérifier le résultat dans l’interface

Avec la dépense de démonstration fournie, le champ mois affiche 2025-03 et la liste contient « Courses alimentaires ». Choisissez un autre mois : la dépense ne correspond plus à la période, et ListeDepenses reçoit un tableau vide ; son message d’absence de résultat s’affiche comme prévu. Revenez à mars pour retrouver la ligne.

Vous pouvez également choisir une catégorie puis changer de mois : les deux critères s’appliquent simultanément. Le tableau complet reste disponible dans App, tandis que seule la sélection filtrée est transmise à la liste. La prochaine leçon calculera le total mensuel à partir des dépenses et de la période choisie.

À retenir

  • Un champ type="month" fournit une valeur au format AAAA-MM.
  • slice(0, 7) extrait le mois d’une date ISO au format AAAA-MM-JJ.
  • Le mois et la catégorie se combinent pour déterminer les dépenses transmises à la liste.
  • Le tableau source des dépenses reste intact ; la sélection affichée répond aux filtres choisis.

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.