Skip to content

Explication de mon code cev3

PasRP-Theo edited this page Jun 5, 2025 · 1 revision

LocalStorage dans cette application

Le localStorage est une API Web qui permet de stocker des données localement dans le navigateur de l'utilisateur. Dans cette application, il est utilisé de manière stratégique pour persister l'historique des suppressions.

Pourquoi utiliser localStorage ici ?

  1. Persistence des données supprimées : Contrairement aux données actives qui sont stockées en base de données, l'historique des suppressions est temporaire et personnel à chaque utilisateur
  2. Performance : Évite des requêtes serveur supplémentaires pour des données d'historique
  3. Expérience utilisateur : L'historique persiste même après fermeture/réouverture du navigateur

Implémentation dans le code

Chargement initial depuis localStorage

useEffect(() => {
  // Chargement des travaux supprimés
  const savedDeletedTravaux = localStorage.getItem('deletedTravaux');
  if (savedDeletedTravaux) {
    try {
      setDeletedTravaux(JSON.parse(savedDeletedTravaux));
    } catch (error) {
      console.error("Erreur lors de la récupération de l'historique travaux :", error);
    }
  }

  // Même logique pour les entretiens
  const savedDeletedEntretiens = localStorage.getItem('deletedEntretiens');
  if (savedDeletedEntretiens) {
    try {
      setDeletedEntretiens(JSON.parse(savedDeletedEntretiens));
    } catch (error) {
      console.error("Erreur lors de la récupération de l'historique entretiens :", error);
    }
  }
}, []);

Sauvegarde automatique

// Sauvegarde des travaux supprimés
useEffect(() => {
  if (deletedTravaux.length > 0) {
    localStorage.setItem('deletedTravaux', JSON.stringify(deletedTravaux));
  } else {
    localStorage.removeItem('deletedTravaux'); // Nettoyage si vide
  }
}, [deletedTravaux]);

// Même logique pour les entretiens supprimés
useEffect(() => {
  if (deletedEntretiens.length > 0) {
    localStorage.setItem('deletedEntretiens', JSON.stringify(deletedEntretiens));
  } else {
    localStorage.removeItem('deletedEntretiens');
  }
}, [deletedEntretiens]);

Nettoyage de l'historique

const handleClearHistory = (type) => {
  if (type === 'travaux') {
    setDeletedTravaux([]);
    localStorage.removeItem('deletedTravaux'); // Suppression explicite
  } else {
    setDeletedEntretiens([]);
    localStorage.removeItem('deletedEntretiens');
  }
};

Avantages de cette approche

  • Automatique : Sauvegarde/chargement transparent
  • Sécurisée : Gestion des erreurs de parsing JSON
  • Optimisée : Suppression des clés vides pour éviter l'encombrement
  • Cohérente : Synchronisation permanente entre état React et localStorage

Analyse détaillée du code TravauxApp

Vue d'ensemble

Cette application React permet de gérer deux types d'éléments : les travaux et les entretiens. Elle offre des fonctionnalités CRUD complètes (Create, Read, Update, Delete) avec une fonctionnalité unique de restauration d'éléments supprimés.

Structure et imports

Imports React et libraries

// Hooks React essentiels
import { useState, useEffect, useCallback } from 'react';
// Navigation
import { useNavigate } from 'react-router-dom';
// Requêtes HTTP
import axios from 'axios';
// Composants Material-UI
import {
  TextField, Button, Typography, Box, Container,
  CircularProgress, Accordion, AccordionSummary, AccordionDetails,
  Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle,
  Tabs, Tab,
} from '@mui/material';

Imports de fonctions utilitaires

import {
  calculateDuree,           // Calcul de durée entre deux dates
  formatDateForInput,       // Formatage date pour input HTML
  formatTimeForInput,       // Formatage heure pour input HTML
  formatDisplayDate,        // Formatage date pour affichage
  calculateTotalCout,       // Calcul du coût total
  createRequestData,        // Création des données pour l'API
  createDeletedTravailWithDate,    // Ajout date de suppression aux travaux
  createDeletedEntretiensWithDate, // Ajout date de suppression aux entretiens
  validateForm             // Validation des formulaires
} from '../utils/logicTravauxEntretiens';

Imports des composants

import TravauxTable from './TravauxTable';    // Tableau principal
import TravauxForm from './TravauxForm';      // Formulaire
import DeletedTable from './DeletedTable';    // Tableau historique suppressions

