From d62e800e96f51f6761c7f66a0a8408db13aa3b29 Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Tue, 13 Oct 2026 12:09:22 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=81=D1=82=D1=83=D0=BF=20=D0=BA?= =?UTF-8?q?=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BA=D0=B0=D0=BC:=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D0=B0=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF?= =?UTF-8?q?=D0=B0=20=D1=81=D1=80=D0=B0=D0=B7=D1=83=20=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=B8=D0=BC=20=D1=81=D0=BE=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=B4=D0=BD=D0=B8=D0=BA=D0=B0=D0=BC=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=BC=D1=83=20=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D1=83=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В форме можно добавить несколько сотрудников списком; уже имеющие доступ к выбранному типу не предлагаются. Ошибки по отдельным сотрудникам не отменяют остальных — неудавшиеся остаются в списке. Co-Authored-By: Claude Sonnet 5.5 --- .../RequestTypeAccessScreen.module.scss | 21 +++++ .../screens/RequestTypeAccessScreen.tsx | 77 +++++++++++++++---- frontend/src/webparts/approvalAdmin/texts.ts | 10 ++- 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.module.scss b/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.module.scss index 02a7fd2..1b6dd1b 100644 --- a/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.module.scss +++ b/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.module.scss @@ -10,3 +10,24 @@ max-width: 320px; flex: 1; } + +.chosen { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 10px; +} + +.chosenItem { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 4px 8px 4px 12px; + border: 1px solid #e1e4ea; + border-radius: 6px; +} + +.chosenEmpty { + opacity: 0.6; +} diff --git a/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.tsx b/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.tsx index 67ef86d..58a61a4 100644 --- a/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.tsx +++ b/frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.tsx @@ -53,7 +53,7 @@ export const RequestTypeAccessScreen: React.FC = const [showForm, setShowForm] = React.useState(false); const [requestTypeId, setRequestTypeId] = React.useState(''); - const [email, setEmail] = React.useState(''); + const [emails, setEmails] = React.useState([]); const submit = useAsyncSubmit(); const [filterRequestTypeId, setFilterRequestTypeId] = useLocalStorageState(ACCESS_FILTER_TYPE_STORAGE_KEY, ''); @@ -97,7 +97,7 @@ export const RequestTypeAccessScreen: React.FC = const openCreate = (): void => { setRequestTypeId(''); - setEmail(''); + setEmails([]); setShowForm(true); }; @@ -106,17 +106,45 @@ export const RequestTypeAccessScreen: React.FC = submit.fail(texts.requestTypeAccess.validationRequestType); return; } - if (!email.trim()) { + if (emails.length === 0) { submit.fail(texts.requestTypeAccess.validationEmail); return; } - submit.run(() => - api.requestTypeAccess.create({ requestTypeId: Number(requestTypeId), email: email.trim() }).then(() => { + // По одному запросу на сотрудника (бэкенд выдаёт доступ по одному). Падение на + // одном не отменяет остальных: неудавшиеся остаются в списке с причиной, чтобы + // можно было поправить и нажать ещё раз. + submit.run(async () => { + const failures: string[] = []; + const failedEmails: string[] = []; + for (const email of emails) { + try { + await api.requestTypeAccess.create({ requestTypeId: Number(requestTypeId), email }); + } catch (e) { + failedEmails.push(email); + failures.push(`${email}: ${(e as Error).message}`); + } + } + + if (failedEmails.length < emails.length) access.reload(); + if (failures.length === 0) { setShowForm(false); - access.reload(); - }) - ); + return; + } + setEmails(failedEmails); + throw new Error(texts.requestTypeAccess.createFailed(failures)); + }); + }; + + // Уже выбранным и тем, у кого доступ к этому типу есть, в списке делать нечего. + const unavailableEmails = new Set( + [...emails, ...access.items.filter(a => String(a.requestTypeId) === requestTypeId).map(a => a.email)] + .map(e => e.toLowerCase()) + ); + const addableEmployees = employees.items.filter(e => !unavailableEmails.has(e.email.toLowerCase())); + const employeeLabel = (email: string): string => { + const employee = employees.items.find(e => e.email.toLowerCase() === email.toLowerCase()); + return employee ? `${employee.fio} · ${employee.email}` : email; }; return ( @@ -207,15 +235,30 @@ export const RequestTypeAccessScreen: React.FC = /> - +
+ value && setEmails(current => [...current, value])} + /> +
+ {emails.length === 0 && {texts.requestTypeAccess.noEmailsSelected}} + {emails.map(email => ( +
+ {employeeLabel(email)} + setEmails(current => current.filter(x => x !== email))} + /> +
+ ))} +
+
diff --git a/frontend/src/webparts/approvalAdmin/texts.ts b/frontend/src/webparts/approvalAdmin/texts.ts index d1bec63..18c2923 100644 --- a/frontend/src/webparts/approvalAdmin/texts.ts +++ b/frontend/src/webparts/approvalAdmin/texts.ts @@ -198,10 +198,14 @@ export const texts = { createTitle: 'Выдать доступ', requestTypeLabel: 'Тип заявки', requestTypePlaceholder: 'Выберите тип заявки', - emailLabel: 'Сотрудник', - emailPlaceholder: 'Найдите по ФИО или email', + emailLabel: 'Сотрудники', + emailPlaceholder: 'Добавьте сотрудника — найдите по ФИО или email', + removeEmail: 'Убрать из списка', + noEmailsSelected: 'Пока никого не выбрано.', + createFailed: (failures: string[]): string => + `Не удалось выдать доступ (остались в списке): ${failures.join('; ')}`, validationRequestType: 'Выберите тип заявки.', - validationEmail: 'Выберите сотрудника.', + validationEmail: 'Выберите хотя бы одного сотрудника.', deleteTitle: 'Отозвать доступ?', deleteText: (email: string, requestTypeName: string): string => `${email} больше не будет видеть все заявки типа «${requestTypeName}».`