From 9b83c544910d1a41d31607b274fced80b022a053 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: Thu, 24 Sep 2026 16:52:53 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=81=D0=B2=D0=BE=D0=B8=D1=85=20=D0=B7=D0=B0?= =?UTF-8?q?=D1=8F=D0=B2=D0=BE=D0=BA=20=D0=B2=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=83=D1=81=D0=B5=20=D0=A7=D0=B5=D1=80=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/myRequests/RequestsTableView.tsx | 60 ++++++++++++++- .../forms/Clearance/ClearanceForm.tsx | 13 ++++ .../ConcreteRequest/ConcreteRequestForm.tsx | 13 ++++ .../forms/Dismissal/DismissalForm.tsx | 13 ++++ .../EmployeeTransfer/EmployeeTransferForm.tsx | 13 ++++ .../RecruitingHire/RecruitingHireForm.tsx | 13 ++++ .../RecruitingSelectionForm.tsx | 13 ++++ .../forms/Referral/ReferralForm.tsx | 13 ++++ .../ServiceRequest/ServiceRequestForm.tsx | 13 ++++ .../forms/Ticket1C/Ticket1CForm.tsx | 13 ++++ .../forms/TmcTransfer/TmcTransferForm.tsx | 13 ++++ .../forms/UnpaidLeave/UnpaidLeaveForm.tsx | 13 ++++ .../forms/Vacation/VacationForm.tsx | 13 ++++ .../WorkplaceSetup/WorkplaceSetupForm.tsx | 13 ++++ .../requestPortal/shared/draftDeletion.ts | 76 +++++++++++++++++++ .../shared/ui/SubmitActions/SubmitActions.tsx | 13 ++++ .../ui/SubmitActions/getDraftDeleteHandler.ts | 39 ++++++++++ src/webparts/requestPortal/shared/ui/index.ts | 1 + 18 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 src/webparts/requestPortal/shared/draftDeletion.ts create mode 100644 src/webparts/requestPortal/shared/ui/SubmitActions/getDraftDeleteHandler.ts diff --git a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx index 87396d5..dd20407 100644 --- a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx +++ b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx @@ -13,8 +13,11 @@ import { searchOwnRequests, } from './myRequests.service'; import { IMyRequest } from './models/IMyRequest'; -import { DataTable, IDataTableColumn } from '@tnsu/ui-kit'; +import { DataTable, IDataTableColumn, IconButton } from '@tnsu/ui-kit'; import { REQUEST_STATUS, REQUEST_STATUS_CHOICES, IRequestType, StatusBadge } from '../../shared/approval'; +import { confirmAndDeleteDraft, isDeletableStatus } from '../../shared/draftDeletion'; +import { friendlyErrorMessage } from '../../shared/diagnostics'; +import { getFormComponent } from '../../forms/registry'; import { onRequestsChanged } from '../../shared/requestEvents'; import { perfScope } from '../../shared/perfTrace'; import { useRoutedQuery } from '../../shared/usePersistedQuery'; @@ -197,12 +200,23 @@ function formatDate(iso: string): string { // (доступ с клавиатуры: Enter/Space). // // Набор колонок зависит от раздела и от фильтра — см. columnsFor. +/** + * Можно ли удалить строку прямо из таблицы: своя заявка в статусе «Черновик» + * и форма этого типа есть в портале (чужие типы из approval-route портал не + * открывает и не удаляет). + */ +function canDeleteRow(r: IMyRequest): boolean { + return r.kind === 'own' && isDeletableStatus(r.status) && !!getFormComponent(r.formKey); +} + const Table: React.FC<{ rows: IMyRequest[]; view: ListView; statusFilter: string; onOpen: (request: IMyRequest) => void; -}> = ({ rows, view, statusFilter, onOpen }) => { + /** Удалить черновик; задан только там, где в таблице бывают свои черновики. */ + onDelete?: (request: IMyRequest) => void; +}> = ({ rows, view, statusFilter, onOpen, onDelete }) => { const cols = columnsFor(view, statusFilter); // Колонки для DataTable строим динамически: базовые всегда, автор/статус/ @@ -241,6 +255,27 @@ const Table: React.FC<{ render: (r) => r.decisionComment || Без комментария }); } + if (onDelete && rows.some(canDeleteRow)) { + columns.push({ + key: 'actions', + title: '', + width: '56px', + render: (r) => + canDeleteRow(r) ? ( + { + // Клик по всей строке открывает заявку — кнопке это не нужно. + e.stopPropagation(); + onDelete(r); + }} + /> + ) : null + }); + } return (
@@ -300,6 +335,18 @@ export const RequestsTableView: React.FC = ({ const [rows, setRows] = React.useState(undefined); const [totalRows, setTotalRows] = React.useState(0); const [error, setError] = React.useState(''); + const [deleteError, setDeleteError] = React.useState(''); + + // Список перечитается сам: confirmAndDeleteDraft шлёт rp-requests-changed. + const handleDelete = (request: IMyRequest): void => { + setDeleteError(''); + confirmAndDeleteDraft({ + formKey: request.formKey, + itemId: request.id, + listId: request.listId, + code: request.code + }).catch((e) => setDeleteError(`Не удалось удалить черновик: ${friendlyErrorMessage(e)}`)); + }; // input — то, что в поле ввода (меняется сразу); в маршрут значение уходит // после паузы в наборе. Данные грузятся по МАРШРУТНОМУ query — он и так уже @@ -545,6 +592,7 @@ export const RequestsTableView: React.FC = ({
{error &&
Не удалось загрузить заявки: {error}
} + {deleteError &&
{deleteError}
} {!error && !loaded &&
Загрузка…
} {!error && loaded && total === 0 && (
@@ -553,7 +601,13 @@ export const RequestsTableView: React.FC = ({ )} {!error && rows && total > 0 && ( <> - +
)} diff --git a/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx b/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx index db25869..d7df482 100644 --- a/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx +++ b/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx @@ -9,6 +9,7 @@ import { Icon, RequestNumber, SubmitActions, + getDraftDeleteHandler, useRequestSubmit } from '../../shared/ui'; import { @@ -277,6 +278,17 @@ const ClearanceForm: React.FC = ({ const sectionProps = { data, set, context, onEmployeeChange: handleEmployeeChange }; + // «Удалить черновик» — только у сохранённой заявки в статусе «Черновик». + const handleDelete = getDraftDeleteHandler({ + formKey: FORM_ID, + requestId, + requestStatus, + listId: editListId, + code: requestCode, + submit, + onDeleted: onCancel + }); + return (
+ )} diff --git a/src/webparts/requestPortal/shared/ui/SubmitActions/getDraftDeleteHandler.ts b/src/webparts/requestPortal/shared/ui/SubmitActions/getDraftDeleteHandler.ts new file mode 100644 index 0000000..5675512 --- /dev/null +++ b/src/webparts/requestPortal/shared/ui/SubmitActions/getDraftDeleteHandler.ts @@ -0,0 +1,39 @@ +import { RequestTypeId } from '../../../models/types'; +import { confirmAndDeleteDraft, isDeletableStatus } from '../../draftDeletion'; +import { friendlyErrorMessage } from '../../diagnostics'; +import { markFormClean } from '../../unsavedChanges'; +import { IRequestSubmit } from '../useRequestSubmit'; + +export interface IDraftDeletionParams { + formKey: RequestTypeId; + /** Id сохранённой заявки; undefined — новая, ещё не сохранённая (удалять нечего). */ + requestId: number | undefined; + requestStatus: string | undefined; + listId?: string; + code?: string; + /** Ошибка удаления показывается там же, где ошибки сохранения формы. */ + submit: IRequestSubmit; + /** После удаления — уйти с формы (обычно onCancel формы: к плиткам / закрыть панель). */ + onDeleted: () => void; +} + +/** + * Обработчик кнопки «Удалить черновик» для SubmitActions (проп onDelete). + * undefined — кнопку не показывать: заявка не сохранена или уже не черновик. + */ +export function getDraftDeleteHandler(params: IDraftDeletionParams): (() => void) | undefined { + const { formKey, requestId, requestStatus, listId, code, submit, onDeleted } = params; + if (!requestId || !isDeletableStatus(requestStatus)) return undefined; + + return () => { + confirmAndDeleteDraft({ formKey, itemId: requestId, listId, code }) + .then((deleted) => { + if (!deleted) return; + // Черновика больше нет — предупреждать о «несохранённых правках» при + // уходе с формы уже не о чем. + markFormClean(); + onDeleted(); + }) + .catch((e) => submit.setError(`Не удалось удалить черновик: ${friendlyErrorMessage(e)}`)); + }; +} diff --git a/src/webparts/requestPortal/shared/ui/index.ts b/src/webparts/requestPortal/shared/ui/index.ts index 4fe9c12..eaa7af1 100644 --- a/src/webparts/requestPortal/shared/ui/index.ts +++ b/src/webparts/requestPortal/shared/ui/index.ts @@ -46,6 +46,7 @@ export { OrgLetterhead } from './OrgLetterhead/OrgLetterhead'; export { LinkField } from './LinkField/LinkField'; export { ErrorOverlay } from './ErrorOverlay/ErrorOverlay'; export { SubmitActions } from './SubmitActions/SubmitActions'; +export { getDraftDeleteHandler } from './SubmitActions/getDraftDeleteHandler'; export { InitiatorLine } from './InitiatorLine/InitiatorLine'; export type { IInitiatorLineProps } from './InitiatorLine/InitiatorLine'; export { useRequestSubmit } from './useRequestSubmit';