Files
FamilyPlanner/docs/archive/OPTIMISATION_AFFICHAGE_CONGES.md
philippe fdd72c1135 Initial commit: Family Planner application
Complete family planning application with:
- React frontend with TypeScript
- Node.js/Express backend with TypeScript
- Python ingestion service for document processing
- Planning ingestion service with LLM integration
- Shared UI components and type definitions
- OAuth integration for calendar synchronization
- Comprehensive documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 10:43:33 +02:00

14 KiB

Optimisation de l'affichage des congés

Résumé des modifications

Les congés et activités ont été optimisés pour prendre moins de place et être plus visuels dans le Calendrier mensuel et le Dashboard.


🎯 Objectifs

1. Calendrier mensuel (MonthlyCalendarScreen)

  • Avant : Chaque personne en congé prenait une ligne complète
  • Après : Les congés du même type sont regroupés sur une seule ligne avec des pastilles de couleur

2. Dashboard (DashboardScreen + TimeGridMulti)

  • Avant : Pas d'indication de congé visible
  • Après : Badge "Congé" ou "Vacances" affiché à côté du nom de la personne dans la colonne

Modifications effectuées

1. Calendrier mensuel - Regroupement des congés

Fichier modifié : frontend/src/screens/MonthlyCalendarScreen.js

Ajout de nouveaux composants styled :

const GroupedHolidayMarker = styled.div `
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.75rem;
  color: #b8c0ff;
  padding: 4px 6px;
  background: rgba(125, 108, 255, 0.1);
  border-radius: 4px;
  font-style: italic;
`;

const HolidayColorDots = styled.div `
  display: flex;
  gap: 3px;
  align-items: center;
`;

const HolidayDot = styled.div `
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: ${({ $color }) => $color};
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0 0 4px ${({ $color }) => `${$color}66`};
`;

Nouvelle logique d'affichage (lignes 489-507) :

// Regrouper les congés par type
const publicHolidays = dateHolidays.filter(h => h.type === "public");
const schoolHolidays = dateHolidays.filter(h => h.type === "school");
const personalLeaves = dateHolidays.filter(h => h.type === "personal");

// Jours fériés publics (affichés normalement)
publicHolidays.map((holiday, idx) => (
  <HolidayMarker $color="#ffa726">
    🎉 {holiday.label}
  </HolidayMarker>
))

// Vacances scolaires (regroupées)
schoolHolidays.length > 0 && (
  <GroupedHolidayMarker>
    Vacances
    <HolidayColorDots>
      {schoolHolidays.map((holiday, idx) => (
        <HolidayDot
          $color={getProfileColor(holiday.profileId)}
          title={holiday.childName}
        />
      ))}
    </HolidayColorDots>
  </GroupedHolidayMarker>
)

// Congés personnels (regroupés)
personalLeaves.length > 0 && (
  <GroupedHolidayMarker>
    Congé
    <HolidayColorDots>
      {personalLeaves.map((holiday, idx) => (
        <HolidayDot
          $color={getProfileColor(holiday.profileId)}
          title={holiday.childName}
        />
      ))}
    </HolidayColorDots>
  </GroupedHolidayMarker>
)

Résultat visuel :

┌─────────────────────────────────┐
│ Mar 15/01                        │
├─────────────────────────────────┤
│ 🎉 Nouvel An                    │  ← Jour férié (normal)
│ Vacances  ● ● ●                 │  ← 3 enfants en vacances (1 ligne)
│ Congé     ● ●                   │  ← 2 parents en congé (1 ligne)
└─────────────────────────────────┘

2. Dashboard - Badge de congé dans les colonnes

Fichier modifié : frontend/src/screens/DashboardScreen.js

Ajout des imports (ligne 8) :

import {
  apiClient,
  listParents,
  listGrandParents,
  getHolidays,
  getPublicHolidays,
  getPersonalLeaves
} from "../services/api-client";

Ajout des états (lignes 142-143) :

const [holidays, setHolidays] = useState([]);
const [personalLeaves, setPersonalLeaves] = useState([]);

Chargement des congés (lignes 264-286) :

