feat: форма обращения сотрудника в HR департамент
Переносит вопросы из Microsoft Forms в собственный интерфейс: компания, анонимность, ФИО и должность, вид обращения, описание ситуации и предложение. Поля автора скрываются при анонимном обращении, а не блокируются: требовать ФИО у анонима — противоречие, а серое неактивное поле выглядит как ошибка формы. Состояние целиком в useFeedbackForm, правила заполнения — в отдельной чистой функции validateFeedback: её можно покрыть тестами без React. Отправки пока нет — по договорённости делаем только UI, submit пишет данные в консоль (TODO в useFeedbackForm). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { EMPTY_FEEDBACK, type FeedbackErrors, type IFeedbackData } from './model';
|
||||
import { TEXTS } from './texts';
|
||||
import { validateFeedback } from './validateFeedback';
|
||||
|
||||
export type SubmitStatus = 'idle' | 'submitting' | 'done';
|
||||
|
||||
export interface IFeedbackForm {
|
||||
data: IFeedbackData;
|
||||
errors: FeedbackErrors;
|
||||
status: SubmitStatus;
|
||||
/** Общая ошибка формы (показывается над полями). */
|
||||
formError: string;
|
||||
set: <K extends keyof IFeedbackData>(key: K, value: IFeedbackData[K]) => void;
|
||||
submit: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Состояние формы обращения: данные, валидация, отправка.
|
||||
* Бэкенда пока нет — submit только имитирует отправку (см. TODO ниже),
|
||||
* поэтому вся логика синхронная и живёт здесь, без сервисного слоя.
|
||||
*/
|
||||
export function useFeedbackForm(): IFeedbackForm {
|
||||
const [data, setData] = useState<IFeedbackData>(EMPTY_FEEDBACK);
|
||||
const [errors, setErrors] = useState<FeedbackErrors>({});
|
||||
const [formError, setFormError] = useState('');
|
||||
const [status, setStatus] = useState<SubmitStatus>('idle');
|
||||
|
||||
const set = useCallback(
|
||||
<K extends keyof IFeedbackData>(key: K, value: IFeedbackData[K]): void => {
|
||||
setData((prev) => ({ ...prev, [key]: value }));
|
||||
// Ошибку поля снимаем сразу при правке — иначе она висит до повторной отправки.
|
||||
setErrors((prev) => {
|
||||
if (!prev[key]) return prev;
|
||||
const next = { ...prev };
|
||||
delete next[key];
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const submit = useCallback((): void => {
|
||||
const found = validateFeedback(data);
|
||||
setErrors(found);
|
||||
|
||||
if (Object.keys(found).length > 0) {
|
||||
setFormError(TEXTS.errors.hasErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
setFormError('');
|
||||
setStatus('submitting');
|
||||
|
||||
// TODO: заменить на реальную отправку, когда определимся с бэкендом.
|
||||
// Сейчас это заглушка по договорённости: делаем только UI.
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Обращение (заглушка отправки):', data);
|
||||
setStatus('done');
|
||||
}, [data]);
|
||||
|
||||
const reset = useCallback((): void => {
|
||||
setData(EMPTY_FEEDBACK);
|
||||
setErrors({});
|
||||
setFormError('');
|
||||
setStatus('idle');
|
||||
}, []);
|
||||
|
||||
return { data, errors, status, formError, set, submit, reset };
|
||||
}
|
||||
Reference in New Issue
Block a user