279 lines
11 KiB
C#
279 lines
11 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.Diagnostics;
|
||
using CORP_MAUI.Models;
|
||
using Microsoft.Maui.Controls; // для TapGestureRecognizer и BindableObject
|
||
|
||
namespace CORP_MAUI.Pages;
|
||
|
||
public partial class EmployeesPage : ContentPage
|
||
{
|
||
private readonly ObservableCollection<User> _employees;
|
||
private List<User> _allEmployees = new();
|
||
|
||
public EmployeesPage()
|
||
{
|
||
try
|
||
{
|
||
Trace.WriteLine("=== EMPLOYEESPAGE CONSTRUCTOR START ===");
|
||
Trace.WriteLine("[EmployeesPage] >>> Вызов InitializeComponent");
|
||
|
||
InitializeComponent();
|
||
|
||
Trace.WriteLine("[EmployeesPage] >>> InitializeComponent завершен");
|
||
|
||
_employees = new ObservableCollection<User>();
|
||
|
||
EmployeesCollectionView.ItemsSource = _employees;
|
||
|
||
Trace.WriteLine("[EmployeesPage] >>> Подписка на UserStatusChanged");
|
||
// Подписываемся на изменения статуса пользователей
|
||
ServiceProvider.WebSocketService.UserStatusChanged += OnUserStatusChanged;
|
||
|
||
Trace.WriteLine("[EmployeesPage] >>> Запуск LoadEmployeesAsync");
|
||
// Загружаем сотрудников
|
||
_ = LoadEmployeesAsync();
|
||
|
||
Trace.WriteLine("=== EMPLOYEESPAGE CONSTRUCTOR END ===");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Trace.WriteLine("=== EMPLOYEESPAGE CONSTRUCTOR ERROR ===");
|
||
Trace.WriteLine($"[EmployeesPage] ❌ Тип ошибки: {ex.GetType().FullName}");
|
||
Trace.WriteLine($"[EmployeesPage] ❌ Сообщение: {ex.Message}");
|
||
Trace.WriteLine($"[EmployeesPage] ❌ StackTrace: {ex.StackTrace}");
|
||
|
||
// Показываем ошибку напрямую
|
||
Task.Run(async () =>
|
||
{
|
||
await Task.Delay(100);
|
||
Page? mainPage = Application.Current?.Windows[0]?.Page;
|
||
if (mainPage != null)
|
||
{
|
||
await mainPage.DisplayAlert("Ошибка EmployeesPage",
|
||
$"Ошибка инициализации:\n{ex.Message}\n\nStackTrace:\n{ex.StackTrace}", "OK");
|
||
}
|
||
});
|
||
throw;
|
||
}
|
||
}
|
||
|
||
private async Task LoadEmployeesAsync()
|
||
{
|
||
try
|
||
{
|
||
Trace.WriteLine("[EmployeesPage] >>> LoadEmployeesAsync START");
|
||
|
||
LoadingIndicator.IsVisible = true;
|
||
LoadingIndicator.IsRunning = true;
|
||
|
||
Trace.WriteLine("[EmployeesPage] >>> Вызов GetAllEmployeesAsync");
|
||
_allEmployees = await ServiceProvider.ApiService.GetAllEmployeesAsync();
|
||
|
||
Trace.WriteLine($"[EmployeesPage] >>> Получено сотрудников: {_allEmployees.Count}");
|
||
|
||
_employees.Clear();
|
||
foreach (User user in _allEmployees.OrderBy(u => u.FullName))
|
||
{
|
||
_employees.Add(user);
|
||
}
|
||
|
||
Trace.WriteLine($"[EmployeesPage] ✅ Сотрудники загружены успешно");
|
||
|
||
// Предзагружаем аватары в фоне
|
||
_ = PreloadAvatarsAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Trace.WriteLine($"[EmployeesPage] ❌ Ошибка загрузки: {ex.GetType().Name}");
|
||
Trace.WriteLine($"[EmployeesPage] Message: {ex.Message}");
|
||
Trace.WriteLine($"[EmployeesPage] StackTrace: {ex.StackTrace}");
|
||
|
||
await DisplayAlert("Ошибка", $"Не удалось загрузить список сотрудников:\n{ex.Message}", "OK");
|
||
}
|
||
finally
|
||
{
|
||
LoadingIndicator.IsVisible = false;
|
||
LoadingIndicator.IsRunning = false;
|
||
RefreshView.IsRefreshing = false;
|
||
}
|
||
}
|
||
|
||
private async void OnRefreshing(object sender, EventArgs e)
|
||
{
|
||
await LoadEmployeesAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Попытаться извлечь User из события нажатия (поддерживает CommandParameter, TapGestureRecognizer и BindingContext)
|
||
/// </summary>
|
||
private static User? GetUserFromTap(object? sender, TappedEventArgs? e)
|
||
{
|
||
// Сначала пробуем Parameter из TappedEventArgs
|
||
if (e?.Parameter is User userFromParam)
|
||
return userFromParam;
|
||
|
||
// Если sender - это TapGestureRecognizer
|
||
if (sender is TapGestureRecognizer tr)
|
||
{
|
||
if (tr.BindingContext is User userFromTr) return userFromTr;
|
||
if (tr.Parent is BindableObject parent && parent.BindingContext is User userFromParent) return userFromParent;
|
||
}
|
||
|
||
// Если sender - это элемент с BindingContext (например Border)
|
||
if (sender is BindableObject bo && bo.BindingContext is User userFromBo)
|
||
{
|
||
return userFromBo;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обработчик клика по карточке сотрудника (открытие профиля)
|
||
/// </summary>
|
||
private async void OnEmployeeCardTapped(object sender, TappedEventArgs e)
|
||
{
|
||
var selectedEmployee = GetUserFromTap(sender, e);
|
||
if (selectedEmployee == null)
|
||
{
|
||
Trace.WriteLine("[EmployeesPage] ⚠️ Не удалось определить выбранного сотрудника в OnEmployeeCardTapped");
|
||
return;
|
||
}
|
||
|
||
Trace.WriteLine($"[EmployeesPage] >>> Открытие профиля: {selectedEmployee.FullName}");
|
||
try
|
||
{
|
||
await Shell.Current.Navigation.PushAsync(new EmployeeProfilePage(selectedEmployee));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Trace.WriteLine($"[EmployeesPage] ❌ Ошибка перехода к профилю: {ex.Message}");
|
||
await DisplayAlert("Ошибка", "Не удалось открыть профиль сотрудника", "OK");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обработчик клика по иконке чата (открытие чата)
|
||
/// </summary>
|
||
private async void OnChatIconTapped(object sender, TappedEventArgs e)
|
||
{
|
||
var selectedEmployee = GetUserFromTap(sender, e);
|
||
if (selectedEmployee == null)
|
||
{
|
||
Trace.WriteLine("[EmployeesPage] ⚠️ Не удалось определить выбранного сотрудника в OnChatIconTapped");
|
||
return;
|
||
}
|
||
|
||
Trace.WriteLine($"[EmployeesPage] >>> Открытие чата с: {selectedEmployee.FullName}");
|
||
try
|
||
{
|
||
await Shell.Current.Navigation.PushAsync(new ChatPage(selectedEmployee));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Trace.WriteLine($"[EmployeesPage] ❌ Ошибка перехода к чату: {ex.Message}");
|
||
await DisplayAlert("Ошибка", "Не удалось открыть чат", "OK");
|
||
}
|
||
}
|
||
|
||
private void OnSearchTextChanged(object sender, TextChangedEventArgs e)
|
||
{
|
||
string searchText = e.NewTextValue?.ToLower() ?? string.Empty;
|
||
|
||
if (string.IsNullOrWhiteSpace(searchText))
|
||
{
|
||
// Показываем всех сотрудников
|
||
_employees.Clear();
|
||
foreach (User user in _allEmployees.OrderBy(u => u.FullName))
|
||
{
|
||
_employees.Add(user);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Фильтруем сотрудников
|
||
List<User> filtered = _allEmployees.Where(u =>
|
||
u.FullName.ToLower().Contains(searchText) ||
|
||
u.Username.ToLower().Contains(searchText) ||
|
||
u.DepartmentName.ToLower().Contains(searchText) ||
|
||
u.PositionName.ToLower().Contains(searchText)
|
||
).ToList();
|
||
|
||
_employees.Clear();
|
||
foreach (User user in filtered.OrderBy(u => u.FullName))
|
||
{
|
||
_employees.Add(user);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnUserStatusChanged(object? sender, User updatedUser)
|
||
{
|
||
// Обновляем статус пользователя в списке
|
||
MainThread.BeginInvokeOnMainThread(() =>
|
||
{
|
||
User? user = _employees.FirstOrDefault(u => u.Id == updatedUser.Id);
|
||
if (user != null)
|
||
{
|
||
user.IsOnline = updatedUser.IsOnline;
|
||
}
|
||
|
||
// Обновляем в основном списке
|
||
User? allUser = _allEmployees?.FirstOrDefault(u => u.Id == updatedUser.Id);
|
||
if (allUser != null)
|
||
{
|
||
allUser.IsOnline = updatedUser.IsOnline;
|
||
}
|
||
});
|
||
}
|
||
|
||
protected override void OnDisappearing()
|
||
{
|
||
base.OnDisappearing();
|
||
ServiceProvider.WebSocketService.UserStatusChanged -= OnUserStatusChanged;
|
||
}
|
||
|
||
private async Task PreloadAvatarsAsync()
|
||
{
|
||
try
|
||
{
|
||
Trace.WriteLine("[EmployeesPage] >>> Начинаем предзагрузку ават<D0B0><D182>ров...");
|
||
await ServiceProvider.ApiService.PreloadUserAvatarsAsync(_allEmployees);
|
||
Trace.WriteLine("[EmployeesPage] ✅ Аватары предзагружены");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Trace.WriteLine($"[EmployeesPage] ⚠️ Ошибка предзагрузки аватаров: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// SelectionChanged handler: откроет профиль выбранного сотрудника (вызывается при SelectionMode=Single)
|
||
/// </summary>
|
||
private async void EmployeesCollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var selected = e.CurrentSelection?.FirstOrDefault() as User;
|
||
if (selected == null)
|
||
return;
|
||
|
||
Trace.WriteLine($"[EmployeesPage] >>> SelectionChanged -> открываем профиль: {selected.FullName}");
|
||
await Shell.Current.Navigation.PushAsync(new EmployeeProfilePage(selected));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Trace.WriteLine($"[EmployeesPage] ❌ Ошибка в SelectionChanged: {ex.Message}");
|
||
await DisplayAlert("Ошибка", "Не удалось открыть профиль сотрудника", "OK");
|
||
}
|
||
finally
|
||
{
|
||
// Снять выбор, чтобы пользователь мог снова выбрать тот же элемент
|
||
if (sender is CollectionView cv)
|
||
{
|
||
cv.SelectedItem = null;
|
||
}
|
||
}
|
||
}
|
||
}
|