feat: подсказка о недопустимой дате увольнения
Если последний рабочий день раньше, чем через месяц от сегодня, под полем показывается причина и минимальная дата. Раньше кнопки отправки просто становились неактивными без объяснения — это случалось у заявлений, пролежавших на доработке: дата, допустимая при подаче, переставала подходить. Правило и текст вынесены в lastWorkingDay.ts и общие для подсказки и проверки перед отправкой. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,14 +28,14 @@ import {
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import { getEmployeeOrganizations, getCompanyDirectorEmail, IEmployeeOrganization } from '../../shared/dictionary';
|
||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||
import { isoDatePlusMonths, formatIsoDate } from '../../shared/dateUtils';
|
||||
import { lastWorkingDayError } from './lastWorkingDay';
|
||||
import {
|
||||
approvalListTitle,
|
||||
approvalListTitleForListId,
|
||||
dataListTitle,
|
||||
dataListTitleForListId
|
||||
} from '../../shared/listTitles';
|
||||
import { FORM_ID, IFormData, EMPTY_DATA, SetField, MIN_NOTICE_MONTHS, IOrganizationRecord, primaryPosition } from './model';
|
||||
import { FORM_ID, IFormData, EMPTY_DATA, SetField, IOrganizationRecord, primaryPosition } from './model';
|
||||
import { saveDismissalRequest, dismissalApprovalService } from './submit';
|
||||
import { loadDismissalRequest } from './load';
|
||||
import { RequestSection } from './sections/RequestSection';
|
||||
@@ -197,15 +197,13 @@ const DismissalForm: React.FC<IRequestFormProps> = ({
|
||||
|
||||
// Последний рабочий день нужно назначать минимум за MIN_NOTICE_MONTHS месяц —
|
||||
// календарь (min у input type=date, см. RequestSection) не даёт выбрать более
|
||||
// раннюю дату, но проверяем и здесь на случай обхода UI.
|
||||
const minLastWorkingDay = isoDatePlusMonths(MIN_NOTICE_MONTHS);
|
||||
const isLastDayValid = !data.LastWorkingDay || data.LastWorkingDay >= minLastWorkingDay;
|
||||
// раннюю дату, но проверяем и здесь на случай обхода UI. Тот же текст
|
||||
// RequestSection показывает подсказкой под полем (см. lastWorkingDay.ts).
|
||||
const lastDayError = lastWorkingDayError(data.LastWorkingDay);
|
||||
const position = primaryPosition(data.Organization);
|
||||
const isValid = !!(data.Employee && position.trim() && data.LastWorkingDay && isLastDayValid && data.Phone.trim());
|
||||
const isValid = !!(data.Employee && position.trim() && data.LastWorkingDay && !lastDayError && data.Phone.trim());
|
||||
const validate = (): string | undefined => {
|
||||
if (data.LastWorkingDay && !isLastDayValid) {
|
||||
return `Последний рабочий день нужно назначать минимум за ${MIN_NOTICE_MONTHS} месяц — минимально доступная дата: ${formatIsoDate(minLastWorkingDay)}.`;
|
||||
}
|
||||
if (lastDayError) return lastDayError;
|
||||
if (!data.Employee || !position.trim() || !data.LastWorkingDay || !data.Phone.trim()) {
|
||||
return 'Заполните обязательные поля: сотрудник, должность, последний рабочий день, контактный телефон.';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { formatIsoDate, isoDatePlusMonths } from '../../shared/dateUtils';
|
||||
import { MIN_NOTICE_MONTHS } from './model';
|
||||
|
||||
/** Самый ранний допустимый последний рабочий день ('YYYY-MM-DD'): сегодня + MIN_NOTICE_MONTHS. */
|
||||
export function minLastWorkingDay(): string {
|
||||
return isoDatePlusMonths(MIN_NOTICE_MONTHS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Почему последний рабочий день не подходит; undefined — подходит или ещё не
|
||||
* выбран. Одна функция и для подсказки под полем (RequestSection), и для
|
||||
* проверки перед отправкой (DismissalForm) — чтобы текст и правило не разошлись.
|
||||
*
|
||||
* Правило считается от СЕГОДНЯ: у заявления, пролежавшего на доработке, дата,
|
||||
* бывшая допустимой при подаче, может перестать подходить — подсказка сразу
|
||||
* объясняет, почему кнопки отправки неактивны.
|
||||
*/
|
||||
export function lastWorkingDayError(value: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
const min = minLastWorkingDay();
|
||||
if (value >= min) return undefined;
|
||||
return `Последний рабочий день нужно назначать минимум за ${MIN_NOTICE_MONTHS} месяц — минимально доступная дата: ${formatIsoDate(min)}.`;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
@import '../../../../../styles/design-tokens';
|
||||
|
||||
// Пояснение под полем, почему значение не подходит (см. lastWorkingDayError).
|
||||
.fieldError {
|
||||
margin-top: $sp-1;
|
||||
font-size: $fs-label;
|
||||
color: $red-500;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { FormGrid, FormField, TextInput, TextArea, PhoneInput, PeopleField, LinkField } from '../../../shared/ui';
|
||||
import { ISectionProps, EMPLOYEE_PLACEHOLDER, MIN_NOTICE_MONTHS } from '../model';
|
||||
import { isoDatePlusMonths } from '../../../shared/dateUtils';
|
||||
import { useFormReadOnly } from '@tnsu/ui-kit';
|
||||
import { ISectionProps, EMPLOYEE_PLACEHOLDER } from '../model';
|
||||
import { lastWorkingDayError, minLastWorkingDay } from '../lastWorkingDay';
|
||||
import { OrganizationsTable } from './OrganizationsTable';
|
||||
import styles from './RequestSection.module.scss';
|
||||
|
||||
interface IRequestSectionProps extends ISectionProps {
|
||||
/**
|
||||
@@ -27,7 +29,12 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
|
||||
onEmployeeChange,
|
||||
onRemoveOrganization,
|
||||
assetsAcceptorEditable
|
||||
}) => (
|
||||
}) => {
|
||||
// В просмотре (DismissalView) секция только для чтения — подсказка там ни к чему.
|
||||
const readOnly = useFormReadOnly();
|
||||
const lastDayError = lastWorkingDayError(data.LastWorkingDay);
|
||||
|
||||
return (
|
||||
<FormGrid>
|
||||
<FormField label="Сотрудник" htmlFor="Employee" required full>
|
||||
<PeopleField
|
||||
@@ -56,15 +63,18 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
|
||||
onRemove={onRemoveOrganization}
|
||||
/>
|
||||
{/* Заявление подаётся заранее — датой увольнения нельзя назначить ближе,
|
||||
чем через MIN_NOTICE_MONTHS месяц от сегодня (min). */}
|
||||
чем через MIN_NOTICE_MONTHS месяц от сегодня (min). Если дата уже стоит
|
||||
раньше (заявление пролежало на доработке) — подсказка под полем, иначе
|
||||
непонятно, почему кнопки отправки неактивны. */}
|
||||
<FormField label="Последний рабочий день" htmlFor="LastWorkingDay" required>
|
||||
<TextInput
|
||||
id="LastWorkingDay"
|
||||
type="date"
|
||||
value={data.LastWorkingDay}
|
||||
onChange={(v) => set('LastWorkingDay', v)}
|
||||
min={isoDatePlusMonths(MIN_NOTICE_MONTHS)}
|
||||
min={minLastWorkingDay()}
|
||||
/>
|
||||
{!readOnly && lastDayError && <div className={styles.fieldError}>{lastDayError}</div>}
|
||||
</FormField>
|
||||
<FormField label="Контактный телефон" htmlFor="Phone" required>
|
||||
<PhoneInput id="Phone" value={data.Phone} onChange={(v) => set('Phone', v)} />
|
||||
@@ -93,3 +103,4 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user