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:
2026-08-20 18:15:10 +05:00
parent e921062e3b
commit 010f78b138
18 changed files with 739 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
import type { FeedbackErrors, IFeedbackData } from './model';
import { TEXTS } from './texts';
/** Ниже этой длины текст не считаем содержательным описанием. */
const MIN_TEXT_LENGTH = 10;
/**
* Правила заполнения обращения.
* Чистая функция: получает данные, возвращает ошибки — её легко покрыть тестами
* и она не зависит от React.
*/
export function validateFeedback(data: IFeedbackData): FeedbackErrors {
const errors: FeedbackErrors = {};
if (!data.company) {
errors.company = TEXTS.errors.selectRequired;
}
// Имя и должность спрашиваем только у неанонимных обращений: у анонимного
// эти поля скрыты, требовать их было бы противоречием.
if (!data.isAnonymous && !data.fullName.trim()) {
errors.fullName = TEXTS.errors.required;
}
if (data.kind === 'other' && !data.kindOther.trim()) {
errors.kindOther = TEXTS.errors.required;
}
if (!data.situation.trim()) {
errors.situation = TEXTS.errors.required;
} else if (data.situation.trim().length < MIN_TEXT_LENGTH) {
errors.situation = TEXTS.errors.tooShort;
}
if (!data.suggestion.trim()) {
errors.suggestion = TEXTS.errors.required;
}
return errors;
}