Feature/routes export #29
@@ -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)]
|
||||
.map(e => e.toLowerCase())
|
||||
// Тех, у кого доступ к этому типу уже есть (и кого уже добавили в список), показываем
|
||||
// в поиске с пометкой, но выбрать нельзя — так видно, что человек не потерялся.
|
||||
const grantedEmails = new Set(
|
||||
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 employee = employees.items.find(e => e.email.toLowerCase() === email.toLowerCase());
|
||||
return employee ? `${employee.fio} · ${employee.email}` : email;
|
||||
@@ -237,7 +248,7 @@ export const RequestTypeAccessScreen: React.FC<IRequestTypeAccessScreenProps> =
|
||||
<Field label={texts.requestTypeAccess.emailLabel} required full>
|
||||
<div>
|
||||
<SearchableSelect
|
||||
options={addableEmployees.map(toEmployeeOption)}
|
||||
options={employeeOptions}
|
||||
value=""
|
||||
loading={employees.isLoading}
|
||||
placeholder={texts.requestTypeAccess.emailPlaceholder}
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
|
||||
&:hover, &.active { background: $gold-50; color: $navy-900; }
|
||||
&.selected { font-weight: 600; color: $navy-900; }
|
||||
&:disabled { cursor: not-allowed; opacity: 0.55; background: transparent; color: $gray-700; }
|
||||
}
|
||||
|
||||
.itemSub {
|
||||
|
||||
@@ -9,6 +9,8 @@ export interface ISearchableOption {
|
||||
label: string;
|
||||
/** Второстепенная строка под названием — например, компания у отдела. */
|
||||
sublabel?: string;
|
||||
/** Показывается в списке (и находится поиском), но выбрать нельзя. */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ISearchableSelectProps {
|
||||
@@ -135,6 +137,7 @@ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
|
||||
type="button"
|
||||
key={opt.value}
|
||||
className={`${styles.item} ${opt.value === value ? styles.selected : ''}`}
|
||||
disabled={opt.disabled}
|
||||
onClick={() => pick(opt.value)}
|
||||
>
|
||||
{opt.label}
|
||||
|
||||
@@ -201,6 +201,8 @@ export const texts = {
|
||||
emailLabel: 'Сотрудники',
|
||||
emailPlaceholder: 'Добавьте сотрудника — найдите по ФИО или email',
|
||||
removeEmail: 'Убрать из списка',
|
||||
alreadyGranted: 'доступ уже есть',
|
||||
alreadyChosen: 'уже в списке',
|
||||
noEmailsSelected: 'Пока никого не выбрано.',
|
||||
createFailed: (failures: string[]): string =>
|
||||
`Не удалось выдать доступ (остались в списке): ${failures.join('; ')}`,
|
||||
|
||||
Reference in New Issue
Block a user