Add ChatsPage and Desktop layout components: Implement initial ChatsPage UI, update AppShell for navigation, and create DesktopHeader and DesktopSidebar for desktop layout.
This commit is contained in:
parent
293179b3c0
commit
c1de303c22
2
.idea/.idea.CORP-MAUI/.idea/deviceManager.xml
generated
2
.idea/.idea.CORP-MAUI/.idea/deviceManager.xml
generated
@ -5,7 +5,7 @@
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
<option name="order" value="DESCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMauiWinUIApplication_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Feb4628845efd624a4bdad5d73b53774c81232eee27ca2aa293d869e5e566_003FMauiWinUIApplication_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AObject_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9a6b1457cbcf17db31a383ba49ef9bcc786cf3ef77146d997eee499b27a46d_003FObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APages_005FChatsPage_002Examl_002Esg_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F663367c3d62bfbf0f5a54e78bb74ef48b661e1_003FPages_005FChatsPage_002Examl_002Esg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APages_005FProfilePage_002Examl_002Esg_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2dc829dd3f62e98b28b779bb99a37aa7f9c521_003FPages_005FProfilePage_002Examl_002Esg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APages_005FProfilePage_002Examl_002Esg_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F72277aaebd0209ffbfec32fae65b113854609f_003FPages_005FProfilePage_002Examl_002Esg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStaticResourceExtension_002Ecs_002Fl_003AC_0021_003FUsers_003F_041D_0430_0442_0430_043B_044C_044F_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc0ecf84680bdd51716cf1bd5ed45e7dc5099b08ef4398c534d8192a1146_003FStaticResourceExtension_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 @@
|
||||
</Shell.ItemTemplate>
|
||||
|
||||
<!-- TabBar для узких экранов (мобильные устройства) -->
|
||||
<!-- Порядок: Меню, Сообщения, Лента, Заявления, Сотрудники -->
|
||||
<!-- Иконки белые на синем фоне, без подписей -->
|
||||
<TabBar x:Name="MobileTabBar">
|
||||
<ShellContent Title="Новости" Icon="feed.png" ContentTemplate="{DataTemplate pages:PostsPage}" />
|
||||
<ShellContent Title="Сотрудники" Icon="employees.png" ContentTemplate="{DataTemplate pages:EmployeesPage}" />
|
||||
<ShellContent Title="Отделы" Icon="department.png" ContentTemplate="{DataTemplate pages:DepartmentsPage}" />
|
||||
<ShellContent Title="Заявления" Icon="statements.png" ContentTemplate="{DataTemplate pages:RequestsPage}" />
|
||||
<ShellContent Title="Профиль" Icon="profile.png" ContentTemplate="{DataTemplate pages:ProfilePage}" />
|
||||
<ShellContent Icon="menu.svg" Route="MenuTab" ContentTemplate="{DataTemplate pages:MenuTabPage}" />
|
||||
<ShellContent Icon="chats.svg" ContentTemplate="{DataTemplate pages:ChatsPage}" />
|
||||
<ShellContent Icon="feed.svg" ContentTemplate="{DataTemplate pages:PostsPage}" />
|
||||
<ShellContent Icon="statements.svg" ContentTemplate="{DataTemplate pages:RequestsPage}" />
|
||||
<ShellContent Icon="employees.svg" ContentTemplate="{DataTemplate pages:EmployeesPage}" />
|
||||
</TabBar>
|
||||
|
||||
<!-- FlyoutItems для широких экранов (скрыт по умолчанию, управляется программно) -->
|
||||
@ -80,10 +82,18 @@
|
||||
<ShellContent ContentTemplate="{DataTemplate pages:DepartmentsPage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<FlyoutItem x:Name="DesktopFlyoutChats" Title="Чаты" Icon="chats.svg" FlyoutDisplayOptions="AsSingleItem" IsVisible="False">
|
||||
<ShellContent ContentTemplate="{DataTemplate pages:ChatsPage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<FlyoutItem x:Name="DesktopFlyoutRequests" Title="Заявления" Icon="statements.png" FlyoutDisplayOptions="AsSingleItem" IsVisible="False">
|
||||
<ShellContent ContentTemplate="{DataTemplate pages:RequestsPage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<FlyoutItem x:Name="DesktopFlyoutDocuments" Title="Документы" Icon="documents.png" FlyoutDisplayOptions="AsSingleItem" IsVisible="False">
|
||||
<ShellContent ContentTemplate="{DataTemplate pages:DocumentsPage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<FlyoutItem x:Name="DesktopFlyoutProfile" Title="Профиль" Icon="profile.png" FlyoutDisplayOptions="AsSingleItem" IsVisible="False">
|
||||
<ShellContent ContentTemplate="{DataTemplate pages:ProfilePage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохраняет индекс текущей вкладки перед переходом на меню
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
@ -55,8 +55,7 @@
|
||||
<!-- Splash Screen -->
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Include="Resources\Images\Icons\SVG\*" />
|
||||
<MauiImage Include="Resources\Images\**\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
@ -74,9 +73,5 @@
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\Images\Icons\SVG\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
96
CORP-MAUI/Controls/DesktopCalendarPanel.xaml
Normal file
96
CORP-MAUI/Controls/DesktopCalendarPanel.xaml
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Controls.DesktopCalendarPanel"
|
||||
WidthRequest="280">
|
||||
|
||||
<!-- Правая панель с календарём -->
|
||||
<Border BackgroundColor="White" Stroke="Transparent" Padding="15">
|
||||
<ScrollView>
|
||||
<VerticalStackLayout Spacing="15">
|
||||
|
||||
<!-- Заголовок -->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Label Grid.Column="0" Text="Календарь" FontSize="18" FontAttributes="Bold" TextColor="#333333"/>
|
||||
<HorizontalStackLayout Grid.Column="1" Spacing="5">
|
||||
<Label Text="Событие" TextColor="#0D6EFD" FontSize="14" VerticalOptions="Center"/>
|
||||
<Border StrokeShape="RoundRectangle 12" BackgroundColor="#0D6EFD"
|
||||
HeightRequest="24" WidthRequest="24">
|
||||
<Label Text="+" TextColor="White" FontSize="16"
|
||||
HorizontalOptions="Center" VerticalOptions="Center"/>
|
||||
</Border>
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
|
||||
<!-- Фильтр -->
|
||||
<HorizontalStackLayout Spacing="5">
|
||||
<Label Text="Компания" TextColor="#666666" FontSize="13"/>
|
||||
<Label Text="▼" TextColor="#666666" FontSize="10"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Навигация по месяцам -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Label Grid.Column="0" Text="‹" FontSize="20" TextColor="#0D6EFD">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnPreviousMonthClicked"/>
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
<Label x:Name="MonthYearLabel" Grid.Column="1" Text="Январь 2026"
|
||||
FontSize="16" FontAttributes="Bold" TextColor="#333333"
|
||||
HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="2" Text="›" FontSize="20" TextColor="#0D6EFD">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnNextMonthClicked"/>
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
<Border Grid.Column="2" StrokeShape="RoundRectangle 15"
|
||||
BackgroundColor="#0D6EFD" Padding="12,6" Margin="20,0,0,0">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnTodayClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Label Text="Сегодня" TextColor="White" FontSize="12"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Дни недели -->
|
||||
<Grid ColumnDefinitions="*,*,*,*,*,*,*" ColumnSpacing="2">
|
||||
<Label Grid.Column="0" Text="пн" TextColor="#999999" FontSize="12" HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="1" Text="вт" TextColor="#999999" FontSize="12" HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="2" Text="ср" TextColor="#999999" FontSize="12" HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="3" Text="чт" TextColor="#999999" FontSize="12" HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="4" Text="пт" TextColor="#999999" FontSize="12" HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="5" Text="сб" TextColor="#0D6EFD" FontSize="12" HorizontalOptions="Center"/>
|
||||
<Label Grid.Column="6" Text="вс" TextColor="#0D6EFD" FontSize="12" HorizontalOptions="Center"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Сетка календаря -->
|
||||
<Grid x:Name="CalendarGrid" RowDefinitions="35,35,35,35,35,35"
|
||||
ColumnDefinitions="*,*,*,*,*,*,*" RowSpacing="2" ColumnSpacing="2">
|
||||
<!-- Дни будут добавлены программно -->
|
||||
</Grid>
|
||||
|
||||
<!-- Разделитель -->
|
||||
<BoxView HeightRequest="1" BackgroundColor="#EEEEEE" Margin="0,10"/>
|
||||
|
||||
<!-- События на неделе -->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Label Grid.Column="0" Text="На этой неделе" TextColor="#666666" FontSize="13"/>
|
||||
<Label x:Name="WeekRangeLabel" Grid.Column="1" Text="19 января – 25 января"
|
||||
TextColor="#666666" FontSize="13"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Список событий -->
|
||||
<VerticalStackLayout x:Name="EventsList" Spacing="8">
|
||||
<!-- Пример события -->
|
||||
<HorizontalStackLayout Spacing="8">
|
||||
<Label Text="🎂" FontSize="14"/>
|
||||
<Label Text="День рождения Ивана Иванова" TextColor="#333333" FontSize="13"/>
|
||||
</HorizontalStackLayout>
|
||||
</VerticalStackLayout>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</Border>
|
||||
|
||||
</ContentView>
|
||||
101
CORP-MAUI/Controls/DesktopCalendarPanel.xaml.cs
Normal file
101
CORP-MAUI/Controls/DesktopCalendarPanel.xaml.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
94
CORP-MAUI/Controls/DesktopHeader.xaml
Normal file
94
CORP-MAUI/Controls/DesktopHeader.xaml
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Controls.DesktopHeader">
|
||||
|
||||
<!-- Верхняя панель для десктопа -->
|
||||
<Grid BackgroundColor="#0D6EFD" HeightRequest="56" Padding="20,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Логотип слева -->
|
||||
<HorizontalStackLayout Grid.Column="0" Spacing="10" VerticalOptions="Center">
|
||||
<Image Source="logo.png" HeightRequest="32" WidthRequest="32"/>
|
||||
<Label Text="HiRo" TextColor="White" FontSize="20" FontAttributes="Bold" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Поисковая строка по центру -->
|
||||
<Border Grid.Column="1"
|
||||
StrokeShape="RoundRectangle 20"
|
||||
BackgroundColor="#3D85FD"
|
||||
Stroke="Transparent"
|
||||
Padding="15,0"
|
||||
HeightRequest="40"
|
||||
MaximumWidthRequest="500"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0" Source="search" HeightRequest="18" WidthRequest="18" VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Entry Grid.Column="1"
|
||||
Placeholder="Поиск по названию, автору или..."
|
||||
PlaceholderColor="#CCDDFF"
|
||||
TextColor="White"
|
||||
BackgroundColor="Transparent"
|
||||
Margin="10,0,0,0"
|
||||
VerticalOptions="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Иконки справа -->
|
||||
<HorizontalStackLayout Grid.Column="2" Spacing="15" VerticalOptions="Center">
|
||||
<!-- Уведомления -->
|
||||
<Grid>
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
BackgroundColor="Transparent"
|
||||
HeightRequest="40"
|
||||
WidthRequest="40"
|
||||
Stroke="Transparent">
|
||||
<Image Source="notifications" HeightRequest="24" WidthRequest="24"
|
||||
HorizontalOptions="Center" VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<!-- Бейдж с количеством -->
|
||||
<Border StrokeShape="RoundRectangle 10"
|
||||
BackgroundColor="#FF4444"
|
||||
HeightRequest="18"
|
||||
WidthRequest="18"
|
||||
HorizontalOptions="End"
|
||||
VerticalOptions="Start"
|
||||
Margin="0,-2,-2,0">
|
||||
<Label Text="1" TextColor="White" FontSize="10"
|
||||
HorizontalOptions="Center" VerticalOptions="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Аватар пользователя -->
|
||||
<Border x:Name="UserAvatarBorder"
|
||||
StrokeShape="RoundRectangle 20"
|
||||
HeightRequest="40"
|
||||
WidthRequest="40"
|
||||
BackgroundColor="#E0E0E0"
|
||||
Stroke="White"
|
||||
StrokeThickness="2">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnProfileClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image x:Name="UserAvatar"
|
||||
Source="default_avatar.png"
|
||||
Aspect="AspectFill"/>
|
||||
</Border>
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
|
||||
</ContentView>
|
||||
42
CORP-MAUI/Controls/DesktopHeader.xaml.cs
Normal file
42
CORP-MAUI/Controls/DesktopHeader.xaml.cs
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
29
CORP-MAUI/Controls/DesktopLayout.xaml
Normal file
29
CORP-MAUI/Controls/DesktopLayout.xaml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:CORP_MAUI.Controls"
|
||||
x:Class="CORP_MAUI.Controls.DesktopLayout">
|
||||
|
||||
<!-- Основной layout для десктопа: Header + (Sidebar | Content | Calendar) -->
|
||||
<Grid RowDefinitions="56,*" BackgroundColor="#F5F5F5">
|
||||
|
||||
<!-- Header -->
|
||||
<controls:DesktopHeader Grid.Row="0" x:Name="Header"/>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="200,*,280">
|
||||
|
||||
<!-- Левый Sidebar -->
|
||||
<controls:DesktopSidebar Grid.Column="0" x:Name="Sidebar"/>
|
||||
|
||||
<!-- Центральный контент (сюда встраивается страница) -->
|
||||
<ContentView Grid.Column="1" x:Name="MainContent" Padding="20" BackgroundColor="#F5F5F5"/>
|
||||
|
||||
<!-- Правая панель с календарём -->
|
||||
<controls:DesktopCalendarPanel Grid.Column="2" x:Name="CalendarPanel"/>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ContentView>
|
||||
1189
CORP-MAUI/Controls/DesktopLayout.xaml.cs
Normal file
1189
CORP-MAUI/Controls/DesktopLayout.xaml.cs
Normal file
File diff suppressed because it is too large
Load Diff
152
CORP-MAUI/Controls/DesktopSidebar.xaml
Normal file
152
CORP-MAUI/Controls/DesktopSidebar.xaml
Normal file
@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Controls.DesktopSidebar"
|
||||
WidthRequest="200">
|
||||
|
||||
<!-- Левая боковая панель навигации -->
|
||||
<Border BackgroundColor="White" Stroke="Transparent">
|
||||
<ScrollView>
|
||||
<VerticalStackLayout Padding="10,20" Spacing="2">
|
||||
|
||||
<!-- Профиль -->
|
||||
<Border x:Name="ProfileItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnProfileClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="profile" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Профиль" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Новости -->
|
||||
<Border x:Name="NewsItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnNewsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="feed" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Новости" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Сотрудники -->
|
||||
<Border x:Name="EmployeesItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnEmployeesClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="employees" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Сотрудники" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Отделы -->
|
||||
<Border x:Name="DepartmentsItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnDepartmentsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="department" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Отделы" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Заявления -->
|
||||
<Border x:Name="RequestsItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnRequestsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="statements" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Заявления" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Чаты -->
|
||||
<Border x:Name="ChatsItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnChatsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="chats" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Чаты" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Документы -->
|
||||
<Border x:Name="DocumentsItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnDocumentsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="documents" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Документы" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Финансы -->
|
||||
<Border x:Name="FinanceItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnFinanceClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="finances" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Финансы" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Настройки -->
|
||||
<Border x:Name="SettingsItem" StrokeShape="RoundRectangle 8" Stroke="Transparent" BackgroundColor="Transparent" Padding="12,10">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnSettingsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Image Source="settings" HeightRequest="20" WidthRequest="20">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Настройки" TextColor="#333333" FontSize="15" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</Border>
|
||||
|
||||
</ContentView>
|
||||
100
CORP-MAUI/Controls/DesktopSidebar.xaml.cs
Normal file
100
CORP-MAUI/Controls/DesktopSidebar.xaml.cs
Normal file
@ -0,0 +1,100 @@
|
||||
namespace CORP_MAUI.Controls;
|
||||
|
||||
public partial class DesktopSidebar : ContentView
|
||||
{
|
||||
public event EventHandler<string>? 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");
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
@ -17,6 +17,12 @@ public class Department
|
||||
|
||||
public User? Head { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер для аватарок сотрудников (используется в UI)
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public HorizontalStackLayout? AvatarsContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Описание для отображения (с fallback если пусто)
|
||||
/// </summary>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CORP_MAUI.Models;
|
||||
|
||||
/// <summary>
|
||||
@ -5,13 +7,138 @@ namespace CORP_MAUI.Models;
|
||||
/// </summary>
|
||||
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<object>? Departments { get; set; }
|
||||
|
||||
[JsonPropertyName("recipients")]
|
||||
public List<object>? 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; }
|
||||
|
||||
// Вычисляемые свойства для отображения
|
||||
|
||||
/// <summary>
|
||||
/// Имя загрузившего документ
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string UploaderName => UploadedBy?.FullName ?? "Неизвестный";
|
||||
|
||||
/// <summary>
|
||||
/// URL аватара загрузившего
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string UploaderAvatarUrl => UploadedBy?.Avatar ?? UploadedBy?.AvatarUrl ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Форматированная дата загрузки
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string DisplayUploadedAt => UploadedAt.ToString("dd.MM.yyyy HH:mm");
|
||||
|
||||
/// <summary>
|
||||
/// Расширение файла (для иконки)
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Иконка типа документа (эмодзи)
|
||||
/// </summary>
|
||||
[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" => "📦",
|
||||
_ => "📄"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Цвет статуса ознакомления
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Color AcknowledgedColor => IsAcknowledged
|
||||
? Color.FromArgb("#28A745") // Зеленый
|
||||
: Color.FromArgb("#FFC107"); // Желтый
|
||||
|
||||
/// <summary>
|
||||
/// Текст статуса ознакомления
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string AcknowledgedText => IsAcknowledged ? "Ознакомлен" : "Не ознакомлен";
|
||||
|
||||
/// <summary>
|
||||
/// Иконка статуса ознакомления
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string AcknowledgedIcon => IsAcknowledged ? "✅" : "⏳";
|
||||
|
||||
/// <summary>
|
||||
/// Короткое описание файла
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string ShortDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
return Description.Length > 100 ? Description.Substring(0, 100) + "..." : Description;
|
||||
}
|
||||
return SentToAll ? "Для всех сотрудников" : "Персональный документ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
|
||||
|
||||
79
CORP-MAUI/Pages/ChatsPage.xaml
Normal file
79
CORP-MAUI/Pages/ChatsPage.xaml
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="CORP_MAUI.Pages.ChatsPage"
|
||||
Title="Чаты"
|
||||
BackgroundColor="White">
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<!-- Заголовок страницы -->
|
||||
<Border Grid.Row="0"
|
||||
BackgroundColor="{StaticResource Primary}"
|
||||
Padding="16"
|
||||
StrokeThickness="0">
|
||||
<Label Text="Чаты"
|
||||
TextColor="White"
|
||||
FontSize="20"
|
||||
FontAttributes="Bold"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Контент-заглушка -->
|
||||
<VerticalStackLayout Grid.Row="1"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center"
|
||||
Spacing="24"
|
||||
Padding="32">
|
||||
|
||||
<!-- Иконка -->
|
||||
<Border WidthRequest="120"
|
||||
HeightRequest="120"
|
||||
StrokeShape="RoundRectangle 60"
|
||||
BackgroundColor="{StaticResource Primary}"
|
||||
Opacity="0.15"
|
||||
HorizontalOptions="Center">
|
||||
<Image Source="chats"
|
||||
WidthRequest="60"
|
||||
HeightRequest="60"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Заголовок -->
|
||||
<Label Text="Чаты скоро появятся"
|
||||
FontSize="24"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Gray900}"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
|
||||
<!-- Описание -->
|
||||
<Label Text="Функционал чатов находится в разработке. Скоро вы сможете общаться с коллегами прямо в приложении."
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource Gray600}"
|
||||
HorizontalTextAlignment="Center"
|
||||
MaximumWidthRequest="300"
|
||||
LineHeight="1.4"/>
|
||||
|
||||
<!-- Дополнительная информация -->
|
||||
<Border BackgroundColor="#E8F4FD"
|
||||
StrokeShape="RoundRectangle 12"
|
||||
Padding="16"
|
||||
StrokeThickness="0"
|
||||
Margin="0,16,0,0">
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<Label Text="💡"
|
||||
FontSize="20"
|
||||
VerticalOptions="Center"/>
|
||||
<Label Text="Пока что вы можете открыть чат с конкретным сотрудником через его профиль"
|
||||
FontSize="13"
|
||||
TextColor="{StaticResource Primary}"
|
||||
MaximumWidthRequest="250"
|
||||
LineHeight="1.3"/>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
||||
9
CORP-MAUI/Pages/ChatsPage.xaml.cs
Normal file
9
CORP-MAUI/Pages/ChatsPage.xaml.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace CORP_MAUI.Pages;
|
||||
|
||||
public partial class ChatsPage : ContentPage
|
||||
{
|
||||
public ChatsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -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">
|
||||
|
||||
<Shell.BackButtonBehavior>
|
||||
<BackButtonBehavior IsVisible="True" TextOverride="Назад" />
|
||||
|
||||
@ -129,7 +129,20 @@ public partial class DepartmentDetailsPage : ContentPage
|
||||
/// </summary>
|
||||
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] ⚠️ Не удалось определить выбранного сотрудника");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -150,7 +167,27 @@ public partial class DepartmentDetailsPage : ContentPage
|
||||
/// </summary>
|
||||
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()
|
||||
|
||||
@ -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">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
|
||||
@ -87,98 +87,19 @@
|
||||
IsRefreshing="False"
|
||||
Refreshing="OnRefreshing">
|
||||
|
||||
<CollectionView
|
||||
x:Name="DepartmentsCollectionView"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnDepartmentSelected"
|
||||
BackgroundColor="Transparent">
|
||||
|
||||
<CollectionView.EmptyView>
|
||||
<VerticalStackLayout Style="{StaticResource EmptyStateContainer}">
|
||||
<Label Text="🏢" Style="{StaticResource EmptyStateIcon}"/>
|
||||
<Label
|
||||
Text="Нет отделов"
|
||||
Style="{StaticResource EmptyStateTitle}"
|
||||
Margin="0,10,0,0"/>
|
||||
<Label
|
||||
Text="Попробуйте изменить параметры поиска"
|
||||
Style="{StaticResource EmptyStateText}"/>
|
||||
</VerticalStackLayout>
|
||||
</CollectionView.EmptyView>
|
||||
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Department">
|
||||
<Border
|
||||
Style="{StaticResource CardBorder}"
|
||||
Margin="10,5">
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
<!-- Название отдела -->
|
||||
<Label
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Text="{Binding Name}"
|
||||
Style="{StaticResource CardTitle}"/>
|
||||
|
||||
<!-- Описание -->
|
||||
<Label
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Text="{Binding DisplayDescription}"
|
||||
Style="{StaticResource CardText}"
|
||||
Margin="0,5,0,0"
|
||||
LineBreakMode="TailTruncation"
|
||||
MaxLines="2"/>
|
||||
|
||||
<!-- Информация -->
|
||||
<HorizontalStackLayout
|
||||
Grid.Column="0"
|
||||
Grid.Row="2"
|
||||
Spacing="15"
|
||||
Margin="0,8,0,0">
|
||||
|
||||
<!-- Количество сотрудников -->
|
||||
<HorizontalStackLayout Spacing="5">
|
||||
<Image Source="employees.png" WidthRequest="16" HeightRequest="16" VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="{StaticResource Primary}"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="{Binding EmployeesCount}" Style="{StaticResource CardMeta}"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Руководитель -->
|
||||
<HorizontalStackLayout Spacing="5">
|
||||
<Image Source="profile.png" WidthRequest="16" HeightRequest="16" VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="{StaticResource Primary}"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label
|
||||
Text="{Binding Head.FullName}"
|
||||
Style="{StaticResource CardMeta}"
|
||||
LineBreakMode="TailTruncation"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Стрелка -->
|
||||
<Label
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Text="›"
|
||||
FontSize="30"
|
||||
TextColor="{StaticResource Gray400}"
|
||||
VerticalOptions="Center"/>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
<ScrollView
|
||||
Orientation="Vertical"
|
||||
HorizontalScrollBarVisibility="Never">
|
||||
<FlexLayout
|
||||
x:Name="DepartmentsFlexLayout"
|
||||
Direction="Row"
|
||||
Wrap="Wrap"
|
||||
JustifyContent="Start"
|
||||
AlignItems="Start"
|
||||
AlignContent="Start"
|
||||
Padding="10,5">
|
||||
</FlexLayout>
|
||||
</ScrollView>
|
||||
</RefreshView>
|
||||
|
||||
<!-- Индикатор загрузки -->
|
||||
|
||||
@ -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<Department> _departments;
|
||||
private List<Department> _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<Department>();
|
||||
|
||||
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<Department> 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<Department> departments)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var dept in departments)
|
||||
{
|
||||
if (dept.Head?.Id == null)
|
||||
continue;
|
||||
|
||||
var card = DepartmentsFlexLayout.Children
|
||||
.OfType<Border>()
|
||||
.FirstOrDefault(b => b.ClassId == dept.Id.ToString());
|
||||
|
||||
if (card?.Content is VerticalStackLayout vstack)
|
||||
{
|
||||
var avatarStack = vstack.Children.OfType<HorizontalStackLayout>().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<Department> filtered;
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
// Показываем все отделы
|
||||
_departments.Clear();
|
||||
foreach (Department department in _allDepartments.OrderBy(d => d.Name))
|
||||
{
|
||||
_departments.Add(department);
|
||||
}
|
||||
filtered = _allDepartments;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Фильтруем отделы
|
||||
List<Department> 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
CORP-MAUI/Pages/DesktopMainPage.xaml
Normal file
12
CORP-MAUI/Pages/DesktopMainPage.xaml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:CORP_MAUI.Controls"
|
||||
x:Class="CORP_MAUI.Pages.DesktopMainPage"
|
||||
Shell.NavBarIsVisible="False"
|
||||
Shell.TabBarIsVisible="False"
|
||||
BackgroundColor="#F5F5F5">
|
||||
|
||||
<controls:DesktopLayout x:Name="DesktopLayout"/>
|
||||
|
||||
</ContentPage>
|
||||
21
CORP-MAUI/Pages/DesktopMainPage.xaml.cs
Normal file
21
CORP-MAUI/Pages/DesktopMainPage.xaml.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
268
CORP-MAUI/Pages/DocumentsPage.xaml
Normal file
268
CORP-MAUI/Pages/DocumentsPage.xaml
Normal file
@ -0,0 +1,268 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:models="clr-namespace:CORP_MAUI.Models"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Pages.DocumentsPage"
|
||||
Title="Документы"
|
||||
BackgroundColor="White">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*">
|
||||
|
||||
<!-- Шапка -->
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="50,*,50"
|
||||
Padding="15,10"
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Кнопка назад (для навигации) -->
|
||||
<ImageButton
|
||||
x:Name="BackButton"
|
||||
Grid.Column="0"
|
||||
Source="arrow_back.png"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
BackgroundColor="Transparent"
|
||||
Clicked="OnBackClicked"
|
||||
IsVisible="False">
|
||||
<ImageButton.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="{StaticResource Primary}"/>
|
||||
</ImageButton.Behaviors>
|
||||
</ImageButton>
|
||||
|
||||
<!-- Заголовок -->
|
||||
<Label
|
||||
Grid.Column="1"
|
||||
Text="Документы"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Gray900}"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"/>
|
||||
|
||||
<!-- Пустое место справа -->
|
||||
<BoxView Grid.Column="2" Color="Transparent"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Поиск с серым фоном -->
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
ColumnDefinitions="Auto,*"
|
||||
Padding="15,5,15,10"
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Круглая кнопка поиска -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
WidthRequest="45"
|
||||
HeightRequest="45"
|
||||
StrokeShape="RoundRectangle 22.5"
|
||||
BackgroundColor="{StaticResource Primary}"
|
||||
Stroke="Transparent"
|
||||
Margin="0,0,10,0">
|
||||
<Image
|
||||
Source="search.png"
|
||||
WidthRequest="24"
|
||||
HeightRequest="24"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
|
||||
<!-- Поле поиска с серым фоном -->
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
StrokeShape="RoundRectangle 22.5"
|
||||
BackgroundColor="{StaticResource Gray300}"
|
||||
Stroke="Transparent"
|
||||
HeightRequest="45"
|
||||
Padding="15,0">
|
||||
<Entry
|
||||
x:Name="SearchEntry"
|
||||
Placeholder="Поиск документов..."
|
||||
PlaceholderColor="{StaticResource Gray500}"
|
||||
TextColor="{StaticResource Gray900}"
|
||||
BackgroundColor="Transparent"
|
||||
VerticalOptions="Center"
|
||||
TextChanged="OnSearchTextChanged"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Фильтры -->
|
||||
<ScrollView
|
||||
Grid.Row="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalScrollBarVisibility="Never"
|
||||
BackgroundColor="White">
|
||||
<HorizontalStackLayout
|
||||
Padding="15,5,15,15"
|
||||
Spacing="10"
|
||||
x:Name="FilterButtons">
|
||||
|
||||
<Border
|
||||
x:Name="FilterAll"
|
||||
Style="{StaticResource ChipButtonActive}">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnFilterAllTapped"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Label Text="Все" FontSize="14" TextColor="White"/>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="FilterAcknowledged"
|
||||
Style="{StaticResource ChipButton}">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnFilterAcknowledgedTapped"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Label Text="✅ Ознакомлен" FontSize="14" TextColor="{StaticResource Gray700}"/>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="FilterNotAcknowledged"
|
||||
Style="{StaticResource ChipButton}">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnFilterNotAcknowledgedTapped"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Label Text="⏳ Не ознакомлен" FontSize="14" TextColor="{StaticResource Gray700}"/>
|
||||
</Border>
|
||||
|
||||
</HorizontalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- Список документов -->
|
||||
<RefreshView
|
||||
Grid.Row="3"
|
||||
x:Name="RefreshView"
|
||||
IsRefreshing="False"
|
||||
Refreshing="OnRefreshing">
|
||||
|
||||
<CollectionView
|
||||
x:Name="DocumentsCollectionView"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnDocumentSelected"
|
||||
BackgroundColor="Transparent">
|
||||
|
||||
<CollectionView.EmptyView>
|
||||
<VerticalStackLayout Style="{StaticResource EmptyStateContainer}">
|
||||
<Label Text="📄" Style="{StaticResource EmptyStateIcon}"/>
|
||||
<Label
|
||||
Text="Нет документов"
|
||||
Style="{StaticResource EmptyStateTitle}"
|
||||
Margin="0,10,0,0"/>
|
||||
<Label
|
||||
Text="Здесь будут отображаться документы для ознакомления"
|
||||
Style="{StaticResource EmptyStateText}"/>
|
||||
</VerticalStackLayout>
|
||||
</CollectionView.EmptyView>
|
||||
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Document">
|
||||
<Border
|
||||
Style="{StaticResource CardBorder}"
|
||||
Margin="10,5"
|
||||
StrokeShape="RoundRectangle 12">
|
||||
|
||||
<Grid ColumnDefinitions="50,*,Auto" RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
<!-- Иконка типа документа -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="3"
|
||||
Style="{StaticResource IconContainer}"
|
||||
VerticalOptions="Start"
|
||||
Margin="0,0,10,0">
|
||||
<Label Text="{Binding FileIcon}" Style="{StaticResource IconContainerText}"/>
|
||||
</Border>
|
||||
|
||||
<!-- Название документа -->
|
||||
<Label
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Text="{Binding Title}"
|
||||
Style="{StaticResource CardTitle}"
|
||||
LineBreakMode="TailTruncation"
|
||||
MaxLines="2"/>
|
||||
|
||||
<!-- Статус ознакомления (бейдж) -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource Badge}"
|
||||
BackgroundColor="{Binding AcknowledgedColor}"
|
||||
VerticalOptions="Center">
|
||||
<Label Text="{Binding AcknowledgedText}" Style="{StaticResource BadgeText}"/>
|
||||
</Border>
|
||||
|
||||
<!-- Описание / Тип документа -->
|
||||
<Label
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
Text="{Binding ShortDescription}"
|
||||
Style="{StaticResource CardSubtitle}"
|
||||
LineBreakMode="TailTruncation"
|
||||
MaxLines="2"
|
||||
Margin="0,5,0,0"/>
|
||||
|
||||
<!-- Информация о загрузке -->
|
||||
<HorizontalStackLayout
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Spacing="10"
|
||||
Margin="0,8,0,0">
|
||||
|
||||
<!-- Кто загрузил -->
|
||||
<HorizontalStackLayout Spacing="5">
|
||||
<Label
|
||||
Text="👤"
|
||||
FontSize="12"
|
||||
VerticalOptions="Center"/>
|
||||
<Label
|
||||
Text="{Binding UploaderName}"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray500}"
|
||||
VerticalOptions="Center"
|
||||
LineBreakMode="TailTruncation"
|
||||
MaxLines="1"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Разделитель -->
|
||||
<Label
|
||||
Text="•"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray400}"
|
||||
VerticalOptions="Center"/>
|
||||
|
||||
<!-- Дата загрузки -->
|
||||
<Label
|
||||
Text="{Binding DisplayUploadedAt}"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray500}"
|
||||
VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</RefreshView>
|
||||
|
||||
<!-- Индикатор загрузки -->
|
||||
<ActivityIndicator
|
||||
x:Name="LoadingIndicator"
|
||||
Grid.Row="3"
|
||||
IsRunning="False"
|
||||
IsVisible="False"
|
||||
Color="{StaticResource Primary}"
|
||||
WidthRequest="50"
|
||||
HeightRequest="50"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"/>
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
272
CORP-MAUI/Pages/DocumentsPage.xaml.cs
Normal file
272
CORP-MAUI/Pages/DocumentsPage.xaml.cs
Normal file
@ -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<Document> _documents;
|
||||
private List<Document> _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<Document>();
|
||||
|
||||
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<Document> 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
|
||||
}
|
||||
@ -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">
|
||||
|
||||
<Shell.BackButtonBehavior>
|
||||
<BackButtonBehavior IsVisible="True" TextOverride="Назад" />
|
||||
|
||||
@ -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">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
|
||||
|
||||
@ -43,53 +43,66 @@
|
||||
Stroke="Transparent"
|
||||
Shadow="{Shadow Brush=Black, Opacity=0.2, Offset='0,4', Radius=8}">
|
||||
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<VerticalStackLayout Spacing="0">
|
||||
|
||||
<!-- Имя пользователя -->
|
||||
<!-- Email -->
|
||||
<Label
|
||||
Text="Имя пользователя"
|
||||
Text="Email"
|
||||
FontSize="14"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Gray700}"/>
|
||||
TextColor="{StaticResource Gray700}"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<Border
|
||||
StrokeShape="RoundRectangle 10"
|
||||
StrokeShape="RoundRectangle 10,10,0,0"
|
||||
BackgroundColor="{StaticResource Gray100}"
|
||||
Stroke="{StaticResource Gray200}"
|
||||
StrokeThickness="1"
|
||||
Padding="12,0"
|
||||
HeightRequest="50">
|
||||
<Entry
|
||||
x:Name="UsernameEntry"
|
||||
Placeholder="Введите имя пользователя"
|
||||
Placeholder="Введите email"
|
||||
PlaceholderColor="{StaticResource Gray400}"
|
||||
TextColor="{StaticResource Gray900}"
|
||||
BackgroundColor="Transparent"
|
||||
Keyboard="Email"
|
||||
ReturnType="Next"
|
||||
Completed="OnEmailCompleted"
|
||||
VerticalOptions="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Пароль -->
|
||||
<Label
|
||||
Text="Пароль"
|
||||
FontSize="14"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Gray700}"
|
||||
Margin="0,10,0,0"/>
|
||||
|
||||
<!-- Пароль (объединён с полем Email) -->
|
||||
<Border
|
||||
StrokeShape="RoundRectangle 10"
|
||||
StrokeShape="RoundRectangle 0,0,10,10"
|
||||
BackgroundColor="{StaticResource Gray100}"
|
||||
Stroke="{StaticResource Gray200}"
|
||||
StrokeThickness="1"
|
||||
Padding="12,0"
|
||||
HeightRequest="50">
|
||||
<Entry
|
||||
x:Name="PasswordEntry"
|
||||
Placeholder="Введите пароль"
|
||||
IsPassword="True"
|
||||
PlaceholderColor="{StaticResource Gray400}"
|
||||
TextColor="{StaticResource Gray900}"
|
||||
BackgroundColor="Transparent"
|
||||
VerticalOptions="Center"/>
|
||||
HeightRequest="50"
|
||||
Margin="0,-1,0,0">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<BoxView
|
||||
Grid.Column="0"
|
||||
WidthRequest="1"
|
||||
BackgroundColor="{StaticResource Gray200}"
|
||||
IsVisible="False"/>
|
||||
<Entry
|
||||
Grid.Column="1"
|
||||
x:Name="PasswordEntry"
|
||||
Placeholder="Введите пароль"
|
||||
IsPassword="True"
|
||||
PlaceholderColor="{StaticResource Gray400}"
|
||||
TextColor="{StaticResource Gray900}"
|
||||
BackgroundColor="Transparent"
|
||||
ReturnType="Done"
|
||||
Completed="OnPasswordCompleted"
|
||||
VerticalOptions="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Разделитель между полями и кнопкой -->
|
||||
<BoxView HeightRequest="20" BackgroundColor="Transparent"/>
|
||||
|
||||
<!-- Сообщение об ошибке -->
|
||||
<Label
|
||||
@ -97,14 +110,14 @@
|
||||
IsVisible="False"
|
||||
TextColor="{StaticResource Error}"
|
||||
FontSize="13"
|
||||
HorizontalOptions="Center"/>
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,0,0,10"/>
|
||||
|
||||
<!-- Кнопка входа -->
|
||||
<Button
|
||||
x:Name="LoginButton"
|
||||
Text="Войти"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
Margin="0,15,0,0"
|
||||
Clicked="OnLoginClicked"/>
|
||||
|
||||
<!-- Индикатор загрузки -->
|
||||
@ -113,7 +126,8 @@
|
||||
IsRunning="False"
|
||||
IsVisible="False"
|
||||
Color="{StaticResource Primary}"
|
||||
HorizontalOptions="Center"/>
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,10,0,0"/>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
@ -142,6 +142,22 @@ public partial class LoginPage : ContentPage
|
||||
SetLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// При нажатии Enter/Next в поле Email - переходим к полю пароля
|
||||
/// </summary>
|
||||
private void OnEmailCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
PasswordEntry.Focus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// При нажатии Enter/Done в поле пароля - отправляем форму
|
||||
/// </summary>
|
||||
private void OnPasswordCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
OnLoginClicked(sender!, e);
|
||||
}
|
||||
|
||||
private async Task ShowError(string message)
|
||||
{
|
||||
|
||||
392
CORP-MAUI/Pages/MenuPage.xaml
Normal file
392
CORP-MAUI/Pages/MenuPage.xaml
Normal file
@ -0,0 +1,392 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Pages.MenuPage"
|
||||
Title="Меню"
|
||||
BackgroundColor="White"
|
||||
Shell.NavBarIsVisible="False"
|
||||
Shell.TabBarIsVisible="False">
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto" Padding="20">
|
||||
|
||||
<!-- Заголовок с кнопкой закрытия -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,20,0,40">
|
||||
<Label Text="Меню"
|
||||
FontSize="24"
|
||||
FontAttributes="Bold"
|
||||
TextColor="#212529"
|
||||
HorizontalOptions="Center"
|
||||
Grid.ColumnSpan="2"/>
|
||||
|
||||
<Border Grid.Column="1"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
StrokeShape="RoundRectangle 20"
|
||||
BackgroundColor="Transparent"
|
||||
Stroke="Transparent"
|
||||
HorizontalOptions="End">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnCloseClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="cross"
|
||||
WidthRequest="24"
|
||||
HeightRequest="24"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="#888888"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Сетка разделов -->
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="*,*,*"
|
||||
RowDefinitions="Auto,Auto,Auto"
|
||||
ColumnSpacing="16"
|
||||
RowSpacing="24"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center"
|
||||
MaximumWidthRequest="400">
|
||||
|
||||
<!-- Новости -->
|
||||
<VerticalStackLayout Grid.Row="0" Grid.Column="0" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnNewsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="feed"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Новости"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Отделы -->
|
||||
<VerticalStackLayout Grid.Row="0" Grid.Column="1" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnDepartmentsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="department"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Отделы"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Сотрудники -->
|
||||
<VerticalStackLayout Grid.Row="0" Grid.Column="2" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnEmployeesClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="employees"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Сотрудники"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Заявления -->
|
||||
<VerticalStackLayout Grid.Row="1" Grid.Column="0" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnRequestsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="statements"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Заявления"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Чаты -->
|
||||
<VerticalStackLayout Grid.Row="1" Grid.Column="1" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnChatsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="chats"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Чаты"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Документы -->
|
||||
<VerticalStackLayout Grid.Row="1" Grid.Column="2" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnDocumentsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="documents"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Документы"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Финансы -->
|
||||
<VerticalStackLayout Grid.Row="2" Grid.Column="0" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnFinancesClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="finances"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Финансы"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Профиль -->
|
||||
<VerticalStackLayout Grid.Row="2" Grid.Column="1" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnProfileClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="profile"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Профиль"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Настройки -->
|
||||
<VerticalStackLayout Grid.Row="2" Grid.Column="2" Spacing="10">
|
||||
<Border StrokeShape="RoundRectangle 20"
|
||||
StrokeThickness="0"
|
||||
HeightRequest="100"
|
||||
WidthRequest="100">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#5B9DF9" Offset="0"/>
|
||||
<GradientStop Color="#3D7BE8" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnSettingsClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image Source="settings"
|
||||
WidthRequest="48"
|
||||
HeightRequest="48"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
<Label Text="Настройки"
|
||||
FontSize="14"
|
||||
TextColor="#212529"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Переключатель темы -->
|
||||
<Border Grid.Row="2"
|
||||
StrokeShape="RoundRectangle 25"
|
||||
BackgroundColor="#0D6EFD"
|
||||
StrokeThickness="0"
|
||||
Padding="4"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,20,0,20">
|
||||
<HorizontalStackLayout Spacing="0">
|
||||
<!-- Светлая тема -->
|
||||
<Border x:Name="LightThemeButton"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
StrokeShape="RoundRectangle 20"
|
||||
BackgroundColor="White"
|
||||
StrokeThickness="0">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnLightThemeClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image x:Name="LightThemeIcon"
|
||||
Source="lightmode"
|
||||
WidthRequest="20"
|
||||
HeightRequest="20"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior x:Name="LightThemeIconTint" TintColor="#0D6EFD"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
|
||||
<!-- Тёмная тема -->
|
||||
<Border x:Name="DarkThemeButton"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
StrokeShape="RoundRectangle 20"
|
||||
BackgroundColor="Transparent"
|
||||
StrokeThickness="0">
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnDarkThemeClicked"/>
|
||||
</Border.GestureRecognizers>
|
||||
<Image x:Name="DarkThemeIcon"
|
||||
Source="darkmode"
|
||||
WidthRequest="20"
|
||||
HeightRequest="20"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior x:Name="DarkThemeIconTint" TintColor="White"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
</Border>
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
||||
168
CORP-MAUI/Pages/MenuPage.xaml.cs
Normal file
168
CORP-MAUI/Pages/MenuPage.xaml.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CORP_MAUI.Pages;
|
||||
|
||||
public partial class MenuPage : ContentPage
|
||||
{
|
||||
// Сохраняем индекс вкладки, с которой открыли меню
|
||||
private static int _previousTabIndex = 2; // По умолчанию - Лента
|
||||
|
||||
public static void SetPreviousTabIndex(int index)
|
||||
{
|
||||
if (index != 0) // 0 - это меню, не сохраняем
|
||||
{
|
||||
_previousTabIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
public MenuPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void OnCloseClicked(object? sender, EventArgs e)
|
||||
{
|
||||
// Сначала переключаемся на предыдущую вкладку
|
||||
if (Shell.Current?.CurrentItem is TabBar tabBar && tabBar.Items.Count > _previousTabIndex)
|
||||
{
|
||||
Shell.Current.CurrentItem = tabBar.Items[_previousTabIndex];
|
||||
}
|
||||
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
}
|
||||
|
||||
private async Task NavigateToTabAsync(int tabIndex)
|
||||
{
|
||||
// Переключаем на нужную вкладку
|
||||
if (Shell.Current?.CurrentItem is TabBar tabBar && tabBar.Items.Count > tabIndex)
|
||||
{
|
||||
Shell.Current.CurrentItem = tabBar.Items[tabIndex];
|
||||
}
|
||||
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
}
|
||||
|
||||
private async void OnNewsClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await NavigateToTabAsync(2); // Лента - третья вкладка (индекс 2)
|
||||
}
|
||||
|
||||
private async void OnDepartmentsClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
// Отделы нет в нижней панели - переходим через Shell navigation
|
||||
try
|
||||
{
|
||||
await Shell.Current.GoToAsync(nameof(DepartmentsPage));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback - показываем сообщение
|
||||
if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
|
||||
{
|
||||
await page.DisplayAlert("Отделы", "Перейдите в раздел 'Отделы' через меню", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnEmployeesClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await NavigateToTabAsync(4); // Сотрудники - пятая вкладка (индекс 4)
|
||||
}
|
||||
|
||||
private async void OnRequestsClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await NavigateToTabAsync(3); // Заявления - четвёртая вкладка (индекс 3)
|
||||
}
|
||||
|
||||
private async void OnChatsClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await NavigateToTabAsync(1); // Сообщения - вторая вкладка (индекс 1)
|
||||
}
|
||||
|
||||
private async void OnDocumentsClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
// Переходим на страницу документов
|
||||
try
|
||||
{
|
||||
await Shell.Current.GoToAsync(nameof(DocumentsPage));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback - показываем сообщение
|
||||
if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
|
||||
{
|
||||
await page.DisplayAlert("Документы", "Не удалось открыть раздел документов", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnFinancesClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
// Финансы пока не реализованы
|
||||
if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
|
||||
{
|
||||
await page.DisplayAlert("Финансы", "Раздел финансов находится в разработке", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnProfileClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
// Профиль нет в нижней панели - переходим через Shell navigation
|
||||
try
|
||||
{
|
||||
await Shell.Current.GoToAsync(nameof(ProfilePage));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback - показываем сообщение
|
||||
if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
|
||||
{
|
||||
await page.DisplayAlert("Профиль", "Перейдите в раздел 'Профиль' через меню", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSettingsClicked(object? sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PopModalAsync(animated: false);
|
||||
// Настройки пока не реализованы
|
||||
if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
|
||||
{
|
||||
await page.DisplayAlert("Настройки", "Раздел настроек находится в разработке", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLightThemeClicked(object? sender, EventArgs e)
|
||||
{
|
||||
// Активируем светлую тему - белый ползунок слева
|
||||
LightThemeButton.BackgroundColor = Colors.White;
|
||||
DarkThemeButton.BackgroundColor = Colors.Transparent;
|
||||
|
||||
// Иконка на белом фоне - акцентный цвет
|
||||
LightThemeIconTint.TintColor = Color.FromArgb("#0D6EFD");
|
||||
// Иконка на прозрачном (синем) фоне - белая
|
||||
DarkThemeIconTint.TintColor = Colors.White;
|
||||
|
||||
// TODO: Применить светлую тему к приложению
|
||||
Trace.WriteLine("[MenuPage] Светлая тема выбрана");
|
||||
}
|
||||
|
||||
private void OnDarkThemeClicked(object? sender, EventArgs e)
|
||||
{
|
||||
// Активируем тёмную тему - белый ползунок справа
|
||||
DarkThemeButton.BackgroundColor = Colors.White;
|
||||
LightThemeButton.BackgroundColor = Colors.Transparent;
|
||||
|
||||
// Иконка на белом фоне - акцентный цвет
|
||||
DarkThemeIconTint.TintColor = Color.FromArgb("#0D6EFD");
|
||||
// Иконка на прозрачном (синем) фоне - белая
|
||||
LightThemeIconTint.TintColor = Colors.White;
|
||||
|
||||
// TODO: Применить тёмную тему к приложению
|
||||
Trace.WriteLine("[MenuPage] Тёмная тема выбрана");
|
||||
}
|
||||
}
|
||||
11
CORP-MAUI/Pages/MenuTabPage.xaml
Normal file
11
CORP-MAUI/Pages/MenuTabPage.xaml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="CORP_MAUI.Pages.MenuTabPage"
|
||||
Title="Меню"
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Эта страница служит только для открытия меню -->
|
||||
<!-- Автоматически открывает модальное меню при появлении -->
|
||||
|
||||
</ContentPage>
|
||||
30
CORP-MAUI/Pages/MenuTabPage.xaml.cs
Normal file
30
CORP-MAUI/Pages/MenuTabPage.xaml.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace CORP_MAUI.Pages;
|
||||
|
||||
public partial class MenuTabPage : ContentPage
|
||||
{
|
||||
public MenuTabPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
// Проверяем, нет ли уже открытого модального окна
|
||||
if (Navigation.ModalStack.Count > 0) return;
|
||||
|
||||
// Небольшая задержка для UI
|
||||
await Task.Delay(10);
|
||||
|
||||
// Открываем модальное меню
|
||||
try
|
||||
{
|
||||
await Navigation.PushModalAsync(new MenuPage(), animated: false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем ошибки навигации
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
x:Class="CORP_MAUI.Pages.PostDetailsPage"
|
||||
x:DataType="viewModels:PostDetailsViewModel"
|
||||
Title="Публикация"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="50,*,50"
|
||||
Padding="15,10"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Кнопка назад -->
|
||||
<Border
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Pages.PostsPage"
|
||||
Title="Лента новостей"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
Grid.Row="1"
|
||||
ColumnDefinitions="Auto,*"
|
||||
Padding="15,5,15,15"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Круглая кнопка поиска -->
|
||||
<Border
|
||||
|
||||
@ -42,12 +42,10 @@ public partial class PostsPage
|
||||
var user = await ServiceProvider.ApiService.GetCurrentUserAsync();
|
||||
if (user != null)
|
||||
{
|
||||
Trace.WriteLine($"[PostsPage] >>> Загрузка аватара для {user.FullName}, URL: {user.DisplayAvatarUrl}");
|
||||
Trace.WriteLine($"[PostsPage] >>> Загрузка аватара для {user.FullName}");
|
||||
|
||||
await ServiceProvider.ApiService.LoadUserAvatarAsync(user);
|
||||
|
||||
Trace.WriteLine($"[PostsPage] IsAvatarLoaded: {user.IsAvatarLoaded}, CachedPath: {user.CachedAvatarFilePath}");
|
||||
|
||||
// Обновляем на главном потоке
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
x:Class="CORP_MAUI.Pages.ProfilePage"
|
||||
x:DataType="viewModels:ProfileViewModel"
|
||||
Title="Профиль"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="50,*,50"
|
||||
Padding="15,10"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Пустое место слева -->
|
||||
<BoxView Grid.Column="0" Color="Transparent"/>
|
||||
@ -113,6 +113,21 @@
|
||||
|
||||
<BoxView HeightRequest="1" Color="{StaticResource Gray200}"/>
|
||||
|
||||
<!-- Телефон -->
|
||||
<Grid ColumnDefinitions="Auto,*" IsVisible="{Binding HasPhone}">
|
||||
<HorizontalStackLayout Grid.Column="0" Spacing="8">
|
||||
<Image Source="phone.png" WidthRequest="20" HeightRequest="20" VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="{StaticResource Primary}"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="Телефон" Style="{StaticResource CardText}" FontAttributes="Bold" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
<Label Grid.Column="1" Text="{Binding Phone}" Style="{StaticResource CardText}" HorizontalOptions="End" VerticalOptions="Center"/>
|
||||
</Grid>
|
||||
|
||||
<BoxView HeightRequest="1" Color="{StaticResource Gray200}" IsVisible="{Binding HasPhone}"/>
|
||||
|
||||
<!-- Отдел -->
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<HorizontalStackLayout Grid.Column="0" Spacing="8">
|
||||
@ -128,6 +143,30 @@
|
||||
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Все отделы (если несколько) -->
|
||||
<Border Style="{StaticResource CardBorderLarge}" IsVisible="{Binding HasDepartments}">
|
||||
<VerticalStackLayout Spacing="12">
|
||||
|
||||
<Label Text="Все отделы" Style="{StaticResource SectionTitle}"/>
|
||||
|
||||
<VerticalStackLayout BindableLayout.ItemsSource="{Binding Departments}" Spacing="8">
|
||||
<BindableLayout.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Department">
|
||||
<HorizontalStackLayout Spacing="8">
|
||||
<Image Source="departments.png" WidthRequest="16" HeightRequest="16" VerticalOptions="Center">
|
||||
<Image.Behaviors>
|
||||
<toolkit:IconTintColorBehavior TintColor="{StaticResource Primary}"/>
|
||||
</Image.Behaviors>
|
||||
</Image>
|
||||
<Label Text="{Binding Name}" Style="{StaticResource CardText}" VerticalOptions="Center"/>
|
||||
</HorizontalStackLayout>
|
||||
</DataTemplate>
|
||||
</BindableLayout.ItemTemplate>
|
||||
</VerticalStackLayout>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Навыки -->
|
||||
<Border Style="{StaticResource CardBorderLarge}" IsVisible="{Binding HasSkills}">
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
x:Class="CORP_MAUI.Pages.RequestsPage"
|
||||
Title="Заявления"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*">
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="50,*,50"
|
||||
Padding="15,10"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Пустое место слева -->
|
||||
<BoxView Grid.Column="0" Color="Transparent"/>
|
||||
@ -38,7 +38,7 @@
|
||||
Grid.Row="1"
|
||||
ColumnDefinitions="Auto,*"
|
||||
Padding="15,5,15,10"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
|
||||
<!-- Круглая кнопка поиска -->
|
||||
<Border
|
||||
@ -85,7 +85,7 @@
|
||||
Grid.Row="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalScrollBarVisibility="Never"
|
||||
BackgroundColor="#E5E5E5">
|
||||
BackgroundColor="White">
|
||||
<HorizontalStackLayout
|
||||
Padding="15,5,15,15"
|
||||
Spacing="10"
|
||||
|
||||
19
CORP-MAUI/Platforms/Android/Resources/values/styles.xml
Normal file
19
CORP-MAUI/Platforms/Android/Resources/values/styles.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Переопределяем стиль BottomNavigationView для Shell TabBar -->
|
||||
<!-- Увеличиваем размер иконок и убираем текст -->
|
||||
<style name="Widget.Design.BottomNavigationView" parent="Widget.MaterialComponents.BottomNavigationView">
|
||||
<!-- Увеличиваем размер иконок на ~15% (стандартный 24dp -> 28dp) -->
|
||||
<item name="itemIconSize">28dp</item>
|
||||
<!-- Убираем текст под иконками - иконки автоматически центрируются -->
|
||||
<item name="labelVisibilityMode">unlabeled</item>
|
||||
<!-- Высота панели -->
|
||||
<item name="android:minHeight">56dp</item>
|
||||
</style>
|
||||
|
||||
<!-- Дополнительный стиль для Material3 -->
|
||||
<style name="Widget.Material3.BottomNavigationView" parent="Widget.MaterialComponents.BottomNavigationView">
|
||||
<item name="itemIconSize">28dp</item>
|
||||
<item name="labelVisibilityMode">unlabeled</item>
|
||||
</style>
|
||||
</resources>
|
||||
6
CORP-MAUI/Resources/Images/Icons/SVG/cross.svg
Normal file
6
CORP-MAUI/Resources/Images/Icons/SVG/cross.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="#0D6EFD" d="m12 14.122l5.303 5.303a1.5 1.5 0 0 0 2.122-2.122L14.12 12l5.304-5.303a1.5 1.5 0 1 0-2.122-2.121L12 9.879L6.697 4.576a1.5 1.5 0 1 0-2.122 2.12L9.88 12l-5.304 5.304a1.5 1.5 0 1 0 2.122 2.12z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 858 B |
@ -783,6 +783,23 @@ public class ApiService : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загрузить аватарки для списка пользователей
|
||||
/// </summary>
|
||||
public Task LoadUsersAvatarsAsync(List<User>? users)
|
||||
{
|
||||
if (users == null) return Task.CompletedTask;
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.AvatarUrl) && !user.AvatarUrl.StartsWith("http"))
|
||||
{
|
||||
user.AvatarUrl = $"{_config.BaseUrl.TrimEnd('/')}/{user.AvatarUrl.TrimStart('/')}";
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить информацию о конкретном пользователе по ID
|
||||
/// </summary>
|
||||
@ -1099,6 +1116,46 @@ public class ApiService : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить все документы (с автоматической пагинацией)
|
||||
/// </summary>
|
||||
public async Task<List<Document>> GetAllDocumentsAsync()
|
||||
{
|
||||
List<Document> allDocuments = new List<Document>();
|
||||
int page = 1;
|
||||
bool hasMore = true;
|
||||
|
||||
try
|
||||
{
|
||||
Trace.WriteLine("[ApiService] >>> Загрузка всех документов...");
|
||||
|
||||
while (hasMore)
|
||||
{
|
||||
PaginatedResponse<Document> response = await GetDocumentsPaginatedAsync(page);
|
||||
|
||||
if (response.Results != null && response.Results.Any())
|
||||
{
|
||||
allDocuments.AddRange(response.Results);
|
||||
Trace.WriteLine($"[ApiService] >>> Загружено документов: {allDocuments.Count} / {response.Count}");
|
||||
hasMore = !string.IsNullOrEmpty(response.Next);
|
||||
page++;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
Trace.WriteLine($"[ApiService] ✅ Всего загружено документов: {allDocuments.Count}");
|
||||
return allDocuments;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"[ApiService] ❌ Ошибка загрузки документов: {ex.Message}");
|
||||
return allDocuments;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// REQUESTS (Запросы)
|
||||
// ============================================
|
||||
|
||||
@ -29,8 +29,12 @@ public class ProfileViewModel : INotifyPropertyChanged
|
||||
OnPropertyChanged(nameof(FullName));
|
||||
OnPropertyChanged(nameof(Username));
|
||||
OnPropertyChanged(nameof(Email));
|
||||
OnPropertyChanged(nameof(Phone));
|
||||
OnPropertyChanged(nameof(HasPhone));
|
||||
OnPropertyChanged(nameof(Position));
|
||||
OnPropertyChanged(nameof(Department));
|
||||
OnPropertyChanged(nameof(Departments));
|
||||
OnPropertyChanged(nameof(HasDepartments));
|
||||
OnPropertyChanged(nameof(StatusText));
|
||||
OnPropertyChanged(nameof(StatusColor));
|
||||
OnPropertyChanged(nameof(Skills));
|
||||
@ -53,8 +57,12 @@ public class ProfileViewModel : INotifyPropertyChanged
|
||||
public string FullName => _user?.FullName ?? "Загрузка...";
|
||||
public string Username => _user != null ? $"@{_user.Username}" : "";
|
||||
public string Email => _user?.Email ?? "-";
|
||||
public string Phone => _user?.PhoneNumber ?? "-";
|
||||
public bool HasPhone => !string.IsNullOrEmpty(_user?.PhoneNumber);
|
||||
public string Position => _user?.PositionName ?? "-";
|
||||
public string Department => _user?.DepartmentName ?? "-";
|
||||
public List<Department>? Departments => _user?.Departments;
|
||||
public bool HasDepartments => _user?.Departments != null && _user.Departments.Any();
|
||||
public bool IsOnline => _user?.IsOnline ?? false;
|
||||
public string StatusText => IsOnline ? "В сети" : "Не в сети";
|
||||
public Color StatusColor => IsOnline ? Colors.LimeGreen : Colors.Gray;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user