feat: Ajout bouton de refus de dossier avec zone de saisie de motif [N3WTS-2]

This commit is contained in:
N3WT DE COMPET
2026-02-16 17:54:20 +01:00
parent 05c68ebfaa
commit 3779a47417
5 changed files with 223 additions and 44 deletions

View File

@ -0,0 +1,69 @@
import React, { useState } from 'react';
import Textarea from '@/components/Textarea';
import Button from '@/components/Form/Button';
/**
* Composant pour afficher le textarea de refus et le bouton d'action
* @param {function} onRefuse - callback appelée avec la raison du refus
* @param {boolean} isLoading - état de chargement
*/
const RefuseSubscription = ({ onRefuse, isLoading }) => {
const [reason, setReason] = useState('');
const [showTextarea, setShowTextarea] = useState(false);
const handleRefuseClick = () => {
setShowTextarea((prev) => !prev);
};
const handleSubmit = (e) => {
e.preventDefault();
if (reason.trim()) {
onRefuse(reason);
setShowTextarea(false);
setReason('');
}
};
return (
<div className="flex flex-col items-stretch justify-center h-full">
{!showTextarea ? (
<Button
text="Refuser le dossier d'inscription"
onClick={handleRefuseClick}
className="bg-red-500 hover:bg-red-700 text-white min-w-[220px] h-10"
type="button"
/>
) : (
<form onSubmit={handleSubmit} className="flex flex-col gap-2 mt-2">
<div className="flex items-start gap-2">
<Textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Ex : Réception de dossier trop tardive"
rows={3}
className="w-full"
required
/>
<button
type="button"
onClick={handleRefuseClick}
className="text-sm text-gray-500 hover:text-red-600 underline mt-1"
tabIndex={-1}
>
Annuler
</button>
</div>
<Button
text={isLoading ? 'Refus en cours...' : 'Confirmer le refus'}
type="submit"
className="bg-red-600 hover:bg-red-800 text-white w-full"
style={{ height: '40px' }}
disabled={isLoading || !reason.trim()}
/>
</form>
)}
</div>
);
};
export default RefuseSubscription;

View File

@ -11,6 +11,7 @@ import logger from '@/utils/logger';
import { School, CheckCircle, Hourglass, FileText } from 'lucide-react';
import SectionHeader from '@/components/SectionHeader';
import Button from '@/components/Form/Button';
import RefuseSubscription from './RefuseSubscription';
export default function ValidateSubscription({
studentId,
@ -20,6 +21,8 @@ export default function ValidateSubscription({
student_file,
onAccept,
classes,
onRefuse, // callback pour refus
isLoadingRefuse = false, // état de chargement refus
}) {
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
const [parentFileTemplates, setParentFileTemplates] = useState([]);
@ -162,8 +165,8 @@ export default function ValidateSubscription({
)}
</div>
{/* Colonne droite : Liste des documents, Option de fusion et Affectation */}
<div className="w-1/4 flex flex-col gap-4">
{/* Colonne droite : Liste des documents, Option de fusion, Affectation, Refus */}
<div className="w-1/4 flex flex-col flex-1 gap-4">
{/* Liste des documents */}
<div className="flex-1 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200 overflow-y-auto">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
@ -194,52 +197,50 @@ export default function ValidateSubscription({
</div>
{/* Option de fusion */}
<div className="bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Option de fusion
</h3>
<div className="flex items-center justify-between">
<ToggleSwitch
label="Fusionner les documents"
checked={mergeDocuments}
onChange={handleToggleMergeDocuments}
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 flex items-center justify-center mb-2 min-h-[70px]">
<ToggleSwitch
label="Fusionner les documents"
checked={mergeDocuments}
onChange={handleToggleMergeDocuments}
className="ml-0"
/>
</div>
{/* Affectation à une classe */}
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 flex items-center gap-4">
<div className="flex-1 flex flex-col">
<SelectChoice
name="associated_class"
label="Liste des classes"
placeHolder="Sélectionner une classe"
selected={formData.associated_class || ''}
callback={(e) => onChange('associated_class', e.target.value)}
choices={classes.map((classe) => ({
value: classe.id,
label: classe.atmosphere_name,
}))}
required
className="mt-2"
/>
</div>
</div>
{/* Section Affectation */}
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Affectation à une classe
</h3>
<div className="flex flex-col gap-4">
<SelectChoice
name="associated_class"
label="Classe"
placeHolder="Sélectionner une classe"
selected={formData.associated_class || ''} // La valeur actuelle de la classe associée
callback={(e) => onChange('associated_class', e.target.value)} // Met à jour formData
choices={classes.map((classe) => ({
value: classe.id,
label: classe.atmosphere_name,
}))} // Liste des classes disponibles
required
/>
<Button
text="Valider le dossier d'inscription"
onClick={(e) => {
e.preventDefault();
handleAssignClass();
}}
primary
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
!isPageValid
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600'
}`}
disabled={!isPageValid}
/>
</div>
{/* Boutons Valider/Refuser en bas, centrés */}
<div className="mt-auto flex justify-center gap-4 py-4">
<Button
text="Valider le dossier d'inscription"
onClick={(e) => {
e.preventDefault();
handleAssignClass();
}}
primary
className={`min-w-[220px] h-10 rounded-md shadow-sm focus:outline-none ${
!isPageValid
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600'
}`}
disabled={!isPageValid}
/>
<RefuseSubscription onRefuse={onRefuse} isLoading={isLoadingRefuse} />
</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
import React from 'react';
/**
* Textarea composant réutilisable
* @param {string} value - Valeur du textarea
* @param {function} onChange - Fonction appelée lors d'un changement
* @param {string} placeholder - Texte d'exemple
* @param {number} rows - Nombre de lignes
* @param {string} className - Classes CSS additionnelles
* @param {object} props - Props additionnels
*/
const Textarea = ({ value, onChange, placeholder = '', rows = 3, className = '', ...props }) => (
<textarea
value={value}
onChange={onChange}
placeholder={placeholder}
rows={rows}
className={`border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-emerald-500 resize-y ${className}`}
{...props}
/>
);
export default Textarea;