feat: согласующий с полным правом правит вложения в подборе и приёме

Алтынай на своём шаге согласования теперь может добавлять и удалять
файлы, а не только поля. Удаление — сразу, новые файлы загружаются
вместе с «Сохранить» или решением. Блоки вложений вынесены из
просмотров в отдельные компоненты на общем хуке useAttachmentEditor.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-06 17:23:08 +05:00
parent 271758527e
commit 0b4fbc4487
10 changed files with 207 additions and 56 deletions
@@ -7,8 +7,9 @@
* нет, как и в routeEditRights.ts. ВРЕМЕННО.
*
* Подключено только в «Заявке на подбор» и «Заявке на прием» (Реферальная
* рекомендация — нет, по решению Тимура 2026-09-24). Вложения этим правом не
* открываются — только поля.
* рекомендация — нет, по решению Тимура 2026-09-24). С 2026-10-06 право
* открывает и вложения: добавлять и удалять файлы (см. shared/attachments/
* useAttachmentEditor).
*/
const FULL_FIELD_EDITOR_EMAILS: ReadonlySet<string> = new Set([
'a.bulturukova@tnsu.kz' // Алтынай Бултурукова
@@ -0,0 +1,64 @@
import { useState } from 'react';
import { deleteAttachment, getAttachments, IAttachmentInfo, uploadAttachments } from './attachments.service';
import { friendlyErrorMessage } from '../diagnostics';
/**
* Подготовка новых файлов группы к загрузке — напр. префикс категории в имени
* (см. forms/RecruitingHire/attachmentCategories). По умолчанию — как есть.
*/
export type PrepareFiles = (groupKey: string, files: File[]) => File[];
export interface IAttachmentEditor {
/** Вложения, уже прикреплённые к заявке. */
items: IAttachmentInfo[];
setItems: (items: IAttachmentInfo[]) => void;
/** Выбранные, но ещё не загруженные файлы — по группам (категориям) формы. */
pending: Record<string, File[]>;
setPending: (groupKey: string, files: File[]) => void;
/** Удалить прикреплённый файл сразу из заявки (по полному имени файла в SP). */
remove: (fileName: string) => void;
/** Загрузить все выбранные файлы и перечитать список вложений. */
uploadPending: (prepare?: PrepareFiles) => Promise<void>;
/** Ошибка последнего удаления — показать рядом со списком. */
error: string | undefined;
}
const keepAsIs: PrepareFiles = (_groupKey, files) => files;
/**
* Правка вложений уже поданной заявки — для согласующего с полным правом
* (см. shared/approval/fullEditRights.ts). Удаление — сразу, как в форме
* автора; новые файлы копятся и загружаются вместе с сохранением полей
* (кнопка «Сохранить» или решение), чтобы не было второй кнопки сохранения.
*
* listRef — Title текущей партиции или url архивной (см. attachments.service).
*/
export function useAttachmentEditor(listRef: string, itemId: number): IAttachmentEditor {
const [items, setItems] = useState<IAttachmentInfo[]>([]);
const [pending, setPendingState] = useState<Record<string, File[]>>({});
const [error, setError] = useState<string | undefined>(undefined);
const setPending = (groupKey: string, files: File[]): void =>
setPendingState((prev) => ({ ...prev, [groupKey]: files }));
const remove = (fileName: string): void => {
setError(undefined);
deleteAttachment(listRef, itemId, fileName)
.then(() => setItems((prev) => prev.filter((a) => a.fileName !== fileName)))
.catch((e) => setError(`Не удалось удалить файл «${fileName}»: ${friendlyErrorMessage(e)}`));
};
const uploadPending = async (prepare: PrepareFiles = keepAsIs): Promise<void> => {
const files = Object.keys(pending).reduce<File[]>(
(acc, groupKey) => acc.concat(prepare(groupKey, pending[groupKey])),
[]
);
if (files.length === 0) return;
await uploadAttachments(listRef, itemId, files);
// Загруженные убираем из выбора, иначе повторное сохранение продублирует их.
setPendingState({});
setItems(await getAttachments(listRef, itemId));
};
return { items, setItems, pending, setPending, remove, uploadPending, error };
}