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}».`