/
TimurIsm
/
OpenEyes
Обзор
Документация
Войти
/
TimurIsm
/
OpenEyes
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
front/src/hooks/useTestForm.jsx
293 строки
10 KB
TimurIsm
first_commit
16 май 2026, 15:34
16 май 2026, 15:34
4d3f198
Код
Авторство
О чём код?
import { useState, useCallback } from 'react'; import { deepTrim } from '../components/stringUtils'; export const MAX_TYPE_LENGTH = 100; export const MAX_SHORT_DESC_LENGTH = 250; export const MAX_FOLDER_LENGTH = 50; export const MAX_MAIN_IMAGE_LENGTH = 100; export const MAX_RESULT_LENGTH = 300; export const MAX_CONTENT_LENGTH = 100; export const MAX_ANSWER_LENGTH = 100; export const MAX_OPTION_LENGTH = 100; export const MAX_OPTIONS_COUNT = 7; const initialFormData = { type: '', short_description: '', full_description: '', main_image: '', folder: '', results: [ { type: 'SUCCESS', text: '' }, { type: 'FAILURE', text: '' } ], questions: [], adminId: '', }; const parseAnswerOptions = (options) => { if (!options) return []; try { if (typeof options === 'string') { const parsed = JSON.parse(options); return Array.isArray(parsed) ? parsed : []; } if (Array.isArray(options)) return options; return []; } catch { return []; } }; export const useTestForm = (initialData = null, mode = 'create') => { const [formData, setFormData] = useState(() => { if (initialData && mode === 'edit') { const folderFromImage = initialData.main_image ? initialData.main_image.split('/').filter(Boolean)[0] || '' : ''; let results = []; if (initialData.results) { if (Array.isArray(initialData.results)) { results = initialData.results; } else if (typeof initialData.results === 'string') { try { results = JSON.parse(initialData.results); if (!Array.isArray(results)) results = []; } catch { results = []; } } } const successResult = results.find(r => r.type === 'SUCCESS') || { type: 'SUCCESS', text: '' }; const failureResult = results.find(r => r.type === 'FAILURE') || { type: 'FAILURE', text: '' }; return { id: initialData.id || '', type: initialData.type || '', short_description: initialData.short_description || '', full_description: initialData.full_description || '', main_image: initialData.main_image || '', folder: initialData.folder || folderFromImage || '', results: [successResult, failureResult], questions: initialData.questions ? initialData.questions.map(q => ({...q, answer_options: parseAnswerOptions(q.answer_options)})) : [], adminId: initialData.adminId || '', }; } return { ...initialFormData }; }); const [alertMessage, setAlertMessage] = useState(null); const handleInputChange = useCallback((field, value, maxLength) => { if (maxLength && value.length > maxLength) { setAlertMessage({type: 'error', message: `Превышено максимальное количество символов (${maxLength})!`}); } else { setFormData(prev => ({ ...prev, [field]: value })); } }, []); const handleResultChange = useCallback((resultType, value) => { if (value.length > MAX_RESULT_LENGTH) { setAlertMessage({type: 'error', message: `Текст результата не может превышать ${MAX_RESULT_LENGTH} символов!`}); return; } setFormData(prev => ({ ...prev, results: prev.results.map(result => result.type === resultType ? { ...result, text: value } : result) })); }, []); const getResultText = useCallback((type) => { const result = formData.results?.find(r => r.type === type); return result ? result.text : ''; }, [formData.results]); const validateImagePath = useCallback((path) => { if (!path || !formData.folder) return true; const imageFolder = path.split('/').filter(Boolean)[0] || ''; const inputFolder = formData.folder.replace(/^\/|\/$/g, ''); return imageFolder === inputFolder; }, [formData.folder]); const handleMainImageFileChange = useCallback((e) => { if (!formData.folder) { setAlertMessage({type: 'error', message: 'Пожалуйста, сначала укажите папку для изображений!'}); return; } const file = e.target.files[0]; if (file) { const cleanFolder = formData.folder.replace(/^\/|\/$/g, ''); const mainImagePath = `/${cleanFolder}/${file.name}`; if (mainImagePath.length <= MAX_MAIN_IMAGE_LENGTH) { setFormData(prev => ({ ...prev, main_image: mainImagePath })); } else { setAlertMessage({type: 'error', message: `Путь к изображению не может превышать ${MAX_MAIN_IMAGE_LENGTH} символов!`}); } } }, [formData.folder]); const handleMainImageManualChange = useCallback((value) => { if (value.length <= MAX_MAIN_IMAGE_LENGTH) { setFormData(prev => ({ ...prev, main_image: value })); } }, []); const handleQuestionFileChange = useCallback((index, e) => { if (!formData.folder) { setAlertMessage({type: 'error', message: 'Пожалуйста, сначала укажите папку для изображений!'}); return; } const file = e.target.files[0]; if (file) { const cleanFolder = formData.folder.replace(/^\/|\/$/g, ''); const updatedQuestions = [...formData.questions]; updatedQuestions[index].content = `/${cleanFolder}/${file.name}`; setFormData(prev => ({ ...prev, questions: updatedQuestions })); } }, [formData.folder, formData.questions]); const handleQuestionChange = useCallback((index, field, value) => { setFormData(prev => { const updatedQuestions = [...prev.questions]; updatedQuestions[index][field] = value; return { ...prev, questions: updatedQuestions }; }); }, []); const handleAddQuestion = useCallback(() => { setFormData(prev => ({ ...prev, questions: [ ...prev.questions, { data_type: 'input', content: '', correct_answer: '', answer_options: [] } ] })); }, []); const handleRemoveQuestion = useCallback((index) => { setFormData(prev => { const updatedQuestions = [...prev.questions]; updatedQuestions.splice(index, 1); return { ...prev, questions: updatedQuestions }; }); }, []); const validateAllImagePaths = useCallback(() => { if (!formData.folder) return true; if (formData.main_image && !validateImagePath(formData.main_image)) { return false; } for (const question of formData.questions) { if (question.content?.startsWith('/') && !validateImagePath(question.content)) { return false; } } return true; }, [formData.folder, formData.main_image, formData.questions, validateImagePath]); const validateForm = useCallback((isEdit = false) => { const errors = []; const cleanedType = deepTrim(formData.type); const cleanedShortDesc = deepTrim(formData.short_description); const cleanedFullDesc = deepTrim(formData.full_description); const cleanedMainImage = deepTrim(formData.main_image); const cleanedFolder = deepTrim(formData.folder); if (!cleanedType || !cleanedShortDesc || !cleanedFullDesc || !cleanedMainImage || !cleanedFolder || formData.questions.length === 0) { errors.push('Все поля обязательны для заполнения!'); } for (const result of formData.results) { const cleanedText = deepTrim(result?.text || ''); if (!cleanedText) { errors.push(`Заполните текст результата для "${result.type === 'SUCCESS' ? 'успешного' : 'неудачного'}" прохождения!`); } } if (formData.type.length > MAX_TYPE_LENGTH) { errors.push(`Название теста не может превышать ${MAX_TYPE_LENGTH} символов!`); } if (formData.short_description.length > MAX_SHORT_DESC_LENGTH) { errors.push(`Краткое описание не может превышать ${MAX_SHORT_DESC_LENGTH} символов!`); } if (formData.folder.length > MAX_FOLDER_LENGTH) { errors.push(`Название папки не может превышать ${MAX_FOLDER_LENGTH} символов!`); } if (formData.main_image.length > MAX_MAIN_IMAGE_LENGTH) { errors.push(`Путь к главному изображению не может превышать ${MAX_MAIN_IMAGE_LENGTH} символов!`); } if (!validateAllImagePaths()) { errors.push('Папка в пути одного или нескольких изображений не совпадает с указанной папкой!'); } for (let i = 0; i < formData.questions.length; i++) { const q = formData.questions[i]; const cleanedContent = deepTrim(q.content || ''); const cleanedCorrectAnswer = deepTrim(q.correct_answer || ''); if (!cleanedContent) { errors.push(`Вопрос #${i + 1}: содержимое вопроса не может быть пустым!`); } if (!cleanedCorrectAnswer) { errors.push(`Вопрос #${i + 1}: правильный ответ не может быть пустым!`); } if (q.data_type === 'select') { const cleanedOptions = (q.answer_options || []).map(opt => deepTrim(opt)).filter(opt => opt !== ''); if (cleanedOptions.length === 0) { errors.push(`Вопрос #${i + 1}: для типа "select" нужно добавить хотя бы один вариант ответа!`); } if (cleanedCorrectAnswer && !cleanedOptions.some(opt => opt === cleanedCorrectAnswer)) { errors.push(`Вопрос #${i + 1}: правильный ответ должен быть одним из добавленных вариантов!`); } } } return errors; }, [formData, validateAllImagePaths]); const resetForm = useCallback(() => { setFormData({ ...initialFormData }); setAlertMessage(null); }, []); return { formData, setFormData, alertMessage, setAlertMessage, handleInputChange, handleResultChange, getResultText, validateImagePath, handleMainImageFileChange, handleMainImageManualChange, handleQuestionFileChange, handleQuestionChange, handleAddQuestion, handleRemoveQuestion, validateForm, validateAllImagePaths, resetForm, }; };