États (State Management)

États principaux des données

const [travaux, setTravaux] = useState([]);              // Liste des travaux actifs
const [entretiens, setEntretiens] = useState([]);        // Liste des entretiens actifs
const [deletedTravaux, setDeletedTravaux] = useState([]); // Historique travaux supprimés
const [deletedEntretiens, setDeletedEntretiens] = useState([]); // Historique entretiens supprimés

États du formulaire

const [currentTab, setCurrentTab] = useState(0);      // Onglet actuel (0=Travaux, 1=Entretiens)
const [titre, setTitre] = useState('');
const [description, setDescription] = useState('');
const [dateDebut, setDateDebut] = useState('');
const [heureDebut, setHeureDebut] = useState('');
const [dateFin, setDateFin] = useState('');
const [cout, setCout] = useState('');

États de gestion et UI

const [editingTravauxId, setEditingTravauxId] = useState(null);    // ID travaux en modification
const [editingEntretienId, setEditingEntretienId] = useState(null); // ID entretien en modification
const [loadingTravaux, setLoadingTravaux] = useState(false);       // Loading travaux
const [loadingEntretiens, setLoadingEntretiens] = useState(false); // Loading entretiens
const [errorTravaux, setErrorTravaux] = useState(null);            // Erreurs travaux
const [errorEntretiens, setErrorEntretiens] = useState(null);      // Erreurs entretiens

const navigate = useNavigate(); // Navigation programmatique

// État pour dialogue de confirmation
const [confirmDialog, setConfirmDialog] = useState({
  open: false,
  title: '',
  message: '',
  confirmAction: null
});

Récupération des données (Data Fetching)

Fonction fetchTravaux

const fetchTravaux = useCallback(() => {
  setLoadingTravaux(true);
  axios.get('/api/travaux')
    .then(response => {
      if (Array.isArray(response.data)) {
        setTravaux(response.data);
      } else {
        setErrorTravaux('Format de données incorrect');
      }
    })
    .catch(error => {
      console.error("Erreur travaux :", error);
      setErrorTravaux('Erreur lors de la récupération des travaux: ' + (error.message || 'Erreur inconnue'));
    })
    .finally(() => setLoadingTravaux(false));
}, []);

Points clés :

  • useCallback : Évite les re-renders inutiles
  • Validation : Vérifie que la réponse est un tableau
  • Gestion d'erreurs : Messages explicites et fallback
  • Loading state : Indicateur visuel pour l'utilisateur

Fonction fetchEntretiens

Structure identique à fetchTravaux mais pour les entretiens :

  • Endpoint /api/entretiens
  • États spécifiques (loadingEntretiens, errorEntretiens)
  • Même logique de validation et gestion d'erreurs

Effets (useEffect)

Chargement initial et localStorage

useEffect(() => {
  fetchTravaux();
  fetchEntretiens();

  // Chargement des données supprimées depuis localStorage
  const savedDeletedTravaux = localStorage.getItem('deletedTravaux');
  if (savedDeletedTravaux) {
    try {
      setDeletedTravaux(JSON.parse(savedDeletedTravaux));
    } catch (error) {
      console.error("Erreur lors de la récupération de l'historique travaux :", error);
    }
  }

  const savedDeletedEntretiens = localStorage.getItem('deletedEntretiens');
  if (savedDeletedEntretiens) {
    try {
      setDeletedEntretiens(JSON.parse(savedDeletedEntretiens));
    } catch (error) {
      console.error("Erreur lors de la récupération de l'historique entretiens :", error);
    }
  }
}, [fetchTravaux, fetchEntretiens]);

Synchronisation avec localStorage

// Sauvegarde automatique des travaux supprimés
useEffect(() => {
  if (deletedTravaux.length > 0) {
    localStorage.setItem('deletedTravaux', JSON.stringify(deletedTravaux));
  } else {
    localStorage.removeItem('deletedTravaux');
  }
}, [deletedTravaux]);

// Même logique pour les entretiens supprimés
useEffect(() => {
  if (deletedEntretiens.length > 0) {
    localStorage.setItem('deletedEntretiens', JSON.stringify(deletedEntretiens));
  } else {
    localStorage.removeItem('deletedEntretiens');
  }
}, [deletedEntretiens]);

Nettoyage automatique des doublons

