Files
n3wt-school/Front-End/src/app/[locale]/admin/structure/FormBuilder/page.js
2026-04-04 20:08:25 +02:00

226 lines
7.2 KiB
JavaScript

'use client';
import React, { useEffect, useMemo, useState } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { ArrowLeft } from 'lucide-react';
import FormTemplateBuilder from '@/components/Form/FormTemplateBuilder';
import { useEstablishment } from '@/context/EstablishmentContext';
import { useCsrfToken } from '@/context/CsrfContext';
import {
fetchRegistrationFileGroups,
fetchRegistrationSchoolFileMasterById,
createRegistrationSchoolFileMaster,
editRegistrationSchoolFileMaster,
} from '@/app/actions/registerFileGroupAction';
import { getSecureFileUrl } from '@/utils/fileUrl';
import logger from '@/utils/logger';
import { useNotification } from '@/context/NotificationContext';
import { FE_ADMIN_STRUCTURE_URL } from '@/utils/Url';
export default function FormBuilderPage() {
const searchParams = useSearchParams();
const router = useRouter();
const { selectedEstablishmentId } = useEstablishment();
const csrfToken = useCsrfToken();
const { showNotification } = useNotification();
const formId = searchParams.get('id');
const preGroupId = searchParams.get('groupId');
const isEditing = !!formId;
const [groups, setGroups] = useState([]);
const [initialData, setInitialData] = useState(null);
const [loading, setLoading] = useState(true);
const [uploadedFile, setUploadedFile] = useState(null);
const [existingFileUrl, setExistingFileUrl] = useState(null);
const normalizeBackendFile = (rawFile, rawFileUrl) => {
if (typeof rawFileUrl === 'string' && rawFileUrl.trim()) {
return rawFileUrl;
}
if (typeof rawFile === 'string' && rawFile.trim()) {
return rawFile;
}
if (rawFile && typeof rawFile === 'object') {
if (typeof rawFile.url === 'string' && rawFile.url.trim()) {
return rawFile.url;
}
if (typeof rawFile.path === 'string' && rawFile.path.trim()) {
return rawFile.path;
}
if (typeof rawFile.name === 'string' && rawFile.name.trim()) {
return rawFile.name;
}
}
return null;
};
const previewFileUrl = useMemo(() => {
if (uploadedFile instanceof File) {
return URL.createObjectURL(uploadedFile);
}
return existingFileUrl || null;
}, [uploadedFile, existingFileUrl]);
useEffect(() => {
return () => {
if (previewFileUrl && previewFileUrl.startsWith('blob:')) {
URL.revokeObjectURL(previewFileUrl);
}
};
}, [previewFileUrl]);
useEffect(() => {
if (!selectedEstablishmentId) return;
Promise.all([
fetchRegistrationFileGroups(selectedEstablishmentId),
formId ? fetchRegistrationSchoolFileMasterById(formId) : Promise.resolve(null),
])
.then(([groupsData, formData]) => {
setGroups(groupsData || []);
if (formData) {
setInitialData(formData);
const resolvedFile = normalizeBackendFile(
formData.file,
formData.file_url
);
if (resolvedFile) {
setExistingFileUrl(resolvedFile);
}
} else if (preGroupId) {
setInitialData({ groups: [{ id: Number(preGroupId) }] });
}
})
.catch((err) => {
logger.error('Error loading FormBuilder data:', err);
})
.finally(() => {
setLoading(false);
});
}, [selectedEstablishmentId, formId, preGroupId]);
const buildFormData = async (name, group_ids, formMasterData) => {
const dataToSend = new FormData();
dataToSend.append(
'data',
JSON.stringify({
name,
groups: group_ids,
formMasterData,
establishment: selectedEstablishmentId,
})
);
if (uploadedFile instanceof File) {
const ext =
uploadedFile.name.lastIndexOf('.') !== -1
? uploadedFile.name.substring(uploadedFile.name.lastIndexOf('.'))
: '';
const cleanName = (name || 'document')
.replace(/[^a-zA-Z0-9_\-]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
dataToSend.append('file', uploadedFile, `${cleanName}${ext}`);
} else if (existingFileUrl && isEditing) {
const lastDot = existingFileUrl.lastIndexOf('.');
const ext = lastDot !== -1 ? existingFileUrl.substring(lastDot) : '';
const cleanName = (name || 'document')
.replace(/[^a-zA-Z0-9_\-]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
try {
const resp = await fetch(getSecureFileUrl(existingFileUrl));
if (resp.ok) {
const blob = await resp.blob();
dataToSend.append('file', blob, `${cleanName}${ext}`);
}
} catch (e) {
logger.error('Could not re-fetch existing file:', e);
}
}
return dataToSend;
};
const handleSave = async ({ name, group_ids, formMasterData, id }) => {
const hasFileField = (formMasterData?.fields || []).some(
(field) => field.type === 'file'
);
const hasUploadedDocument =
uploadedFile instanceof File || Boolean(existingFileUrl);
if (hasFileField && !hasUploadedDocument) {
showNotification(
'Un document PDF doit être uploadé si le formulaire contient un champ fichier.',
'error',
'Erreur'
);
return;
}
try {
const dataToSend = await buildFormData(name, group_ids, formMasterData);
if (isEditing) {
await editRegistrationSchoolFileMaster(id || formId, dataToSend, csrfToken);
showNotification(
`Le formulaire "${name}" a été modifié avec succès.`,
'success',
'Succès'
);
} else {
await createRegistrationSchoolFileMaster(dataToSend, csrfToken);
showNotification(
`Le formulaire "${name}" a été créé avec succès.`,
'success',
'Succès'
);
}
router.push(FE_ADMIN_STRUCTURE_URL);
} catch (err) {
logger.error('Error saving form:', err);
showNotification('Erreur lors de la sauvegarde du formulaire', 'error', 'Erreur');
}
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<p className="text-gray-500">Chargement...</p>
</div>
);
}
return (
<div className="w-full min-h-screen bg-neutral">
{/* Header sticky */}
<div className="sticky top-0 z-10 bg-white border-b border-gray-200 px-4 py-3 flex items-center gap-4">
<button
onClick={() => router.push(FE_ADMIN_STRUCTURE_URL)}
className="flex items-center gap-2 text-primary hover:text-secondary font-label font-medium transition-colors"
>
<ArrowLeft size={20} />
Retour
</button>
<h1 className="text-lg font-headline font-semibold text-gray-800">
{isEditing ? 'Modifier le formulaire' : 'Créer un formulaire personnalisé'}
</h1>
</div>
<div className="max-w-5xl mx-auto px-4 py-6 space-y-4">
{/* FormTemplateBuilder */}
<FormTemplateBuilder
onSave={handleSave}
initialData={initialData}
groups={groups}
isEditing={isEditing}
masterFile={previewFileUrl}
onMasterFileUpload={(file) => setUploadedFile(file)}
/>
</div>
</div>
);
}