fix: improve recipient picker - load all users with avatars

RecipientPicker (заявления):
- Added pagination support to load ALL users (not just first page)
- Added avatar images from media URLs instead of base64
- Falls back to initials when no avatar available
- Loads users in batches of 100 per page

RecipientPickerHandler (чаты):
- Added pagination support for full user list
- Keeps existing avatar display logic
- Loads 100 users per page when not searching
- Limits to 20 results when searching

CSS improvements:
- Added styles for avatar images (.user-avatar-img)
- Added placeholder styles for initials
- Proper object-fit and border-radius for avatars
This commit is contained in:
admin i 2025-12-08 12:09:32 +03:00
parent 62e03ad538
commit ff9b094dd7
3 changed files with 93 additions and 19 deletions

View File

@ -182,6 +182,28 @@
font-weight: 600;
font-size: 0.75rem;
flex-shrink: 0;
overflow: hidden;
}
/* Изображение аватара */
.user-avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
/* Плейсхолдер с инициалами */
.user-avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--bs-primary);
color: white;
font-weight: 600;
font-size: 0.75rem;
}
/* Информация о пользователе */

View File

@ -52,16 +52,11 @@ export class RecipientPicker {
async loadData() {
this.state.loading = true;
try {
const [usersRes, deptsRes] = await Promise.all([
fetch(this.options.apiUsersUrl),
fetch(this.options.apiDepartmentsUrl)
]);
if (usersRes.ok) {
const usersData = await usersRes.json();
this.users = usersData.results || usersData;
}
// Загружаем пользователей с пагинацией (все страницы)
this.users = await this.loadAllUsers();
// Загружаем отделы
const deptsRes = await fetch(this.options.apiDepartmentsUrl);
if (deptsRes.ok) {
const deptsData = await deptsRes.json();
this.departments = deptsData.results || deptsData;
@ -73,6 +68,37 @@ export class RecipientPicker {
}
}
/**
* Загрузка всех пользователей с автоматической обработкой пагинации
*/
async loadAllUsers() {
const allUsers = [];
let nextUrl = this.options.apiUsersUrl + '?page_size=100&active=true';
while (nextUrl) {
try {
const response = await fetch(nextUrl);
if (!response.ok) break;
const data = await response.json();
const users = data.results || data;
if (Array.isArray(users)) {
allUsers.push(...users);
}
// Проверяем наличие следующей страницы
nextUrl = data.next || null;
} catch (error) {
console.error('RecipientPicker: Error loading users page:', error);
break;
}
}
console.log(`RecipientPicker: Loaded ${allUsers.length} users`);
return allUsers;
}
render() {
this.container.innerHTML = `
<div class="recipient-picker-email">
@ -255,9 +281,17 @@ export class RecipientPicker {
const displayName = `${user.last_name} ${user.first_name}`;
const subtitle = user.position || user.email || '';
// Аватар или инициалы
let avatarHtml;
if (user.avatar) {
avatarHtml = `<img src="${user.avatar}" alt="${displayName}" class="user-avatar-img">`;
} else {
avatarHtml = `<div class="user-avatar-placeholder">${this.getInitials(user)}</div>`;
}
return `
<div class="dropdown-item-user" data-action="add-${type}" data-user-id="${user.id}">
<div class="user-avatar-small">${this.getInitials(user)}</div>
<div class="user-avatar-small">${avatarHtml}</div>
<div class="user-info">
<div class="user-name">${displayName}</div>
${subtitle ? `<div class="user-subtitle">${subtitle}</div>` : ''}

View File

@ -127,21 +127,39 @@ export class RecipientPicker {
if (this.searchQuery) {
url.searchParams.set('search', this.searchQuery);
// При поиске ограничиваем результаты
url.searchParams.set('page_size', '20');
} else {
// Без поиска загружаем все страницы
url.searchParams.set('page_size', '100');
url.searchParams.set('active', 'true');
}
url.searchParams.set('ordering', 'last_name,first_name');
url.searchParams.set('page_size', '20');
try {
const response = await fetch(url.toString(), { headers: this.headers });
let allItems = [];
let nextUrl = url.toString();
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
// Загружаем все страницы пагинации
while (nextUrl) {
const response = await fetch(nextUrl, { headers: this.headers });
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const data = await response.json();
const items = Array.isArray(data) ? data : (data.results || []);
this._renderResults(items);
const data = await response.json();
const items = Array.isArray(data) ? data : (data.results || []);
allItems.push(...items);
// Проверяем наличие следующей страницы
// При поиске загружаем только первую страницу
nextUrl = (!this.searchQuery && data.next) ? data.next : null;
}
console.log(`RecipientPickerHandler: Loaded ${allItems.length} employees`);
this._renderResults(allItems);
} catch (error) {
this.elResults.innerHTML = '<div class="rp-empty">Не удалось загрузить сотрудников</div>';
console.error('RecipientPicker fetch error:', error);