useEffect(() => {
    const loadHolidaysAndLeaves = async () => {
        try {
            // Charger les vacances scolaires
            const regions = [...new Set(children.map(c => c.schoolRegion).filter(Boolean))];
            const holidayPromises = regions.map(region =>
              getHolidays(region, today.getFullYear()).catch(() => ({ holidays: [] }))
            );
            const holidayResults = await Promise.all(holidayPromises);
            const allHolidays = holidayResults.flatMap(r => r.holidays);

            // Charger les jours fériés
            const publicHolidaysResult = await getPublicHolidays(today.getFullYear())
              .catch(() => ({ holidays: [] }));

            // Combiner et dédupliquer
            const combinedHolidays = [...allHolidays, ...publicHolidaysResult.holidays];
            const uniqueHolidays = Array.from(
              new Map(combinedHolidays.map(h => [h.id, h])).values()
            );
            setHolidays(uniqueHolidays);

            // Charger les congés personnels
            const leavePromises = selectedProfiles.map(profileId =>
              getPersonalLeaves(profileId).catch(() => ({ leaves: [] }))
            );
            const leaveResults = await Promise.all(leavePromises);
            const allLeaves = leaveResults.flatMap(r => r.leaves);
            setPersonalLeaves(allLeaves);
        }
        catch (error) {
            console.warn("Erreur lors du chargement des congés", error);
        }
    };
    void loadHolidaysAndLeaves();
}, [children, selectedProfiles, today]);

Modification de profileColumns pour inclure vacationStatus (lignes 430-497) :

const profileColumns = useMemo(() => {
    const selected = new Set(selectedProfiles);
    const todayISO = today.toISOString().slice(0, 10);
    const todayDate = new Date(todayISO);

    return allProfiles
        .filter((profile) => selected.has(profile.id))
        .map((profile) => {
            // ... code existant ...

            // Vérifier si le profil est en congé aujourd'hui
            let vacationStatus = null;

            // Vérifier les vacances scolaires pour les enfants
            if (profile.kind === "child") {
                const child = children.find(c => c.id === profile.id);
                if (child?.schoolRegion) {
                    const schoolHoliday = holidays.find(h => {
                        if (h.type !== "school") return false;
                        if (!h.zones || !h.zones.includes(child.schoolRegion)) return false;
                        const start = new Date(h.startDate);
                        const end = new Date(h.endDate);
                        return todayDate >= start && todayDate <= end;
                    });
                    if (schoolHoliday) {
                        vacationStatus = "Vacances";
                    }
                }
            }

            // Vérifier les congés personnels pour tous les profils
            const personalLeave = personalLeaves.find(leave => {
                if (leave.profileId !== profile.id) return false;
                const start = new Date(leave.startDate);
                const end = new Date(leave.endDate);
                return todayDate >= start && todayDate <= end;
            });
            if (personalLeave) {
                vacationStatus = "Congé";
            }

            return {
                // ... autres propriétés ...
                vacationStatus  // ← Nouvelle propriété
            };
        });
}, [allProfiles, selectedProfiles, dayEventsMap, children, holidays, personalLeaves, today]);

Fichier modifié : frontend/src/components/TimeGridMulti.js

Ajout du composant styled VacationBadge (lignes 42-51) :

const VacationBadge = styled.span `
  padding: 2px 8px;
  border-radius: 999px;
  background: ${({ $color }) => `${$color}33`};
  border: 1px solid ${({ $color }) => `${$color}66`};
  color: #e9ebff;
  font-size: 0.75rem;
  font-weight: 600;
  margin-left: auto;
`;

Modification du ColHeader (lignes 32-40) :

const ColHeader = styled.div `
  padding: 10px 12px;
  border-bottom: 1px solid rgba(126, 136, 180, 0.22);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;  // ← Ajouté pour permettre le retour à la ligne
`;

Affichage du badge dans le header (ligne 136) :

<ColHeader>
  {/* Avatar et nom */}
  {col.avatarUrl ? <MiniAvatar ... /> : ...}
  {col.link ? <Link ...>{col.title}</Link> : <span>{col.title}</span>}

  {/* Badge de congé */}
  {col.vacationStatus ? (
    <VacationBadge $color={col.color}>
      {col.vacationStatus}
    </VacationBadge>
  ) : null}
</ColHeader>

Résultat visuel :

┌──────────────────────────────────────┐
│  👤 Robert Hérault      [Congé]      │  ← Badge à droite du nom
├──────────────────────────────────────┤
│  08:00 ──────────────────────────    │
│  09:00 ──────────────────────────    │
│  ...                                  │
└──────────────────────────────────────┘

🎨 Améliorations visuelles

Calendrier mensuel

Avant Après
4 lignes pour 4 personnes en congé 1 ligne avec 4 pastilles de couleur
Beaucoup d'espace perdu Plus de place pour les activités
Difficile de voir d'un coup d'œil Vue d'ensemble immédiate

