corp-app/CORP-MAUI/Controls/DesktopLayout.xaml.cs

1258 lines
54 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics;
namespace CORP_MAUI.Controls;
public partial class DesktopLayout : ContentView
{
public DesktopLayout()
{
InitializeComponent();
Sidebar.NavigationRequested += OnNavigationRequested;
// По умолчанию показываем ленту новостей
LoadDefaultContent();
}
private void LoadDefaultContent()
{
// Создаём контент для ленты новостей в десктопном стиле
var newsContent = CreateDesktopPostsContent();
SetContent(newsContent);
}
private View CreateDesktopPostsContent()
{
// Заголовок страницы с поиском и кнопкой добавления
var header = new Grid
{
ColumnDefinitions = new ColumnDefinitionCollection
{
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto)
},
Margin = new Thickness(0, 0, 0, 20)
};
var title = new Label
{
Text = "Лента новостей",
FontSize = 24,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#333333"),
VerticalOptions = LayoutOptions.Center
};
// Поисковая строка (по центру)
var searchBox = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 20 },
BackgroundColor = Color.FromArgb("#F5F5F5"),
Stroke = Color.FromArgb("#E0E0E0"),
StrokeThickness = 1,
Padding = new Thickness(15, 8),
MaximumWidthRequest = 300,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
var searchLayout = new HorizontalStackLayout { Spacing = 10 };
searchLayout.Children.Add(new Label { Text = "🔍", FontSize = 14, TextColor = Color.FromArgb("#888888"), VerticalOptions = LayoutOptions.Center });
searchLayout.Children.Add(new Entry { Placeholder = "Поиск по названию, автору или...", PlaceholderColor = Color.FromArgb("#888888"), BackgroundColor = Colors.Transparent, WidthRequest = 200 });
searchBox.Content = searchLayout;
// Кнопка добавления новости
var addButton = new HorizontalStackLayout
{
Spacing = 8,
VerticalOptions = LayoutOptions.Center
};
var plusBorder = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 12 },
BackgroundColor = Color.FromArgb("#0D6EFD"),
HeightRequest = 24,
WidthRequest = 24,
Content = new Label { Text = "+", FontSize = 16, TextColor = Colors.White, HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center }
};
var addLabel = new Label
{
Text = "Новость",
FontSize = 16,
TextColor = Color.FromArgb("#0D6EFD"),
VerticalOptions = LayoutOptions.Center
};
addButton.Children.Add(plusBorder);
addButton.Children.Add(addLabel);
Grid.SetColumn(title, 0);
Grid.SetColumn(searchBox, 1);
Grid.SetColumn(addButton, 2);
header.Children.Add(title);
header.Children.Add(searchBox);
header.Children.Add(addButton);
// Контейнер для контента
// Вместо простого VerticalStackLayout используем Grid, чтобы выделить header и scrollable area
var root = new Grid
{
RowDefinitions = new RowDefinitionCollection
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }
},
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Fill
};
root.Add(header);
Grid.SetRow(header, 0);
// Здесь будет ScrollView с постами (загружается асинхронно)
var postsContainer = new VerticalStackLayout
{
Spacing = 15,
HorizontalOptions = LayoutOptions.Fill
};
// Placeholder пока загружаются посты
var loadingIndicator = new ActivityIndicator
{
IsRunning = true,
Color = Color.FromArgb("#0D6EFD"),
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 50)
};
postsContainer.Children.Add(loadingIndicator);
// Корректно настраиваем ScrollView: Fill и layout-constraints
var scrollView = new ScrollView { Content = postsContainer, VerticalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill };
// Помещаем scrollView в фиксированную ячейку Grid
root.Add(scrollView);
Grid.SetRow(scrollView, 1);
// Загружаем посты асинхронно
_ = LoadPostsAsync(postsContainer);
return root;
}
private async Task LoadPostsAsync(VerticalStackLayout container)
{
try
{
var response = await ServiceProvider.ApiService.GetPostsPaginatedAsync();
var posts = response?.Results;
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
if (posts == null || !posts.Any())
{
container.Children.Add(new Label
{
Text = "Нет новостей",
TextColor = Color.FromArgb("#888888"),
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 50)
});
return;
}
foreach (var post in posts)
{
var postCard = CreatePostCard(post);
// Даем карточке разумные ограничения по ширине — чтобы не становилась слишком большой
if (postCard is View v)
{
v.HorizontalOptions = LayoutOptions.Fill;
v.WidthRequest = double.NaN; // позволяет занять доступную ширину
v.MaximumWidthRequest = 900; // не растягивать на весь экран
}
container.Children.Add(postCard);
}
});
}
catch (Exception ex)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
container.Children.Add(new Label
{
Text = $"Ошибка загрузки: {ex.Message}",
TextColor = Color.FromArgb("#FF0000"),
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 50)
});
});
}
}
private View CreatePostCard(CORP_MAUI.Models.Post post)
{
var card = new Border
{
BackgroundColor = Colors.White,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 12 },
Stroke = Colors.Transparent,
Padding = new Thickness(20),
Shadow = new Shadow
{
Brush = new SolidColorBrush(Colors.Black),
Opacity = 0.1f,
Offset = new Point(0, 2),
Radius = 8
},
HorizontalOptions = LayoutOptions.Fill
};
var content = new VerticalStackLayout { Spacing = 12 };
// Автор и дата
var authorRow = new HorizontalStackLayout { Spacing = 10 };
var avatarImage = new Image
{
Source = post.Author?.DisplayAvatarSource,
Aspect = Aspect.AspectFill,
HeightRequest = 40,
WidthRequest = 40
};
var avatar = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 20 },
HeightRequest = 40,
WidthRequest = 40,
BackgroundColor = Color.FromArgb("#E0E0E0"),
Stroke = Colors.Transparent,
Content = avatarImage
};
var authorInfo = new VerticalStackLayout();
authorInfo.Children.Add(new Label
{
Text = post.Author?.FullName ?? "Автор",
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#333333")
});
authorInfo.Children.Add(new Label
{
Text = post.CreatedAt.ToString("dd.MM.yyyy HH:mm"),
FontSize = 12,
TextColor = Color.FromArgb("#888888")
});
authorRow.Children.Add(avatar);
authorRow.Children.Add(authorInfo);
content.Children.Add(authorRow);
// Изображение поста (если есть)
if (!string.IsNullOrEmpty(post.DisplayImageUrl))
{
var imageContainer = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
Stroke = Colors.Transparent,
HeightRequest = 300,
Margin = new Thickness(0, 10)
};
var postImage = new Image
{
Source = post.DisplayImageUrl,
Aspect = Aspect.AspectFill,
HeightRequest = 300,
HorizontalOptions = LayoutOptions.Fill
};
imageContainer.Content = postImage;
content.Children.Add(imageContainer);
}
// Заголовок
if (!string.IsNullOrEmpty(post.Title))
{
content.Children.Add(new Label
{
Text = post.Title,
FontSize = 18,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#0D6EFD")
});
}
// Текст
if (!string.IsNullOrEmpty(post.Content))
{
var isTruncated = post.Content.Length > 200;
var textContent = isTruncated
? post.Content.Substring(0, 200) + "..."
: post.Content;
var textRow = new HorizontalStackLayout { Spacing = 5 };
textRow.Children.Add(new Label
{
Text = textContent,
TextColor = Color.FromArgb("#333333"),
LineBreakMode = LineBreakMode.WordWrap,
HorizontalOptions = LayoutOptions.FillAndExpand
});
if (isTruncated)
{
textRow.Children.Add(new Label
{
Text = "Показать дальше",
TextColor = Color.FromArgb("#0D6EFD"),
FontSize = 14,
VerticalOptions = LayoutOptions.End
});
}
content.Children.Add(textRow);
}
// Лайки и комментарии
var statsRow = new HorizontalStackLayout { Spacing = 20, Margin = new Thickness(0, 10, 0, 0) };
statsRow.Children.Add(new HorizontalStackLayout
{
Spacing = 5,
Children =
{
new Label { Text = "👍", FontSize = 14 },
new Label { Text = post.LikesCount.ToString(), TextColor = Color.FromArgb("#888888") }
}
});
statsRow.Children.Add(new HorizontalStackLayout
{
Spacing = 5,
Children =
{
new Label { Text = "💬", FontSize = 14 },
new Label { Text = post.CommentsCount.ToString(), TextColor = Color.FromArgb("#888888") }
}
});
content.Children.Add(statsRow);
card.Content = content;
return card;
}
public void SetContent(View content)
{
MainContent.Content = content;
}
private void OnNavigationRequested(object? sender, string route)
{
switch (route)
{
case "Profile":
LoadProfileContent();
break;
case "News":
LoadDefaultContent();
break;
case "Employees":
LoadEmployeesContent();
break;
case "Departments":
LoadDepartmentsContent();
break;
case "Requests":
LoadRequestsContent();
break;
case "Chats":
LoadChatsContent();
break;
case "Documents":
LoadDocumentsContent();
break;
}
}
private void LoadProfileContent()
{
var content = new VerticalStackLayout { Spacing = 15 };
content.Children.Add(CreatePageHeader("Мой профиль"));
var loadingIndicator = new ActivityIndicator
{
IsRunning = true,
Color = Color.FromArgb("#0D6EFD"),
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 50)
};
content.Children.Add(loadingIndicator);
SetContent(content);
_ = LoadProfileAsync(content);
}
private async Task LoadProfileAsync(VerticalStackLayout container)
{
try
{
var user = await ServiceProvider.ApiService.GetCurrentUserAsync();
// Предзагружаем аватар
if (user != null)
{
await ServiceProvider.ApiService.LoadUserAvatarAsync(user);
}
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
container.Children.Add(CreatePageHeader("Мой профиль"));
if (user == null)
{
container.Children.Add(new Label { Text = "Не удалось загрузить профиль", TextColor = Colors.Red });
return;
}
var card = new Border
{
BackgroundColor = Colors.White,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 12 },
Stroke = Colors.Transparent,
Padding = new Thickness(30),
Shadow = new Shadow { Brush = new SolidColorBrush(Colors.Black), Opacity = 0.1f, Offset = new Point(0, 2), Radius = 8 }
};
var profileContent = new HorizontalStackLayout { Spacing = 30 };
// Аватар
var avatarImage = new Image
{
Source = user.DisplayAvatarSource,
Aspect = Aspect.AspectFill,
HeightRequest = 120,
WidthRequest = 120
};
var avatarBorder = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 60 },
HeightRequest = 120,
WidthRequest = 120,
BackgroundColor = Color.FromArgb("#E0E0E0"),
Stroke = Colors.Transparent,
Content = avatarImage
};
// Индикатор онлайн
if (user.IsOnline)
{
var onlineIndicator = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
HeightRequest = 16,
WidthRequest = 16,
BackgroundColor = Color.FromArgb("#28A745"),
Stroke = Colors.White,
StrokeThickness = 2,
VerticalOptions = LayoutOptions.End,
HorizontalOptions = LayoutOptions.End,
Margin = new Thickness(0, 0, 8, 8)
};
var avatarGrid = new Grid { Children = { avatarBorder, onlineIndicator } };
profileContent.Children.Add(avatarGrid);
}
else
{
profileContent.Children.Add(avatarBorder);
}
// Информация о пользователе
var infoStack = new VerticalStackLayout { Spacing = 10 };
infoStack.Children.Add(new Label { Text = user.FullName, FontSize = 24, FontAttributes = FontAttributes.Bold, TextColor = Color.FromArgb("#333333") });
if (!string.IsNullOrEmpty(user.PositionName))
infoStack.Children.Add(new Label { Text = user.PositionName, FontSize = 16, TextColor = Color.FromArgb("#0D6EFD") });
infoStack.Children.Add(new Label { Text = user.Email, FontSize = 14, TextColor = Color.FromArgb("#666666") });
if (!string.IsNullOrEmpty(user.PhoneNumber))
infoStack.Children.Add(new Label { Text = user.PhoneNumber, FontSize = 14, TextColor = Color.FromArgb("#666666") });
// Отделы
if (user.Departments != null && user.Departments.Any())
{
infoStack.Children.Add(new Label { Text = "Отделы:", FontSize = 13, TextColor = Color.FromArgb("#888888"), Margin = new Thickness(0, 10, 0, 0) });
foreach (var dept in user.Departments)
{
infoStack.Children.Add(new Label { Text = $"• {dept.Name}", FontSize = 14, TextColor = Color.FromArgb("#404040") });
}
}
// Навыки
if (user.Skills != null && user.Skills.Any())
{
infoStack.Children.Add(new Label { Text = "Навыки:", FontSize = 13, TextColor = Color.FromArgb("#888888"), Margin = new Thickness(0, 10, 0, 0) });
var skillsLayout = new FlexLayout { Wrap = Microsoft.Maui.Layouts.FlexWrap.Wrap, JustifyContent = Microsoft.Maui.Layouts.FlexJustify.Start };
foreach (var skill in user.Skills)
{
var skillBadge = new Border
{
Padding = new Thickness(10, 5),
Margin = new Thickness(0, 5, 8, 0),
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 12 },
BackgroundColor = Color.FromArgb("#E8F4FD"),
Content = new Label { Text = skill.Name, FontSize = 12, TextColor = Color.FromArgb("#0D6EFD") }
};
skillsLayout.Children.Add(skillBadge);
}
infoStack.Children.Add(skillsLayout);
}
profileContent.Children.Add(infoStack);
card.Content = profileContent;
container.Children.Add(card);
});
}
catch (Exception ex)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
container.Children.Add(new Label { Text = $"Ошибка: {ex.Message}", TextColor = Colors.Red });
});
}
}
private void LoadEmployeesContent()
{
var content = new VerticalStackLayout { Spacing = 15 };
content.Children.Add(CreatePageHeader("Сотрудники"));
var container = new VerticalStackLayout { Spacing = 10 };
container.Children.Add(new ActivityIndicator { IsRunning = true, Color = Color.FromArgb("#0D6EFD"), HorizontalOptions = LayoutOptions.Center, Margin = new Thickness(0, 50) });
// Устанавливаем FillAndExpand чтобы контейнер занял пространство внутри MainContent
content.Children.Add(new ScrollView { Content = container, VerticalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill });
SetContent(content);
_ = LoadEmployeesAsync(container);
}
private async Task LoadEmployeesAsync(VerticalStackLayout container)
{
try
{
var response = await ServiceProvider.ApiService.GetEmployeesPaginatedAsync();
var employees = response?.Results;
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
if (employees == null || !employees.Any())
{
container.Children.Add(new Label { Text = "Нет сотрудников", TextColor = Color.FromArgb("#888888"), HorizontalOptions = LayoutOptions.Center });
return;
}
foreach (var emp in employees)
{
var card = new Border
{
BackgroundColor = Colors.White,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
Stroke = Colors.Transparent,
Padding = new Thickness(15),
Margin = new Thickness(0, 0, 0, 5),
HorizontalOptions = LayoutOptions.Fill
};
var row = new HorizontalStackLayout { Spacing = 15 };
// Аватарка сотрудника
var avatarImage = new Image
{
Source = emp.DisplayAvatarSource,
Aspect = Aspect.AspectFill,
HeightRequest = 40,
WidthRequest = 40
};
var avatarBorder = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 20 },
HeightRequest = 40,
WidthRequest = 40,
BackgroundColor = Color.FromArgb("#E0E0E0"),
Stroke = Colors.Transparent,
Content = avatarImage
};
row.Children.Add(avatarBorder);
var info = new VerticalStackLayout();
info.Children.Add(new Label { Text = emp.FullName, FontAttributes = FontAttributes.Bold, TextColor = Color.FromArgb("#333333") });
info.Children.Add(new Label { Text = emp.PositionName ?? emp.Email, FontSize = 12, TextColor = Color.FromArgb("#888888") });
row.Children.Add(info);
card.Content = row;
// Добавляем обработчик нажатия для открытия профиля сотрудника
var tapGesture = new TapGestureRecognizer();
int employeeId = emp.Id; // Захватываем ID для замыкания
tapGesture.Tapped += async (_, _) =>
{
try
{
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] >>> Открытие профиля сотрудника ID: {employeeId}");
var nav = ResolveNavigation();
if (nav != null)
{
await nav.PushAsync(new CORP_MAUI.Pages.EmployeeProfilePage(employeeId));
return;
}
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ❌ Не удалось найти навигацию для открытия профиля {employeeId}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ❌ Ошибка навигации к профилю: {ex.Message}");
}
};
card.GestureRecognizers.Add(tapGesture);
container.Children.Add(card);
}
});
}
catch (Exception ex)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
container.Children.Add(new Label { Text = $"Ошибка: {ex.Message}", TextColor = Colors.Red });
});
}
}
private void LoadDepartmentsContent()
{
// Заголовок с количеством и поиском
var headerRow = new Grid
{
ColumnDefinitions = new ColumnDefinitionCollection
{
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto)
},
Margin = new Thickness(0, 0, 0, 20)
};
var titleStack = new VerticalStackLayout();
titleStack.Children.Add(new Label { Text = "Отделы", FontSize = 24, FontAttributes = FontAttributes.Bold, TextColor = Color.FromArgb("#333333") });
var countLabel = new Label { Text = "Всего отделов: ...", FontSize = 13, TextColor = Color.FromArgb("#888888") };
titleStack.Children.Add(countLabel);
Grid.SetColumn(titleStack, 0);
headerRow.Children.Add(titleStack);
// Поисковая строка
var searchBox = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 20 },
BackgroundColor = Color.FromArgb("#F5F5F5"),
Stroke = Color.FromArgb("#E0E0E0"),
StrokeThickness = 1,
Padding = new Thickness(15, 8),
MaximumWidthRequest = 300,
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
};
var searchLayout = new HorizontalStackLayout { Spacing = 10 };
searchLayout.Children.Add(new Image { Source = "search", HeightRequest = 16, WidthRequest = 16 });
searchLayout.Children.Add(new Entry { Placeholder = "Поиск по названию, автору", PlaceholderColor = Color.FromArgb("#888888"), BackgroundColor = Colors.Transparent, WidthRequest = 200 });
searchBox.Content = searchLayout;
Grid.SetColumn(searchBox, 2);
headerRow.Children.Add(searchBox);
// Контейнер для сетки отделов с прокруткой
var gridContainer = new VerticalStackLayout { Spacing = 12, VerticalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill };
// Добавляем ActivityIndicator пока загружается
var activityIndicator = new ActivityIndicator
{
IsRunning = true,
Color = Color.FromArgb("#0D6EFD"),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 50)
};
gridContainer.Children.Add(activityIndicator);
// Используем ScrollView вокруг контейнера (и обеспечиваем FillAndExpand у scrollView)
var scrollView = new ScrollView
{
Content = gridContainer,
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Fill
};
// Основной контейнер
var content = new Grid
{
RowDefinitions = new RowDefinitionCollection
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }
},
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Fill
};
Grid.SetRow(headerRow, 0);
content.Children.Add(headerRow);
Grid.SetRow(scrollView, 1);
content.Children.Add(scrollView);
SetContent(content);
_ = LoadDepartmentsAsync(gridContainer, countLabel);
}
private async Task LoadDepartmentsAsync(VerticalStackLayout container, Label countLabel)
{
try
{
System.Diagnostics.Debug.WriteLine("[DesktopLayout] >>> Начало загрузки отделов");
var departments = await ServiceProvider.ApiService.GetAllDepartmentsAsync();
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] Получено отделов: {departments?.Count ?? 0}");
await MainThread.InvokeOnMainThreadAsync(() =>
{
System.Diagnostics.Debug.WriteLine("[DesktopLayout] Очистка контейнера...");
container.Children.Clear();
if (departments == null || !departments.Any())
{
System.Diagnostics.Debug.WriteLine("[DesktopLayout] ⚠️ Нет отделов для отображения");
var emptyLabel = new Label { Text = "Нет отделов", TextColor = Color.FromArgb("#888888"), HorizontalOptions = LayoutOptions.Center };
container.Children.Add(emptyLabel);
return;
}
countLabel.Text = $"Всего отделов: {departments.Count}";
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] Создание {departments.Count} карточек отделов...");
// Для корректной прокрутки и размеров используем Grid с равномерными колонками (Star) и поместим его в ScrollView
var grid = new Grid
{
ColumnDefinitions = new ColumnDefinitionCollection
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
},
ColumnSpacing = 16,
RowSpacing = 16,
HorizontalOptions = LayoutOptions.Fill
};
// Добавляем карточки в Grid
for (int i = 0; i < departments.Count; i++)
{
var card = CreateDepartmentCard(departments[i]);
int row = i / 2;
int col = i % 2;
// Добавляем строку если нужно
if (row >= grid.RowDefinitions.Count)
{
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
}
Grid.SetRow(card, row);
Grid.SetColumn(card, col);
grid.Children.Add(card);
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ✅ Добавлена карточка {i + 1} в позицию [{row}, {col}]");
}
// Оборачиваем grid в контейнер, даём ему FillAndExpand чтобы прокрутка работала корректно
grid.HorizontalOptions = LayoutOptions.Fill;
container.Children.Add(grid);
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ✅ Grid создан с {grid.RowDefinitions.Count} строками");
// Загружаем аватарки сотрудников для каждого отдела
foreach (var dept in departments)
{
_ = LoadDepartmentEmployeesAsync(dept);
}
});
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ❌ Ошибка загрузки отделов: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] Stack trace: {ex.StackTrace}");
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
var errorLabel = new Label { Text = $"Ошибка: {ex.Message}", TextColor = Colors.Red };
container.Children.Add(errorLabel);
});
}
}
private View CreateDepartmentCard(CORP_MAUI.Models.Department dept)
{
var card = new Border
{
BackgroundColor = Colors.White,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 12 },
Stroke = Color.FromArgb("#E0E0E0"),
StrokeThickness = 1,
Padding = new Thickness(16),
Margin = new Thickness(0),
HorizontalOptions = LayoutOptions.Fill
};
var content = new VerticalStackLayout { Spacing = 8 };
// Название отдела
content.Children.Add(new Label
{
Text = dept.Name,
FontSize = 15,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#333333"),
LineBreakMode = LineBreakMode.TailTruncation,
MaxLines = 1
});
// Руководитель
content.Children.Add(new Label
{
Text = "Руководитель:",
FontSize = 11,
TextColor = Color.FromArgb("#888888")
});
string headName = dept.Head?.FullName ?? "Не назначен";
content.Children.Add(new Label
{
Text = headName,
FontSize = 12,
TextColor = Color.FromArgb("#0D6EFD"),
FontAttributes = FontAttributes.None,
LineBreakMode = LineBreakMode.TailTruncation,
MaxLines = 1
});
// Количество сотрудников
content.Children.Add(new Label
{
Text = $"{dept.EmployeesCount} сотрудников",
FontSize = 11,
TextColor = Color.FromArgb("#666666"),
Margin = new Thickness(0, 4, 0, 4)
});
// Аватарки сотрудников (серые кружки, которые потом заменятся на реальные)
var avatarsRow = new HorizontalStackLayout { Spacing = -8, HorizontalOptions = LayoutOptions.Start };
// Сохраняем ссылку на контейнер аватарок в свойстве отдела для последующего обновления
dept.AvatarsContainer = avatarsRow;
int avatarCount = Math.Min(dept.EmployeesCount, 5);
for (int i = 0; i < avatarCount; i++)
{
var avatar = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 15 },
HeightRequest = 30,
WidthRequest = 30,
BackgroundColor = Color.FromArgb("#E0E0E0"),
Stroke = Colors.White,
StrokeThickness = 2
};
avatarsRow.Children.Add(avatar);
}
content.Children.Add(avatarsRow);
card.Content = content;
// Добавляем обработчик нажатия на карточку для навигации
var tapGesture = new TapGestureRecognizer();
int deptId = dept.Id; // Захватываем ID для замыкания
tapGesture.Tapped += async (_, _) =>
{
try
{
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] >>> Открытие отдела ID: {deptId}");
var nav = ResolveNavigation();
if (nav != null)
{
await nav.PushAsync(new CORP_MAUI.Pages.DepartmentDetailsPage(deptId));
return;
}
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ❌ Не удалось найти навигацию для открытия отдела {deptId}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] ❌ Ошибка навигации к отделу: {ex.Message}");
}
};
card.GestureRecognizers.Add(tapGesture);
return card;
}
private async Task LoadDepartmentEmployeesAsync(CORP_MAUI.Models.Department dept)
{
try
{
// Получаем сотрудников отдела
var employees = await ServiceProvider.ApiService.GetDepartmentEmployeesAsync(dept.Id);
if (employees != null && employees.Any())
{
// Обновляем аватарки в UI
await MainThread.InvokeOnMainThreadAsync(() =>
{
// Найдем карточку отдела и обновим аватарки
if (dept.AvatarsContainer != null)
{
dept.AvatarsContainer.Children.Clear();
int avatarCount = Math.Min(employees.Count, 5);
for (int i = 0; i < avatarCount; i++)
{
var emp = employees[i];
var avatarImage = new Image
{
Source = emp.DisplayAvatarSource,
Aspect = Aspect.AspectFill,
HeightRequest = 30,
WidthRequest = 30
};
var avatar = new Border
{
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 15 },
HeightRequest = 30,
WidthRequest = 30,
BackgroundColor = Color.FromArgb("#E0E0E0"),
Stroke = Colors.White,
StrokeThickness = 2,
Content = avatarImage
};
dept.AvatarsContainer.Children.Add(avatar);
}
}
});
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DesktopLayout] Ошибка загрузки сотрудников отдела {dept.Id}: {ex.Message}");
}
}
private void LoadRequestsContent()
{
var content = new VerticalStackLayout { Spacing = 15 };
content.Children.Add(CreatePageHeader("Заявления"));
var container = new VerticalStackLayout { Spacing = 10 };
container.Children.Add(new ActivityIndicator { IsRunning = true, Color = Color.FromArgb("#0D6EFD"), HorizontalOptions = LayoutOptions.Center, Margin = new Thickness(0, 50) });
content.Children.Add(new ScrollView { Content = container });
SetContent(content);
_ = LoadRequestsAsync(container);
}
private async Task LoadRequestsAsync(VerticalStackLayout container)
{
try
{
var response = await ServiceProvider.ApiService.GetRequestsPaginatedAsync();
var requests = response?.Results;
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
if (requests == null || !requests.Any())
{
container.Children.Add(new Label { Text = "Нет заявлений", TextColor = Color.FromArgb("#888888"), HorizontalOptions = LayoutOptions.Center });
return;
}
foreach (var req in requests)
{
var statusColor = req.Status switch
{
"approved" => Color.FromArgb("#28A745"),
"rejected" => Color.FromArgb("#DC3545"),
_ => Color.FromArgb("#FFC107")
};
var card = new Border
{
BackgroundColor = Colors.White,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
Stroke = Colors.Transparent,
Padding = new Thickness(15),
Margin = new Thickness(0, 0, 0, 5)
};
var info = new VerticalStackLayout { Spacing = 5 };
info.Children.Add(new Label { Text = req.DisplayTitle ?? req.Title, FontSize = 16, FontAttributes = FontAttributes.Bold, TextColor = Color.FromArgb("#333333") });
info.Children.Add(new Label { Text = req.Employee?.FullName ?? "", FontSize = 13, TextColor = Color.FromArgb("#666666") });
var statusLabel = new Label { Text = req.DisplayStatus ?? req.Status, FontSize = 12, TextColor = statusColor };
info.Children.Add(statusLabel);
card.Content = info;
container.Children.Add(card);
}
});
}
catch (Exception ex)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
container.Children.Add(new Label { Text = $"Ошибка: {ex.Message}", TextColor = Colors.Red });
});
}
}
private void LoadChatsContent()
{
var content = new VerticalStackLayout { Spacing = 15 };
content.Children.Add(CreatePageHeader("Чаты"));
var placeholder = new VerticalStackLayout
{
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 100)
};
placeholder.Children.Add(new Label { Text = "💬", FontSize = 48, HorizontalOptions = LayoutOptions.Center });
placeholder.Children.Add(new Label { Text = "Раздел чатов находится в разработке", FontSize = 16, TextColor = Color.FromArgb("#888888"), HorizontalOptions = LayoutOptions.Center });
content.Children.Add(placeholder);
SetContent(content);
}
private void LoadDocumentsContent()
{
var content = new VerticalStackLayout { Spacing = 15 };
content.Children.Add(CreatePageHeader("Документы"));
var container = new VerticalStackLayout { Spacing = 10 };
container.Children.Add(new ActivityIndicator { IsRunning = true, Color = Color.FromArgb("#0D6EFD"), HorizontalOptions = LayoutOptions.Center, Margin = new Thickness(0, 50) });
content.Children.Add(new ScrollView { Content = container });
SetContent(content);
_ = LoadDocumentsAsync(container);
}
private async Task LoadDocumentsAsync(VerticalStackLayout container)
{
try
{
var documents = await ServiceProvider.ApiService.GetAllDocumentsAsync();
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
if (documents == null || !documents.Any())
{
var placeholder = new VerticalStackLayout
{
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 50)
};
placeholder.Children.Add(new Label { Text = "📄", FontSize = 48, HorizontalOptions = LayoutOptions.Center });
placeholder.Children.Add(new Label { Text = "Нет документов для отображения", FontSize = 16, TextColor = Color.FromArgb("#888888"), HorizontalOptions = LayoutOptions.Center });
container.Children.Add(placeholder);
return;
}
foreach (var doc in documents)
{
var statusColor = doc.IsAcknowledged
? Color.FromArgb("#28A745") // Зеленый - ознакомлен
: Color.FromArgb("#FFC107"); // Желтый - не ознакомлен
var card = new Border
{
BackgroundColor = Colors.White,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
Stroke = Colors.Transparent,
Padding = new Thickness(15),
Margin = new Thickness(0, 0, 0, 5),
Shadow = new Shadow { Brush = new SolidColorBrush(Colors.Black), Opacity = 0.05f, Offset = new Point(0, 2), Radius = 4 }
};
var row = new Grid
{
ColumnDefinitions = new ColumnDefinitionCollection
{
new ColumnDefinition(new GridLength(50)),
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto)
}
};
// Иконка документа
var iconBorder = new Border
{
WidthRequest = 40,
HeightRequest = 40,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
BackgroundColor = Color.FromArgb("#E8F0FE"),
Stroke = Colors.Transparent,
VerticalOptions = LayoutOptions.Center
};
iconBorder.Content = new Label
{
Text = doc.FileIcon,
FontSize = 20,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
Grid.SetColumn(iconBorder, 0);
row.Children.Add(iconBorder);
// Информация о документе
var info = new VerticalStackLayout { Spacing = 3, VerticalOptions = LayoutOptions.Center };
info.Children.Add(new Label { Text = doc.Title, FontSize = 15, FontAttributes = FontAttributes.Bold, TextColor = Color.FromArgb("#333333"), LineBreakMode = LineBreakMode.TailTruncation, MaxLines = 1 });
info.Children.Add(new Label { Text = doc.ShortDescription, FontSize = 12, TextColor = Color.FromArgb("#666666"), LineBreakMode = LineBreakMode.TailTruncation, MaxLines = 1 });
var detailsRow = new HorizontalStackLayout { Spacing = 10 };
detailsRow.Children.Add(new Label { Text = $"👤 {doc.UploaderName}", FontSize = 11, TextColor = Color.FromArgb("#888888") });
detailsRow.Children.Add(new Label { Text = "•", FontSize = 11, TextColor = Color.FromArgb("#CCCCCC") });
detailsRow.Children.Add(new Label { Text = doc.DisplayUploadedAt, FontSize = 11, TextColor = Color.FromArgb("#888888") });
info.Children.Add(detailsRow);
Grid.SetColumn(info, 1);
row.Children.Add(info);
// Статус ознакомления
var statusBadge = new Border
{
BackgroundColor = statusColor,
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 10 },
Stroke = Colors.Transparent,
Padding = new Thickness(10, 5),
VerticalOptions = LayoutOptions.Center
};
statusBadge.Content = new Label
{
Text = doc.AcknowledgedText,
FontSize = 11,
TextColor = Colors.White
};
Grid.SetColumn(statusBadge, 2);
row.Children.Add(statusBadge);
card.Content = row;
// Обработчик нажатия - открытие документа
var tapGesture = new TapGestureRecognizer();
tapGesture.Tapped += async (s, e) =>
{
if (!string.IsNullOrEmpty(doc.FileUrl))
{
try
{
await Launcher.OpenAsync(new Uri(doc.FileUrl));
}
catch (Exception ex)
{
if (Application.Current?.Windows.Count > 0)
await Application.Current.Windows[0].Page!.DisplayAlert("Ошибка", $"Не удалось открыть документ: {ex.Message}", "OK");
}
}
};
card.GestureRecognizers.Add(tapGesture);
container.Children.Add(card);
}
});
}
catch (Exception ex)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
container.Children.Clear();
container.Children.Add(new Label { Text = $"Ошибка загрузки документов: {ex.Message}", TextColor = Colors.Red, HorizontalOptions = LayoutOptions.Center });
});
}
}
private View CreatePageHeader(string title, string? buttonText = null)
{
var header = new Grid
{
ColumnDefinitions = new ColumnDefinitionCollection
{
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto)
},
Margin = new Thickness(0, 0, 0, 20)
};
var titleLabel = new Label
{
Text = title,
FontSize = 24,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#333333"),
VerticalOptions = LayoutOptions.Center
};
Grid.SetColumn(titleLabel, 0);
header.Children.Add(titleLabel);
if (!string.IsNullOrEmpty(buttonText))
{
var addButton = new HorizontalStackLayout { Spacing = 8, VerticalOptions = LayoutOptions.Center };
addButton.Children.Add(new Label { Text = "+", FontSize = 20, TextColor = Color.FromArgb("#0D6EFD"), VerticalOptions = LayoutOptions.Center });
addButton.Children.Add(new Label { Text = buttonText, FontSize = 16, TextColor = Color.FromArgb("#0D6EFD"), VerticalOptions = LayoutOptions.Center });
Grid.SetColumn(addButton, 2);
header.Children.Add(addButton);
}
return header;
}
private INavigation? ResolveNavigation()
{
// Попробуем найти Page, обойдя родителей
Element? el = this;
while (el != null)
{
if (el is Page p)
return p.Navigation;
el = el.Parent;
}
// Application.Current.MainPage может быть Shell или NavigationPage
if (Application.Current?.MainPage?.Navigation != null)
return Application.Current.MainPage.Navigation;
// Shell.Current.Navigation
if (Shell.Current?.Navigation != null)
return Shell.Current.Navigation;
// Попробуем взять первую открытую Window
if (Application.Current?.Windows != null && Application.Current.Windows.Count > 0)
return Application.Current.Windows[0].Page?.Navigation;
return null;
}
}