useEffect(() => {
  // Évite les doublons entre données actives et historique supprimé
  const activeTravauxIDs = new Set(travaux.map(t => t.TravauxID));
  const filteredDeletedTravaux = deletedTravaux.filter(dt => !activeTravauxIDs.has(dt.TravauxID));
  
  if (filteredDeletedTravaux.length !== deletedTravaux.length || 
      filteredDeletedTravaux.some((item, index) => item.TravauxID !== deletedTravaux[index].TravauxID)) {
    setDeletedTravaux(filteredDeletedTravaux);
  }

  // Même logique pour les entretiens
  const activeEntretienIDs = new Set(entretiens.map(e => e.EntretienID));
  const filteredDeletedEntretiens = deletedEntretiens.filter(de => !activeEntretienIDs.has(de.EntretienID));
  
  if (filteredDeletedEntretiens.length !== deletedEntretiens.length || 
      filteredDeletedEntretiens.some((item, index) => item.EntretienID !== deletedEntretiens[index].EntretienID)) {
    setDeletedEntretiens(filteredDeletedEntretiens);
  }
}, [travaux, entretiens, deletedTravaux, deletedEntretiens]);

Gestion des formulaires

Changement d'onglet

const handleTabChange = (event, newValue) => {
  setCurrentTab(newValue);
  resetForm(); // Remet à zéro le formulaire
};

Gestion des erreurs API

const handleApiError = useCallback((error, setError) => {
  if (error.response) {
    if (error.response.status === 400) {
      console.warn("Erreur 400 :", error.response.data);
      setError("Erreur 400 : Données invalides ou incomplètes.");
    } else if (error.response.status === 500) {
      console.error("Erreur 500 :", error.response.data);
      setError("Erreur 500 : Erreur serveur lors de l'enregistrement.");
    } else {
      setError(`Erreur ${error.response.status} : ${error.response.data?.message || 'Erreur inconnue.'}`);
    }
  } else {
    console.error("Erreur réseau ou autre :", error);
    setError("Erreur de connexion ou réponse inattendue.");
  }
}, []);

Soumission du formulaire

const handleSubmit = async (e) => {
  e.preventDefault();

  // 1. Validation des données
  const validationError = validateForm({
    titre, description, dateDebut, heureDebut, dateFin, cout
  });

  if (validationError) {
    if (currentTab === 0) {
      setErrorTravaux(validationError);
    } else {
      setErrorEntretiens(validationError);
    }
    return;
  }

  // 2. Préparation des données
  const requestData = createRequestData(titre, description, dateDebut, heureDebut, dateFin, cout);
  const isEditingTravaux = editingTravauxId !== null;
  const isEditingEntretien = editingEntretienId !== null;

  // 3. Envoi selon le type (travaux/entretiens)
  if (currentTab === 0) {
    setLoadingTravaux(true);
    setErrorTravaux(null);
    try {
      let response;
      if (isEditingTravaux) {
        response = await axios.put(`/api/travaux/update/${editingTravauxId}`, requestData);
      } else {
        response = await axios.post('/api/travaux', requestData);
      }
      fetchTravaux(); // Rafraîchissement
      resetForm();    // Reset du formulaire
    } catch (error) {
      handleApiError(error, setErrorTravaux);
    } finally {
      setLoadingTravaux(false);
    }
  } else {
    // Logique identique pour les entretiens
    setLoadingEntretiens(true);
    setErrorEntretiens(null);
    try {
      let response;
      if (isEditingEntretien) {
        response = await axios.put(`/api/entretiens/update/${editingEntretienId}`, requestData);
      } else {
        response = await axios.post('/api/entretiens', requestData);
      }
      fetchEntretiens();
      resetForm();
    } catch (error) {
      handleApiError(error, setErrorEntretiens);
    } finally {
      setLoadingEntretiens(false);
    }
  }
};

Reset du formulaire

const resetForm = () => {
  setTitre('');
  setDescription('');
  setDateDebut('');
  setHeureDebut('');
  setDateFin('');
  setCout('');
  setEditingTravauxId(null);
  setEditingEntretienId(null);
  setErrorTravaux(null);
  setErrorEntretiens(null);
};

Gestion des suppressions

Suppression avec confirmation

const handleDeleteClick = (id, type) => {
  setConfirmDialog({
    open: true,
    title: "Confirmation de suppression",
    message: `Êtes-vous sûr de vouloir supprimer ${type === 'travaux' ? 'ces travaux' : 'cet entretien'} ?`,
    confirmAction: async () => {
      setConfirmDialog(prev => ({ ...prev, open: false }));
      if (type === 'travaux') {
        await executeDeleteTravaux(id);
      } else {
        await executeDeleteEntretien(id);
      }
    }
  });
};

