05cd491769
Каждый вид обращения раньше показывал одинаковый набор обязательных полей. getAppealFieldsConfig теперь описывает и видимость, и обязательность отдельно: жалоба — предложение по-прежнему можно оставить, но требовать его как для самостоятельного вида "Предложение" неверно. validateFeedback читает то же правило, что и форма, — иначе одно поле могло бы требовать то, что другое не показывает. FormCard в FeedbackForm дублировал заголовок и подзаголовок PageShell — убран (пустой title у ui-kit не создаёт строки, гэпа не остаётся). Подсказка у переключателя "Обращение анонимное" разбита на две сноски (* и **), как в исходной форме, а не слитный абзац. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
53 lines
2.1 KiB
TypeScript
53 lines
2.1 KiB
TypeScript
import { getAppealFieldsConfig, type FeedbackErrors, type 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;
|
|
}
|
|
|
|
// Правило видимости/обязательности общее с формой (AppealSection), поэтому
|
|
// валидируем по нему же — иначе форма ругалась бы на скрытое или необязательное поле.
|
|
const fields = getAppealFieldsConfig(data.kind);
|
|
|
|
if (fields.situation.visible) {
|
|
const value = data.situation.trim();
|
|
if (fields.situation.required && !value) {
|
|
errors.situation = TEXTS.errors.required;
|
|
} else if (value && value.length < MIN_TEXT_LENGTH) {
|
|
errors.situation = TEXTS.errors.tooShort;
|
|
}
|
|
}
|
|
|
|
if (fields.suggestion.visible) {
|
|
const value = data.suggestion.trim();
|
|
if (fields.suggestion.required && !value) {
|
|
errors.suggestion = TEXTS.errors.required;
|
|
} else if (value && value.length < MIN_TEXT_LENGTH) {
|
|
errors.suggestion = TEXTS.errors.tooShort;
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|