diff --git a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx index 8dc4c1e..2d71b6b 100644 --- a/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx +++ b/src/webparts/requestPortal/forms/Ticket1C/Ticket1CForm.tsx @@ -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 = ({ ); 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 = ({ }); return ( -
+
+ ); }; diff --git a/src/webparts/requestPortal/forms/Ticket1C/validation.ts b/src/webparts/requestPortal/forms/Ticket1C/validation.ts new file mode 100644 index 0000000..28ad771 --- /dev/null +++ b/src/webparts/requestPortal/forms/Ticket1C/validation.ts @@ -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 = [ + 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); +}