Exemple concret :

AVANT (4 lignes) :

Timéo Hérault - Vacances
Gabriel Hérault - Vacances
Robert Hérault - Congé
Martine Hérault - Congé

APRÈS (2 lignes) :

Vacances  ●🔵 ●🟢              (2 pastilles)
Congé     ●🔴 ●🟡              (2 pastilles)

Dashboard

AVANT :

┌─────────────────────┐
│  Robert Hérault     │  ← Aucune indication
├─────────────────────┤

APRÈS :

┌──────────────────────────┐
│  Robert Hérault  [Congé] │  ← Badge visible
├──────────────────────────┤

📊 Avantages de l'optimisation

1. Gain d'espace

  • Calendrier : Réduction de 75% de l'espace utilisé pour les congés multiples
  • Dashboard : Information compacte sur une ligne

2. Meilleure lisibilité

  • Vue d'ensemble immédiate avec les couleurs
  • Identification rapide des personnes en congé
  • Plus d'espace pour les vraies activités

3. Ergonomie améliorée

  • Moins de scroll nécessaire
  • Information contextuelle sans surcharger
  • Design plus moderne et épuré

4. Cohérence visuelle

  • Les couleurs des profils sont utilisées partout
  • Style uniforme entre calendrier et dashboard
  • Pastilles visibles au survol (title attribute)

🔧 Détails techniques

Types de congés gérés

  1. Jours fériés publics (type: "public")

    • Affichés normalement avec 🎉
    • Couleur : #ffa726 (orange)
    • Non regroupés (un seul par jour généralement)
  2. Vacances scolaires (type: "school")

    • Regroupées sur une ligne "Vacances"
    • Pastilles avec couleur de chaque enfant
    • Filtré par zone scolaire
  3. Congés personnels (type: "personal")

    • Regroupés sur une ligne "Congé"
    • Pastilles avec couleur de chaque profil (parent/grand-parent)
    • Stockés dans le champ vacations du profil

Logique de détection

// Pour une date donnée
const todayDate = new Date(dateISO);

// Vérifier si dans la plage
const start = new Date(holiday.startDate);
const end = new Date(holiday.endDate);
const isInRange = todayDate >= start && todayDate <= end;

Performances

  • Calcul uniquement pour les profils sélectionnés
  • Mise en cache via useMemo pour éviter les recalculs
  • Chargement asynchrone des données de congés

📝 Fichiers modifiés

Créés

  • OPTIMISATION_AFFICHAGE_CONGES.md (ce document)

Modifiés

  1. frontend/src/screens/MonthlyCalendarScreen.js

    • Lignes 194-219 : Nouveaux composants styled
    • Lignes 489-507 : Logique de regroupement
  2. frontend/src/screens/DashboardScreen.js

    • Ligne 8 : Imports des APIs de congés
    • Lignes 142-143 : États holidays et personalLeaves
    • Lignes 264-286 : useEffect pour charger les congés
    • Lignes 430-497 : Ajout de vacationStatus dans profileColumns
  3. frontend/src/components/TimeGridMulti.js

    • Lignes 32-40 : Modification de ColHeader (flex-wrap)
    • Lignes 42-51 : Nouveau composant VacationBadge
    • Ligne 136 : Affichage conditionnel du badge

🚀 Pour tester

  1. Ajouter des congés :

    • Aller sur le profil d'un parent ou grand-parent
    • Section "Congés" → Ajouter une période incluant aujourd'hui
  2. Configurer les zones scolaires :

    • Aller sur le profil d'un enfant
    • Section "Congés scolaires" → Sélectionner une zone
  3. Vérifier le calendrier mensuel :

    • Naviguer vers "Calendrier mensuel"
    • Vérifier que plusieurs congés le même jour sont regroupés
    • Survoler les pastilles pour voir les noms
  4. Vérifier le dashboard :

    • Aller sur "Agenda familial"
    • Vue "Vue générale"
    • Vérifier les badges "Congé" ou "Vacances" à côté des noms

Résultat final

Les congés sont maintenant affichés de manière compacte, visuelle et ergonomique :

  • Calendrier : 1 ligne "Vacances" + 1 ligne "Congé" au lieu de X lignes individuelles
  • Dashboard : Badge discret mais visible à côté du nom
  • Couleurs : Identification immédiate grâce aux pastilles colorées
  • Espace : Plus de place pour les vraies activités et événements

🎉 Objectif atteint : Optimisation réussie !