Exécution de la suppression (Travaux)

const executeDeleteTravaux = async (id) => {
  try {
    // 1. Récupération de l'élément à supprimer
    const travauxToDelete = travaux.find(t => t.TravauxID === id);
    
    if (travauxToDelete) {
      // 2. Création d'une copie avec date de suppression
      const travauxWithDeleteDate = createDeletedTravailWithDate(travauxToDelete);
      
      // 3. Suppression de la liste active (optimistic update)
      setTravaux(prev => prev.filter(t => t.TravauxID !== id));
      
      // 4. Ajout à l'historique des suppressions
      setDeletedTravaux(prev => [travauxWithDeleteDate, ...prev]);
    }
    
    // 5. Suppression côté serveur
    await axios.delete(`/api/travaux/delete/${id}`);
  } catch (error) {
    handleDeleteError(error, setErrorTravaux, fetchTravaux);
  }
};

Gestion des erreurs de suppression

const handleDeleteError = (error, setError, refreshFunction) => {
  if (error.response?.status === 404) {
    console.warn("Élément non trouvé :", error.response.data);
    setError("Erreur 404 : Élément introuvable.");
  } else if (error.response?.status === 500) {
    console.error("Erreur serveur :", error.response.data);
    setError("Erreur 500 : Impossible de supprimer l'élément.");
  } else {
    console.error("Autre erreur :", error);
    setError("Erreur inattendue lors de la suppression.");
  }
  refreshFunction(); // Resynchronisation avec le serveur
};

Fonctionnalité de restauration

Restauration d'un élément supprimé

const handleRestoreClick = async (deletedItem, type) => {
  try {
    // 1. Préparation des données pour la restauration
    const itemToRestore = {
      titre: deletedItem.Titre,
      description: deletedItem.Description,
      dateDebut: formatDateForInput(deletedItem.DateDebut),
      heureDebut: formatTimeForInput(deletedItem.DateDebut),
      dateFin: formatDateForInput(deletedItem.DateFin),
      cout: deletedItem.Cout
    };

    // 2. Formatage des données pour l'API
    const requestData = createRequestData(
      itemToRestore.titre,
      itemToRestore.description,
      itemToRestore.dateDebut,
      itemToRestore.heureDebut,
      itemToRestore.dateFin,
      itemToRestore.cout
    );

    if (type === 'travaux') {
      // 3. Suppression de l'historique des suppressions
      setDeletedTravaux(prev => prev.filter(t => t.TravauxID !== deletedItem.TravauxID));
      
      // 4. Recréation côté serveur
      await axios.post('/api/travaux', requestData);
      
      // 5. Rafraîchissement des données
      fetchTravaux();
    } else {
      // Même logique pour les entretiens
      setDeletedEntretiens(prev => prev.filter(e => e.EntretienID !== deletedItem.EntretienID));
      await axios.post('/api/entretiens', requestData);
      fetchEntretiens();
    }
  } catch (error) {
    console.error("Erreur lors de la restauration :", error);
    if (type === 'travaux') {
      setErrorTravaux("Erreur lors de la restauration des travaux.");
      fetchTravaux(); // Resynchronisation en cas d'échec
    } else {
      setErrorEntretiens("Erreur lors de la restauration de l'entretien.");
      fetchEntretiens();
    }
  }
};

Nettoyage de l'historique

const handleClearHistory = (type) => {
  setConfirmDialog({
    open: true,
    title: "Vider l'historique",
    message: `Êtes-vous sûr de vouloir vider tout l'historique des suppressions ${type === 'travaux' ? 'des travaux' : 'des entretiens'} ? Cette action est irréversible.`,
    confirmAction: () => {
      setConfirmDialog(prev => ({ ...prev, open: false }));
      if (type === 'travaux') {
        setDeletedTravaux([]);
        localStorage.removeItem('deletedTravaux');
      } else {
        setDeletedEntretiens([]);
        localStorage.removeItem('deletedEntretiens');
      }
    }
  });
};

Fonctions de modification

Modification d'un travail

