Доступ к заявкам: сотрудники с уже выданным доступом показываются в поиске с пометкой и недоступны для выбора
Tests / frontend-test (pull_request) Successful in 1m37s
Tests / backend-test (pull_request) Successful in 2m5s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-13 12:10:40 +05:00
parent d62e800e96
commit e88593a39c
4 changed files with 23 additions and 6 deletions
@@ -136,12 +136,23 @@ export const RequestTypeAccessScreen: React.FC<IRequestTypeAccessScreenProps> =
}); });
}; };
// Уже выбранным и тем, у кого доступ к этому типу есть, в списке делать нечего. // Тех, у кого доступ к этому типу уже есть (и кого уже добавили в список), показываем
const unavailableEmails = new Set( // в поиске с пометкой, но выбрать нельзя — так видно, что человек не потерялся.
[...emails, ...access.items.filter(a => String(a.requestTypeId) === requestTypeId).map(a => a.email)] const grantedEmails = new Set(
.map(e => e.toLowerCase()) access.items.filter(a => String(a.requestTypeId) === requestTypeId).map(a => a.email.toLowerCase())
); );
const addableEmployees = employees.items.filter(e => !unavailableEmails.has(e.email.toLowerCase())); const chosenEmails = new Set(emails.map(e => e.toLowerCase()));
const employeeOptions = employees.items.map(employee => {
const option = toEmployeeOption(employee);
const key = employee.email.toLowerCase();
if (grantedEmails.has(key)) {
return { ...option, disabled: true, sublabel: `${option.sublabel ?? ''} · ${texts.requestTypeAccess.alreadyGranted}` };
}
if (chosenEmails.has(key)) {
return { ...option, disabled: true, sublabel: `${option.sublabel ?? ''} · ${texts.requestTypeAccess.alreadyChosen}` };
}
return option;
});
const employeeLabel = (email: string): string => { const employeeLabel = (email: string): string => {
const employee = employees.items.find(e => e.email.toLowerCase() === email.toLowerCase()); const employee = employees.items.find(e => e.email.toLowerCase() === email.toLowerCase());
return employee ? `${employee.fio} · ${employee.email}` : email; return employee ? `${employee.fio} · ${employee.email}` : email;
@@ -237,7 +248,7 @@ export const RequestTypeAccessScreen: React.FC<IRequestTypeAccessScreenProps> =
<Field label={texts.requestTypeAccess.emailLabel} required full> <Field label={texts.requestTypeAccess.emailLabel} required full>
<div> <div>
<SearchableSelect <SearchableSelect
options={addableEmployees.map(toEmployeeOption)} options={employeeOptions}
value="" value=""
loading={employees.isLoading} loading={employees.isLoading}
placeholder={texts.requestTypeAccess.emailPlaceholder} placeholder={texts.requestTypeAccess.emailPlaceholder}
@@ -116,6 +116,7 @@
&:hover, &.active { background: $gold-50; color: $navy-900; } &:hover, &.active { background: $gold-50; color: $navy-900; }
&.selected { font-weight: 600; color: $navy-900; } &.selected { font-weight: 600; color: $navy-900; }
&:disabled { cursor: not-allowed; opacity: 0.55; background: transparent; color: $gray-700; }
} }
.itemSub { .itemSub {
@@ -9,6 +9,8 @@ export interface ISearchableOption {
label: string; label: string;
/** Второстепенная строка под названием — например, компания у отдела. */ /** Второстепенная строка под названием — например, компания у отдела. */
sublabel?: string; sublabel?: string;
/** Показывается в списке (и находится поиском), но выбрать нельзя. */
disabled?: boolean;
} }
export interface ISearchableSelectProps { export interface ISearchableSelectProps {
@@ -135,6 +137,7 @@ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
type="button" type="button"
key={opt.value} key={opt.value}
className={`${styles.item} ${opt.value === value ? styles.selected : ''}`} className={`${styles.item} ${opt.value === value ? styles.selected : ''}`}
disabled={opt.disabled}
onClick={() => pick(opt.value)} onClick={() => pick(opt.value)}
> >
{opt.label} {opt.label}
@@ -201,6 +201,8 @@ export const texts = {
emailLabel: 'Сотрудники', emailLabel: 'Сотрудники',
emailPlaceholder: 'Добавьте сотрудника — найдите по ФИО или email', emailPlaceholder: 'Добавьте сотрудника — найдите по ФИО или email',
removeEmail: 'Убрать из списка', removeEmail: 'Убрать из списка',
alreadyGranted: 'доступ уже есть',
alreadyChosen: 'уже в списке',
noEmailsSelected: 'Пока никого не выбрано.', noEmailsSelected: 'Пока никого не выбрано.',
createFailed: (failures: string[]): string => createFailed: (failures: string[]): string =>
`Не удалось выдать доступ (остались в списке): ${failures.join('; ')}`, `Не удалось выдать доступ (остались в списке): ${failures.join('; ')}`,