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>
447 lines
14 KiB
Markdown
447 lines
14 KiB
Markdown
# 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 :**
|
|
```javascript
|
|
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) :**
|
|
```javascript
|
|
// 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) :**
|
|
```javascript
|
|
import {
|
|
apiClient,
|
|
listParents,
|
|
listGrandParents,
|
|
getHolidays,
|
|
getPublicHolidays,
|
|
getPersonalLeaves
|
|
} from "../services/api-client";
|
|
```
|
|
|
|
**Ajout des états (lignes 142-143) :**
|
|
```javascript
|
|
const [holidays, setHolidays] = useState([]);
|
|
const [personalLeaves, setPersonalLeaves] = useState([]);
|
|
```
|
|
|
|
**Chargement des congés (lignes 264-286) :**
|
|
```javascript
|
|
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) :**
|
|
```javascript
|
|
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) :**
|
|
```javascript
|
|
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) :**
|
|
```javascript
|
|
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) :**
|
|
```javascript
|
|
<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
|
|
|
|
```javascript
|
|
// 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 !**
|