Files
FamilyPlanner/frontend/src/screens/ChildDetailScreen.tsx
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

1031 lines
32 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import styled from "styled-components";
import { useChildren } from "../state/ChildrenContext";
import { API_BASE_URL, uploadPlanning, uploadAvatar } from "../services/api-client";
const Container = styled.div`
max-width: 1400px;
margin: 0 auto;
`;
const Header = styled.header`
display: flex;
align-items: center;
gap: 24px;
margin-bottom: 32px;
padding: 30px;
border-radius: 24px;
background: linear-gradient(135deg, rgba(29, 36, 66, 0.95) 0%, rgba(45, 27, 78, 0.95) 100%);
border: 1px solid rgba(126, 136, 180, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
`;
const BackButton = styled.button`
padding: 12px 20px;
border-radius: 12px;
border: 1px solid rgba(126, 136, 180, 0.4);
background: rgba(16, 22, 52, 0.9);
color: #d9dcff;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
&:hover {
transform: translateY(-2px);
background: rgba(26, 32, 62, 0.9);
}
`;
const Avatar = styled.div<{ $imageUrl?: string; $color: string }>`
width: 120px;
height: 120px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 2.5rem;
background: ${({ $imageUrl, $color }) => $imageUrl ? `url(${$imageUrl}) center/cover` : $color};
color: ${({ $imageUrl }) => ($imageUrl ? "transparent" : "#040411")};
border: 4px solid ${({ $color }) => $color};
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
`;
const HeaderInfo = styled.div`
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
`;
const Title = styled.h1`
margin: 0;
font-size: 2rem;
font-weight: 700;
`;
const Meta = styled.div`
display: flex;
gap: 20px;
flex-wrap: wrap;
color: var(--text-muted);
font-size: 14px;
`;
const MetaItem = styled.div<{ $icon?: string }>`
display: flex;
align-items: center;
gap: 8px;
&::before {
content: '${props => props.$icon || "•"}';
color: #66d9ff;
}
`;
const PronoteStatus = styled.div<{ $connected: boolean }>`
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
background: ${props => props.$connected ? 'rgba(46, 213, 115, 0.2)' : 'rgba(255, 107, 107, 0.2)'};
color: ${props => props.$connected ? '#2ed573' : '#ff6b6b'};
border: 1px solid ${props => props.$connected ? 'rgba(46, 213, 115, 0.4)' : 'rgba(255, 107, 107, 0.4)'};
&::before {
content: '●';
font-size: 8px;
}
`;
const ActionButtons = styled.div`
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 20px;
`;
const Button = styled.button`
padding: 12px 24px;
border-radius: 12px;
border: none;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
&:hover {
transform: translateY(-2px);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
const PrimaryButton = styled(Button)`
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #ffffff;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
&:hover {
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
`;
const SecondaryButton = styled(Button)`
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
&:hover {
background: rgba(255, 255, 255, 0.2);
}
`;
const PronoteButton = styled(Button)`
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
box-shadow: 0 4px 15px rgba(240, 147, 251, 0.4);
&:hover {
box-shadow: 0 6px 20px rgba(240, 147, 251, 0.6);
}
`;
const DangerButton = styled(Button)`
background: rgba(255, 107, 107, 0.2);
color: #ff6b6b;
border: 1px solid rgba(255, 107, 107, 0.4);
&:hover {
background: rgba(255, 107, 107, 0.3);
}
`;
const MainGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 20px;
`;
const Card = styled.section`
padding: 25px;
border-radius: 20px;
background: rgba(29, 36, 66, 0.92);
border: 1px solid rgba(126, 136, 180, 0.22);
display: flex;
flex-direction: column;
gap: 20px;
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
}
`;
const CardHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
`;
const CardTitle = styled.h2<{ $icon?: string }>`
margin: 0;
font-size: 20px;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
&::before {
content: '${props => props.$icon || ""}';
font-size: 24px;
color: #66d9ff;
}
`;
const CardBadge = styled.span`
background: rgba(255, 255, 255, 0.2);
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
`;
const Select = styled.select`
padding: 12px 14px;
border-radius: 12px;
border: 1px solid rgba(126, 136, 180, 0.28);
background: rgba(16, 22, 52, 0.9);
color: #ffffff;
cursor: pointer;
width: 100%;
font-size: 14px;
`;
const Label = styled.label`
display: flex;
flex-direction: column;
gap: 8px;
font-weight: 600;
font-size: 14px;
`;
const HolidayList = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
max-height: 400px;
overflow-y: auto;
`;
const HolidayItem = styled.div`
padding: 15px;
border-radius: 10px;
background: rgba(46, 213, 115, 0.1);
border-left: 3px solid #2ed573;
`;
const HolidayName = styled.div`
font-weight: 600;
font-size: 14px;
margin-bottom: 5px;
`;
const HolidayDate = styled.div`
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 3px;
`;
const HolidayType = styled.div`
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
font-style: italic;
`;
const PersonalNotes = styled.textarea`
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
min-height: 150px;
color: white;
font-size: 14px;
line-height: 1.6;
resize: vertical;
font-family: inherit;
&:focus {
outline: none;
border-color: #667eea;
background: rgba(255, 255, 255, 0.08);
}
`;
const EmptyState = styled.div`
text-align: center;
padding: 30px;
color: rgba(255, 255, 255, 0.5);
font-size: 14px;
`;
const StatusMessage = styled.div`
padding: 12px;
border-radius: 12px;
background: rgba(12, 18, 42, 0.7);
border: 1px solid rgba(126, 136, 180, 0.24);
color: var(--text-muted);
text-align: center;
`;
const ErrorText = styled.div`
color: #ff7b8a;
font-size: 0.9rem;
`;
const SuccessText = styled.div`
color: #2ed573;
font-size: 0.9rem;
`;
// Modal components
const ModalOverlay = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(5px);
`;
const ModalContent = styled.div<{ $width?: string }>`
background: linear-gradient(135deg, rgba(29, 36, 66, 0.98) 0%, rgba(45, 27, 78, 0.98) 100%);
border-radius: 24px;
padding: 40px;
max-width: ${({ $width }) => $width || '500px'};
width: 90%;
border: 1px solid rgba(126, 136, 180, 0.3);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
max-height: 90vh;
overflow-y: auto;
`;
const ModalTitle = styled.h2`
margin: 0 0 24px 0;
font-size: 24px;
text-align: center;
`;
const Input = styled.input`
width: 100%;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid rgba(126, 136, 180, 0.28);
background: rgba(16, 22, 52, 0.9);
color: #ffffff;
font-size: 14px;
margin-bottom: 16px;
&:focus {
outline: none;
border-color: #667eea;
}
&::placeholder {
color: rgba(255, 255, 255, 0.5);
}
`;
const FileInput = styled.input`
display: none;
`;
const FileUploadArea = styled.div<{ $isDragOver: boolean }>`
border: 2px dashed ${({ $isDragOver }) => $isDragOver ? '#667eea' : 'rgba(126, 136, 180, 0.4)'};
border-radius: 12px;
padding: 40px;
text-align: center;
background: ${({ $isDragOver }) => $isDragOver ? 'rgba(102, 126, 234, 0.1)' : 'rgba(255, 255, 255, 0.05)'};
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 16px;
&:hover {
background: rgba(255, 255, 255, 0.08);
border-color: #667eea;
}
`;
const FileUploadIcon = styled.div`
font-size: 48px;
margin-bottom: 12px;
`;
const FileUploadText = styled.div`
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
margin-bottom: 8px;
`;
const FileUploadHint = styled.div`
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
`;
const SelectedFile = styled.div`
background: rgba(102, 126, 234, 0.2);
padding: 12px 16px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
`;
const FileName = styled.div`
color: #ffffff;
font-size: 14px;
font-weight: 600;
`;
const RemoveFileButton = styled.button`
background: rgba(255, 107, 107, 0.3);
border: none;
color: #ff6b6b;
padding: 6px 12px;
border-radius: 8px;
cursor: pointer;
font-size: 12px;
&:hover {
background: rgba(255, 107, 107, 0.5);
}
`;
const ModalButtons = styled.div`
display: flex;
gap: 12px;
margin-top: 24px;
`;
const ColorPicker = styled.input`
width: 60px;
height: 40px;
border-radius: 8px;
border: 1px solid rgba(126, 136, 180, 0.28);
background: transparent;
cursor: pointer;
`;
const REGION_LABELS: Record<string, string> = {
"zone-a": "Zone A (Besançon, Bordeaux, Clermont-Ferrand, Dijon, Grenoble, Limoges, Lyon, Poitiers)",
"zone-b": "Zone B (Aix-Marseille, Amiens, Caen, Lille, Nancy-Metz, Nantes, Nice, Orléans-Tours, Reims, Rennes, Rouen, Strasbourg)",
"zone-c": "Zone C (Créteil, Montpellier, Paris, Toulouse, Versailles)",
corse: "Corse",
monaco: "Monaco",
guadeloupe: "Guadeloupe",
guyane: "Guyane",
martinique: "Martinique",
reunion: "Réunion",
mayotte: "Mayotte"
};
interface Holiday {
id: string;
title: string;
startDate: string;
endDate: string;
description?: string;
}
interface Child {
id: string;
fullName: string;
colorHex: string;
email?: string;
notes?: string;
avatar?: { url: string };
schoolRegion?: string;
}
export const ChildDetailScreen = () => {
const { childId } = useParams<{ childId: string }>();
const navigate = useNavigate();
const { children, updateChild, deleteChild } = useChildren();
const fileInputRef = useRef<HTMLInputElement>(null);
const editAvatarInputRef = useRef<HTMLInputElement>(null);
const [child, setChild] = useState<Child | null>(null);
const [selectedRegion, setSelectedRegion] = useState<string>("");
const [holidays, setHolidays] = useState<Holiday[]>([]);
const [loadingHolidays, setLoadingHolidays] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
// États des modals
const [showImportModal, setShowImportModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
// États Import
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isDragOver, setIsDragOver] = useState(false);
const [importing, setImporting] = useState(false);
const [importProgress, setImportProgress] = useState<string>("");
// États Edit
const [editName, setEditName] = useState("");
const [editEmail, setEditEmail] = useState("");
const [editColor, setEditColor] = useState("#667eea");
const [editAvatar, setEditAvatar] = useState<File | null>(null);
const [editAvatarPreview, setEditAvatarPreview] = useState<string | null>(null);
const [personalNotes, setPersonalNotes] = useState("");
useEffect(() => {
const foundChild = children.find((c: Child) => c.id === childId);
if (foundChild) {
setChild(foundChild);
setSelectedRegion(foundChild.schoolRegion ?? "");
setPersonalNotes(foundChild.notes || "");
}
}, [childId, children]);
useEffect(() => {
if (selectedRegion) {
loadHolidays(selectedRegion);
} else {
setHolidays([]);
}
}, [selectedRegion]);
const loadHolidays = async (region: string) => {
setLoadingHolidays(true);
setError(null);
try {
const response = await fetch(`${API_BASE_URL}/holidays?region=${region}&year=${new Date().getFullYear()}`);
const data = await response.json();
if (data.success) {
setHolidays(data.holidays);
} else {
setError("Impossible de charger les congés");
}
} catch (err) {
setError("Erreur de connexion au serveur");
} finally {
setLoadingHolidays(false);
}
};
const handleSaveRegion = async () => {
if (!child || !childId) return;
setSaving(true);
setError(null);
try {
await updateChild(childId, {
schoolRegion: selectedRegion || undefined
});
setSuccess("Région mise à jour avec succès !");
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
setError("Impossible de sauvegarder la région");
} finally {
setSaving(false);
}
};
const handleSaveNotes = async () => {
if (!child || !childId) return;
try {
await updateChild(childId, {
notes: personalNotes
});
setSuccess("Notes sauvegardées !");
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
setError("Erreur lors de la sauvegarde");
}
};
// Import functionality
const handleOpenImport = () => {
setShowImportModal(true);
setSelectedFile(null);
setImportProgress("");
setError(null);
};
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
setSelectedFile(files[0]);
}
};
const handleDragOver = (event: React.DragEvent) => {
event.preventDefault();
setIsDragOver(true);
};
const handleDragLeave = () => {
setIsDragOver(false);
};
const handleDrop = (event: React.DragEvent) => {
event.preventDefault();
setIsDragOver(false);
const files = event.dataTransfer.files;
if (files && files.length > 0) {
setSelectedFile(files[0]);
}
};
const handleImport = async () => {
if (!selectedFile || !childId) return;
setImporting(true);
setError(null);
setImportProgress("Analyse du fichier en cours...");
try {
const result = await uploadPlanning(childId, selectedFile);
setImportProgress(`Import réussi ! ${result.schedule.activities?.length || 0} activités détectées.`);
setTimeout(() => {
setShowImportModal(false);
setSuccess("Fichier importé avec succès !");
setTimeout(() => setSuccess(null), 3000);
}, 2000);
} catch (err) {
setError("Erreur lors de l'import du fichier");
setImportProgress("");
} finally {
setImporting(false);
}
};
// Edit functionality
const handleOpenEdit = () => {
if (!child) return;
setEditName(child.fullName);
setEditEmail(child.email || "");
setEditColor(child.colorHex);
setEditAvatar(null);
setEditAvatarPreview(child.avatar?.url || null);
setShowEditModal(true);
setError(null);
};
const handleEditAvatarSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
const file = files[0];
setEditAvatar(file);
// Create preview
const reader = new FileReader();
reader.onload = (e) => {
setEditAvatarPreview(e.target?.result as string);
};
reader.readAsDataURL(file);
}
};
const handleSaveEdit = async () => {
if (!child || !childId) return;
setSaving(true);
setError(null);
try {
let avatarData = undefined;
// Upload avatar if changed
if (editAvatar) {
const uploadResult = await uploadAvatar(editAvatar);
avatarData = { url: uploadResult.url };
}
// Update child
await updateChild(childId, {
fullName: editName,
email: editEmail || undefined,
colorHex: editColor,
...(avatarData && { avatar: avatarData })
});
setShowEditModal(false);
setSuccess("Profil mis à jour avec succès !");
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
setError("Erreur lors de la modification du profil");
} finally {
setSaving(false);
}
};
// Delete functionality
const handleOpenDelete = () => {
setShowDeleteModal(true);
setError(null);
};
const handleConfirmDelete = async () => {
if (!childId) return;
setSaving(true);
setError(null);
try {
await deleteChild(childId);
navigate("/profiles");
} catch (err) {
setError("Erreur lors de la suppression du profil");
setSaving(false);
}
};
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
return new Date(dateStr).toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric"
});
};
if (!child) {
return <Container><StatusMessage>Chargement du profil...</StatusMessage></Container>;
}
const initials = child.fullName
.split(" ")
.filter(Boolean)
.map((part) => part.charAt(0))
.join("")
.slice(0, 2)
.toUpperCase();
return (
<Container>
<Header>
<BackButton onClick={() => navigate("/profiles")}> Retour</BackButton>
<Avatar $color={child.colorHex} $imageUrl={child.avatar?.url}>
{!child.avatar?.url && initials}
</Avatar>
<HeaderInfo>
<Title>{child.fullName}</Title>
<Meta>
<MetaItem $icon="📍">
{selectedRegion ? REGION_LABELS[selectedRegion]?.split('(')[0] : "Zone non définie"}
</MetaItem>
{child.email && <MetaItem $icon="📧">{child.email}</MetaItem>}
</Meta>
<ActionButtons>
<PrimaryButton onClick={() => navigate(`/child/${childId}/planning`)}>
<span>📅</span>
Planning
</PrimaryButton>
<SecondaryButton onClick={handleOpenImport}>
<span>📥</span>
Importer
</SecondaryButton>
<SecondaryButton onClick={handleOpenEdit}>
<span></span>
Modifier
</SecondaryButton>
<DangerButton onClick={handleOpenDelete}>
<span>🗑</span>
Supprimer
</DangerButton>
</ActionButtons>
</HeaderInfo>
</Header>
{error && <ErrorText style={{ marginBottom: '20px', textAlign: 'center' }}>{error}</ErrorText>}
{success && <SuccessText style={{ marginBottom: '20px', textAlign: 'center' }}>{success}</SuccessText>}
<MainGrid>
{/* Région scolaire et congés */}
<Card>
<CardTitle $icon="🏖️">Congés scolaires</CardTitle>
<Label>
Zone scolaire
<Select
value={selectedRegion}
onChange={(e) => setSelectedRegion(e.target.value)}
>
<option value="">-- Aucune zone sélectionnée --</option>
{Object.entries(REGION_LABELS).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</Select>
</Label>
<SecondaryButton onClick={handleSaveRegion} disabled={saving || !selectedRegion}>
{saving ? "Enregistrement..." : "Enregistrer la région"}
</SecondaryButton>
{loadingHolidays ? (
<StatusMessage>Chargement des congés...</StatusMessage>
) : holidays.length > 0 ? (
<HolidayList>
{holidays.slice(0, 5).map((holiday) => (
<HolidayItem key={holiday.id}>
<HolidayName>{holiday.title}</HolidayName>
<HolidayDate>
{holiday.startDate === holiday.endDate
? formatDate(holiday.startDate)
: `Du ${formatDate(holiday.startDate)} au ${formatDate(holiday.endDate)}`}
</HolidayDate>
{holiday.description && <HolidayType>{holiday.description}</HolidayType>}
</HolidayItem>
))}
</HolidayList>
) : selectedRegion && (
<EmptyState>Aucun congé trouvé</EmptyState>
)}
</Card>
{/* Notes personnelles */}
<Card>
<CardTitle $icon="📝">Notes personnelles</CardTitle>
<PersonalNotes
value={personalNotes}
onChange={(e) => setPersonalNotes(e.target.value)}
placeholder="Ajoutez vos notes personnelles ici..."
/>
<PrimaryButton onClick={handleSaveNotes}>
<span>💾</span>
Enregistrer les notes
</PrimaryButton>
</Card>
</MainGrid>
{/* Modal Import */}
{showImportModal && (
<ModalOverlay onClick={() => !importing && setShowImportModal(false)}>
<ModalContent $width="600px" onClick={(e) => e.stopPropagation()}>
<ModalTitle>Importer un planning</ModalTitle>
<p style={{ textAlign: 'center', color: 'rgba(255,255,255,0.7)', marginBottom: '20px' }}>
Importez un fichier Excel, PDF, Image (JPG/PNG) ou JSON contenant le planning.
</p>
{!selectedFile ? (
<FileUploadArea
$isDragOver={isDragOver}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
<FileUploadIcon>📁</FileUploadIcon>
<FileUploadText>
Glissez-déposez un fichier ici ou cliquez pour parcourir
</FileUploadText>
<FileUploadHint>
Formats acceptés : Excel (.xlsx, .xls), PDF, Images (.jpg, .png), JSON
</FileUploadHint>
</FileUploadArea>
) : (
<SelectedFile>
<FileName>📄 {selectedFile.name}</FileName>
<RemoveFileButton onClick={() => setSelectedFile(null)}>
Retirer
</RemoveFileButton>
</SelectedFile>
)}
<FileInput
ref={fileInputRef}
type="file"
accept=".xlsx,.xls,.pdf,.jpg,.jpeg,.png,.json"
onChange={handleFileSelect}
/>
{importProgress && (
<StatusMessage style={{ marginBottom: '16px' }}>
{importProgress}
</StatusMessage>
)}
{error && <ErrorText style={{ marginBottom: '16px' }}>{error}</ErrorText>}
<ModalButtons>
<SecondaryButton
onClick={() => setShowImportModal(false)}
disabled={importing}
style={{ flex: 1 }}
>
Annuler
</SecondaryButton>
<PrimaryButton
onClick={handleImport}
disabled={!selectedFile || importing}
style={{ flex: 1 }}
>
{importing ? "Import en cours..." : "Importer"}
</PrimaryButton>
</ModalButtons>
</ModalContent>
</ModalOverlay>
)}
{/* Modal Edit */}
{showEditModal && (
<ModalOverlay onClick={() => !saving && setShowEditModal(false)}>
<ModalContent onClick={(e) => e.stopPropagation()}>
<ModalTitle>Modifier le profil</ModalTitle>
<Label>
Nom complet
<Input
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
placeholder="Nom complet"
/>
</Label>
<Label>
Email (optionnel)
<Input
type="email"
value={editEmail}
onChange={(e) => setEditEmail(e.target.value)}
placeholder="email@exemple.com"
/>
</Label>
<Label>
Couleur
<ColorPicker
type="color"
value={editColor}
onChange={(e) => setEditColor(e.target.value)}
/>
</Label>
<Label>
Avatar (optionnel)
<SecondaryButton onClick={() => editAvatarInputRef.current?.click()} type="button">
<span>🖼</span>
Choisir un avatar
</SecondaryButton>
<FileInput
ref={editAvatarInputRef}
type="file"
accept="image/*"
onChange={handleEditAvatarSelect}
/>
{editAvatarPreview && (
<div style={{ textAlign: 'center', marginTop: '10px' }}>
<img
src={editAvatarPreview}
alt="Preview"
style={{ width: '80px', height: '80px', borderRadius: '50%', objectFit: 'cover' }}
/>
</div>
)}
</Label>
{error && <ErrorText>{error}</ErrorText>}
<ModalButtons>
<SecondaryButton
onClick={() => setShowEditModal(false)}
disabled={saving}
style={{ flex: 1 }}
>
Annuler
</SecondaryButton>
<PrimaryButton
onClick={handleSaveEdit}
disabled={saving || !editName}
style={{ flex: 1 }}
>
{saving ? "Enregistrement..." : "Enregistrer"}
</PrimaryButton>
</ModalButtons>
</ModalContent>
</ModalOverlay>
)}
{/* Modal Delete */}
{showDeleteModal && (
<ModalOverlay onClick={() => !saving && setShowDeleteModal(false)}>
<ModalContent onClick={(e) => e.stopPropagation()}>
<ModalTitle>Supprimer le profil</ModalTitle>
<p style={{ textAlign: 'center', color: 'rgba(255,255,255,0.8)', marginBottom: '20px' }}>
Êtes-vous sûr de vouloir supprimer le profil de <strong>{child.fullName}</strong> ?
</p>
<p style={{ textAlign: 'center', color: '#ff6b6b', fontSize: '14px', marginBottom: '24px' }}>
Cette action déplacera le profil dans les archives. Vous pourrez le restaurer depuis les paramètres.
</p>
{error && <ErrorText style={{ marginBottom: '16px' }}>{error}</ErrorText>}
<ModalButtons>
<SecondaryButton
onClick={() => setShowDeleteModal(false)}
disabled={saving}
style={{ flex: 1 }}
>
Annuler
</SecondaryButton>
<DangerButton
onClick={handleConfirmDelete}
disabled={saving}
style={{ flex: 1 }}
>
{saving ? "Suppression..." : "Supprimer"}
</DangerButton>
</ModalButtons>
</ModalContent>
</ModalOverlay>
)}
</Container>
);
};