const handleEditTravaux = (travail) => {
  setCurrentTab(0);                                    // Basculer sur l'onglet Travaux
  setTitre(travail.Titre || '');                       // Remplir le formulaire
  setDescription(travail.Description);
  setDateDebut(formatDateForInput(travail.DateDebut));
  setHeureDebut(formatTimeForInput(travail.DateDebut));
  setDateFin(formatDateForInput(travail.DateFin));
  setCout(travail.Cout);
  setEditingTravauxId(travail.TravauxID);              // Mode édition
  setEditingEntretienId(null);                         // Reset autre type
  setErrorTravaux(null);                               // Reset erreurs
  setErrorEntretiens(null);
  window.scrollTo({ top: 0, behavior: 'smooth' });    // Scroll vers le formulaire
};

Modification d'un entretien

const handleEditEntretien = (entretien) => {
  setCurrentTab(1);                                        // Basculer sur l'onglet Entretiens
  setTitre(entretien.Titre || '');                         // Remplir le formulaire
  setDescription(entretien.Description);
  setDateDebut(formatDateForInput(entretien.DateDebut));
  setHeureDebut(formatTimeForInput(entretien.DateDebut));
  setDateFin(formatDateForInput(entretien.DateFin));
  setCout(entretien.Cout);
  setEditingEntretienId(entretien.EntretienID);            // Mode édition
  setEditingTravauxId(null);                               // Reset autre type
  setErrorTravaux(null);                                   // Reset erreurs
  setErrorEntretiens(null);
  window.scrollTo({ top: 0, behavior: 'smooth' });        // Scroll vers le formulaire
};

Calculs et données dérivées

Calculs des totaux

const totalCoutTravaux = calculateTotalCout(travaux);
const totalCoutEntretiens = calculateTotalCout(entretiens);
const totalDeletedCoutTravaux = calculateTotalCout(deletedTravaux);
const totalDeletedCoutEntretiens = calculateTotalCout(deletedEntretiens);

Fusion et tri des données

// Fusion de tous les éléments actifs avec type et tri par ID décroissant
const allItems = [
  ...travaux.map(t => ({ ...t, type: 'travaux', id: t.TravauxID })),
  ...entretiens.map(e => ({ ...e, type: 'entretiens', id: e.EntretienID }))
].sort((a, b) => b.id - a.id);

// Fusion de tous les éléments supprimés avec tri par date de suppression
const allDeletedItems = [
  ...deletedTravaux.map(t => ({ ...t, type: 'travaux', id: t.TravauxID })),
  ...deletedEntretiens.map(e => ({ ...e, type: 'entretiens', id: e.EntretienID }))
].sort((a, b) => new Date(b.dateSuppressionIso) - new Date(a.dateSuppressionIso));

Variables d'état dérivées

const isLoading = loadingTravaux || loadingEntretiens;
const currentError = currentTab === 0 ? errorTravaux : errorEntretiens;
const isEditing = editingTravauxId !== null || editingEntretienId !== null;

Interface utilisateur (JSX)

Structure principale

