feat: подсветка незаполненных полей в заявке в отдел 1С

Проверки обязательных полей вынесены в validation.ts и показываются под
полями через FieldIssuesProvider, как в приёме, подборе и отпуске: форма
прокручивается к первому полю, над кнопками — кликабельный список.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-16 15:44:36 +05:00
parent 4f0d02fe59
commit 739f247268
2 changed files with 35 additions and 16 deletions
@@ -3,6 +3,8 @@ import { useState, useMemo, useCallback, useEffect } from 'react';
import styles from './Ticket1CForm.module.scss';
import {
Alert,
FieldIssuesProvider,
issueLabels,
FormCard,
InitiatorLine,
FormModeProvider,
@@ -49,7 +51,8 @@ import {
dataListTitle,
dataListTitleForListId
} from '../../shared/listTitles';
import { FORM_ID, IFormData, EMPTY_DATA, SetField, hasOtherDatabase } from './model';
import { FORM_ID, IFormData, EMPTY_DATA, SetField } from './model';
import { formIssues } from './validation';
import { saveTicketRequest, ticket1cApprovalService } from './submit';
import { loadTicketRequest } from './load';
import { getApplicantProfile } from './services/profile.service';
@@ -240,20 +243,11 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
);
const matrixEditor = useMemo(() => ({ ...matrix, rows: matrixRows }), [matrix, matrixRows]);
const isOtherDatabaseMissing = hasOtherDatabase(data) && !data.DB1COther.trim();
const areRequiredFilled = !!(
data.TaskName.trim() &&
data.WorkType &&
data.Organization &&
data.DB1C.length > 0 &&
data.Question.trim()
);
const isValid = areRequiredFilled && !isOtherDatabaseMissing;
// Проблемы полей (см. validation.ts) подсвечиваются под полями через FieldIssuesProvider.
const issues = formIssues(data);
const isValid = issues.length === 0;
const validate = (): string | undefined => {
if (!areRequiredFilled) {
return 'Заполните обязательные поля: наименование задачи, вид работ, организация, база 1С, вопрос.';
}
if (isOtherDatabaseMissing) return 'Выбрана база 1С «Другое» — укажите её название.';
if (!isValid) return `Заполните или исправьте поля: ${issueLabels(issues)}.`;
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
return undefined;
@@ -391,7 +385,7 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
});
return (
<div>
<FieldIssuesProvider draftIssues={issues} submitIssues={issues}>
<button type="button" className={styles.back} onClick={onCancel}>
<Icon name="arrow-left" size="sm" />
<span>К списку заявок</span>
@@ -486,7 +480,7 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
submitLabel="Отправить"
/>
</FormCard>
</div>
</FieldIssuesProvider>
);
};
@@ -0,0 +1,25 @@
import type { IFieldIssue } from '../../shared/ui';
import { IFormData, hasOtherDatabase } from './model';
/** Название базы вручную обязательно, только если в «База 1С» отмечено «Другое». */
function otherDatabaseIssue(data: IFormData): IFieldIssue | undefined {
if (!hasOtherDatabase(data) || data.DB1COther.trim()) return undefined;
return { field: 'DB1COther', label: 'Другая база 1С', message: 'выбрано «Другое» — укажите название базы' };
}
/**
* Всё, что мешает сохранить или отправить заявку, в порядке формы (пусто — можно).
* field — ключ FormField (htmlFor/fieldKey): по нему поле подсвечивается и к нему
* прокручивается форма.
*/
export function formIssues(data: IFormData): IFieldIssue[] {
const issues: Array<IFieldIssue | undefined> = [
data.TaskName.trim() ? undefined : { field: 'TaskName', label: 'Наименование задачи' },
data.WorkType ? undefined : { field: 'WorkType', label: 'Виды работ' },
data.Organization ? undefined : { field: 'Organization', label: 'Организация' },
data.DB1C.length > 0 ? undefined : { field: 'DB1C', label: 'База 1С' },
otherDatabaseIssue(data),
data.Question.trim() ? undefined : { field: 'Question', label: 'Вопрос' }
];
return issues.filter((issue): issue is IFieldIssue => issue !== undefined);
}