Доступ к заявкам: сотрудники с уже выданным доступом показываются в поиске с пометкой и недоступны для выбора
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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('; ')}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user