return (
  <Container maxWidth="lg">
    {/* Titre principal */}
    <Box sx={{ display: 'flex', justifyContent: 'center' }}>
      <Typography variant="h4" gutterBottom>
        Gestion des travaux et entretiens
      </Typography>
    </Box>

    {/* Layout principal en deux colonnes */}
    <Box sx={{ display: 'flex', gap: 4, flexDirection: { xs: 'column', md: 'row' } }}>
      {/* Colonne gauche : Formulaire */}
      <Box sx={{ flex: 1, bgcolor: 'white', padding: 3, borderRadius: 2, /* ... styles */ }}>
        {/* Titre dynamique */}
        <Typography variant="h5" gutterBottom>
          {isEditing
            ? `Modifier ${currentTab === 0 ? 'des travaux' : 'un entretien'}`
            : `Ajouter ${currentTab === 0 ? 'des travaux' : 'un entretien'}`}
        </Typography>

        {/* Onglets */}
        <Tabs value={currentTab} onChange={handleTabChange} sx={{ marginBottom: 3 }}>
          <Tab label="Travaux" />
          <Tab label="Entretiens" />
        </Tabs>

        {/* Formulaire */}
        <TravauxForm
          titre={titre}
          description={description}
          dateDebut={dateDebut}
          heureDebut={heureDebut}
          dateFin={dateFin}
          cout={cout}
          setTitre={setTitre}
          setDescription={setDescription}
          setDateDebut={setDateDebut}
          setHeureDebut={setHeureDebut}
          setDateFin={setDateFin}
          setCout={setCout}
          handleSubmit={handleSubmit}
          resetForm={resetForm}
          isEditing={isEditing}
          isLoading={isLoading}
          currentTab={currentTab}
          currentError={currentError}
        />
      </Box>

      {/* Colonne droite : Tableaux */}
      <Box sx={{ flex: 1, bgcolor: 'white', padding: 3, borderRadius: 2, /* ... styles */ }}>
        <Typography variant="h5" gutterBottom>
          Tous les travaux et entretiens
        </Typography>

        {/* Gestion des états de chargement/erreur */}
        {isLoading && allItems.length === 0 ? (
          <Box sx={{ display: 'flex', justifyContent: 'center', padding: 4 }}>
            <CircularProgress />
          </Box>
        ) : currentError && allItems.length === 0 ? (
          <Typography color="error">{currentError}</Typography>
        ) : allItems.length > 0 ? (
          <>
            {/* Tableau principal */}
            <TravauxTable
              items={allItems}
              handleEditTravaux={handleEditTravaux}
              handleEditEntretien={handleEditEntretien}
              handleDeleteClick={handleDeleteClick}
              calculateDuree={calculateDuree}
              formatDisplayDate={formatDisplayDate}
            />

            {/* Affichage des totaux */}
            <Box sx={{ marginTop: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
              <Typography variant="h6">
                Coût total des travaux : {totalCoutTravaux.toFixed(2)}</Typography>
              <Typography variant="h6">
                Coût total des entretiens : {totalCoutEntretiens.toFixed(2)}</Typography>
              <Typography variant="h6" sx={{ fontWeight: 'bold', color: 'primary.main' }}>
                Coût total général : {(totalCoutTravaux + totalCoutEntretiens).toFixed(2)}</Typography>
            </Box>
          </>
        ) : (
          <Typography sx={{ padding: 2, textAlign: 'center' }}>
            Aucun travaux ou entretiens enregistrés.
          </Typography>
        )}

        {/* Tableau des éléments supprimés */}
        <DeletedTable
          allDeletedItems={allDeletedItems}
          totalDeletedCoutTravaux={totalDeletedCoutTravaux}
          totalDeletedCoutEntretiens={totalDeletedCoutEntretiens}
          handleRestoreClick={handleRestoreClick}
          handleClearHistory={handleClearHistory}
          deletedTravauxLength={deletedTravaux.length}
          deletedEntretiensLength={deletedEntretiens.length}
          calculateDuree={calculateDuree}
          formatDisplayDate={formatDisplayDate}
        />
      </Box>
    </Box>

    {/* Dialogue de confirmation */}
    <Dialog
      open={confirmDialog.open}
      onClose={handleCloseConfirmDialog}
      aria-labelledby="alert-dialog-title"
      aria-describedby="alert-dialog-description"
    >
      <DialogTitle id="alert-dialog-title">{confirmDialog.title}</DialogTitle>
      <DialogContent>
        <DialogContentText id="alert-dialog-description">
          {confirmDialog.message}
        </DialogContentText>
      </DialogContent>
      <DialogActions>
        <Button onClick={handleCloseConfirmDialog} color="primary">
          Annuler
        </Button>
        <Button
          onClick={() => confirmDialog.confirmAction && confirmDialog.confirmAction()}
          color="primary"
          variant="contained"
          autoFocus
        >
          Confirmer
        </Button>
      </DialogActions>
    </Dialog>
  </Container>
);

LocalStorage dans cette application

Pourquoi utiliser localStorage ?

  1. Persistence des données supprimées : L'historique des suppressions persiste entre les sessions
  2. Performance : Évite des requêtes serveur pour des données temporaires
  3. Expérience utilisateur : Données disponibles instantanément au chargement

Implémentation du localStorage

Chargement initial

useEffect(() => {
  const savedDeletedTravaux = localStorage.getItem('deletedTravaux');
  if (savedDeletedTravaux) {
    try {
      setDeletedTravaux(JSON.parse(savedDeletedTravaux));
    } catch (error) {
      console.error("Erreur parsing JSON :", error);
    }
  }
}, []);

Sauvegarde automatique

useEffect(() => {
  if (deletedTravaux.length > 0) {
    localStorage.setItem('deletedTravaux', JSON.stringify(deletedTravaux));
  } else {
    localStorage.removeItem('deletedTravaux'); // Nettoyage si vide
  }
}, [

Clone this wiki locally