mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-05 12:41:27 +00:00
226 lines
7.2 KiB
JavaScript
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>
|
|
);
|
|
}
|