From c1de303c22aaf5dfc99bad23e523596f8ff64c82 Mon Sep 17 00:00:00 2001 From: Danil Iarosh Date: Wed, 21 Jan 2026 14:49:26 +0300 Subject: [PATCH] Add ChatsPage and Desktop layout components: Implement initial ChatsPage UI, update AppShell for navigation, and create DesktopHeader and DesktopSidebar for desktop layout. --- .idea/.idea.CORP-MAUI/.idea/deviceManager.xml | 2 +- CORP-MAUI.sln.DotSettings.user | 1 + CORP-MAUI/App.xaml.cs | 24 +- CORP-MAUI/AppShell.xaml | 24 +- CORP-MAUI/AppShell.xaml.cs | 118 +- CORP-MAUI/CORP-MAUI.csproj | 7 +- CORP-MAUI/Controls/DesktopCalendarPanel.xaml | 96 ++ .../Controls/DesktopCalendarPanel.xaml.cs | 101 ++ CORP-MAUI/Controls/DesktopHeader.xaml | 94 ++ CORP-MAUI/Controls/DesktopHeader.xaml.cs | 42 + CORP-MAUI/Controls/DesktopLayout.xaml | 29 + CORP-MAUI/Controls/DesktopLayout.xaml.cs | 1189 +++++++++++++++++ CORP-MAUI/Controls/DesktopSidebar.xaml | 152 +++ CORP-MAUI/Controls/DesktopSidebar.xaml.cs | 100 ++ CORP-MAUI/MauiProgram.cs | 3 +- CORP-MAUI/Models/Department.cs | 6 + CORP-MAUI/Models/Document.cs | 137 +- CORP-MAUI/Pages/ChatPage.xaml | 2 +- CORP-MAUI/Pages/ChatsPage.xaml | 79 ++ CORP-MAUI/Pages/ChatsPage.xaml.cs | 9 + CORP-MAUI/Pages/DepartmentDetailsPage.xaml | 2 +- CORP-MAUI/Pages/DepartmentDetailsPage.xaml.cs | 45 +- CORP-MAUI/Pages/DepartmentsPage.xaml | 107 +- CORP-MAUI/Pages/DepartmentsPage.xaml.cs | 331 +++-- CORP-MAUI/Pages/DesktopMainPage.xaml | 12 + CORP-MAUI/Pages/DesktopMainPage.xaml.cs | 21 + CORP-MAUI/Pages/DocumentsPage.xaml | 268 ++++ CORP-MAUI/Pages/DocumentsPage.xaml.cs | 272 ++++ CORP-MAUI/Pages/EmployeeProfilePage.xaml | 2 +- CORP-MAUI/Pages/EmployeesPage.xaml | 2 +- CORP-MAUI/Pages/LoginPage.xaml | 68 +- CORP-MAUI/Pages/LoginPage.xaml.cs | 16 + CORP-MAUI/Pages/MenuPage.xaml | 392 ++++++ CORP-MAUI/Pages/MenuPage.xaml.cs | 168 +++ CORP-MAUI/Pages/MenuTabPage.xaml | 11 + CORP-MAUI/Pages/MenuTabPage.xaml.cs | 30 + CORP-MAUI/Pages/PostDetailsPage.xaml | 4 +- CORP-MAUI/Pages/PostsPage.xaml | 4 +- CORP-MAUI/Pages/PostsPage.xaml.cs | 4 +- CORP-MAUI/Pages/ProfilePage.xaml | 43 +- CORP-MAUI/Pages/RequestsPage.xaml | 8 +- .../Android/Resources/values/styles.xml | 19 + .../Resources/Images/Icons/SVG/cross.svg | 6 + CORP-MAUI/Services/ApiService.cs | 57 + CORP-MAUI/ViewModels/ProfileViewModel.cs | 8 + 45 files changed, 3825 insertions(+), 290 deletions(-) create mode 100644 CORP-MAUI/Controls/DesktopCalendarPanel.xaml create mode 100644 CORP-MAUI/Controls/DesktopCalendarPanel.xaml.cs create mode 100644 CORP-MAUI/Controls/DesktopHeader.xaml create mode 100644 CORP-MAUI/Controls/DesktopHeader.xaml.cs create mode 100644 CORP-MAUI/Controls/DesktopLayout.xaml create mode 100644 CORP-MAUI/Controls/DesktopLayout.xaml.cs create mode 100644 CORP-MAUI/Controls/DesktopSidebar.xaml create mode 100644 CORP-MAUI/Controls/DesktopSidebar.xaml.cs create mode 100644 CORP-MAUI/Pages/ChatsPage.xaml create mode 100644 CORP-MAUI/Pages/ChatsPage.xaml.cs create mode 100644 CORP-MAUI/Pages/DesktopMainPage.xaml create mode 100644 CORP-MAUI/Pages/DesktopMainPage.xaml.cs create mode 100644 CORP-MAUI/Pages/DocumentsPage.xaml create mode 100644 CORP-MAUI/Pages/DocumentsPage.xaml.cs create mode 100644 CORP-MAUI/Pages/MenuPage.xaml create mode 100644 CORP-MAUI/Pages/MenuPage.xaml.cs create mode 100644 CORP-MAUI/Pages/MenuTabPage.xaml create mode 100644 CORP-MAUI/Pages/MenuTabPage.xaml.cs create mode 100644 CORP-MAUI/Platforms/Android/Resources/values/styles.xml create mode 100644 CORP-MAUI/Resources/Images/Icons/SVG/cross.svg diff --git a/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml b/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml index 91f9558..eebfe8e 100644 --- a/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml +++ b/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml @@ -5,7 +5,7 @@ diff --git a/CORP-MAUI.sln.DotSettings.user b/CORP-MAUI.sln.DotSettings.user index fcfc28a..6c09311 100644 --- a/CORP-MAUI.sln.DotSettings.user +++ b/CORP-MAUI.sln.DotSettings.user @@ -7,6 +7,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/CORP-MAUI/App.xaml.cs b/CORP-MAUI/App.xaml.cs index 2646859..d8a94f0 100644 --- a/CORP-MAUI/App.xaml.cs +++ b/CORP-MAUI/App.xaml.cs @@ -1,4 +1,4 @@ -using CORP_MAUI.Pages; +using CORP_MAUI.Pages; using CORP_MAUI.Models; using CORP_MAUI.Services; using System.Diagnostics; @@ -136,12 +136,30 @@ public partial class App : Application // Продолжаем работу без WebSocket - это не критично } - // Переходим сразу на главную страницу (AppShell) - Trace.WriteLine("[App] >>> Navigating to AppShell"); + // Переходим сразу на главную страницу + // На Windows/Desktop используем DesktopMainPage, на мобильных - AppShell + Trace.WriteLine("[App] >>> Navigating to main page"); await MainThread.InvokeOnMainThreadAsync(() => { +#if WINDOWS || MACCATALYST + // На десктопе проверяем ширину экрана + double screenWidth = DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density; + Trace.WriteLine($"[App] Screen width: {screenWidth}"); + + if (screenWidth >= 768) + { + window.Page = new DesktopMainPage(); + Trace.WriteLine("[App] ✅ Navigation to DesktopMainPage completed"); + } + else + { + window.Page = new AppShell(); + Trace.WriteLine("[App] ✅ Navigation to AppShell completed"); + } +#else window.Page = new AppShell(); Trace.WriteLine("[App] ✅ Navigation to AppShell completed"); +#endif }); } else diff --git a/CORP-MAUI/AppShell.xaml b/CORP-MAUI/AppShell.xaml index 2f5acfa..55015d3 100644 --- a/CORP-MAUI/AppShell.xaml +++ b/CORP-MAUI/AppShell.xaml @@ -7,8 +7,8 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" Shell.TabBarBackgroundColor="#0D6EFD" Shell.TabBarForegroundColor="White" - Shell.TabBarTitleColor="White" - Shell.TabBarUnselectedColor="#FFFFFF99" + Shell.TabBarTitleColor="Transparent" + Shell.TabBarUnselectedColor="White" FlyoutBackgroundColor="#0D6EFD" FlyoutWidth="220" Shell.NavBarIsVisible="False" @@ -59,12 +59,14 @@ + + - - - - - + + + + + @@ -80,10 +82,18 @@ + + + + + + + + diff --git a/CORP-MAUI/AppShell.xaml.cs b/CORP-MAUI/AppShell.xaml.cs index b308f2a..187190b 100644 --- a/CORP-MAUI/AppShell.xaml.cs +++ b/CORP-MAUI/AppShell.xaml.cs @@ -8,12 +8,13 @@ public partial class AppShell : Shell // Порог ширины для переключения на вертикальное меню (в пикселях) private const double WideScreenThreshold = 768; private bool _isDesktopMode; + private int _lastNonMenuTabIndex = 2; // По умолчанию - Лента public AppShell() { try { - Trace.WriteLine("=== APPSHELL CONSTRUCTOR START ==="); + Trace.WriteLine("=== APP SHELL CONSTRUCTOR START ==="); Trace.WriteLine("[AppShell] >>> Вызов InitializeComponent"); InitializeComponent(); @@ -25,19 +26,32 @@ public partial class AppShell : Shell Routing.RegisterRoute(nameof(ChatPage), typeof(ChatPage)); Routing.RegisterRoute(nameof(DepartmentDetailsPage), typeof(DepartmentDetailsPage)); Routing.RegisterRoute(nameof(EmployeeProfilePage), typeof(EmployeeProfilePage)); + Routing.RegisterRoute(nameof(MenuPage), typeof(MenuPage)); + Routing.RegisterRoute(nameof(DepartmentsPage), typeof(DepartmentsPage)); + Routing.RegisterRoute(nameof(ProfilePage), typeof(ProfilePage)); + Routing.RegisterRoute(nameof(DocumentsPage), typeof(DocumentsPage)); + + // Подписываемся на навигацию для сохранения предыдущей вкладки + Navigating += OnShellNavigating; Trace.WriteLine("[AppShell] ✅ AppShell инициализирован"); - Trace.WriteLine("=== APPSHELL CONSTRUCTOR END ==="); + Trace.WriteLine("=== APP SHELL CONSTRUCTOR END ==="); // Устанавливаем начальный режим меню UpdateLayoutMode(); + // При запуске переключаемся на ленту (индекс 2), а не на меню (индекс 0) + if (MobileTabBar?.Items?.Count > 2) + { + CurrentItem = MobileTabBar.Items[2]; + } + // Инициализация без ожидания (так await нельзя из синхронного конструктора) _ = InitializeAsync(); } catch (Exception ex) { - Trace.WriteLine("=== APPSHELL CONSTRUCTOR ERROR ==="); + Trace.WriteLine("=== APP SHELL CONSTRUCTOR ERROR ==="); Trace.WriteLine($"[AppShell] ❌ Тип ошибки: {ex.GetType().FullName}"); Trace.WriteLine($"[AppShell] ❌ Сообщение: {ex.Message}"); Trace.WriteLine($"[AppShell] ❌ StackTrace: {ex.StackTrace}"); @@ -114,6 +128,31 @@ public partial class AppShell : Shell } } + /// + /// Сохраняет индекс текущей вкладки перед переходом на меню + /// + private void OnShellNavigating(object? sender, ShellNavigatingEventArgs e) + { + // Если переходим на MenuTabPage, сохраняем текущую вкладку + if (e.Target?.Location?.OriginalString?.Contains("MenuTab") == true) + { + // Сохраняем индекс текущей вкладки (не меню) + if (MobileTabBar != null) + { + for (int i = 1; i < MobileTabBar.Items.Count; i++) + { + if (MobileTabBar.Items[i] == MobileTabBar.CurrentItem) + { + _lastNonMenuTabIndex = i; + MenuPage.SetPreviousTabIndex(i); + Trace.WriteLine($"[AppShell] Сохранена предыдущая вкладка: {i}"); + break; + } + } + } + } + } + protected override void OnSizeAllocated(double width, double height) { base.OnSizeAllocated(width, height); @@ -130,20 +169,48 @@ public partial class AppShell : Shell try { // Получаем текущую ширину окна - double currentWidth = width ?? (Application.Current?.Windows.FirstOrDefault()?.Width ?? 0); + double currentWidth = width ?? 0; + + // Если ширина не передана, пытаемся получить из разных источников + if (currentWidth <= 0) + { + // Пробуем получить из Window + var window = Application.Current?.Windows.FirstOrDefault(); + if (window != null && window.Width > 0) + { + currentWidth = window.Width; + } + } if (currentWidth <= 0) { - // Если ширина не определена, используем DeviceDisplay - currentWidth = DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density; + // Пробуем получить из DeviceDisplay + var displayInfo = DeviceDisplay.MainDisplayInfo; + if (displayInfo.Width > 0 && displayInfo.Density > 0) + { + currentWidth = displayInfo.Width / displayInfo.Density; + } + } + + // На Windows/Desktop по умолчанию считаем широким экраном если не удалось определить + if (currentWidth <= 0) + { +#if WINDOWS || MACCATALYST + currentWidth = 1200; // Предполагаем десктоп +#else + currentWidth = 400; // Предполагаем мобильное устройство +#endif } bool shouldBeDesktop = currentWidth >= WideScreenThreshold; Trace.WriteLine($"[AppShell] UpdateLayoutMode: width={currentWidth}, threshold={WideScreenThreshold}, shouldBeDesktop={shouldBeDesktop}, currentMode={_isDesktopMode}"); + // При первом запуске (_isDesktopMode = false) или при изменении режима + bool isFirstRun = !_isDesktopMode && shouldBeDesktop; + // Проверяем, нужно ли менять режим - if (shouldBeDesktop == _isDesktopMode) + if (shouldBeDesktop == _isDesktopMode && !isFirstRun) { return; // Режим не изменился } @@ -171,24 +238,29 @@ public partial class AppShell : Shell { try { - Trace.WriteLine("[AppShell] >>> Переключение на десктопный режим (Flyout)"); + Trace.WriteLine("[AppShell] >>> Переключение на десктопный режим (DesktopLayout)"); // Скрываем TabBar MobileTabBar.IsVisible = false; - // Показываем FlyoutItems - DesktopFlyoutNews.IsVisible = true; - DesktopFlyoutEmployees.IsVisible = true; - DesktopFlyoutDepartments.IsVisible = true; - DesktopFlyoutRequests.IsVisible = true; - DesktopFlyoutProfile.IsVisible = true; + // Скрываем FlyoutItems (не используем Flyout) + DesktopFlyoutNews.IsVisible = false; + DesktopFlyoutEmployees.IsVisible = false; + DesktopFlyoutDepartments.IsVisible = false; + DesktopFlyoutChats.IsVisible = false; + DesktopFlyoutRequests.IsVisible = false; + + DesktopFlyoutDocuments.IsVisible = false; + DesktopFlyoutProfile.IsVisible = false; - // Включаем Flyout в режим Locked (всегда видим) - FlyoutBehavior = FlyoutBehavior.Locked; - FlyoutIsPresented = true; + // Отключаем Flyout + FlyoutBehavior = FlyoutBehavior.Disabled; - // Переходим на первый FlyoutItem - CurrentItem = DesktopFlyoutNews; + // Переключаем MainPage на DesktopMainPage + if (Application.Current?.Windows.Count > 0) + { + Application.Current.Windows[0].Page = new DesktopMainPage(); + } Trace.WriteLine("[AppShell] ✅ Десктопный режим активирован"); } @@ -204,11 +276,19 @@ public partial class AppShell : Shell { Trace.WriteLine("[AppShell] >>> Переключение на мобильный режим (TabBar)"); + // Если текущая страница - DesktopMainPage, возвращаем AppShell + if (Application.Current?.Windows.Count > 0 && Application.Current.Windows[0].Page is DesktopMainPage) + { + Application.Current.Windows[0].Page = this; + } + // Скрываем FlyoutItems DesktopFlyoutNews.IsVisible = false; DesktopFlyoutEmployees.IsVisible = false; DesktopFlyoutDepartments.IsVisible = false; + DesktopFlyoutChats.IsVisible = false; DesktopFlyoutRequests.IsVisible = false; + DesktopFlyoutDocuments.IsVisible = false; DesktopFlyoutProfile.IsVisible = false; // Отключаем Flyout diff --git a/CORP-MAUI/CORP-MAUI.csproj b/CORP-MAUI/CORP-MAUI.csproj index 119b159..a648348 100644 --- a/CORP-MAUI/CORP-MAUI.csproj +++ b/CORP-MAUI/CORP-MAUI.csproj @@ -55,8 +55,7 @@ - - + @@ -74,9 +73,5 @@ - - - - diff --git a/CORP-MAUI/Controls/DesktopCalendarPanel.xaml b/CORP-MAUI/Controls/DesktopCalendarPanel.xaml new file mode 100644 index 0000000..7e44289 --- /dev/null +++ b/CORP-MAUI/Controls/DesktopCalendarPanel.xaml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CORP-MAUI/Controls/DesktopCalendarPanel.xaml.cs b/CORP-MAUI/Controls/DesktopCalendarPanel.xaml.cs new file mode 100644 index 0000000..24ca8f3 --- /dev/null +++ b/CORP-MAUI/Controls/DesktopCalendarPanel.xaml.cs @@ -0,0 +1,101 @@ +using System.Globalization; +using Microsoft.Maui.Controls.Shapes; + +namespace CORP_MAUI.Controls; + +public partial class DesktopCalendarPanel : ContentView +{ + private DateTime _currentMonth; + private static readonly CultureInfo RuCulture = new("ru-RU"); + + public DesktopCalendarPanel() + { + InitializeComponent(); + _currentMonth = DateTime.Today; + UpdateCalendar(); + } + + private void UpdateCalendar() + { + MonthYearLabel.Text = _currentMonth.ToString("MMMM yyyy", RuCulture); + MonthYearLabel.Text = char.ToUpper(MonthYearLabel.Text[0]) + MonthYearLabel.Text[1..]; + CalendarGrid.Children.Clear(); + + var firstDay = new DateTime(_currentMonth.Year, _currentMonth.Month, 1); + int startColumn = ((int)firstDay.DayOfWeek + 6) % 7; + int daysInMonth = DateTime.DaysInMonth(_currentMonth.Year, _currentMonth.Month); + + int row = 0; + int col = startColumn; + + for (int day = 1; day <= daysInMonth; day++) + { + var dayDate = new DateTime(_currentMonth.Year, _currentMonth.Month, day); + bool isToday = dayDate.Date == DateTime.Today; + bool isWeekend = col >= 5; + + var dayLabel = new Label + { + Text = day.ToString(), + FontSize = 14, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = isToday ? Colors.White : (isWeekend ? Color.FromArgb("#0D6EFD") : Color.FromArgb("#333333")) + }; + + View dayView; + if (isToday) + { + var border = new Border + { + StrokeShape = new RoundRectangle { CornerRadius = 17 }, + BackgroundColor = Color.FromArgb("#0D6EFD"), + HeightRequest = 34, + WidthRequest = 34, + Content = dayLabel + }; + dayView = border; + } + else + { + dayView = dayLabel; + } + + Grid.SetRow(dayView, row); + Grid.SetColumn(dayView, col); + CalendarGrid.Children.Add(dayView); + + col++; + if (col > 6) { col = 0; row++; } + } + + UpdateWeekRange(); + } + + private void UpdateWeekRange() + { + var today = DateTime.Today; + int diff = ((int)today.DayOfWeek + 6) % 7; + var weekStart = today.AddDays(-diff); + var weekEnd = weekStart.AddDays(6); + WeekRangeLabel.Text = $"{weekStart:d MMMM} – {weekEnd:d MMMM}"; + } + + private void OnPreviousMonthClicked(object? sender, EventArgs e) + { + _currentMonth = _currentMonth.AddMonths(-1); + UpdateCalendar(); + } + + private void OnNextMonthClicked(object? sender, EventArgs e) + { + _currentMonth = _currentMonth.AddMonths(1); + UpdateCalendar(); + } + + private void OnTodayClicked(object? sender, EventArgs e) + { + _currentMonth = DateTime.Today; + UpdateCalendar(); + } +} diff --git a/CORP-MAUI/Controls/DesktopHeader.xaml b/CORP-MAUI/Controls/DesktopHeader.xaml new file mode 100644 index 0000000..1e0ed3b --- /dev/null +++ b/CORP-MAUI/Controls/DesktopHeader.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CORP-MAUI/Controls/DesktopHeader.xaml.cs b/CORP-MAUI/Controls/DesktopHeader.xaml.cs new file mode 100644 index 0000000..e01bb0a --- /dev/null +++ b/CORP-MAUI/Controls/DesktopHeader.xaml.cs @@ -0,0 +1,42 @@ +namespace CORP_MAUI.Controls; + +public partial class DesktopHeader : ContentView +{ + public DesktopHeader() + { + InitializeComponent(); + LoadUserAvatar(); + } + + private async void LoadUserAvatar() + { + try + { + var user = await ServiceProvider.ApiService.GetCurrentUserAsync(); + if (user != null && !string.IsNullOrEmpty(user.Avatar)) + { + // Если Avatar содержит base64 данные + if (user.Avatar.StartsWith("data:image")) + { + var base64Data = user.Avatar.Substring(user.Avatar.IndexOf(",") + 1); + UserAvatar.Source = ImageSource.FromStream(() => + new MemoryStream(Convert.FromBase64String(base64Data))); + } + else + { + // Иначе это URL + UserAvatar.Source = user.Avatar; + } + } + } + catch + { + // Используем аватар по умолчанию + } + } + + private async void OnProfileClicked(object sender, EventArgs e) + { + await Shell.Current.GoToAsync("//ProfilePage"); + } +} diff --git a/CORP-MAUI/Controls/DesktopLayout.xaml b/CORP-MAUI/Controls/DesktopLayout.xaml new file mode 100644 index 0000000..97baa86 --- /dev/null +++ b/CORP-MAUI/Controls/DesktopLayout.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CORP-MAUI/Controls/DesktopLayout.xaml.cs b/CORP-MAUI/Controls/DesktopLayout.xaml.cs new file mode 100644 index 0000000..7bfae25 --- /dev/null +++ b/CORP-MAUI/Controls/DesktopLayout.xaml.cs @@ -0,0 +1,1189 @@ +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); + + // Контейнер для контента + var content = new VerticalStackLayout + { + Spacing = 15 + }; + content.Children.Add(header); + + // Здесь будет ScrollView с постами (загружается асинхронно) + var postsContainer = new VerticalStackLayout + { + Spacing = 15 + }; + + // Placeholder пока загружаются посты + var loadingIndicator = new ActivityIndicator + { + IsRunning = true, + Color = Color.FromArgb("#0D6EFD"), + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(0, 50) + }; + postsContainer.Children.Add(loadingIndicator); + + var scrollView = new ScrollView { Content = postsContainer }; + content.Children.Add(scrollView); + + // Загружаем посты асинхронно + _ = LoadPostsAsync(postsContainer); + + return content; + } + + 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); + 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 + } + }; + + 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 + }; + 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 + }); + + 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) }); + content.Children.Add(new ScrollView { Content = container }); + + 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) + }; + + 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}"); + await Shell.Current.Navigation.PushAsync(new CORP_MAUI.Pages.EmployeeProfilePage(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 }; + + // Добавляем 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); + + 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 с 2 колонками + 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}]"); + } + + 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 }; + + // Сохраняем ссылку на контейнер аватарок в свойстве отдела для последующего обновления + 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}"); + await Shell.Current.Navigation.PushAsync(new CORP_MAUI.Pages.DepartmentDetailsPage(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; + } +} diff --git a/CORP-MAUI/Controls/DesktopSidebar.xaml b/CORP-MAUI/Controls/DesktopSidebar.xaml new file mode 100644 index 0000000..8cdf0b0 --- /dev/null +++ b/CORP-MAUI/Controls/DesktopSidebar.xaml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CORP-MAUI/Controls/DesktopSidebar.xaml.cs b/CORP-MAUI/Controls/DesktopSidebar.xaml.cs new file mode 100644 index 0000000..f5d9672 --- /dev/null +++ b/CORP-MAUI/Controls/DesktopSidebar.xaml.cs @@ -0,0 +1,100 @@ +namespace CORP_MAUI.Controls; + +public partial class DesktopSidebar : ContentView +{ + public event EventHandler? NavigationRequested; + private string _currentRoute = "News"; + private readonly Color _activeBackground = Color.FromArgb("#E8F0FE"); + private readonly Color _inactiveBackground = Colors.Transparent; + + public DesktopSidebar() + { + InitializeComponent(); + // По умолчанию выделяем "Новости" + UpdateActiveItem("News"); + } + + private void UpdateActiveItem(string route) + { + _currentRoute = route; + + // Сбрасываем все фоны + ProfileItem.BackgroundColor = _inactiveBackground; + NewsItem.BackgroundColor = _inactiveBackground; + EmployeesItem.BackgroundColor = _inactiveBackground; + DepartmentsItem.BackgroundColor = _inactiveBackground; + RequestsItem.BackgroundColor = _inactiveBackground; + ChatsItem.BackgroundColor = _inactiveBackground; + DocumentsItem.BackgroundColor = _inactiveBackground; + FinanceItem.BackgroundColor = _inactiveBackground; + SettingsItem.BackgroundColor = _inactiveBackground; + + // Выделяем активный пункт + switch (route) + { + case "Profile": ProfileItem.BackgroundColor = _activeBackground; break; + case "News": NewsItem.BackgroundColor = _activeBackground; break; + case "Employees": EmployeesItem.BackgroundColor = _activeBackground; break; + case "Departments": DepartmentsItem.BackgroundColor = _activeBackground; break; + case "Requests": RequestsItem.BackgroundColor = _activeBackground; break; + case "Chats": ChatsItem.BackgroundColor = _activeBackground; break; + case "Documents": DocumentsItem.BackgroundColor = _activeBackground; break; + case "Finance": FinanceItem.BackgroundColor = _activeBackground; break; + case "Settings": SettingsItem.BackgroundColor = _activeBackground; break; + } + } + + private void OnProfileClicked(object? sender, EventArgs e) + { + UpdateActiveItem("Profile"); + NavigationRequested?.Invoke(this, "Profile"); + } + + private void OnNewsClicked(object? sender, EventArgs e) + { + UpdateActiveItem("News"); + NavigationRequested?.Invoke(this, "News"); + } + + private void OnEmployeesClicked(object? sender, EventArgs e) + { + UpdateActiveItem("Employees"); + NavigationRequested?.Invoke(this, "Employees"); + } + + private void OnDepartmentsClicked(object? sender, EventArgs e) + { + UpdateActiveItem("Departments"); + NavigationRequested?.Invoke(this, "Departments"); + } + + private void OnRequestsClicked(object? sender, EventArgs e) + { + UpdateActiveItem("Requests"); + NavigationRequested?.Invoke(this, "Requests"); + } + + private void OnChatsClicked(object? sender, EventArgs e) + { + UpdateActiveItem("Chats"); + NavigationRequested?.Invoke(this, "Chats"); + } + + private void OnDocumentsClicked(object? sender, EventArgs e) + { + UpdateActiveItem("Documents"); + NavigationRequested?.Invoke(this, "Documents"); + } + + private async void OnFinanceClicked(object? sender, EventArgs e) + { + if (Application.Current?.Windows.Count > 0) + await Application.Current.Windows[0].Page!.DisplayAlert("Финансы", "Раздел финансов находится в разработке", "OK"); + } + + private async void OnSettingsClicked(object? sender, EventArgs e) + { + if (Application.Current?.Windows.Count > 0) + await Application.Current.Windows[0].Page!.DisplayAlert("Настройки", "Раздел настроек находится в разработке", "OK"); + } +} diff --git a/CORP-MAUI/MauiProgram.cs b/CORP-MAUI/MauiProgram.cs index 263eb53..726da1a 100644 --- a/CORP-MAUI/MauiProgram.cs +++ b/CORP-MAUI/MauiProgram.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using CommunityToolkit.Maui; using CORP_MAUI.Services; using CORP_MAUI.Pages; @@ -19,6 +19,7 @@ public static class MauiProgram fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); + #if DEBUG Trace.Listeners.Clear(); Trace.Listeners.Add(new DefaultTraceListener()); diff --git a/CORP-MAUI/Models/Department.cs b/CORP-MAUI/Models/Department.cs index e570f01..32ef57d 100644 --- a/CORP-MAUI/Models/Department.cs +++ b/CORP-MAUI/Models/Department.cs @@ -17,6 +17,12 @@ public class Department public User? Head { get; set; } + /// + /// Контейнер для аватарок сотрудников (используется в UI) + /// + [JsonIgnore] + public HorizontalStackLayout? AvatarsContainer { get; set; } + /// /// Описание для отображения (с fallback если пусто) /// diff --git a/CORP-MAUI/Models/Document.cs b/CORP-MAUI/Models/Document.cs index ccbd0e0..21d44c0 100644 --- a/CORP-MAUI/Models/Document.cs +++ b/CORP-MAUI/Models/Document.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace CORP_MAUI.Models; /// @@ -5,13 +7,138 @@ namespace CORP_MAUI.Models; /// public class Document { + [JsonPropertyName("id")] public int Id { get; set; } + + [JsonPropertyName("title")] public string Title { get; set; } = string.Empty; + + [JsonPropertyName("description")] public string Description { get; set; } = string.Empty; + + [JsonPropertyName("uploaded_by")] + public User? UploadedBy { get; set; } + + [JsonPropertyName("uploaded_at")] + public DateTime UploadedAt { get; set; } + + [JsonPropertyName("sent_to_all")] + public bool SentToAll { get; set; } + + [JsonPropertyName("departments")] + public List? Departments { get; set; } + + [JsonPropertyName("recipients")] + public List? Recipients { get; set; } + + [JsonPropertyName("file_url")] public string FileUrl { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public User? Author { get; set; } - public string Category { get; set; } = string.Empty; + + [JsonPropertyName("is_acknowledged")] + public bool IsAcknowledged { get; set; } + + // Вычисляемые свойства для отображения + + /// + /// Имя загрузившего документ + /// + [JsonIgnore] + public string UploaderName => UploadedBy?.FullName ?? "Неизвестный"; + + /// + /// URL аватара загрузившего + /// + [JsonIgnore] + public string UploaderAvatarUrl => UploadedBy?.Avatar ?? UploadedBy?.AvatarUrl ?? string.Empty; + + /// + /// Форматированная дата загрузки + /// + [JsonIgnore] + public string DisplayUploadedAt => UploadedAt.ToString("dd.MM.yyyy HH:mm"); + + /// + /// Расширение файла (для иконки) + /// + [JsonIgnore] + public string FileExtension + { + get + { + if (string.IsNullOrEmpty(FileUrl)) return string.Empty; + try + { + // Декодируем URL и получаем расширение + string decodedUrl = Uri.UnescapeDataString(FileUrl); + int lastDot = decodedUrl.LastIndexOf('.'); + if (lastDot > 0 && lastDot < decodedUrl.Length - 1) + { + return decodedUrl.Substring(lastDot + 1).ToLower(); + } + } + catch + { + // Игнорируем ошибки декодирования + } + return string.Empty; + } + } + + /// + /// Иконка типа документа (эмодзи) + /// + [JsonIgnore] + public string FileIcon + { + get + { + return FileExtension switch + { + "pdf" => "📕", + "doc" or "docx" => "📘", + "xls" or "xlsx" => "📗", + "ppt" or "pptx" => "📙", + "txt" => "📄", + "jpg" or "jpeg" or "png" or "gif" or "webp" => "🖼️", + "zip" or "rar" or "7z" => "📦", + _ => "📄" + }; + } + } + + /// + /// Цвет статуса ознакомления + /// + [JsonIgnore] + public Color AcknowledgedColor => IsAcknowledged + ? Color.FromArgb("#28A745") // Зеленый + : Color.FromArgb("#FFC107"); // Желтый + + /// + /// Текст статуса ознакомления + /// + [JsonIgnore] + public string AcknowledgedText => IsAcknowledged ? "Ознакомлен" : "Не ознакомлен"; + + /// + /// Иконка статуса ознакомления + /// + [JsonIgnore] + public string AcknowledgedIcon => IsAcknowledged ? "✅" : "⏳"; + + /// + /// Короткое описание файла + /// + [JsonIgnore] + public string ShortDescription + { + get + { + if (!string.IsNullOrEmpty(Description)) + { + return Description.Length > 100 ? Description.Substring(0, 100) + "..." : Description; + } + return SentToAll ? "Для всех сотрудников" : "Персональный документ"; + } + } } - diff --git a/CORP-MAUI/Pages/ChatPage.xaml b/CORP-MAUI/Pages/ChatPage.xaml index dba513c..e80210f 100644 --- a/CORP-MAUI/Pages/ChatPage.xaml +++ b/CORP-MAUI/Pages/ChatPage.xaml @@ -5,7 +5,7 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" x:Class="CORP_MAUI.Pages.ChatPage" Title="Чат" - BackgroundColor="#E5E5E5"> + BackgroundColor="White"> diff --git a/CORP-MAUI/Pages/ChatsPage.xaml b/CORP-MAUI/Pages/ChatsPage.xaml new file mode 100644 index 0000000..47be685 --- /dev/null +++ b/CORP-MAUI/Pages/ChatsPage.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/CORP-MAUI/Pages/ChatsPage.xaml.cs b/CORP-MAUI/Pages/ChatsPage.xaml.cs new file mode 100644 index 0000000..7dbff53 --- /dev/null +++ b/CORP-MAUI/Pages/ChatsPage.xaml.cs @@ -0,0 +1,9 @@ +namespace CORP_MAUI.Pages; + +public partial class ChatsPage : ContentPage +{ + public ChatsPage() + { + InitializeComponent(); + } +} diff --git a/CORP-MAUI/Pages/DepartmentDetailsPage.xaml b/CORP-MAUI/Pages/DepartmentDetailsPage.xaml index 6cbc66c..7b93e3e 100644 --- a/CORP-MAUI/Pages/DepartmentDetailsPage.xaml +++ b/CORP-MAUI/Pages/DepartmentDetailsPage.xaml @@ -5,7 +5,7 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" x:Class="CORP_MAUI.Pages.DepartmentDetailsPage" Title="Информация об отделе" - BackgroundColor="#E5E5E5"> + BackgroundColor="White"> diff --git a/CORP-MAUI/Pages/DepartmentDetailsPage.xaml.cs b/CORP-MAUI/Pages/DepartmentDetailsPage.xaml.cs index b3a35ea..f4e4203 100644 --- a/CORP-MAUI/Pages/DepartmentDetailsPage.xaml.cs +++ b/CORP-MAUI/Pages/DepartmentDetailsPage.xaml.cs @@ -129,7 +129,20 @@ public partial class DepartmentDetailsPage : ContentPage /// private async void OnTeamMemberCardTapped(object sender, TappedEventArgs e) { - if (e.Parameter is User selectedUser) + User? selectedUser = null; + + // Попробуем получить параметр из TappedEventArgs + if (e.Parameter is User paramUser) + { + selectedUser = paramUser; + } + // Иначе попробуем получить из BindingContext отправителя + else if (sender is BindableObject bindable && bindable.BindingContext is User contextUser) + { + selectedUser = contextUser; + } + + if (selectedUser != null) { Trace.WriteLine($"[DepartmentDetailsPage] >>> Открытие профиля: {selectedUser.FullName}"); @@ -143,6 +156,10 @@ public partial class DepartmentDetailsPage : ContentPage await DisplayAlert("Ошибка", "Не удалось открыть профиль сотрудника", "OK"); } } + else + { + Trace.WriteLine("[DepartmentDetailsPage] ⚠️ Не удалось определить выбранного сотрудника"); + } } /// @@ -150,7 +167,27 @@ public partial class DepartmentDetailsPage : ContentPage /// private async void OnChatIconTapped(object sender, TappedEventArgs e) { - if (e.Parameter is User selectedUser) + User? selectedUser = null; + + // Попробуем получить параметр из TappedEventArgs + if (e.Parameter is User paramUser) + { + selectedUser = paramUser; + } + // Иначе попробуем получить из BindingContext отправителя или родительского элемента + else if (sender is BindableObject bindable) + { + if (bindable.BindingContext is User contextUser) + { + selectedUser = contextUser; + } + else if (bindable is View view && view.Parent is View parent && parent.BindingContext is User parentUser) + { + selectedUser = parentUser; + } + } + + if (selectedUser != null) { Trace.WriteLine($"[DepartmentDetailsPage] >>> Открытие чата с: {selectedUser.FullName}"); @@ -164,6 +201,10 @@ public partial class DepartmentDetailsPage : ContentPage await DisplayAlert("Ошибка", "Не удалось открыть чат", "OK"); } } + else + { + Trace.WriteLine("[DepartmentDetailsPage] ⚠️ Не удалось определить пользователя для чата"); + } } private async Task PreloadAvatarsAsync() diff --git a/CORP-MAUI/Pages/DepartmentsPage.xaml b/CORP-MAUI/Pages/DepartmentsPage.xaml index 5f12e9a..1a2a4de 100644 --- a/CORP-MAUI/Pages/DepartmentsPage.xaml +++ b/CORP-MAUI/Pages/DepartmentsPage.xaml @@ -5,7 +5,7 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" x:Class="CORP_MAUI.Pages.DepartmentsPage" Title="Отделы" - BackgroundColor="#E5E5E5"> + BackgroundColor="White"> @@ -87,98 +87,19 @@ IsRefreshing="False" Refreshing="OnRefreshing"> - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/CORP-MAUI/Pages/DepartmentsPage.xaml.cs b/CORP-MAUI/Pages/DepartmentsPage.xaml.cs index 603aa05..f8b5d5c 100644 --- a/CORP-MAUI/Pages/DepartmentsPage.xaml.cs +++ b/CORP-MAUI/Pages/DepartmentsPage.xaml.cs @@ -1,12 +1,12 @@ -using System.Collections.ObjectModel; using System.Diagnostics; using CORP_MAUI.Models; +using CommunityToolkit.Maui.Behaviors; +using Microsoft.Maui.Controls.Shapes; namespace CORP_MAUI.Pages; public partial class DepartmentsPage : ContentPage { - private readonly ObservableCollection _departments; private List _allDepartments = new(); public DepartmentsPage() @@ -14,43 +14,13 @@ public partial class DepartmentsPage : ContentPage try { Trace.WriteLine("=== DEPARTMENTSPAGE CONSTRUCTOR START ==="); - Trace.WriteLine("[DepartmentsPage] >>> Вызов InitializeComponent"); - InitializeComponent(); - - Trace.WriteLine("[DepartmentsPage] >>> InitializeComponent завершен"); - - _departments = new ObservableCollection(); - - DepartmentsCollectionView.ItemsSource = _departments; - - Trace.WriteLine("[DepartmentsPage] >>> Запуск LoadDepartmentsAsync"); - // Загружаем отделы _ = LoadDepartmentsAsync(); - Trace.WriteLine("=== DEPARTMENTSPAGE CONSTRUCTOR END ==="); } catch (Exception ex) { - Trace.WriteLine("=== DEPARTMENTSPAGE CONSTRUCTOR ERROR ==="); - Trace.WriteLine($"[DepartmentsPage] ❌ Тип ошибки: {ex.GetType().FullName}"); - Trace.WriteLine($"[DepartmentsPage] ❌ Сообщение: {ex.Message}"); - Trace.WriteLine($"[DepartmentsPage] ❌ StackTrace: {ex.StackTrace}"); - - // Показываем ошибку напрямую - Task.Run(async () => - { - await Task.Delay(100); - if (Application.Current?.Windows.Count > 0) - { - Page? mainPage = Application.Current.Windows[0]?.Page; - if (mainPage != null) - { - await mainPage.DisplayAlert("Ошибка DepartmentsPage", - $"Ошибка инициализации:\n{ex.Message}\n\nStackTrace:\n{ex.StackTrace}", "OK"); - } - } - }); + Trace.WriteLine($"[DepartmentsPage] ❌ Ошибка: {ex.Message}"); throw; } } @@ -59,51 +29,14 @@ public partial class DepartmentsPage : ContentPage { try { - Trace.WriteLine("[DepartmentsPage] >>> LoadDepartmentsAsync START"); - LoadingIndicator.IsVisible = true; LoadingIndicator.IsRunning = true; - Trace.WriteLine("[DepartmentsPage] >>> Вызов GetAllDepartmentsAsync"); _allDepartments = await ServiceProvider.ApiService.GetAllDepartmentsAsync(); - - Trace.WriteLine($"[DepartmentsPage] >>> Получено отделов: {_allDepartments.Count}"); - - // Детальное логирование первого отдела для проверки - if (_allDepartments.Count > 0) - { - var firstDept = _allDepartments[0]; - Trace.WriteLine($"[DepartmentsPage] 📊 Первый отдел:"); - Trace.WriteLine($" - ID: {firstDept.Id}"); - Trace.WriteLine($" - Name: '{firstDept.Name}'"); - Trace.WriteLine($" - Description: '{firstDept.Description}'"); - Trace.WriteLine($" - Description Length: {firstDept.Description?.Length ?? 0}"); - Trace.WriteLine($" - Description IsNullOrEmpty: {string.IsNullOrEmpty(firstDept.Description)}"); - Trace.WriteLine($" - EmployeesCount: {firstDept.EmployeesCount}"); - Trace.WriteLine($" - Head: {firstDept.Head?.FullName ?? "null"}"); - - // Выводим все отделы для полной картины - Trace.WriteLine($"[DepartmentsPage] 📋 Все отделы:"); - foreach (var dept in _allDepartments) - { - Trace.WriteLine($" [{dept.Id}] {dept.Name} - Description: '{dept.Description}' (len: {dept.Description?.Length ?? 0})"); - } - } - - _departments.Clear(); - foreach (Department department in _allDepartments.OrderBy(d => d.Name)) - { - _departments.Add(department); - } - - Trace.WriteLine($"[DepartmentsPage] ✅ Отделы загружены успешно"); + RenderDepartments(_allDepartments.OrderBy(d => d.Name).ToList()); } catch (Exception ex) { - Trace.WriteLine($"[DepartmentsPage] ❌ Ошибка загрузки: {ex.GetType().Name}"); - Trace.WriteLine($"[DepartmentsPage] Message: {ex.Message}"); - Trace.WriteLine($"[DepartmentsPage] StackTrace: {ex.StackTrace}"); - await DisplayAlert("Ошибка", $"Не удалось загрузить список отделов:\n{ex.Message}", "OK"); } finally @@ -114,61 +47,241 @@ public partial class DepartmentsPage : ContentPage } } + private void RenderDepartments(List departments) + { + DepartmentsFlexLayout.Children.Clear(); + + if (departments.Count == 0) + { + var emptyLabel = new Label + { + Text = "Нет отделов", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + Margin = new Thickness(20) + }; + emptyLabel.SetAppThemeColor(Label.TextColorProperty, Color.FromArgb("#1F1F1F"), Color.FromArgb("#E1E1E1")); + DepartmentsFlexLayout.Children.Add(emptyLabel); + return; + } + + double screenWidth = DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density; + // Для мобильных устройств (< 600px) - одна карточка в ряд, иначе две + double cardWidth = screenWidth < 600 ? screenWidth - 30 : (screenWidth / 2) - 25; + + foreach (var department in departments) + { + var card = CreateDepartmentCard(department, cardWidth); + DepartmentsFlexLayout.Children.Add(card); + } + + _ = LoadDepartmentAvatarsAsync(departments); + } + + private Border CreateDepartmentCard(Department department, double cardWidth) + { + var avatarStack = new HorizontalStackLayout + { + Spacing = -8, + Margin = new Thickness(0, 5, 0, 0), + HeightRequest = 32, + MinimumHeightRequest = 32 + }; + + var card = new Border + { + StrokeShape = new RoundRectangle { CornerRadius = 16 }, + BackgroundColor = Colors.White, + Stroke = Colors.Transparent, + Padding = new Thickness(16), + Margin = new Thickness(5), + WidthRequest = cardWidth, + HeightRequest = 150, + MaximumHeightRequest = 150, + Content = new VerticalStackLayout + { + Spacing = 6, + Children = + { + new Label + { + Text = department.Name, + FontSize = 15, + FontAttributes = FontAttributes.Bold, + TextColor = Color.FromArgb("#1F1F1F"), + LineBreakMode = LineBreakMode.TailTruncation, + MaxLines = 1 + }, + new Label + { + Text = department.DisplayDescription, + FontSize = 12, + TextColor = Color.FromArgb("#6B6B6B"), + LineBreakMode = LineBreakMode.TailTruncation, + MaxLines = 2 + }, + new HorizontalStackLayout + { + Spacing = 5, + Margin = new Thickness(0, 3, 0, 0), + Children = + { + CreateIconWithText("employees.png", department.EmployeesCount.ToString()), + new Label + { + Text = "•", + FontSize = 12, + TextColor = Color.FromArgb("#999999"), + VerticalOptions = LayoutOptions.Center + }, + CreateIconWithText("profile.png", department.Head?.FullName ?? "Не назначен") + } + }, + avatarStack + } + } + }; + + card.Shadow = new Shadow + { + Brush = new SolidColorBrush(Color.FromArgb("#40000000")), + Offset = new Point(0, 2), + Radius = 8, + Opacity = 0.15f + }; + + var tapGesture = new TapGestureRecognizer(); + tapGesture.Tapped += async (_, _) => + { + try + { + await Navigation.PushAsync(new DepartmentDetailsPage(department.Id)); + } + catch + { + await DisplayAlert("Ошибка", "Не удалось открыть страницу отдела", "OK"); + } + }; + card.GestureRecognizers.Add(tapGesture); + card.ClassId = department.Id.ToString(); + + return card; + } + + private HorizontalStackLayout CreateIconWithText(string iconSource, string text) + { + var image = new Image + { + Source = iconSource, + WidthRequest = 14, + HeightRequest = 14, + VerticalOptions = LayoutOptions.Center + }; + + var iconBehavior = new IconTintColorBehavior + { + TintColor = Color.FromArgb("#007AFF") + }; + image.Behaviors.Add(iconBehavior); + + return new HorizontalStackLayout + { + Spacing = 4, + Children = + { + image, + new Label + { + Text = text, + FontSize = 12, + TextColor = Color.FromArgb("#6B6B6B"), + VerticalOptions = LayoutOptions.Center, + LineBreakMode = LineBreakMode.TailTruncation + } + } + }; + } + + private async Task LoadDepartmentAvatarsAsync(List departments) + { + try + { + foreach (var dept in departments) + { + if (dept.Head?.Id == null) + continue; + + var card = DepartmentsFlexLayout.Children + .OfType() + .FirstOrDefault(b => b.ClassId == dept.Id.ToString()); + + if (card?.Content is VerticalStackLayout vstack) + { + var avatarStack = vstack.Children.OfType().LastOrDefault(); + if (avatarStack != null) + { + try + { + var user = await ServiceProvider.ApiService.GetUserByIdAsync(dept.Head.Id); + if (!string.IsNullOrEmpty(user?.AvatarUrl)) + { + MainThread.BeginInvokeOnMainThread(() => + { + avatarStack.Children.Clear(); + var avatarBorder = new Border + { + StrokeShape = new RoundRectangle { CornerRadius = 15 }, + WidthRequest = 30, + HeightRequest = 30, + StrokeThickness = 2, + Stroke = Colors.White, + Content = new Image + { + Source = user.AvatarUrl, + Aspect = Aspect.AspectFill + } + }; + avatarStack.Children.Add(avatarBorder); + }); + } + } + catch (Exception) + { + // Игнорируем ошибки загрузки отдельных аватаров + } + } + } + } + } + catch (Exception ex) + { + Trace.WriteLine($"[DepartmentsPage] ⚠️ Ошибка загрузки аватаров: {ex.Message}"); + } + } + private async void OnRefreshing(object sender, EventArgs e) { await LoadDepartmentsAsync(); } - private async void OnDepartmentSelected(object sender, SelectionChangedEventArgs e) - { - if (e.CurrentSelection.FirstOrDefault() is Department selectedDepartment) - { - // Сбрасываем выбор - DepartmentsCollectionView.SelectedItem = null; - - Trace.WriteLine($"[DepartmentsPage] >>> Выбран отдел: {selectedDepartment.Name} (ID: {selectedDepartment.Id})"); - - // Переход на страницу деталей отдела - try - { - await Navigation.PushAsync(new DepartmentDetailsPage(selectedDepartment.Id)); - } - catch (Exception ex) - { - Trace.WriteLine($"[DepartmentsPage] ❌ Ошибка перехода к деталям отдела: {ex.Message}"); - await DisplayAlert("Ошибка", "Не удалось открыть страницу отдела", "OK"); - } - } - } - private void OnSearchTextChanged(object sender, TextChangedEventArgs e) { string searchText = e.NewTextValue?.ToLower() ?? string.Empty; + List filtered; if (string.IsNullOrWhiteSpace(searchText)) { - // Показываем все отделы - _departments.Clear(); - foreach (Department department in _allDepartments.OrderBy(d => d.Name)) - { - _departments.Add(department); - } + filtered = _allDepartments; } else { - // Фильтруем отделы - List filtered = _allDepartments.Where(d => + filtered = _allDepartments.Where(d => d.Name.ToLower().Contains(searchText) || - d.Description.ToLower().Contains(searchText) || + (d.Description?.ToLower().Contains(searchText) ?? false) || (d.Head?.FullName.ToLower().Contains(searchText) ?? false) ).ToList(); - - _departments.Clear(); - foreach (Department department in filtered.OrderBy(d => d.Name)) - { - _departments.Add(department); - } } + + RenderDepartments(filtered.OrderBy(d => d.Name).ToList()); } } - diff --git a/CORP-MAUI/Pages/DesktopMainPage.xaml b/CORP-MAUI/Pages/DesktopMainPage.xaml new file mode 100644 index 0000000..8f1165d --- /dev/null +++ b/CORP-MAUI/Pages/DesktopMainPage.xaml @@ -0,0 +1,12 @@ + + + + + + diff --git a/CORP-MAUI/Pages/DesktopMainPage.xaml.cs b/CORP-MAUI/Pages/DesktopMainPage.xaml.cs new file mode 100644 index 0000000..81acb0c --- /dev/null +++ b/CORP-MAUI/Pages/DesktopMainPage.xaml.cs @@ -0,0 +1,21 @@ +using CORP_MAUI.Controls; + +namespace CORP_MAUI.Pages; + +public partial class DesktopMainPage : ContentPage +{ + public DesktopMainPage() + { + InitializeComponent(); + } + + public DesktopMainPage(View content) : this() + { + DesktopLayout.SetContent(content); + } + + public void SetContent(View content) + { + DesktopLayout.SetContent(content); + } +} diff --git a/CORP-MAUI/Pages/DocumentsPage.xaml b/CORP-MAUI/Pages/DocumentsPage.xaml new file mode 100644 index 0000000..84c2b00 --- /dev/null +++ b/CORP-MAUI/Pages/DocumentsPage.xaml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CORP-MAUI/Pages/DocumentsPage.xaml.cs b/CORP-MAUI/Pages/DocumentsPage.xaml.cs new file mode 100644 index 0000000..23f80ad --- /dev/null +++ b/CORP-MAUI/Pages/DocumentsPage.xaml.cs @@ -0,0 +1,272 @@ +using System.Collections.ObjectModel; +using System.Diagnostics; +using CORP_MAUI.Models; + +namespace CORP_MAUI.Pages; + +public partial class DocumentsPage : ContentPage +{ + private readonly ObservableCollection _documents; + private List _allDocuments = new(); + private string _currentFilter = "all"; + private string _searchText = string.Empty; + + public DocumentsPage() + { + try + { + Trace.WriteLine("=== DOCUMENTS PAGE CONSTRUCTOR START ==="); + Trace.WriteLine("[DocumentsPage] >>> Вызов InitializeComponent"); + + InitializeComponent(); + + Trace.WriteLine("[DocumentsPage] >>> InitializeComponent завершен"); + + _documents = new ObservableCollection(); + + DocumentsCollectionView.ItemsSource = _documents; + + Trace.WriteLine("[DocumentsPage] >>> Запуск LoadDocumentsAsync"); + // Загружаем документы + _ = LoadDocumentsAsync(); + + Trace.WriteLine("=== DOCUMENTS PAGE CONSTRUCTOR END ==="); + } + catch (Exception ex) + { + Trace.WriteLine("=== DOCUMENTS PAGE CONSTRUCTOR ERROR ==="); + Trace.WriteLine($"[DocumentsPage] ❌ Тип ошибки: {ex.GetType().FullName}"); + Trace.WriteLine($"[DocumentsPage] ❌ Сообщение: {ex.Message}"); + Trace.WriteLine($"[DocumentsPage] ❌ StackTrace: {ex.StackTrace}"); + + // Показываем ошибку напрямую + Task.Run(async () => + { + await Task.Delay(100); + if (Application.Current?.Windows.Count > 0) + { + Page? mainPage = Application.Current.Windows[0].Page; + if (mainPage != null) + { + await mainPage.DisplayAlert("Ошибка DocumentsPage", + $"Ошибка инициализации:\n{ex.Message}\n\nStackTrace:\n{ex.StackTrace}", "OK"); + } + } + }); + throw; + } + } + + private async Task LoadDocumentsAsync() + { + try + { + Trace.WriteLine("[DocumentsPage] >>> LoadDocumentsAsync START"); + + LoadingIndicator.IsVisible = true; + LoadingIndicator.IsRunning = true; + + Trace.WriteLine("[DocumentsPage] >>> Вызов GetAllDocumentsAsync"); + _allDocuments = await ServiceProvider.ApiService.GetAllDocumentsAsync(); + + Trace.WriteLine($"[DocumentsPage] >>> Получено документов: {_allDocuments.Count}"); + + ApplyFilters(); + + Trace.WriteLine($"[DocumentsPage] ✅ Документы загружены успешно"); + } + catch (Exception ex) + { + Trace.WriteLine($"[DocumentsPage] ❌ Ошибка загрузки: {ex.GetType().Name}"); + Trace.WriteLine($"[DocumentsPage] Message: {ex.Message}"); + Trace.WriteLine($"[DocumentsPage] StackTrace: {ex.StackTrace}"); + + await DisplayAlert("Ошибка", $"Не удалось загрузить список документов:\n{ex.Message}", "OK"); + } + finally + { + LoadingIndicator.IsVisible = false; + LoadingIndicator.IsRunning = false; + RefreshView.IsRefreshing = false; + } + } + + private void ApplyFilters() + { + IEnumerable filtered = _allDocuments; + + // Фильтр по статусу ознакомления + filtered = _currentFilter switch + { + "acknowledged" => filtered.Where(d => d.IsAcknowledged), + "not_acknowledged" => filtered.Where(d => !d.IsAcknowledged), + _ => filtered + }; + + // Фильтр по поиску + if (!string.IsNullOrWhiteSpace(_searchText)) + { + string search = _searchText.ToLower(); + filtered = filtered.Where(d => + d.Title.ToLower().Contains(search) || + d.Description.ToLower().Contains(search) || + d.UploaderName.ToLower().Contains(search)); + } + + // Сортировка по дате загрузки (новые сверху) + filtered = filtered.OrderByDescending(d => d.UploadedAt); + + _documents.Clear(); + foreach (var document in filtered) + { + _documents.Add(document); + } + + Trace.WriteLine($"[DocumentsPage] >>> Отображено документов: {_documents.Count}"); + } + + private async void OnRefreshing(object sender, EventArgs e) + { + await LoadDocumentsAsync(); + } + + private void OnSearchTextChanged(object sender, TextChangedEventArgs e) + { + _searchText = e.NewTextValue ?? string.Empty; + ApplyFilters(); + } + + private async void OnDocumentSelected(object sender, SelectionChangedEventArgs e) + { + if (e.CurrentSelection.FirstOrDefault() is Document selectedDocument) + { + // Сбрасываем выделение + DocumentsCollectionView.SelectedItem = null; + + Trace.WriteLine($"[DocumentsPage] >>> Выбран документ: {selectedDocument.Title}"); + + // Показываем диалог с выбором действия + string action = await DisplayActionSheet( + selectedDocument.Title, + "Отмена", + null, + "📥 Открыть документ", + "ℹ️ Информация о документе"); + + if (action == "📥 Открыть документ") + { + await OpenDocumentAsync(selectedDocument); + } + else if (action == "ℹ️ Информация о документе") + { + await ShowDocumentInfoAsync(selectedDocument); + } + } + } + + private async Task OpenDocumentAsync(Document document) + { + try + { + if (string.IsNullOrEmpty(document.FileUrl)) + { + await DisplayAlert("Ошибка", "URL документа не найден", "OK"); + return; + } + + Trace.WriteLine($"[DocumentsPage] >>> Открытие документа: {document.FileUrl}"); + + // Открываем документ в браузере + await Launcher.OpenAsync(new Uri(document.FileUrl)); + } + catch (Exception ex) + { + Trace.WriteLine($"[DocumentsPage] ❌ Ошибка открытия документа: {ex.Message}"); + await DisplayAlert("Ошибка", $"Не удалось открыть документ:\n{ex.Message}", "OK"); + } + } + + private async Task ShowDocumentInfoAsync(Document document) + { + string info = $"📄 {document.Title}\n\n"; + + if (!string.IsNullOrEmpty(document.Description)) + { + info += $"📝 Описание:\n{document.Description}\n\n"; + } + + info += $"👤 Загрузил: {document.UploaderName}\n"; + info += $"📅 Дата загрузки: {document.DisplayUploadedAt}\n"; + info += $"📁 Тип файла: {document.FileExtension.ToUpper()}\n"; + info += $"📨 Для всех: {(document.SentToAll ? "Да" : "Нет")}\n"; + info += $"{document.AcknowledgedIcon} Статус: {document.AcknowledgedText}"; + + await DisplayAlert("Информация о документе", info, "OK"); + } + + private async void OnBackClicked(object sender, EventArgs e) + { + await Shell.Current.GoToAsync(".."); + } + + #region Фильтры + + private void OnFilterAllTapped(object sender, TappedEventArgs e) + { + SetActiveFilter("all"); + ApplyFilters(); + } + + private void OnFilterAcknowledgedTapped(object sender, TappedEventArgs e) + { + SetActiveFilter("acknowledged"); + ApplyFilters(); + } + + private void OnFilterNotAcknowledgedTapped(object sender, TappedEventArgs e) + { + SetActiveFilter("not_acknowledged"); + ApplyFilters(); + } + + private void SetActiveFilter(string filter) + { + _currentFilter = filter; + + // Получаем стили из Application.Current.Resources + Style? chipButton = Application.Current?.Resources["ChipButton"] as Style; + Style? chipButtonActive = Application.Current?.Resources["ChipButtonActive"] as Style; + Color? grayColor = Application.Current?.Resources["Gray700"] as Color ?? Colors.Gray; + + // Сбрасываем все фильтры на неактивные + if (chipButton != null) + { + FilterAll.Style = chipButton; + FilterAcknowledged.Style = chipButton; + FilterNotAcknowledged.Style = chipButton; + } + + // Обновляем цвет текста у неактивных + if (FilterAll.Content is Label allLabel) + allLabel.TextColor = grayColor; + if (FilterAcknowledged.Content is Label acknowledgedLabel) + acknowledgedLabel.TextColor = grayColor; + if (FilterNotAcknowledged.Content is Label notAcknowledgedLabel) + notAcknowledgedLabel.TextColor = grayColor; + + // Активируем выбранный фильтр + Border activeFilter = filter switch + { + "acknowledged" => FilterAcknowledged, + "not_acknowledged" => FilterNotAcknowledged, + _ => FilterAll + }; + + if (chipButtonActive != null) + activeFilter.Style = chipButtonActive; + if (activeFilter.Content is Label activeLabel) + activeLabel.TextColor = Colors.White; + } + + #endregion +} diff --git a/CORP-MAUI/Pages/EmployeeProfilePage.xaml b/CORP-MAUI/Pages/EmployeeProfilePage.xaml index 3e78525..c0a54bd 100644 --- a/CORP-MAUI/Pages/EmployeeProfilePage.xaml +++ b/CORP-MAUI/Pages/EmployeeProfilePage.xaml @@ -5,7 +5,7 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" x:Class="CORP_MAUI.Pages.EmployeeProfilePage" Title="Профиль сотрудника" - BackgroundColor="#E5E5E5"> + BackgroundColor="White"> diff --git a/CORP-MAUI/Pages/EmployeesPage.xaml b/CORP-MAUI/Pages/EmployeesPage.xaml index c4f2a3e..e1c6876 100644 --- a/CORP-MAUI/Pages/EmployeesPage.xaml +++ b/CORP-MAUI/Pages/EmployeesPage.xaml @@ -5,7 +5,7 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" x:Class="CORP_MAUI.Pages.EmployeesPage" Title="Сотрудники" - BackgroundColor="#E5E5E5"> + BackgroundColor="White"> diff --git a/CORP-MAUI/Pages/LoginPage.xaml b/CORP-MAUI/Pages/LoginPage.xaml index abac6a2..491642f 100644 --- a/CORP-MAUI/Pages/LoginPage.xaml +++ b/CORP-MAUI/Pages/LoginPage.xaml @@ -43,53 +43,66 @@ Stroke="Transparent" Shadow="{Shadow Brush=Black, Opacity=0.2, Offset='0,4', Radius=8}"> - + - +