425 lines
19 KiB
TypeScript
425 lines
19 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { Calendar, Activity, Heart, Pill, Droplet, Dumbbell, FileText } from 'lucide-react';
|
|
|
|
interface HealthData {
|
|
// 1. Informations de base
|
|
date?: string;
|
|
poids?: number;
|
|
temperature?: number;
|
|
tension?: string; // Format: "120/80"
|
|
frequenceCardiaque?: number;
|
|
|
|
// 2. État de santé / symptômes
|
|
maladie?: string;
|
|
symptomes?: string;
|
|
niveauDouleur?: number; // 0-10
|
|
niveauEnergie?: 'faible' | 'moyen' | 'bon';
|
|
qualiteSommeil?: 'mauvais' | 'moyen' | 'bon' | number; // ou 1-5
|
|
|
|
// 3. Médicaments et soins
|
|
medicaments?: string;
|
|
traitementEnCours?: boolean;
|
|
consultationMedicale?: boolean;
|
|
notesMedicales?: string;
|
|
|
|
// 4. Habitudes & mode de vie
|
|
hydratation?: number; // verres d'eau
|
|
activitePhysique?: 'aucune' | 'légère' | 'intense';
|
|
dureeActivite?: number; // minutes
|
|
alimentationParticuliere?: boolean;
|
|
commentaireAlimentation?: string;
|
|
stress?: 'faible' | 'moyen' | 'élevé';
|
|
|
|
// 5. Champ libre
|
|
notesPersonnelles?: string;
|
|
}
|
|
|
|
interface HealthFormProps {
|
|
content: string;
|
|
onContentChange: (content: string) => void;
|
|
date?: string;
|
|
}
|
|
|
|
export const HealthForm: React.FC<HealthFormProps> = ({ content, onContentChange, date }) => {
|
|
const [data, setData] = useState<HealthData>(() => {
|
|
// Parse existing content if available
|
|
if (content) {
|
|
try {
|
|
return JSON.parse(content);
|
|
} catch {
|
|
// If not JSON, try to parse as markdown or return empty
|
|
return {};
|
|
}
|
|
}
|
|
return {};
|
|
});
|
|
|
|
// Use ref to track last sent content to prevent infinite loops
|
|
const lastSentContentRef = useRef<string>('');
|
|
|
|
// Initialize date if not set
|
|
useEffect(() => {
|
|
if (!data.date && date) {
|
|
setData(prev => ({ ...prev, date: date.split('T')[0] }));
|
|
} else if (!data.date) {
|
|
setData(prev => ({ ...prev, date: new Date().toISOString().split('T')[0] }));
|
|
}
|
|
}, [data.date, date]);
|
|
|
|
// Update parent content whenever data changes (with a small delay to avoid too many updates)
|
|
// Only update if content actually changed to prevent infinite loops
|
|
useEffect(() => {
|
|
const jsonContent = JSON.stringify(data, null, 2);
|
|
// Only update if content is different from last sent content
|
|
if (jsonContent !== lastSentContentRef.current) {
|
|
const timer = setTimeout(() => {
|
|
lastSentContentRef.current = jsonContent;
|
|
onContentChange(jsonContent);
|
|
}, 500); // Increased delay to reduce updates
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [data, onContentChange]); // Stable dependencies
|
|
|
|
const updateField = (field: keyof HealthData, value: any) => {
|
|
setData(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full bg-carnet-bg overflow-y-auto">
|
|
<div className="p-6 space-y-6">
|
|
{/* 1. Informations de base */}
|
|
<section className="border-b border-carnet-border pb-4">
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<Calendar className="h-5 w-5 text-carnet-text-primary" />
|
|
<h3 className="text-lg font-semibold text-carnet-text-primary">Informations de base</h3>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Date
|
|
</label>
|
|
<input
|
|
type="date"
|
|
value={data.date || ''}
|
|
onChange={(e) => updateField('date', e.target.value)}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Poids (kg)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
step="0.1"
|
|
value={data.poids || ''}
|
|
onChange={(e) => updateField('poids', e.target.value ? parseFloat(e.target.value) : undefined)}
|
|
placeholder="Ex: 70.5"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Température corporelle (°C)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
step="0.1"
|
|
value={data.temperature || ''}
|
|
onChange={(e) => updateField('temperature', e.target.value ? parseFloat(e.target.value) : undefined)}
|
|
placeholder="Ex: 37.2"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Tension artérielle (optionnel)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={data.tension || ''}
|
|
onChange={(e) => updateField('tension', e.target.value)}
|
|
placeholder="Ex: 120/80"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Fréquence cardiaque (bpm)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
value={data.frequenceCardiaque || ''}
|
|
onChange={(e) => updateField('frequenceCardiaque', e.target.value ? parseInt(e.target.value) : undefined)}
|
|
placeholder="Ex: 72"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* 2. État de santé / symptômes */}
|
|
<section className="border-b border-carnet-border pb-4">
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<Activity className="h-5 w-5 text-carnet-text-primary" />
|
|
<h3 className="text-lg font-semibold text-carnet-text-primary">État de santé / symptômes</h3>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Maladie / état
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={data.maladie || ''}
|
|
onChange={(e) => updateField('maladie', e.target.value)}
|
|
placeholder="Ex: grippe, rhume, migraine, fatigue, aucun"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Symptômes
|
|
</label>
|
|
<textarea
|
|
value={data.symptomes || ''}
|
|
onChange={(e) => updateField('symptomes', e.target.value)}
|
|
placeholder="Ex: fièvre, toux, maux de tête, douleurs, nausées..."
|
|
rows={3}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Niveau de douleur (0-10)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
max="10"
|
|
value={data.niveauDouleur || ''}
|
|
onChange={(e) => updateField('niveauDouleur', e.target.value ? parseInt(e.target.value) : undefined)}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Niveau d'énergie
|
|
</label>
|
|
<select
|
|
value={data.niveauEnergie || ''}
|
|
onChange={(e) => updateField('niveauEnergie', e.target.value as 'faible' | 'moyen' | 'bon' || undefined)}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
>
|
|
<option value="">Sélectionner</option>
|
|
<option value="faible">Faible</option>
|
|
<option value="moyen">Moyen</option>
|
|
<option value="bon">Bon</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Qualité du sommeil
|
|
</label>
|
|
<select
|
|
value={data.qualiteSommeil || ''}
|
|
onChange={(e) => {
|
|
const value = e.target.value;
|
|
if (value === 'mauvais' || value === 'moyen' || value === 'bon') {
|
|
updateField('qualiteSommeil', value);
|
|
} else if (value) {
|
|
updateField('qualiteSommeil', parseInt(value));
|
|
} else {
|
|
updateField('qualiteSommeil', undefined);
|
|
}
|
|
}}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
>
|
|
<option value="">Sélectionner</option>
|
|
<option value="mauvais">Mauvais</option>
|
|
<option value="moyen">Moyen</option>
|
|
<option value="bon">Bon</option>
|
|
<option value="1">1</option>
|
|
<option value="2">2</option>
|
|
<option value="3">3</option>
|
|
<option value="4">4</option>
|
|
<option value="5">5</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* 3. Médicaments et soins */}
|
|
<section className="border-b border-carnet-border pb-4">
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<Pill className="h-5 w-5 text-carnet-text-primary" />
|
|
<h3 className="text-lg font-semibold text-carnet-text-primary">Médicaments et soins</h3>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Médicaments pris
|
|
</label>
|
|
<textarea
|
|
value={data.medicaments || ''}
|
|
onChange={(e) => updateField('medicaments', e.target.value)}
|
|
placeholder="Ex: Paracétamol 500mg, 2 comprimés"
|
|
rows={2}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="flex items-center space-x-2">
|
|
<input
|
|
type="checkbox"
|
|
id="traitementEnCours"
|
|
checked={data.traitementEnCours || false}
|
|
onChange={(e) => updateField('traitementEnCours', e.target.checked)}
|
|
className="w-4 h-4 text-primary border-carnet-border rounded focus:ring-primary"
|
|
/>
|
|
<label htmlFor="traitementEnCours" className="text-sm text-carnet-text-primary">
|
|
Traitement en cours
|
|
</label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<input
|
|
type="checkbox"
|
|
id="consultationMedicale"
|
|
checked={data.consultationMedicale || false}
|
|
onChange={(e) => updateField('consultationMedicale', e.target.checked)}
|
|
className="w-4 h-4 text-primary border-carnet-border rounded focus:ring-primary"
|
|
/>
|
|
<label htmlFor="consultationMedicale" className="text-sm text-carnet-text-primary">
|
|
Consultation médicale
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Notes médicales
|
|
</label>
|
|
<textarea
|
|
value={data.notesMedicales || ''}
|
|
onChange={(e) => updateField('notesMedicales', e.target.value)}
|
|
placeholder="Notes médicales..."
|
|
rows={3}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* 4. Habitudes & mode de vie */}
|
|
<section className="border-b border-carnet-border pb-4">
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<Dumbbell className="h-5 w-5 text-carnet-text-primary" />
|
|
<h3 className="text-lg font-semibold text-carnet-text-primary">Habitudes & mode de vie (optionnel)</h3>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Hydratation (verres d'eau / jour)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
value={data.hydratation || ''}
|
|
onChange={(e) => updateField('hydratation', e.target.value ? parseInt(e.target.value) : undefined)}
|
|
placeholder="Ex: 8"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Activité physique
|
|
</label>
|
|
<select
|
|
value={data.activitePhysique || ''}
|
|
onChange={(e) => updateField('activitePhysique', e.target.value as 'aucune' | 'légère' | 'intense' || undefined)}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
>
|
|
<option value="">Sélectionner</option>
|
|
<option value="aucune">Aucune</option>
|
|
<option value="légère">Légère</option>
|
|
<option value="intense">Intense</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Durée de l'activité (minutes)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
value={data.dureeActivite || ''}
|
|
onChange={(e) => updateField('dureeActivite', e.target.value ? parseInt(e.target.value) : undefined)}
|
|
placeholder="Ex: 30"
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-carnet-text-primary mb-1">
|
|
Stress
|
|
</label>
|
|
<select
|
|
value={data.stress || ''}
|
|
onChange={(e) => updateField('stress', e.target.value as 'faible' | 'moyen' | 'élevé' || undefined)}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
>
|
|
<option value="">Sélectionner</option>
|
|
<option value="faible">Faible</option>
|
|
<option value="moyen">Moyen</option>
|
|
<option value="élevé">Élevé</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center space-x-2 mb-2">
|
|
<input
|
|
type="checkbox"
|
|
id="alimentationParticuliere"
|
|
checked={data.alimentationParticuliere || false}
|
|
onChange={(e) => updateField('alimentationParticuliere', e.target.checked)}
|
|
className="w-4 h-4 text-primary border-carnet-border rounded focus:ring-primary"
|
|
/>
|
|
<label htmlFor="alimentationParticuliere" className="text-sm text-carnet-text-primary">
|
|
Alimentation particulière
|
|
</label>
|
|
</div>
|
|
{data.alimentationParticuliere && (
|
|
<textarea
|
|
value={data.commentaireAlimentation || ''}
|
|
onChange={(e) => updateField('commentaireAlimentation', e.target.value)}
|
|
placeholder="Commentaire sur l'alimentation..."
|
|
rows={2}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* 5. Champ libre */}
|
|
<section>
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<FileText className="h-5 w-5 text-carnet-text-primary" />
|
|
<h3 className="text-lg font-semibold text-carnet-text-primary">Notes personnelles</h3>
|
|
</div>
|
|
<div>
|
|
<textarea
|
|
value={data.notesPersonnelles || ''}
|
|
onChange={(e) => updateField('notesPersonnelles', e.target.value)}
|
|
placeholder="Ex: Mal dormi, début de grippe, me sens mieux aujourd'hui..."
|
|
rows={4}
|
|
className="w-full px-3 py-2 border border-carnet-border rounded-md text-sm text-carnet-text-primary bg-white focus:outline-none focus:ring-1 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|