feat: подсветка незаполненных полей и переход к ним в приёме и подборе
Пользователи не понимали, почему кнопка «Отправить на согласование» серая. Теперь в заявках на приём и подбор кнопки активны: нажатие при незаполненных или неверных полях ничего не сохраняет, а подсвечивает эти поля с подсказкой под ними и прокручивает к первому. Над кнопками — список проблем, пункты ведут к полю. Механизм общий (FieldIssuesProvider + FormField), остальные формы пока работают по-старому и переводятся по тому же паттерну. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@ import * as React from 'react';
|
||||
import { Button, Icon, FormActions, Alert } from '@tnsu/ui-kit';
|
||||
import { IRequestSubmit } from '../useRequestSubmit';
|
||||
import { isCurrentUserTester } from './testerGroupAccess';
|
||||
import { SubmitIssues } from './SubmitIssues';
|
||||
import { useFieldIssuesControl } from '../FieldIssues/FieldIssuesContext';
|
||||
|
||||
/**
|
||||
* ВРЕМЕННАЯ ЗАМОРОЗКА (включена 2026-09-21 по просьбе Тимура Городецкого):
|
||||
@@ -65,6 +67,11 @@ export interface ISubmitActionsProps {
|
||||
// Логика доступности — здесь, в одном месте, чтобы все формы вели себя одинаково:
|
||||
// • сохранять/отправлять нельзя, пока идёт сохранение или заявка уже read-only;
|
||||
// • «Отмена» доступна всегда (возврат к списку), кроме момента сохранения.
|
||||
//
|
||||
// Форма внутри FieldIssuesProvider (см. shared/ui/FieldIssues) ведёт себя иначе:
|
||||
// кнопки сохранения не гаснут из-за незаполненных полей — нажатие при проблемах
|
||||
// ничего не сохраняет, а подсвечивает поля и прокручивает к первому. Над кнопками
|
||||
// список проблем со ссылками на поля. Формы без провайдера — как раньше, по isValid.
|
||||
export const SubmitActions: React.FC<ISubmitActionsProps> = ({
|
||||
submit,
|
||||
isValid,
|
||||
@@ -98,11 +105,33 @@ export const SubmitActions: React.FC<ISubmitActionsProps> = ({
|
||||
|
||||
const frozen = SUBMISSION_FROZEN && !unfrozenForm && !isTester;
|
||||
const locked = saving || readOnly || frozen;
|
||||
const canSubmit = submitValid ?? isValid;
|
||||
const issuesControl = useFieldIssuesControl();
|
||||
// С провайдером кнопки не гаснут по полям: проблемы показываются по нажатию.
|
||||
const canSaveDraft = issuesControl ? true : isValid;
|
||||
const canSubmit = issuesControl ? true : submitValid ?? isValid;
|
||||
|
||||
const handleSaveDraft = (): void => {
|
||||
if (issuesControl && issuesControl.draftIssues.length > 0) {
|
||||
issuesControl.reveal('draft');
|
||||
return;
|
||||
}
|
||||
onSaveDraft();
|
||||
};
|
||||
const handleSubmit = (): void => {
|
||||
if (issuesControl && issuesControl.submitIssues.length > 0) {
|
||||
issuesControl.reveal('submit');
|
||||
return;
|
||||
}
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{frozen && !readOnly && <Alert variant="warn">Подача заявок временно недоступна.</Alert>}
|
||||
{/* При заморозке кнопки неактивны не из-за полей — список только сбил бы с толку. */}
|
||||
{issuesControl && !frozen && !readOnly && (
|
||||
<SubmitIssues issues={issuesControl.submitIssues} onSelect={(field) => issuesControl.reveal('submit', field)} />
|
||||
)}
|
||||
<FormActions>
|
||||
{onDelete && (
|
||||
<Button variant="danger" onClick={onDelete} disabled={saving}>
|
||||
@@ -113,11 +142,11 @@ export const SubmitActions: React.FC<ISubmitActionsProps> = ({
|
||||
<Button variant="secondary" onClick={onCancel} disabled={saving}>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onSaveDraft} disabled={!isValid || locked}>
|
||||
<Button variant="secondary" onClick={handleSaveDraft} disabled={!canSaveDraft || locked}>
|
||||
<Icon name="document" size="sm" />
|
||||
Сохранить черновик
|
||||
</Button>
|
||||
<Button variant="accent" onClick={onSubmit} disabled={!canSubmit || locked}>
|
||||
<Button variant="accent" onClick={handleSubmit} disabled={!canSubmit || locked}>
|
||||
<Icon name="send" size="sm" />
|
||||
{saving && kind === 'submit' ? 'Отправка…' : submitLabel}
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
@import '../../../../../styles/design-tokens';
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: $sp-1 0 0;
|
||||
padding-left: $sp-5;
|
||||
}
|
||||
|
||||
// Пункт списка — ссылка на поле: выглядит как текст с подчёркиванием, без рамки кнопки.
|
||||
.link {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
cursor: pointer;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as React from 'react';
|
||||
import { Alert } from '@tnsu/ui-kit';
|
||||
import styles from './SubmitIssues.module.scss';
|
||||
import { IFieldIssue, ISSUES_ANCHOR_ATTR, issueListText } from '../FieldIssues/fieldIssues';
|
||||
|
||||
interface ISubmitIssuesProps {
|
||||
/** Что мешает отправке — в порядке формы. */
|
||||
issues: IFieldIssue[];
|
||||
/** Клик по пункту — прокрутка к полю. */
|
||||
onSelect: (field: string) => void;
|
||||
}
|
||||
|
||||
const TITLE = 'Чтобы отправить на согласование, заполните или исправьте:';
|
||||
|
||||
/**
|
||||
* Список причин, по которым заявку пока нельзя отправить. Пункт кликабелен —
|
||||
* ведёт к полю: на длинной форме иначе пришлось бы искать его самому.
|
||||
*/
|
||||
export const SubmitIssues: React.FC<ISubmitIssuesProps> = ({ issues, onSelect }) => {
|
||||
if (issues.length === 0) return null;
|
||||
|
||||
const anchorProps = { [ISSUES_ANCHOR_ATTR]: '' };
|
||||
return (
|
||||
<div {...anchorProps}>
|
||||
<Alert variant="warn">
|
||||
<div className={styles.title}>{TITLE}</div>
|
||||
<ul className={styles.list}>
|
||||
{issues.map((issue) => (
|
||||
<li key={`${issue.field}-${issue.label}`}>
|
||||
<button type="button" className={styles.link} onClick={() => onSelect(issue.field)}>
|
||||
{issueListText(issue)}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user