refactor: подписанные вложения — общий модуль shared/attachments
Подпись вложения префиксом в имени файла и поле одного слота жили внутри «Заявки на приём», а теперь нужны и другим формам. Перенесены в shared/attachments без изменения поведения: приём реэкспортирует общее, его форма и просмотр не тронуты. useAttachmentEditor принимает новую, ещё не сохранённую заявку (itemId пустой) и явное «куда грузить» — id появляется только после первого сохранения. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
@import '../../../../styles/design-tokens';
|
||||
|
||||
.empty {
|
||||
font-family: $font-sans;
|
||||
font-size: 13.5px;
|
||||
color: $gray-500;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { AttachmentList, FileDropzone, FormField } from '../../shared/ui';
|
||||
import { IAttachmentEditor } from '../../shared/attachments/useAttachmentEditor';
|
||||
import { attachmentsOfCategory, categoryPrefix, uncategorizedAttachments } from './attachmentCategories';
|
||||
import styles from './HireAttachmentField.module.scss';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
editor: IAttachmentEditor;
|
||||
editable: boolean;
|
||||
/** Группа новых файлов в editor.pending. */
|
||||
groupKey: string;
|
||||
/** Категория в имени файла; не задана — «прочие» вложения без префикса. */
|
||||
category?: string;
|
||||
}
|
||||
|
||||
/** Поле одной категории вложений: прикреплённые файлы + (в правке) выбор новых. */
|
||||
export const HireAttachmentField: React.FC<Props> = ({ label, editor, editable, groupKey, category }) => {
|
||||
const items = category ? attachmentsOfCategory(editor.items, category) : uncategorizedAttachments(editor.items);
|
||||
// В списке имена показаны без префикса категории — для удаления возвращаем его.
|
||||
const removeWithPrefix = (name: string): void => editor.remove(category ? categoryPrefix(category) + name : name);
|
||||
|
||||
return (
|
||||
<FormField label={label} full>
|
||||
{items.length > 0 && <AttachmentList items={items} onRemove={editable ? removeWithPrefix : undefined} />}
|
||||
{items.length === 0 && !editable && <span className={styles.empty}>—</span>}
|
||||
{editable && (
|
||||
<FileDropzone files={editor.pending[groupKey] || []} onChange={(files) => editor.setPending(groupKey, files)} />
|
||||
)}
|
||||
</FormField>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, CollapseSection } from '../../shared/ui';
|
||||
import { IAttachmentEditor, PrepareFiles } from '../../shared/attachments/useAttachmentEditor';
|
||||
import { AttachmentSlotField } from '../../shared/attachments/AttachmentSlotField';
|
||||
import { ATT_CATEGORY_ID, ATT_CATEGORY_RESUME, prefixFiles, uncategorizedAttachments } from './attachmentCategories';
|
||||
import { HireAttachmentField } from './HireAttachmentField';
|
||||
|
||||
/**
|
||||
* Группы новых файлов → категория в имени файла (см. attachmentCategories).
|
||||
@@ -37,9 +37,16 @@ export const HireAttachments: React.FC<Props> = ({ editor, editable }) => {
|
||||
return (
|
||||
<CollapseSection title="Вложения" icon="upload">
|
||||
{editor.error && <Alert variant="err">{editor.error}</Alert>}
|
||||
<HireAttachmentField label="Удостоверение" groupKey={GROUP_ID} category={ATT_CATEGORY_ID} {...fieldProps} />
|
||||
<HireAttachmentField label="Резюме" groupKey={GROUP_RESUME} category={ATT_CATEGORY_RESUME} {...fieldProps} />
|
||||
{showOther && <HireAttachmentField label="Прочие вложения" groupKey={GROUP_OTHER} {...fieldProps} />}
|
||||
<AttachmentSlotField label="Удостоверение" groupKey={GROUP_ID} category={ATT_CATEGORY_ID} {...fieldProps} />
|
||||
<AttachmentSlotField label="Резюме" groupKey={GROUP_RESUME} category={ATT_CATEGORY_RESUME} {...fieldProps} />
|
||||
{showOther && (
|
||||
<AttachmentSlotField
|
||||
label="Прочие вложения"
|
||||
groupKey={GROUP_OTHER}
|
||||
knownCategories={[ATT_CATEGORY_ID, ATT_CATEGORY_RESUME]}
|
||||
{...fieldProps}
|
||||
/>
|
||||
)}
|
||||
</CollapseSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,50 +1,24 @@
|
||||
import { IAttachmentInfo } from '../../shared/attachments/attachments.service';
|
||||
import { attachmentsWithoutCategories } from '../../shared/attachments/attachmentCategories';
|
||||
|
||||
/**
|
||||
* Категории вложений заявки на прием. Вложения SharePoint — плоская коллекция
|
||||
* файлов на элементе списка, «категории» у них нет. Поэтому категорию кодируем
|
||||
* ПРЕФИКСОМ в имени файла: «<Категория> — <исходное имя>». При загрузке имя
|
||||
* файла переписываем, при показе — группируем по префиксу и префикс срезаем.
|
||||
* Категории вложений заявки на прием. Механизм подписи префиксом в имени файла —
|
||||
* общий (shared/attachments/attachmentCategories); здесь только категории этой
|
||||
* формы. Общие функции реэкспортируются, чтобы импорты формы не менялись.
|
||||
*/
|
||||
export const ATT_CATEGORY_ID = 'Удостоверение';
|
||||
export const ATT_CATEGORY_RESUME = 'Резюме';
|
||||
|
||||
const SEP = ' — ';
|
||||
|
||||
/** Префикс имени файла для категории: «Удостоверение — ». */
|
||||
export function categoryPrefix(category: string): string {
|
||||
return `${category}${SEP}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Копии File с префиксом категории в имени — для загрузки. File.name неизменяемо,
|
||||
* поэтому создаём новый File с тем же содержимым и типом, но другим именем.
|
||||
*/
|
||||
export function prefixFiles(files: File[], category: string): File[] {
|
||||
const prefix = categoryPrefix(category);
|
||||
return files.map((f) => new File([f], prefix + f.name, { type: f.type }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Вложения указанной категории, с ОБРЕЗАННЫМ префиксом в имени (для показа).
|
||||
* url сохраняется как есть; чтобы удалить такой файл, префикс надо вернуть
|
||||
* обратно (см. categoryPrefix в обработчике удаления формы).
|
||||
*/
|
||||
export function attachmentsOfCategory(items: IAttachmentInfo[], category: string): IAttachmentInfo[] {
|
||||
const prefix = categoryPrefix(category);
|
||||
return items
|
||||
.filter((a) => a.fileName.indexOf(prefix) === 0)
|
||||
.map((a) => ({ url: a.url, fileName: a.fileName.slice(prefix.length) }));
|
||||
}
|
||||
export {
|
||||
categoryPrefix,
|
||||
prefixFiles,
|
||||
attachmentsOfCategory
|
||||
} from '../../shared/attachments/attachmentCategories';
|
||||
|
||||
/**
|
||||
* Вложения без известной категории (старые заявки, загруженные до разделения).
|
||||
* Показываем как есть, чтобы файлы не «исчезали» с экрана.
|
||||
*/
|
||||
export function uncategorizedAttachments(items: IAttachmentInfo[]): IAttachmentInfo[] {
|
||||
const idPrefix = categoryPrefix(ATT_CATEGORY_ID);
|
||||
const resumePrefix = categoryPrefix(ATT_CATEGORY_RESUME);
|
||||
return items.filter(
|
||||
(a) => a.fileName.indexOf(idPrefix) !== 0 && a.fileName.indexOf(resumePrefix) !== 0
|
||||
);
|
||||
return attachmentsWithoutCategories(items, [ATT_CATEGORY_ID, ATT_CATEGORY_RESUME]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user