Files
n3wt-school/Front-End/src/components/Inscription/InscriptionFormShared.js
2025-11-30 17:24:25 +01:00

878 lines
28 KiB
JavaScript

// Import des dépendances nécessaires
import React, { useState, useEffect } from 'react';
import Button from '@/components/Form/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import {
fetchSchoolFileTemplatesFromRegistrationFiles,
fetchParentFileTemplatesFromRegistrationFiles,
fetchRegistrationSchoolFileMasters,
saveFormResponses,
fetchFormResponses,
autoSaveRegisterForm,
} from '@/app/actions/subscriptionAction';
import {
downloadTemplate,
editRegistrationSchoolFileTemplates,
editRegistrationParentFileTemplates,
} from '@/app/actions/registerFileGroupAction';
import {
fetchRegistrationPaymentModes,
fetchTuitionPaymentModes,
fetchRegistrationPaymentPlans,
fetchTuitionPaymentPlans,
} from '@/app/actions/schoolAction';
import { fetchProfiles } from '@/app/actions/authAction';
import { BASE_URL, FE_PARENTS_HOME_URL } from '@/utils/Url';
import logger from '@/utils/logger';
import FilesToUpload from '@/components/Inscription/FilesToUpload';
import DynamicFormsList from '@/components/Inscription/DynamicFormsList';
import AutoSaveIndicator from '@/components/AutoSaveIndicator';
import StudentInfoForm from '@/components/Inscription/StudentInfoForm';
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
import SiblingInputFields from '@/components/Inscription/SiblingInputFields';
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
import ProgressStep from '@/components/ProgressStep';
import { CheckCircle, Hourglass } from 'lucide-react';
import { useRouter } from 'next/navigation';
/**
* Composant de formulaire d'inscription partagé
* @param {string} studentId - ID de l'étudiant
* @param {string} csrfToken - Token CSRF pour la sécurité
* @param {function} onSubmit - Fonction de soumission du formulaire
* @param {string} cancelUrl - URL de redirection en cas d'annulation
* @param {object} errors - Erreurs de validation du formulaire
*/
export default function InscriptionFormShared({
studentId,
csrfToken,
selectedEstablishmentId,
apiDocuseal,
onSubmit,
errors = {}, // Nouvelle prop pour les erreurs
enable = true,
}) {
// États pour gérer les données du formulaire
const [formData, setFormData] = useState({
id: '',
photo: null,
last_name: '',
first_name: '',
gender: '',
address: '',
birth_date: '',
birth_place: '',
birth_postal_code: '',
nationality: '',
attending_physician: '',
level: '',
photo: '',
});
const [guardians, setGuardians] = useState([]);
const [siblings, setSiblings] = useState([]);
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
const [registrationPaymentPlans, setRegistrationPaymentPlans] = useState([]);
const [tuitionPaymentPlans, setTuitionPaymentPlans] = useState([]);
// États pour la gestion des fichiers
const [uploadedFiles, setUploadedFiles] = useState([]);
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
const [parentFileTemplates, setParentFileTemplates] = useState([]);
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [formResponses, setFormResponses] = useState({});
const [currentPage, setCurrentPage] = useState(1);
const [isPage1Valid, setIsPage1Valid] = useState(false);
const [isPage2Valid, setIsPage2Valid] = useState(false);
const [isPage3Valid, setIsPage3Valid] = useState(false);
const [isPage4Valid, setIsPage4Valid] = useState(false);
const [isPage5Valid, setIsPage5Valid] = useState(false);
const [isPage6Valid, setIsPage6Valid] = useState(false);
const [hasInteracted, setHasInteracted] = useState(false);
// État pour suivre l'index du fichier en cours
const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0);
const [profiles, setProfiles] = useState([]);
const [isSaving, setIsSaving] = useState(false);
const [lastSaved, setLastSaved] = useState(null);
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
const router = useRouter();
// Mettre à jour les états en fonction de la valeur de `enable`
useEffect(() => {
if (!enable) {
setIsPage1Valid(true);
setIsPage2Valid(true);
setIsPage3Valid(true);
setIsPage4Valid(true);
setIsPage5Valid(true);
setIsPage6Valid(true);
}
}, [enable]);
useEffect(() => {
// Trouver le premier template non signé
const firstUnsignedIndex = schoolFileTemplates.findIndex(
(template) => template.file === null
);
// Mettre à jour l'index du template actuel
if (firstUnsignedIndex !== -1) {
setCurrentTemplateIndex(firstUnsignedIndex);
} else {
// Si tous les templates sont signés, définir un index hors limites
setCurrentTemplateIndex(0);
}
}, [schoolFileTemplates]);
useEffect(() => {
// Vérifier si tous les formulaires maîtres sont complétés
const allCompleted =
schoolFileMasters.length === 0 ||
schoolFileMasters.every((master) => master.completed === true);
// Mettre à jour isPage5Valid en fonction de cette condition
setIsPage5Valid(allCompleted);
if (allCompleted) {
setCurrentTemplateIndex(0);
}
}, [schoolFileMasters]);
useEffect(() => {
// Vérifier si tous les documents avec is_required = true ont leur champ "file" différent de null
const allRequiredUploaded = parentFileTemplates
.filter((template) => template.is_required) // Ne garder que les documents requis
.every((template) => template.file !== null); // Vérifier que chaque fichier requis est uploadé
// Mettre à jour isPage6Valid en fonction de cette condition
setIsPage6Valid(allRequiredUploaded);
logger.debug(allRequiredUploaded);
}, [parentFileTemplates]);
// Auto-sauvegarde périodique (toutes les 30 secondes)
useEffect(() => {
if (!enable || !autoSaveEnabled) return;
const interval = setInterval(() => {
autoSave();
}, 30000); // 30 secondes
return () => clearInterval(interval);
}, [enable, autoSaveEnabled, formData, guardians, siblings]);
// Auto-sauvegarde quand les données changent (avec debounce)
useEffect(() => {
if (!enable || !autoSaveEnabled) return;
const timeout = setTimeout(() => {
autoSave();
}, 2000); // Attendre 2 secondes après le dernier changement
return () => clearTimeout(timeout);
}, [formData, guardians, siblings]);
/**
* Fonction d'auto-sauvegarde qui sauvegarde les données en cours
*/
const autoSave = async () => {
if (!autoSaveEnabled || !studentId || isSaving) {
return;
}
try {
setIsSaving(true);
logger.debug('Auto-sauvegarde en cours...', {
studentId,
formDataKeys: Object.keys(formData),
paymentFields: {
registration_payment: formData.registration_payment,
registration_payment_plan: formData.registration_payment_plan,
tuition_payment: formData.tuition_payment,
tuition_payment_plan: formData.tuition_payment_plan,
},
guardians: guardians.length,
siblings: siblings.length,
currentPage,
});
// Fonction helper pour nettoyer les données avant sauvegarde
const cleanDataForAutoSave = (data) => {
const cleaned = {};
Object.keys(data).forEach((key) => {
const value = data[key];
// Garder seulement les valeurs non-vides et valides
if (value !== null && value !== undefined && value !== '') {
// Pour les dates, vérifier le format
if (key === 'birth_date' && value) {
// Vérifier que la date est dans un format valide
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (dateRegex.test(value)) {
cleaned[key] = value;
}
}
// Pour les codes postaux, vérifier que c'est un nombre
else if (key === 'birth_postal_code' && value) {
if (!isNaN(value) && value.toString().trim() !== '') {
cleaned[key] = parseInt(value);
}
}
// Pour les champs de paiement, toujours les inclure s'ils ont une valeur
else if (key.includes('payment') && value) {
cleaned[key] = value;
}
// Pour les autres champs, garder la valeur si elle n'est pas vide
else if (value.toString().trim() !== '') {
cleaned[key] = value;
}
}
});
return cleaned;
};
// Préparer les données à sauvegarder avec nettoyage
const cleanedFormData = cleanDataForAutoSave(formData);
const dataToSave = {
student: cleanedFormData,
guardians: guardians.filter(
(guardian) =>
guardian &&
(guardian.first_name || guardian.last_name || guardian.email)
),
siblings: siblings.filter(
(sibling) => sibling && (sibling.first_name || sibling.last_name)
),
currentPage: currentPage,
};
// Utiliser la fonction d'auto-save dédiée
await autoSaveRegisterForm(studentId, dataToSave, csrfToken);
setLastSaved(new Date());
logger.debug('Auto-sauvegarde réussie');
} catch (error) {
logger.error("Erreur lors de l'auto-sauvegarde:", error);
// Ne pas afficher d'erreur à l'utilisateur pour l'auto-save
} finally {
setIsSaving(false);
}
};
/**
* Gère la sauvegarde à chaque changement d'étape
*/
const saveStepData = async () => {
await autoSave();
};
/**
* Gère la soumission d'un formulaire dynamique
*/
const handleDynamicFormSubmit = async (formData, templateId) => {
try {
logger.debug('Soumission du formulaire dynamique:', {
templateId,
formData,
csrfToken: !!csrfToken,
});
// Trouver le template correspondant pour récupérer sa configuration
const currentTemplate = schoolFileMasters.find(
(master) => master.id === templateId
);
if (!currentTemplate) {
throw new Error(`Template avec l'ID ${templateId} non trouvé`);
}
// Construire la structure complète avec la configuration et les réponses
const formTemplateData = {
id: currentTemplate.id,
title:
currentTemplate.formMasterData?.title ||
currentTemplate.title ||
currentTemplate.name ||
'Formulaire',
fields: (
currentTemplate.formMasterData?.fields ||
currentTemplate.fields ||
[]
).map((field) => ({
...field,
// Ajouter la réponse de l'utilisateur selon le type de champ
...(field.type === 'checkbox'
? { checked: formData[field.id] || false }
: {}),
...(field.type === 'radio' ? { selected: formData[field.id] } : {}),
...(field.type === 'text' ||
field.type === 'textarea' ||
field.type === 'email'
? { value: formData[field.id] || '' }
: {}),
})),
submitLabel: currentTemplate.formMasterData?.submitLabel || 'Valider',
responses: formData, // Garder aussi les réponses brutes pour facilité d'accès
};
// Sauvegarder les réponses du formulaire via l'API RegistrationSchoolFileTemplate
logger.debug('Appel API saveFormResponses avec:', {
templateId,
formTemplateData,
});
const result = await saveFormResponses(
templateId,
formTemplateData,
csrfToken
);
logger.debug("Réponse de l'API:", result);
// Mettre à jour l'état local des réponses
setFormResponses((prev) => ({
...prev,
[templateId]: formData,
}));
// Mettre à jour l'état local pour indiquer que le formulaire est complété
setSchoolFileMasters((prevMasters) => {
return prevMasters.map((master) =>
master.id === templateId
? { ...master, completed: true, responses: formData }
: master
);
});
logger.debug('Formulaire dynamique sauvegardé avec succès');
return Promise.resolve();
} catch (error) {
logger.error('Erreur lors de la soumission du formulaire dynamique:', {
templateId,
error: error.message,
stack: error.stack,
});
// Afficher l'erreur à l'utilisateur
alert(`Erreur lors de la sauvegarde du formulaire: ${error.message}`);
return Promise.reject(error);
}
};
/**
* Gère les changements de validation des formulaires dynamiques
*/
const handleDynamicFormsValidationChange = (isValid) => {
setIsPage5Valid(isValid);
};
useEffect(() => {
fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((data) => {
setSchoolFileTemplates(data);
});
fetchParentFileTemplatesFromRegistrationFiles(studentId).then((data) => {
setParentFileTemplates(data);
// Initialiser uploadedFiles avec uniquement les fichiers dont `file` n'est pas null
const filteredFiles = data
.filter((item) => item.file !== null)
.map((item) => ({
id: item.id,
fileName: item.file,
}));
setUploadedFiles(filteredFiles);
});
fetchProfiles()
.then((data) => {
setProfiles(data);
})
.catch((error) => logger.error('Error fetching profiles : ', error));
if (selectedEstablishmentId) {
// Fetch data for school file masters
fetchRegistrationSchoolFileMasters(selectedEstablishmentId)
.then(async (data) => {
logger.debug('School file masters fetched:', data);
setSchoolFileMasters(data);
// Récupérer les données existantes de chaque template
const responsesMap = {};
for (const master of data) {
if (master.id) {
try {
const templateData = await fetchFormResponses(master.id);
if (templateData && templateData.formTemplateData) {
// Si on a les réponses brutes sauvegardées, les utiliser
if (templateData.formTemplateData.responses) {
responsesMap[master.id] =
templateData.formTemplateData.responses;
} else {
// Sinon, extraire les réponses depuis les champs
const responses = {};
if (templateData.formTemplateData.fields) {
templateData.formTemplateData.fields.forEach((field) => {
if (
field.type === 'checkbox' &&
field.checked !== undefined
) {
responses[field.id] = field.checked;
} else if (
field.type === 'radio' &&
field.selected !== undefined
) {
responses[field.id] = field.selected;
} else if (
(field.type === 'text' ||
field.type === 'textarea' ||
field.type === 'email') &&
field.value !== undefined
) {
responses[field.id] = field.value;
}
});
}
responsesMap[master.id] = responses;
}
}
} catch (error) {
logger.debug(
`Pas de données existantes pour le template ${master.id}:`,
error
);
// Ce n'est pas critique si un template n'a pas de données
}
}
}
setFormResponses(responsesMap);
})
.catch((error) =>
logger.error('Error fetching school file masters:', error)
);
// Fetch data for registration payment modes
handleRegistrationPaymentModes();
// Fetch data for tuition payment modes
handleTuitionPaymentModes();
// Fetch data for registration payment plans
handleRegistrationPaymentPlans();
// Fetch data for tuition payment plans
handleTuitionnPaymentPlans();
}
}, [selectedEstablishmentId]);
const handleRegistrationPaymentModes = () => {
fetchRegistrationPaymentModes(selectedEstablishmentId)
.then((data) => {
setRegistrationPaymentModes(data);
})
.catch((error) =>
logger.error('Error fetching registration payment modes:', error)
);
};
const handleTuitionPaymentModes = () => {
fetchTuitionPaymentModes(selectedEstablishmentId)
.then((data) => {
setTuitionPaymentModes(data);
})
.catch((error) =>
logger.error('Error fetching tuition payment modes:', error)
);
};
const handleRegistrationPaymentPlans = () => {
fetchRegistrationPaymentPlans(selectedEstablishmentId)
.then((data) => {
setRegistrationPaymentPlans(data);
})
.catch((error) =>
logger.error('Error fetching registration payment plans:', error)
);
};
const handleTuitionnPaymentPlans = () => {
fetchTuitionPaymentPlans(selectedEstablishmentId)
.then((data) => {
setTuitionPaymentPlans(data);
})
.catch((error) =>
logger.error('Error fetching registration tuition plans:', error)
);
};
const handleFileUpload = (file, selectedFile) => {
if (!file || !selectedFile) {
logger.error('Données manquantes pour le téléversement.');
return Promise.reject(
new Error('Données manquantes pour le téléversement.')
);
}
const updateData = new FormData();
updateData.append('file', file);
return editRegistrationParentFileTemplates(
selectedFile.id,
updateData,
csrfToken
)
.then((response) => {
logger.debug('Template mis à jour avec succès :', response);
setUploadedFiles((prev) => {
const updatedFiles = prev.map((uploadedFile) =>
uploadedFile.id === selectedFile.id
? { ...uploadedFile, fileName: response.data.file } // Met à jour le fichier téléversé
: uploadedFile
);
// Si le fichier n'existe pas encore, l'ajouter
if (!updatedFiles.find((file) => file.id === selectedFile.id)) {
updatedFiles.push({
id: selectedFile.id,
fileName: response.data.file,
});
}
return updatedFiles;
});
// Mettre à jour parentFileTemplates
setParentFileTemplates((prevTemplates) =>
prevTemplates.map((template) =>
template.id === selectedFile.id
? { ...template, file: response.data.file }
: template
)
);
return response; // Retourner la réponse pour signaler le succès
})
.catch((error) => {
logger.error('Erreur lors de la mise à jour du fichier :', error);
throw error; // Relancer l'erreur pour que l'appelant puisse la capturer
});
};
const handleDeleteFile = (templateId) => {
const fileToDelete = uploadedFiles.find(
(file) => parseInt(file.id) === templateId && file.fileName
);
if (!fileToDelete) {
logger.error('Aucun fichier trouvé pour suppression.');
return;
}
// Créer un FormData avec un champ vide pour "file"
const updateData = new FormData();
updateData.append('file', ''); // Envoyer chaine vide pour indiquer qu'aucun fichier n'est uploadé
return editRegistrationParentFileTemplates(
templateId,
updateData,
csrfToken
)
.then((response) => {
logger.debug('Fichier supprimé avec succès dans la base :', response);
setIsPage6Valid(false);
// Mettre à jour l'état local pour refléter la suppression
setUploadedFiles((prev) =>
prev.map((uploadedFile) =>
uploadedFile.id === templateId
? { ...uploadedFile, fileName: null, fileUrl: null } // Réinitialiser les champs liés au fichier
: uploadedFile
)
);
// Mettre à jour l'état local pour refléter la suppression dans parentFileTemplates
setParentFileTemplates((prevTemplates) =>
prevTemplates.map((template) =>
template.id === templateId ? { ...template, file: null } : template
)
);
return response;
})
.catch((error) => {
logger.error(
'Erreur lors de la suppression du fichier dans la base :',
error
);
throw error;
});
};
// Soumission du formulaire
const handleSubmit = (e) => {
e.preventDefault();
// Vérifier si le mode de paiement sélectionné est un prélèvement SEPA
const isSepaPayment = formData.isSepa === 1;
// Préparer les données JSON
const jsonData = {
student: {
...formData,
guardians: guardians,
siblings: siblings.map(({ id, ...rest }) =>
id && typeof id === 'string' && id.startsWith('temp-')
? rest
: { id, ...rest }
), // Supprimer les IDs temporaires
},
establishment: selectedEstablishmentId,
status: isSepaPayment ? 8 : 3,
tuition_payment: formData.tuition_payment,
registration_payment: formData.registration_payment,
tuition_payment_plan: formData.tuition_payment_plan,
registration_payment_plan: formData.registration_payment_plan,
};
// Créer un objet FormData
const formDataToSend = new FormData();
// Ajouter les données JSON sous forme de chaîne
formDataToSend.append('data', JSON.stringify(jsonData));
// Ajouter la photo si elle est présente
if (formData.photo) {
formDataToSend.append('photo', formData.photo);
}
logger.debug('submit : ', jsonData);
// Appeler la fonction onSubmit avec les données FormData
onSubmit(formDataToSend);
};
const handleNextPage = async () => {
// Sauvegarder avant de passer à l'étape suivante
await saveStepData();
setHasInteracted(false);
setCurrentPage(currentPage + 1);
};
const handlePreviousPage = async () => {
// Sauvegarder avant de revenir à l'étape précédente
await saveStepData();
setCurrentPage(currentPage - 1);
};
const stepTitles = {
1: 'Elève',
2: 'Responsables légaux',
3: 'Frères et soeurs',
4: 'Modalités de paiement',
5: 'Formulaires à signer',
6: 'Pièces à fournir',
};
const steps = [
'Élève',
'Responsable',
'Fratrie',
'Paiement',
'Formulaires',
'Documents parent',
];
const isStepValid = (stepNumber) => {
switch (stepNumber) {
case 1:
return isPage1Valid;
case 2:
return isPage2Valid;
case 3:
return isPage3Valid;
case 4:
return isPage4Valid;
case 5:
return isPage5Valid;
case 6:
return isPage6Valid;
default:
return false;
}
};
// Rendu du composant
return (
<div className="mx-auto p-6">
<DjangoCSRFToken csrfToken={csrfToken} />
<ProgressStep
steps={steps}
stepTitles={stepTitles}
currentStep={currentPage}
setStep={setCurrentPage}
isStepValid={isStepValid}
/>
{/* Indicateur de sauvegarde automatique */}
{enable && (
<AutoSaveIndicator
isSaving={isSaving}
lastSaved={lastSaved}
autoSaveEnabled={autoSaveEnabled}
onToggleAutoSave={() => setAutoSaveEnabled(!autoSaveEnabled)}
/>
)}
<div className="flex-1 h-full mt-6">
{/* Page 1 : Informations sur l'élève */}
{currentPage === 1 && (
<StudentInfoForm
studentId={studentId}
formData={formData}
setFormData={setFormData}
guardians={guardians}
setGuardians={setGuardians}
setSiblings={setSiblings}
errors={errors}
setIsPageValid={setIsPage1Valid}
hasInteracted={hasInteracted}
setHasInteracted={setHasInteracted}
enable={enable}
/>
)}
{/* Page 2 : Informations sur les responsables légaux */}
{currentPage === 2 && (
<ResponsableInputFields
guardians={guardians}
setGuardians={setGuardians}
profiles={profiles}
errors={errors}
setIsPageValid={setIsPage2Valid}
enable={enable}
/>
)}
{/* Étape 3 : Frères et Sœurs */}
{currentPage === 3 && (
<SiblingInputFields
siblings={siblings}
setSiblings={setSiblings}
setFormData={setFormData}
errors={errors.siblings || []}
setIsPageValid={setIsPage3Valid}
enable={enable}
/>
)}
{/* Page 4 : Informations sur les modalités de paiement */}
{currentPage === 4 && (
<>
<PaymentMethodSelector
formData={formData}
setFormData={setFormData}
registrationPaymentModes={registrationPaymentModes}
tuitionPaymentModes={tuitionPaymentModes}
registrationPaymentPlans={registrationPaymentPlans}
tuitionPaymentPlans={tuitionPaymentPlans}
errors={errors}
setIsPageValid={setIsPage4Valid}
enable={enable}
/>
</>
)}
{/* Page 5 : Formulaires dynamiques d'inscription */}
{currentPage === 5 && (
<DynamicFormsList
schoolFileMasters={schoolFileMasters}
existingResponses={formResponses}
onFormSubmit={handleDynamicFormSubmit}
onValidationChange={handleDynamicFormsValidationChange}
enable={enable}
/>
)}
{/* Dernière page : Section Fichiers parents */}
{currentPage === 6 && (
<FilesToUpload
parentFileTemplates={parentFileTemplates}
uploadedFiles={uploadedFiles}
onFileUpload={handleFileUpload}
onFileDelete={handleDeleteFile}
enable={enable}
/>
)}
</div>
{/* Boutons de contrôle */}
<div className="flex justify-center space-x-4 mt-12">
{enable ? (
<>
{currentPage > 1 && (
<Button
text="Précédent"
onClick={(e) => {
e.preventDefault();
handlePreviousPage();
}}
primary
/>
)}
{currentPage < steps.length ? (
<Button
text="Suivant"
onClick={(e) => {
e.preventDefault();
handleNextPage();
}}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(currentPage === 1 && !isPage1Valid) ||
(currentPage === 2 && !isPage2Valid) ||
(currentPage === 3 && !isPage3Valid) ||
(currentPage === 4 && !isPage4Valid) ||
(currentPage === 5 && !isPage5Valid)
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600'
}`}
disabled={
(currentPage === 1 && !isPage1Valid) ||
(currentPage === 2 && !isPage2Valid) ||
(currentPage === 3 && !isPage3Valid) ||
(currentPage === 4 && !isPage4Valid) ||
(currentPage === 5 && !isPage5Valid)
}
primary
name="Next"
/>
) : (
<Button
text="Valider"
onClick={(e) => {
e.preventDefault();
handleSubmit(e);
}}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
currentPage === 6 && !isPage6Valid
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600'
}`}
disabled={currentPage === 6 && !isPage6Valid}
primary
/>
)}
</>
) : (
<Button
onClick={() => router.push(FE_PARENTS_HOME_URL)}
text="Quitter"
primary
className="bg-emerald-500 text-white hover:bg-emerald-600"
/>
)}
</div>
</div>
);
}