From 739f2472682cca058a85bd22f93a303e93524434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Fri, 16 Oct 2026 15:44:36 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BF=D0=BE=D0=B4=D1=81=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BD=D0=B5=D0=B7=D0=B0=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=B2=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BA=D0=B5=20=D0=B2?= =?UTF-8?q?=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=201=D0=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверки обязательных полей вынесены в validation.ts и показываются под полями через FieldIssuesProvider, как в приёме, подборе и отпуске: форма прокручивается к первому полю, над кнопками — кликабельный список. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../forms/Ticket1C/Ticket1CForm.tsx | 26 +++++++------------ .../forms/Ticket1C/validation.ts | 25 ++++++++++++++++++ 2 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 src/webparts/requestPortal/forms/Ticket1C/validation.ts 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); +}