Pārlūkot izejas kodu

Initial commit\

shitsobu 1 nedēļu atpakaļ
revīzija
5c1e6812f8
54 mainītis faili ar 2258 papildinājumiem un 0 dzēšanām
  1. 6 0
      .gitignore
  2. 3 0
      practiceApplicaiton.slnx
  3. 13 0
      practiceApplicaiton/App.xaml
  4. 14 0
      practiceApplicaiton/App.xaml.cs
  5. 22 0
      practiceApplicaiton/AppShell.xaml
  6. 10 0
      practiceApplicaiton/AppShell.xaml.cs
  7. 30 0
      practiceApplicaiton/BookingEditPage.xaml
  8. 79 0
      practiceApplicaiton/BookingEditPage.xaml.cs
  9. 78 0
      practiceApplicaiton/BookingsPage.xaml
  10. 72 0
      practiceApplicaiton/BookingsPage.xaml.cs
  11. 22 0
      practiceApplicaiton/ClientEditPage.xaml
  12. 58 0
      practiceApplicaiton/ClientEditPage.xaml.cs
  13. 44 0
      practiceApplicaiton/ClientsPage.xaml
  14. 56 0
      practiceApplicaiton/ClientsPage.xaml.cs
  15. 75 0
      practiceApplicaiton/Database.cs
  16. 16 0
      practiceApplicaiton/HotelApp.csproj
  17. 46 0
      practiceApplicaiton/LoginPage.xaml
  18. 58 0
      practiceApplicaiton/LoginPage.xaml.cs
  19. 36 0
      practiceApplicaiton/MainPage.xaml
  20. 23 0
      practiceApplicaiton/MainPage.xaml.cs
  21. 24 0
      practiceApplicaiton/MauiProgram.cs
  22. 47 0
      practiceApplicaiton/Models.cs
  23. 6 0
      practiceApplicaiton/Platforms/Android/AndroidManifest.xml
  24. 11 0
      practiceApplicaiton/Platforms/Android/MainActivity.cs
  25. 16 0
      practiceApplicaiton/Platforms/Android/MainApplication.cs
  26. 6 0
      practiceApplicaiton/Platforms/Android/Resources/values/colors.xml
  27. 10 0
      practiceApplicaiton/Platforms/MacCatalyst/AppDelegate.cs
  28. 14 0
      practiceApplicaiton/Platforms/MacCatalyst/Entitlements.plist
  29. 40 0
      practiceApplicaiton/Platforms/MacCatalyst/Info.plist
  30. 16 0
      practiceApplicaiton/Platforms/MacCatalyst/Program.cs
  31. 8 0
      practiceApplicaiton/Platforms/Windows/App.xaml
  32. 25 0
      practiceApplicaiton/Platforms/Windows/App.xaml.cs
  33. 46 0
      practiceApplicaiton/Platforms/Windows/Package.appxmanifest
  34. 17 0
      practiceApplicaiton/Platforms/Windows/app.manifest
  35. 10 0
      practiceApplicaiton/Platforms/iOS/AppDelegate.cs
  36. 32 0
      practiceApplicaiton/Platforms/iOS/Info.plist
  37. 16 0
      practiceApplicaiton/Platforms/iOS/Program.cs
  38. 51 0
      practiceApplicaiton/Platforms/iOS/Resources/PrivacyInfo.xcprivacy
  39. 8 0
      practiceApplicaiton/Properties/launchSettings.json
  40. 86 0
      practiceApplicaiton/ReportPage.xaml
  41. 33 0
      practiceApplicaiton/ReportPage.xaml.cs
  42. 279 0
      practiceApplicaiton/Repository.cs
  43. 4 0
      practiceApplicaiton/Resources/AppIcon/appicon.svg
  44. 8 0
      practiceApplicaiton/Resources/AppIcon/appiconfg.svg
  45. BIN
      practiceApplicaiton/Resources/Fonts/OpenSans-Regular.ttf
  46. BIN
      practiceApplicaiton/Resources/Fonts/OpenSans-Semibold.ttf
  47. BIN
      practiceApplicaiton/Resources/Images/dotnet_bot.png
  48. 15 0
      practiceApplicaiton/Resources/Raw/AboutAssets.txt
  49. 8 0
      practiceApplicaiton/Resources/Splash/splash.svg
  50. 44 0
      practiceApplicaiton/Resources/Styles/Colors.xaml
  51. 434 0
      practiceApplicaiton/Resources/Styles/Styles.xaml
  52. 54 0
      practiceApplicaiton/RoomsPage.xaml
  53. 55 0
      practiceApplicaiton/RoomsPage.xaml.cs
  54. 74 0
      practiceApplicaiton/practiceApplicaiton.csproj

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+.vs/
+bin/
+obj/
+
+*.user
+*.suo

+ 3 - 0
practiceApplicaiton.slnx

@@ -0,0 +1,3 @@
+<Solution>
+  <Project Path="practiceApplicaiton/practiceApplicaiton.csproj" />
+</Solution>

+ 13 - 0
practiceApplicaiton/App.xaml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="practiceApplicaiton.App">
+    <Application.Resources>
+        <ResourceDictionary>
+            <Color x:Key="Primary">#2E4057</Color>
+            <Color x:Key="Secondary">#4472C4</Color>
+            <Color x:Key="Accent">#FFF2CC</Color>
+            <Color x:Key="Danger">#C0392B</Color>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>

+ 14 - 0
practiceApplicaiton/App.xaml.cs

@@ -0,0 +1,14 @@
+namespace practiceApplicaiton;
+
+public partial class App : Application
+{
+    public App()
+    {
+        InitializeComponent();
+    }
+
+    protected override Window CreateWindow(IActivationState? activationState)
+    {
+        return new Window(new LoginPage());
+    }
+}

+ 22 - 0
practiceApplicaiton/AppShell.xaml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+       xmlns:local="clr-namespace:practiceApplicaiton"
+       x:Class="practiceApplicaiton.AppShell"
+       Shell.NavBarIsVisible="True">
+
+    <TabBar>
+        <Tab Title="Бронирования" Icon="booking.png">
+            <ShellContent ContentTemplate="{DataTemplate local:BookingsPage}"/>
+        </Tab>
+        <Tab Title="Клиенты" Icon="client.png">
+            <ShellContent ContentTemplate="{DataTemplate local:ClientsPage}"/>
+        </Tab>
+        <Tab Title="Комнаты" Icon="room.png">
+            <ShellContent ContentTemplate="{DataTemplate local:RoomsPage}"/>
+        </Tab>
+        <Tab Title="Отчёт" Icon="report.png">
+            <ShellContent ContentTemplate="{DataTemplate local:ReportPage}"/>
+        </Tab>
+    </TabBar>
+</Shell>

+ 10 - 0
practiceApplicaiton/AppShell.xaml.cs

@@ -0,0 +1,10 @@
+namespace practiceApplicaiton;
+
+public partial class AppShell : Shell
+{
+    public AppShell()
+    {
+        InitializeComponent();
+        Title = $"Гостиничный корпус  |  {Database.CurrentUserName}";
+    }
+}

+ 30 - 0
practiceApplicaiton/BookingEditPage.xaml

@@ -0,0 +1,30 @@
+<?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="practiceApplicaiton.BookingEditPage"
+             Title="Бронирование">
+    <ScrollView>
+        <VerticalStackLayout Padding="20" Spacing="12">
+
+            <Label Text="Клиент" FontAttributes="Bold"/>
+            <Picker x:Name="ClientPicker" Title="Выберите клиента"/>
+
+            <Label Text="Комната" FontAttributes="Bold"/>
+            <Picker x:Name="RoomPicker" Title="Выберите комнату"/>
+
+            <Label Text="Дата заезда" FontAttributes="Bold"/>
+            <DatePicker x:Name="CheckInPicker"/>
+
+            <Label Text="Дата выезда" FontAttributes="Bold"/>
+            <DatePicker x:Name="CheckOutPicker"/>
+
+            <Label Text="Цена за ночь (₽)" FontAttributes="Bold"/>
+            <Entry x:Name="PriceEntry" Keyboard="Numeric" Placeholder="2500"/>
+
+            <Button Text="Сохранить" BackgroundColor="#4472C4"
+                    TextColor="White" CornerRadius="8"
+                    Clicked="OnSaveClicked"/>
+
+        </VerticalStackLayout>
+    </ScrollView>
+</ContentPage>

+ 79 - 0
practiceApplicaiton/BookingEditPage.xaml.cs

@@ -0,0 +1,79 @@
+namespace practiceApplicaiton;
+
+public partial class BookingEditPage : ContentPage
+{
+    private readonly Booking? _booking;
+    private List<Client> _clients = [];
+    private List<Room> _rooms = [];
+
+    public BookingEditPage(Booking? booking)
+    {
+        InitializeComponent();
+        _booking = booking;
+        Title = booking == null ? "Новое бронирование" : "Редактировать бронирование";
+    }
+
+    protected override async void OnAppearing()
+    {
+        base.OnAppearing();
+        _clients = await Repository.GetClientsAsync();
+        _rooms   = await Repository.GetRoomsAsync();
+
+        ClientPicker.ItemsSource = _clients.Select(c => c.FullName).ToList();
+        RoomPicker.ItemsSource   = _rooms.Select(r => $"{r.RoomNumber} ({r.TypeName})").ToList();
+
+        if (_booking != null)
+        {
+            var ci = _clients.FindIndex(c => c.ClientId == _booking.ClientId);
+            var ri = _rooms.FindIndex(r => r.RoomId == _booking.RoomId);
+            ClientPicker.SelectedIndex  = ci;
+            RoomPicker.SelectedIndex    = ri;
+            CheckInPicker.Date          = _booking.CheckInDate;
+            CheckOutPicker.Date         = _booking.CheckOutDate;
+            PriceEntry.Text             = _booking.PricePerNight.ToString();
+        }
+        else
+        {
+            CheckInPicker.Date  = DateTime.Today;
+            CheckOutPicker.Date = DateTime.Today.AddDays(3);
+        }
+    }
+
+    private async void OnSaveClicked(object sender, EventArgs e)
+    {
+        if (ClientPicker.SelectedIndex < 0 || RoomPicker.SelectedIndex < 0)
+        {
+            await DisplayAlert("Ошибка", "Выберите клиента и комнату", "OK");
+            return;
+        }
+        if (!decimal.TryParse(PriceEntry.Text, out var price) || price <= 0)
+        {
+            await DisplayAlert("Ошибка", "Введите корректную цену", "OK");
+            return;
+        }
+        if (CheckOutPicker.Date <= CheckInPicker.Date)
+        {
+            await DisplayAlert("Ошибка", "Дата выезда должна быть позже заезда", "OK");
+            return;
+        }
+
+        try
+        {
+            var client = _clients[ClientPicker.SelectedIndex];
+            var room   = _rooms[RoomPicker.SelectedIndex];
+
+            if (_booking == null)
+                await Repository.CreateBookingAsync(client.ClientId, room.RoomId,
+                    (DateTime)CheckInPicker.Date, (DateTime)CheckOutPicker.Date, price);
+            else
+                await Repository.UpdateBookingAsync(_booking.BookingId,
+                    (DateTime)CheckInPicker.Date, (DateTime)CheckOutPicker.Date, price);
+
+            await Navigation.PopAsync();
+        }
+        catch (Exception ex)
+        {
+            await DisplayAlert("Ошибка", ex.Message, "OK");
+        }
+    }
+}

+ 78 - 0
practiceApplicaiton/BookingsPage.xaml

@@ -0,0 +1,78 @@
+<?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="practiceApplicaiton.BookingsPage"
+             Title="Бронирования">
+    <Grid RowDefinitions="Auto,*,Auto" Padding="16" RowSpacing="10">
+
+        <!-- Поиск -->
+        <Grid Grid.Row="0" ColumnDefinitions="*,Auto" ColumnSpacing="8">
+            <Entry x:Name="SearchEntry" Placeholder="Поиск по фамилии или номеру комнаты"
+                   TextChanged="OnSearchChanged"/>
+            <Button Grid.Column="1" Text="+ Добавить"
+                    BackgroundColor="#4472C4" TextColor="White"
+                    CornerRadius="8" Clicked="OnAddClicked"
+                    IsVisible="{Binding CanEdit}"/>
+        </Grid>
+
+        <!-- Список -->
+        <CollectionView Grid.Row="1" x:Name="BookingsList"
+                        SelectionMode="Single"
+                        SelectionChanged="OnSelectionChanged">
+            <CollectionView.ItemTemplate>
+                <DataTemplate>
+                    <Frame Margin="0,4" Padding="12" CornerRadius="8"
+                           BackgroundColor="White" BorderColor="#E0E0E0">
+                        <Grid ColumnDefinitions="*,Auto">
+                            <VerticalStackLayout Spacing="4">
+                                <Label Text="{Binding ClientFullName}" TextColor="#4472C4"
+                                       FontAttributes="Bold" FontSize="15"/>
+                                <Label TextColor="#555">
+                                    <Label.FormattedText>
+                                        <FormattedString>
+                                            <Span Text="Комната: "/>
+                                            <Span Text="{Binding RoomNumber}" FontAttributes="Bold"/>
+                                            <Span Text="  ("/>
+                                            <Span Text="{Binding RoomType}"/>
+                                            <Span Text=")"/>
+                                        </FormattedString>
+                                    </Label.FormattedText>
+                                </Label>
+                                <Label TextColor="#555">
+                                    <Label.FormattedText>
+                                        <FormattedString>
+                                            <Span Text="{Binding CheckInDate, StringFormat='{0:dd.MM.yyyy}'}"/>
+                                            <Span Text=" — "/>
+                                            <Span Text="{Binding CheckOutDate, StringFormat='{0:dd.MM.yyyy}'}"/>
+                                            <Span Text="  "/>
+                                            <Span Text="{Binding Nights}"/>
+                                            <Span Text=" ночей"/>
+                                        </FormattedString>
+                                    </Label.FormattedText>
+                                </Label>
+                            </VerticalStackLayout>
+                            <Label Grid.Column="1"
+                                   Text="{Binding TotalCost, StringFormat='{0:N0} ₽'}"
+                                   FontAttributes="Bold" FontSize="16"
+                                   TextColor="#2E4057" VerticalOptions="Center"/>
+                        </Grid>
+                    </Frame>
+                </DataTemplate>
+            </CollectionView.ItemTemplate>
+        </CollectionView>
+
+        <!-- Кнопки действий -->
+        <Grid Grid.Row="2" ColumnDefinitions="*,*" ColumnSpacing="8"
+              IsVisible="{Binding CanEdit}">
+            <Button Text="Редактировать" BackgroundColor="#4472C4"
+                    TextColor="White" CornerRadius="8"
+                    x:Name="EditButton" IsEnabled="False"
+                    Clicked="OnEditClicked"/>
+            <Button Grid.Column="1" Text="Удалить"
+                    BackgroundColor="#C0392B" TextColor="White"
+                    CornerRadius="8" x:Name="DeleteButton" IsEnabled="False"
+                    Clicked="OnDeleteClicked"/>
+        </Grid>
+
+    </Grid>
+</ContentPage>

+ 72 - 0
practiceApplicaiton/BookingsPage.xaml.cs

@@ -0,0 +1,72 @@
+namespace practiceApplicaiton;
+
+public partial class BookingsPage : ContentPage
+{
+    private Booking? _selected;
+    public bool CanEdit => Database.IsAdmin;
+
+    public BookingsPage()
+    {
+        InitializeComponent();
+        BindingContext = this;
+    }
+
+    protected override async void OnAppearing()
+    {
+        base.OnAppearing();
+        await LoadAsync();
+    }
+
+    private async Task LoadAsync(string search = "")
+    {
+        try
+        {
+            var data = await Repository.GetBookingsAsync(search);
+            BookingsList.ItemsSource = data;
+        }
+        catch (Exception ex)
+        {
+            await DisplayAlert("Ошибка", ex.Message, "OK");
+        }
+    }
+
+    private async void OnSearchChanged(object sender, TextChangedEventArgs e)
+        => await LoadAsync(e.NewTextValue);
+
+    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        _selected = e.CurrentSelection.FirstOrDefault() as Booking;
+        EditButton.IsEnabled = _selected != null;
+        DeleteButton.IsEnabled = _selected != null;
+    }
+
+    private async void OnAddClicked(object sender, EventArgs e)
+    {
+        await Navigation.PushAsync(new BookingEditPage(null));
+        await LoadAsync();
+    }
+
+    private async void OnEditClicked(object sender, EventArgs e)
+    {
+        if (_selected == null) return;
+        await Navigation.PushAsync(new BookingEditPage(_selected));
+        await LoadAsync();
+    }
+
+    private async void OnDeleteClicked(object sender, EventArgs e)
+    {
+        if (_selected == null) return;
+        bool confirm = await DisplayAlert("Удалить",
+            $"Удалить бронирование #{_selected.BookingId}?", "Да", "Отмена");
+        if (!confirm) return;
+        try
+        {
+            await Repository.DeleteBookingAsync(_selected.BookingId);
+            await LoadAsync();
+        }
+        catch (Exception ex)
+        {
+            await DisplayAlert("Ошибка", ex.Message, "OK");
+        }
+    }
+}

+ 22 - 0
practiceApplicaiton/ClientEditPage.xaml

@@ -0,0 +1,22 @@
+<?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="practiceApplicaiton.ClientEditPage"
+             Title="Клиент">
+    <ScrollView>
+        <VerticalStackLayout Padding="20" Spacing="12">
+            <Label Text="Фамилия" FontAttributes="Bold" TextColor="#4472C4"/>
+            <Entry x:Name="LastNameEntry" Placeholder="Иванов"/>
+            <Label Text="Имя" FontAttributes="Bold" TextColor="#4472C4"/>
+            <Entry x:Name="FirstNameEntry" Placeholder="Александр"/>
+            <Label Text="Отчество" FontAttributes="Bold" TextColor="#4472C4"/>
+            <Entry x:Name="PatronymicEntry" Placeholder="(необязательно)"/>
+            <Label Text="Телефон" FontAttributes="Bold" TextColor="#4472C4"/>
+            <Entry x:Name="PhoneEntry" Keyboard="Telephone" Placeholder="+79001234567"/>
+            <Label Text="Турфирма" FontAttributes="Bold" TextColor="#4472C4"/>
+            <Picker x:Name="FirmPicker" Title="Выберите турфирму" TextColor="#4472C4"/>
+            <Button Text="Сохранить" BackgroundColor="#4472C4"
+                    TextColor="White" CornerRadius="8" Clicked="OnSaveClicked"/>
+        </VerticalStackLayout>
+    </ScrollView>
+</ContentPage>

+ 58 - 0
practiceApplicaiton/ClientEditPage.xaml.cs

@@ -0,0 +1,58 @@
+namespace practiceApplicaiton;
+
+public partial class ClientEditPage : ContentPage
+{
+    private readonly Client? _client;
+    private List<TourFirm> _firms = [];
+
+    public ClientEditPage(Client? client)
+    {
+        InitializeComponent();
+        _client = client;
+        Title = client == null ? "Новый клиент" : "Редактировать клиента";
+    }
+
+    protected override async void OnAppearing()
+    {
+        base.OnAppearing();
+        _firms = await Repository.GetFirmsAsync();
+        FirmPicker.ItemsSource = _firms.Select(f => f.FirmName).ToList();
+
+        if (_client != null)
+        {
+            LastNameEntry.Text   = _client.LastName;
+            FirstNameEntry.Text  = _client.FirstName;
+            PatronymicEntry.Text = _client.Patronymic;
+            PhoneEntry.Text      = _client.PhoneNumber;
+            FirmPicker.SelectedIndex = _firms.FindIndex(f => f.FirmId == _client.FirmId);
+        }
+    }
+
+    private async void OnSaveClicked(object sender, EventArgs e)
+    {
+        if (string.IsNullOrWhiteSpace(LastNameEntry.Text) ||
+            string.IsNullOrWhiteSpace(FirstNameEntry.Text) ||
+            string.IsNullOrWhiteSpace(PhoneEntry.Text) ||
+            FirmPicker.SelectedIndex < 0)
+        {
+            await DisplayAlert("Ошибка", "Заполните все обязательные поля", "OK");
+            return;
+        }
+        var c = new Client
+        {
+            ClientId    = _client?.ClientId ?? 0,
+            FirmId      = _firms[FirmPicker.SelectedIndex].FirmId,
+            LastName    = LastNameEntry.Text.Trim(),
+            FirstName   = FirstNameEntry.Text.Trim(),
+            Patronymic  = PatronymicEntry.Text?.Trim() ?? "",
+            PhoneNumber = PhoneEntry.Text.Trim(),
+        };
+        try
+        {
+            if (_client == null) await Repository.AddClientAsync(c);
+            else                 await Repository.UpdateClientAsync(c);
+            await Navigation.PopAsync();
+        }
+        catch (Exception ex) { await DisplayAlert("Ошибка", ex.Message, "OK"); }
+    }
+}

+ 44 - 0
practiceApplicaiton/ClientsPage.xaml

@@ -0,0 +1,44 @@
+<?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="practiceApplicaiton.ClientsPage"
+             Title="Клиенты">
+    <Grid RowDefinitions="Auto,*,Auto" Padding="16" RowSpacing="10">
+
+        <Grid Grid.Row="0" ColumnDefinitions="*,Auto" ColumnSpacing="8">
+            <Entry x:Name="SearchEntry" Placeholder="Поиск по фамилии или телефону"
+                   TextChanged="OnSearchChanged"/>
+            <Button Grid.Column="1" Text="+ Добавить"
+                    BackgroundColor="#4472C4" TextColor="White" CornerRadius="8"
+                    Clicked="OnAddClicked"/>
+        </Grid>
+
+        <CollectionView Grid.Row="1" x:Name="ClientsList"
+                        SelectionMode="Single" SelectionChanged="OnSelectionChanged">
+            <CollectionView.ItemTemplate>
+                <DataTemplate>
+                    <Frame Margin="0,4" Padding="12" CornerRadius="8"
+                           BackgroundColor="White" BorderColor="#E0E0E0">
+                        <Grid ColumnDefinitions="*,Auto">
+                            <VerticalStackLayout Spacing="3">
+                                <Label Text="{Binding FullName}" TextColor="#4472C4"
+                                       FontAttributes="Bold" FontSize="15"/>
+                                <Label Text="{Binding PhoneNumber}" TextColor="#555"/>
+                                <Label Text="{Binding FirmName}" TextColor="#888" FontSize="12"/>
+                            </VerticalStackLayout>
+                        </Grid>
+                    </Frame>
+                </DataTemplate>
+            </CollectionView.ItemTemplate>
+        </CollectionView>
+
+        <Grid Grid.Row="2" ColumnDefinitions="*,*" ColumnSpacing="8">
+            <Button Text="Редактировать" BackgroundColor="#4472C4"
+                    TextColor="White" CornerRadius="8"
+                    x:Name="EditButton" IsEnabled="False" Clicked="OnEditClicked"/>
+            <Button Grid.Column="1" Text="Удалить"
+                    BackgroundColor="#C0392B" TextColor="White" CornerRadius="8"
+                    x:Name="DeleteButton" IsEnabled="False" Clicked="OnDeleteClicked"/>
+        </Grid>
+    </Grid>
+</ContentPage>

+ 56 - 0
practiceApplicaiton/ClientsPage.xaml.cs

@@ -0,0 +1,56 @@
+namespace practiceApplicaiton;
+
+public partial class ClientsPage : ContentPage
+{
+    private Client? _selected;
+
+    public ClientsPage() => InitializeComponent();
+
+    protected override async void OnAppearing()
+    {
+        base.OnAppearing();
+        await LoadAsync();
+    }
+
+    private async Task LoadAsync(string search = "")
+    {
+        try { ClientsList.ItemsSource = await Repository.GetClientsAsync(search); }
+        catch (Exception ex) { await DisplayAlert("Ошибка", ex.Message, "OK"); }
+    }
+
+    private async void OnSearchChanged(object sender, TextChangedEventArgs e)
+        => await LoadAsync(e.NewTextValue);
+
+    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        _selected = e.CurrentSelection.FirstOrDefault() as Client;
+        EditButton.IsEnabled = DeleteButton.IsEnabled = _selected != null;
+    }
+
+    private async void OnAddClicked(object sender, EventArgs e)
+    {
+        await Navigation.PushAsync(new ClientEditPage(null));
+        await LoadAsync();
+    }
+
+    private async void OnEditClicked(object sender, EventArgs e)
+    {
+        if (_selected == null) return;
+        await Navigation.PushAsync(new ClientEditPage(_selected));
+        await LoadAsync();
+    }
+
+    private async void OnDeleteClicked(object sender, EventArgs e)
+    {
+        if (_selected == null) return;
+        bool ok = await DisplayAlert("Удалить",
+            $"Удалить клиента {_selected.FullName}?", "Да", "Отмена");
+        if (!ok) return;
+        try
+        {
+            await Repository.DeleteClientAsync(_selected.ClientId);
+            await LoadAsync();
+        }
+        catch (Exception ex) { await DisplayAlert("Ошибка", ex.Message, "OK"); }
+    }
+}

+ 75 - 0
practiceApplicaiton/Database.cs

@@ -0,0 +1,75 @@
+using Npgsql;
+
+namespace practiceApplicaiton;
+
+/// <summary>
+/// Роли приложения (совпадают с ролями PostgreSQL)
+/// </summary>
+public enum AppRole { Admin, Manager }
+
+/// <summary>
+/// Менеджер подключения к БД.
+/// Строка подключения формируется по роли — каждая роль имеет
+/// отдельного пользователя PostgreSQL с ограниченными правами.
+/// </summary>
+public static class Database
+{
+    // Текущая роль сессии
+    public static AppRole CurrentRole { get; private set; }
+    public static string CurrentUserName { get; private set; } = "";
+
+    private static string _connectionString = "";
+
+    private static readonly Dictionary<AppRole, string> AppPasswords = new()
+{
+    { AppRole.Admin,   "admin" },
+    { AppRole.Manager, "manager" },
+};
+
+    /// <summary>
+    /// Инициализирует подключение с проверкой логина/пароля через PostgreSQL.
+    /// Возвращает true если успешно.
+    /// </summary>
+    public static async Task<bool> ConnectAsync(AppRole role, string password)
+    {
+        // Простая проверка пароля на уровне приложения (дополнительный слой)
+        if (password != AppPasswords[role])
+            return false;
+
+        var cs = new NpgsqlConnectionStringBuilder
+        {
+            Host = "edu-pg.itiscaf.ru",
+            Port = 5432,
+            Database = "dbwork",
+            Username = "zakharov_pi",
+            Password = "z4HpEB87TGS",
+            SslMode = SslMode.Prefer,
+        };
+
+        _connectionString = cs.ConnectionString;
+        CurrentRole = role;
+
+        // Проверяем реальное подключение
+        try
+        {
+            await using var conn = new NpgsqlConnection(_connectionString);
+            await conn.OpenAsync();
+            return true;
+        }
+        catch
+        {
+            _connectionString = "";
+            return false;
+        }
+    }
+
+    public static NpgsqlConnection GetConnection()
+    {
+        if (string.IsNullOrEmpty(_connectionString))
+            throw new InvalidOperationException("Нет подключения к БД");
+        return new NpgsqlConnection(_connectionString);
+    }
+
+    public static bool IsConnected => !string.IsNullOrEmpty(_connectionString);
+    public static bool IsAdmin => CurrentRole == AppRole.Admin;
+}

+ 16 - 0
practiceApplicaiton/HotelApp.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>net10.0-windows10.0.19041.0</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>HotelApp</RootNamespace>
+    <UseMaui>true</UseMaui>
+    <SingleProject>true</SingleProject>
+    <Nullable>enable</Nullable>
+    <ApplicationTitle>Гостиничный корпус</ApplicationTitle>
+    <ApplicationId>com.hotel.app</ApplicationId>
+    <ApplicationVersion>1</ApplicationVersion>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Npgsql" Version="8.0.3" />
+  </ItemGroup>
+</Project>

+ 46 - 0
practiceApplicaiton/LoginPage.xaml

@@ -0,0 +1,46 @@
+<?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="practiceApplicaiton.LoginPage"
+             Title="Вход в систему"
+             BackgroundColor="#F5F7FA">
+    <VerticalStackLayout Padding="60" Spacing="20" VerticalOptions="Center">
+
+        <Label Text="Гостиничный корпус"
+               FontSize="28" FontAttributes="Bold"
+               HorizontalOptions="Center" TextColor="#2E4057"/>
+
+        <Label Text="Вход в систему" FontSize="16"
+               HorizontalOptions="Center" TextColor="#666"/>
+
+        <Frame BackgroundColor="LightGray" CornerRadius="12" Padding="24"
+               BorderColor="#E0E0E0" HasShadow="True">
+            <VerticalStackLayout Spacing="16">
+
+                <Label Text="Роль" FontAttributes="Bold" TextColor="#2E4057"/>
+                <Picker x:Name="RolePicker" Title="Выберите роль">
+                    <Picker.Items>
+                        <x:String>Администратор</x:String>
+                        <x:String>Менеджер</x:String>
+                    </Picker.Items>
+                </Picker>
+
+                <Label Text="Пароль" FontAttributes="Bold" TextColor="#2E4057"/>
+                <Entry x:Name="PasswordEntry" IsPassword="True"
+                       Placeholder="Введите пароль"
+                       ReturnCommand="{Binding LoginCommand}"/>
+
+                <Label x:Name="ErrorLabel" TextColor="Red"
+                       IsVisible="False" HorizontalOptions="Center"/>
+
+                <Button x:Name="LoginButton"
+                        Text="Войти"
+                        BackgroundColor="#4472C4"
+                        TextColor="White"
+                        CornerRadius="8"
+                        Clicked="OnLoginClicked"/>
+            </VerticalStackLayout>
+        </Frame>
+
+    </VerticalStackLayout>
+</ContentPage>

+ 58 - 0
practiceApplicaiton/LoginPage.xaml.cs

@@ -0,0 +1,58 @@
+namespace practiceApplicaiton;
+
+public partial class LoginPage : ContentPage
+{
+    public LoginPage()
+    {
+        InitializeComponent();
+        RolePicker.SelectedIndex = 1; // По умолчанию — Менеджер
+    }
+
+    private async void OnLoginClicked(object sender, EventArgs e)
+    {
+        ErrorLabel.IsVisible = false;
+        LoginButton.IsEnabled = false;
+
+        if (RolePicker.SelectedIndex < 0)
+        {
+            ShowError("Выберите роль");
+            return;
+        }
+
+        var role = RolePicker.SelectedIndex == 0 ? AppRole.Admin : AppRole.Manager;
+        var password = PasswordEntry.Text ?? "";
+
+        if (string.IsNullOrWhiteSpace(password))
+        {
+            ShowError("Введите пароль");
+            return;
+        }
+
+        try
+        {
+            var ok = await Database.ConnectAsync(role, password);
+            if (ok)
+            {
+                Application.Current!.Windows[0].Page = new AppShell();
+            }
+            else
+            {
+                ShowError("Неверный пароль или нет доступа к БД");
+            }
+        }
+        catch (Exception ex)
+        {
+            ShowError($"Ошибка подключения: {ex.Message}");
+        }
+        finally
+        {
+            LoginButton.IsEnabled = true;
+        }
+    }
+
+    private void ShowError(string msg)
+    {
+        ErrorLabel.Text = msg;
+        ErrorLabel.IsVisible = true;
+    }
+}

+ 36 - 0
practiceApplicaiton/MainPage.xaml

@@ -0,0 +1,36 @@
+<?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="practiceApplicaiton.MainPage">
+
+    <ScrollView>
+        <VerticalStackLayout
+            Padding="30,0"
+            Spacing="25">
+            <Image
+                Source="dotnet_bot.png"
+                HeightRequest="185"
+                Aspect="AspectFit"
+                SemanticProperties.Description="dot net bot in a submarine number ten" />
+
+            <Label
+                Text="Hello, World!"
+                Style="{StaticResource Headline}"
+                SemanticProperties.HeadingLevel="Level1" />
+
+            <Label
+                Text="Welcome to &#10;.NET Multi-platform App UI"
+                Style="{StaticResource SubHeadline}"
+                SemanticProperties.HeadingLevel="Level2"
+                SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
+
+            <Button
+                x:Name="CounterBtn"
+                Text="Click me" 
+                SemanticProperties.Hint="Counts the number of times you click"
+                Clicked="OnCounterClicked"
+                HorizontalOptions="Fill" />
+        </VerticalStackLayout>
+    </ScrollView>
+
+</ContentPage>

+ 23 - 0
practiceApplicaiton/MainPage.xaml.cs

@@ -0,0 +1,23 @@
+namespace practiceApplicaiton;
+
+public partial class MainPage : ContentPage
+{
+    int count = 0;
+
+    public MainPage()
+    {
+        InitializeComponent();
+    }
+
+    private void OnCounterClicked(object? sender, EventArgs e)
+    {
+        count++;
+
+        if (count == 1)
+            CounterBtn.Text = $"Clicked {count} time";
+        else
+            CounterBtn.Text = $"Clicked {count} times";
+
+        SemanticScreenReader.Announce(CounterBtn.Text);
+    }
+}

+ 24 - 0
practiceApplicaiton/MauiProgram.cs

@@ -0,0 +1,24 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Maui.Hosting;
+
+namespace practiceApplicaiton;
+
+public static class MauiProgram
+{
+    public static MauiApp CreateMauiApp()
+    {
+        var builder = MauiApp.CreateBuilder();
+        builder
+            .UseMauiApp<App>()
+            .ConfigureFonts(fonts =>
+            {
+                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+            });
+
+#if DEBUG
+        builder.Logging.AddDebug();
+#endif
+        return builder.Build();
+    }
+}

+ 47 - 0
practiceApplicaiton/Models.cs

@@ -0,0 +1,47 @@
+namespace practiceApplicaiton;
+
+public class Booking
+{
+    public int BookingId { get; set; }
+    public int ClientId { get; set; }
+    public int RoomId { get; set; }
+    public DateTime CheckInDate { get; set; }
+    public DateTime CheckOutDate { get; set; }
+    public decimal PricePerNight { get; set; }
+
+    // Из JOIN — для отображения
+    public string ClientFullName { get; set; } = "";
+    public int RoomNumber { get; set; }
+    public string RoomType { get; set; } = "";
+    public int Nights => (CheckOutDate - CheckInDate).Days;
+    public decimal TotalCost => PricePerNight * Nights; // Расчётный атрибут
+}
+
+public class Client
+{
+    public int ClientId { get; set; }
+    public int FirmId { get; set; }
+    public string LastName { get; set; } = "";
+    public string FirstName { get; set; } = "";
+    public string Patronymic { get; set; } = "";
+    public string PhoneNumber { get; set; } = "";
+    public string FirmName { get; set; } = "";
+    public string FullName => $"{LastName} {FirstName} {Patronymic}".Trim();
+}
+
+public class Room
+{
+    public int RoomId { get; set; }
+    public int RoomTypeId { get; set; }
+    public int RoomNumber { get; set; }
+    public int Occupancy { get; set; }
+    public decimal Rating { get; set; }
+    public string TypeName { get; set; } = "";
+}
+
+public class TourFirm
+{
+    public int FirmId { get; set; }
+    public string FirmName { get; set; } = "";
+    public int PeopleCount { get; set; }
+}

+ 6 - 0
practiceApplicaiton/Platforms/Android/AndroidManifest.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+	<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
+	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+	<uses-permission android:name="android.permission.INTERNET" />
+</manifest>

+ 11 - 0
practiceApplicaiton/Platforms/Android/MainActivity.cs

@@ -0,0 +1,11 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace practiceApplicaiton
+{
+    [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+    public class MainActivity : MauiAppCompatActivity
+    {
+    }
+}

+ 16 - 0
practiceApplicaiton/Platforms/Android/MainApplication.cs

@@ -0,0 +1,16 @@
+using Android.App;
+using Android.Runtime;
+
+namespace practiceApplicaiton
+{
+    [Application]
+    public class MainApplication : MauiApplication
+    {
+        public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+            : base(handle, ownership)
+        {
+        }
+
+        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+    }
+}

+ 6 - 0
practiceApplicaiton/Platforms/Android/Resources/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#512BD4</color>
+    <color name="colorPrimaryDark">#2B0B98</color>
+    <color name="colorAccent">#2B0B98</color>
+</resources>

+ 10 - 0
practiceApplicaiton/Platforms/MacCatalyst/AppDelegate.cs

@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace practiceApplicaiton
+{
+    [Register("AppDelegate")]
+    public class AppDelegate : MauiUIApplicationDelegate
+    {
+        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+    }
+}

+ 14 - 0
practiceApplicaiton/Platforms/MacCatalyst/Entitlements.plist

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+    <!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
+    <dict>
+        <!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
+        <key>com.apple.security.app-sandbox</key>
+        <true/>
+        <!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
+        <key>com.apple.security.network.client</key>
+        <true/>
+    </dict>
+</plist>
+

+ 40 - 0
practiceApplicaiton/Platforms/MacCatalyst/Info.plist

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <!-- The Mac App Store requires you specify if the app uses encryption. -->
+    <!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
+    <!-- <key>ITSAppUsesNonExemptEncryption</key> -->
+    <!-- Please indicate <true/> or <false/> here. -->
+
+    <!-- Specify the category for your app here. -->
+    <!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
+    <!-- <key>LSApplicationCategoryType</key> -->
+    <!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
+	<key>UIDeviceFamily</key>
+	<array>
+		<integer>2</integer>
+	</array>
+	<key>LSApplicationCategoryType</key>
+	<string>public.app-category.lifestyle</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>arm64</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>XSAppIconAssets</key>
+	<string>Assets.xcassets/appicon.appiconset</string>
+</dict>
+</plist>

+ 16 - 0
practiceApplicaiton/Platforms/MacCatalyst/Program.cs

@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace practiceApplicaiton
+{
+    public class Program
+    {
+        // This is the main entry point of the application.
+        static void Main(string[] args)
+        {
+            // if you want to use a different Application Delegate class from "AppDelegate"
+            // you can specify it here.
+            UIApplication.Main(args, null, typeof(AppDelegate));
+        }
+    }
+}

+ 8 - 0
practiceApplicaiton/Platforms/Windows/App.xaml

@@ -0,0 +1,8 @@
+<maui:MauiWinUIApplication
+    x:Class="practiceApplicaiton.WinUI.App"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:maui="using:Microsoft.Maui"
+    xmlns:local="using:practiceApplicaiton.WinUI">
+
+</maui:MauiWinUIApplication>

+ 25 - 0
practiceApplicaiton/Platforms/Windows/App.xaml.cs

@@ -0,0 +1,25 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace practiceApplicaiton.WinUI
+{
+    /// <summary>
+    /// Provides application-specific behavior to supplement the default Application class.
+    /// </summary>
+    public partial class App : MauiWinUIApplication
+    {
+        /// <summary>
+        /// Initializes the singleton application object.  This is the first line of authored code
+        /// executed, and as such is the logical equivalent of main() or WinMain().
+        /// </summary>
+        public App()
+        {
+            this.InitializeComponent();
+        }
+
+        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+    }
+
+}

+ 46 - 0
practiceApplicaiton/Platforms/Windows/Package.appxmanifest

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Package
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+  IgnorableNamespaces="uap rescap">
+
+  <Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
+
+  <mp:PhoneIdentity PhoneProductId="112B3CB4-4445-4031-85BE-0DA4696D2A30" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+
+  <Properties>
+    <DisplayName>$placeholder$</DisplayName>
+    <PublisherDisplayName>User Name</PublisherDisplayName>
+    <Logo>$placeholder$.png</Logo>
+  </Properties>
+
+  <Dependencies>
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
+    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
+  </Dependencies>
+
+  <Resources>
+    <Resource Language="x-generate" />
+  </Resources>
+
+  <Applications>
+    <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
+      <uap:VisualElements
+        DisplayName="$placeholder$"
+        Description="$placeholder$"
+        Square150x150Logo="$placeholder$.png"
+        Square44x44Logo="$placeholder$.png"
+        BackgroundColor="transparent">
+        <uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
+        <uap:SplashScreen Image="$placeholder$.png" />
+      </uap:VisualElements>
+    </Application>
+  </Applications>
+
+  <Capabilities>
+    <rescap:Capability Name="runFullTrust" />
+  </Capabilities>
+
+</Package>

+ 17 - 0
practiceApplicaiton/Platforms/Windows/app.manifest

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="practiceApplicaiton.WinUI.app"/>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <!-- The combination of below two tags have the following effect:
+           1) Per-Monitor for >= Windows 10 Anniversary Update
+           2) System < Windows 10 Anniversary Update
+      -->
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
+
+      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+    </windowsSettings>
+  </application>
+</assembly>

+ 10 - 0
practiceApplicaiton/Platforms/iOS/AppDelegate.cs

@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace practiceApplicaiton
+{
+    [Register("AppDelegate")]
+    public class AppDelegate : MauiUIApplicationDelegate
+    {
+        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+    }
+}

+ 32 - 0
practiceApplicaiton/Platforms/iOS/Info.plist

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UIDeviceFamily</key>
+	<array>
+		<integer>1</integer>
+		<integer>2</integer>
+	</array>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>arm64</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>XSAppIconAssets</key>
+	<string>Assets.xcassets/appicon.appiconset</string>
+</dict>
+</plist>

+ 16 - 0
practiceApplicaiton/Platforms/iOS/Program.cs

@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace practiceApplicaiton
+{
+    public class Program
+    {
+        // This is the main entry point of the application.
+        static void Main(string[] args)
+        {
+            // if you want to use a different Application Delegate class from "AppDelegate"
+            // you can specify it here.
+            UIApplication.Main(args, null, typeof(AppDelegate));
+        }
+    }
+}

+ 51 - 0
practiceApplicaiton/Platforms/iOS/Resources/PrivacyInfo.xcprivacy

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
+The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
+
+You are responsible for adding extra entries as needed for your application.
+
+More information: https://aka.ms/maui-privacy-manifest
+-->
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>NSPrivacyAccessedAPITypes</key>
+    <array>
+        <dict>
+            <key>NSPrivacyAccessedAPIType</key>
+            <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
+            <key>NSPrivacyAccessedAPITypeReasons</key>
+            <array>
+                <string>C617.1</string>
+            </array>
+        </dict>
+        <dict>
+            <key>NSPrivacyAccessedAPIType</key>
+            <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
+            <key>NSPrivacyAccessedAPITypeReasons</key>
+            <array>
+                <string>35F9.1</string>
+            </array>
+        </dict>
+        <dict>
+            <key>NSPrivacyAccessedAPIType</key>
+            <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
+            <key>NSPrivacyAccessedAPITypeReasons</key>
+            <array>
+                <string>E174.1</string>
+            </array>
+        </dict>
+        <!--
+            The entry below is only needed when you're using the Preferences API in your app.
+        <dict>
+            <key>NSPrivacyAccessedAPIType</key>
+            <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
+            <key>NSPrivacyAccessedAPITypeReasons</key>
+            <array>
+                <string>CA92.1</string>
+            </array>
+        </dict> -->
+    </array>
+</dict>
+</plist>

+ 8 - 0
practiceApplicaiton/Properties/launchSettings.json

@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "Windows Machine": {
+      "commandName": "Project",
+      "nativeDebugging": false
+    }
+  }
+}

+ 86 - 0
practiceApplicaiton/ReportPage.xaml

@@ -0,0 +1,86 @@
+<?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="practiceApplicaiton.ReportPage"
+             Title="Отчёт по бронированиям">
+    <Grid RowDefinitions="Auto,*,Auto" Padding="16" RowSpacing="10">
+
+        <!-- Фильтр дат -->
+        <Frame Grid.Row="0" BackgroundColor="White" CornerRadius="10"
+               Padding="16" BorderColor="#E0E0E0">
+            <Grid ColumnDefinitions="*,*,Auto" ColumnSpacing="10">
+                <VerticalStackLayout Grid.Column="0">
+                    <Label Text="С" FontAttributes="Bold" TextColor="#2E4057"/>
+                    <DatePicker x:Name="DateFrom" BackgroundColor="Gray" TextColor="#4472C4"/>
+                </VerticalStackLayout>
+                <VerticalStackLayout Grid.Column="1">
+                    <Label Text="По" FontAttributes="Bold" TextColor="#2E4057"/>
+                    <DatePicker x:Name="DateTo" BackgroundColor="Gray" TextColor="#4472C4"/>
+                </VerticalStackLayout>
+                <Button Grid.Column="2" Text="Показать"
+                        BackgroundColor="#4472C4" TextColor="White"
+                        CornerRadius="8" VerticalOptions="End"
+                        Clicked="OnGenerateClicked"/>
+            </Grid>
+        </Frame>
+
+        <!-- Список -->
+        <CollectionView Grid.Row="1" x:Name="ReportList">
+            <CollectionView.Header>
+                <Grid ColumnDefinitions="*,80,80,100" Padding="8,4"
+                      BackgroundColor="#2E4057">
+                    <Label Grid.Column="0" Text="Клиент / Комната"
+                           TextColor="#4472C4" FontAttributes="Bold"/>
+                    <Label Grid.Column="1" Text="Заезд"
+                           TextColor="#4472C4" FontAttributes="Bold" HorizontalOptions="Center"/>
+                    <Label Grid.Column="2" Text="Ночей"
+                           TextColor="#4472C4" FontAttributes="Bold" HorizontalOptions="Center"/>
+                    <Label Grid.Column="3" Text="Итого"
+                           TextColor="#4472C4" FontAttributes="Bold" HorizontalOptions="End"/>
+                </Grid>
+            </CollectionView.Header>
+            <CollectionView.ItemTemplate>
+                <DataTemplate>
+                    <Grid ColumnDefinitions="*,80,80,100" Padding="8,6"
+                          BackgroundColor="White">
+                        <VerticalStackLayout Grid.Column="0">
+                            <Label Text="{Binding ClientFullName}" FontAttributes="Bold" FontSize="13" TextColor="#4472C4"/>
+                            <Label TextColor="#777" FontSize="12">
+                                <Label.FormattedText>
+                                    <FormattedString>
+                                        <Span Text="Ком. " TextColor="#4472C4"/>
+                                        <Span Text="{Binding RoomNumber}" TextColor="#4472C4"/>
+                                        <Span Text=" · " TextColor="#4472C4"/>
+                                        <Span Text="{Binding RoomType}" TextColor="#4472C4"/>
+                                    </FormattedString>
+                                </Label.FormattedText>
+                            </Label>
+                        </VerticalStackLayout>
+                        <Label Grid.Column="1"
+                               Text="{Binding CheckInDate, StringFormat='{0:dd.MM}'}"
+                               HorizontalOptions="Center" VerticalOptions="Center" TextColor="#4472C4"/>
+                        <Label Grid.Column="2"
+                               Text="{Binding Nights}" TextColor="#4472C4"
+                               HorizontalOptions="Center" VerticalOptions="Center"/>
+                        <Label Grid.Column="3"
+                               Text="{Binding TotalCost, StringFormat='{0:N0} ₽'}"
+                               FontAttributes="Bold" TextColor="#2E4057"
+                               HorizontalOptions="End" VerticalOptions="Center"/>
+                    </Grid>
+                </DataTemplate>
+            </CollectionView.ItemTemplate>
+        </CollectionView>
+
+        <!-- Итог -->
+        <Frame Grid.Row="2" BackgroundColor="#2E4057" CornerRadius="10" Padding="16">
+            <Grid ColumnDefinitions="*,Auto">
+                <Label Text="Итого за период:" TextColor="#E0E0E0"
+                       FontAttributes="Bold" FontSize="16"/>
+                <Label Grid.Column="1" x:Name="TotalLabel"
+                       Text="0 ₽" TextColor="#FFF2CC"
+                       FontAttributes="Bold" FontSize="18"/>
+            </Grid>
+        </Frame>
+
+    </Grid>
+</ContentPage>

+ 33 - 0
practiceApplicaiton/ReportPage.xaml.cs

@@ -0,0 +1,33 @@
+namespace practiceApplicaiton;
+
+public partial class ReportPage : ContentPage
+{
+    public ReportPage()
+    {
+        InitializeComponent();
+        DateFrom.Date = DateTime.Today.AddMonths(-1);
+        DateTo.Date   = DateTime.Today;
+    }
+
+    private async void OnGenerateClicked(object sender, EventArgs e)
+    {
+        if (DateTo.Date < DateFrom.Date)
+        {
+            await DisplayAlert("Ошибка", "Дата «По» не может быть раньше «С»", "OK");
+            return;
+        }
+
+        try
+        {
+            var data = await Repository.GetBookingReportAsync((DateTime)DateFrom.Date, (DateTime)DateTo.Date);
+            ReportList.ItemsSource = data;
+
+            var total = data.Sum(b => b.TotalCost);
+            TotalLabel.Text = $"{total:N0} ₽";
+        }
+        catch (Exception ex)
+        {
+            await DisplayAlert("Ошибка", ex.Message, "OK");
+        }
+    }
+}

+ 279 - 0
practiceApplicaiton/Repository.cs

@@ -0,0 +1,279 @@
+using Npgsql;
+
+namespace practiceApplicaiton;
+
+/// <summary>
+/// Все операции с БД. Использует параметризованные запросы
+/// для защиты от SQL-инъекций.
+/// </summary>
+public static class Repository
+{
+    // ── БРОНИРОВАНИЯ ────────────────────────────────────────────────
+
+    public static async Task<List<Booking>> GetBookingsAsync(string search = "")
+    {
+        var list = new List<Booking>();
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+
+        var sql = """
+            SELECT b.booking_id, b.client_id, b.room_id,
+                   b.check_in_date, b.check_out_date, b.price_per_night,
+                   c.last_name || ' ' || c.first_name || ' ' || COALESCE(c.patronymic,'') AS full_name,
+                   r.room_number, rt.type_name
+            FROM Booking b
+            JOIN Client c   ON c.client_id = b.client_id
+            JOIN Room r     ON r.room_id = b.room_id
+            JOIN RoomType rt ON rt.room_type_id = r.room_type_id
+            WHERE (@search = '' OR LOWER(c.last_name) LIKE LOWER(@search)
+                               OR CAST(r.room_number AS TEXT) LIKE @search)
+            ORDER BY b.check_in_date DESC
+            """;
+
+        await using var cmd = new NpgsqlCommand(sql, conn);
+        cmd.Parameters.AddWithValue("search", string.IsNullOrEmpty(search) ? "" : $"%{search}%");
+
+        await using var reader = await cmd.ExecuteReaderAsync();
+        while (await reader.ReadAsync())
+        {
+            list.Add(new Booking
+            {
+                BookingId = reader.GetInt32(0),
+                ClientId = reader.GetInt32(1),
+                RoomId = reader.GetInt32(2),
+                CheckInDate = reader.GetDateTime(3),
+                CheckOutDate = reader.GetDateTime(4),
+                PricePerNight = reader.GetDecimal(5),
+                ClientFullName = reader.GetString(6).Trim(),
+                RoomNumber = reader.GetInt32(7),
+                RoomType = reader.GetString(8),
+            });
+        }
+        return list;
+    }
+
+    public static async Task CreateBookingAsync(int clientId, int roomId,
+        DateTime checkIn, DateTime checkOut, decimal price)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        // Вызываем хранимую процедуру
+        await using var cmd = new NpgsqlCommand(
+            "CALL create_booking(@cid, @rid, @ci, @co, @price)", conn);
+        cmd.Parameters.AddWithValue("cid", clientId);
+        cmd.Parameters.AddWithValue("rid", roomId);
+        cmd.Parameters.AddWithValue("ci", checkIn);
+        cmd.Parameters.AddWithValue("co", checkOut);
+        cmd.Parameters.AddWithValue("price", price);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    public static async Task UpdateBookingAsync(int bookingId, DateTime checkIn,
+        DateTime checkOut, decimal price)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand("""
+            UPDATE Booking SET check_in_date=@ci, check_out_date=@co, price_per_night=@price
+            WHERE booking_id=@id
+            """, conn);
+        cmd.Parameters.AddWithValue("ci", checkIn);
+        cmd.Parameters.AddWithValue("co", checkOut);
+        cmd.Parameters.AddWithValue("price", price);
+        cmd.Parameters.AddWithValue("id", bookingId);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    public static async Task DeleteBookingAsync(int bookingId)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand(
+            "DELETE FROM Booking WHERE booking_id=@id", conn);
+        cmd.Parameters.AddWithValue("id", bookingId);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    // ── КЛИЕНТЫ ─────────────────────────────────────────────────────
+
+    public static async Task<List<Client>> GetClientsAsync(string search = "")
+    {
+        var list = new List<Client>();
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+
+        var sql = """
+            SELECT c.client_id, c.firm_id, c.last_name, c.first_name,
+                   COALESCE(c.patronymic,''), c.phone_number, tf.firm_name
+            FROM Client c
+            JOIN TourFirm tf ON tf.firm_id = c.firm_id
+            WHERE (@s = '' OR LOWER(c.last_name) LIKE LOWER(@s)
+                           OR c.phone_number LIKE @s)
+            ORDER BY c.last_name
+            """;
+        await using var cmd = new NpgsqlCommand(sql, conn);
+        cmd.Parameters.AddWithValue("s", string.IsNullOrEmpty(search) ? "" : $"%{search}%");
+
+        await using var reader = await cmd.ExecuteReaderAsync();
+        while (await reader.ReadAsync())
+            list.Add(new Client
+            {
+                ClientId = reader.GetInt32(0),
+                FirmId = reader.GetInt32(1),
+                LastName = reader.GetString(2),
+                FirstName = reader.GetString(3),
+                Patronymic = reader.GetString(4),
+                PhoneNumber = reader.GetString(5),
+                FirmName = reader.GetString(6),
+            });
+        return list;
+    }
+
+    public static async Task AddClientAsync(Client c)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand("""
+            INSERT INTO Client (firm_id,last_name,first_name,patronymic,phone_number)
+            VALUES (@fid,@ln,@fn,@pat,@ph)
+            """, conn);
+        cmd.Parameters.AddWithValue("fid", c.FirmId);
+        cmd.Parameters.AddWithValue("ln", c.LastName);
+        cmd.Parameters.AddWithValue("fn", c.FirstName);
+        cmd.Parameters.AddWithValue("pat", (object?)c.Patronymic ?? DBNull.Value);
+        cmd.Parameters.AddWithValue("ph", c.PhoneNumber);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    public static async Task UpdateClientAsync(Client c)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand("""
+            UPDATE Client SET firm_id=@fid, last_name=@ln, first_name=@fn,
+                              patronymic=@pat, phone_number=@ph
+            WHERE client_id=@id
+            """, conn);
+        cmd.Parameters.AddWithValue("fid", c.FirmId);
+        cmd.Parameters.AddWithValue("ln", c.LastName);
+        cmd.Parameters.AddWithValue("fn", c.FirstName);
+        cmd.Parameters.AddWithValue("pat", (object?)c.Patronymic ?? DBNull.Value);
+        cmd.Parameters.AddWithValue("ph", c.PhoneNumber);
+        cmd.Parameters.AddWithValue("id", c.ClientId);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    public static async Task DeleteClientAsync(int clientId)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand(
+            "DELETE FROM Client WHERE client_id=@id", conn);
+        cmd.Parameters.AddWithValue("id", clientId);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    // ── КОМНАТЫ ─────────────────────────────────────────────────────
+
+    public static async Task<List<Room>> GetRoomsAsync()
+    {
+        var list = new List<Room>();
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand("""
+            SELECT r.room_id, r.room_type_id, r.room_number, r.occupancy, r.rating, rt.type_name
+            FROM Room r JOIN RoomType rt ON rt.room_type_id = r.room_type_id
+            ORDER BY r.room_number
+            """, conn);
+        await using var reader = await cmd.ExecuteReaderAsync();
+        while (await reader.ReadAsync())
+            list.Add(new Room
+            {
+                RoomId = reader.GetInt32(0),
+                RoomTypeId = reader.GetInt32(1),
+                RoomNumber = reader.GetInt32(2),
+                Occupancy = reader.GetInt32(3),
+                Rating = reader.GetDecimal(4),
+                TypeName = reader.GetString(5),
+            });
+        return list;
+    }
+
+    public static async Task UpdateRoomRatingAsync(int roomId, decimal rating)
+    {
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand(
+            "UPDATE Room SET rating=@r WHERE room_id=@id", conn);
+        cmd.Parameters.AddWithValue("r", rating);
+        cmd.Parameters.AddWithValue("id", roomId);
+        await cmd.ExecuteNonQueryAsync();
+    }
+
+    // ── ВСПОМОГАТЕЛЬНЫЕ ─────────────────────────────────────────────
+
+    public static async Task<List<TourFirm>> GetFirmsAsync()
+    {
+        var list = new List<TourFirm>();
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand(
+            "SELECT firm_id, firm_name, people_count FROM TourFirm ORDER BY firm_name", conn);
+        await using var reader = await cmd.ExecuteReaderAsync();
+        while (await reader.ReadAsync())
+            list.Add(new TourFirm
+            {
+                FirmId = reader.GetInt32(0),
+                FirmName = reader.GetString(1),
+                PeopleCount = reader.GetInt32(2),
+            });
+        return list;
+    }
+
+    // ── ОТЧЁТ — итоговая стоимость бронирований ─────────────────────
+
+    public static async Task<List<Booking>> GetBookingReportAsync(
+        DateTime from, DateTime to)
+    {
+        var list = new List<Booking>();
+        await using var conn = Database.GetConnection();
+        await conn.OpenAsync();
+        await using var cmd = new NpgsqlCommand("""
+            SELECT b.booking_id, b.client_id, b.room_id,
+                   b.check_in_date, b.check_out_date, b.price_per_night,
+                   c.last_name || ' ' || c.first_name AS full_name,
+                   r.room_number, rt.type_name
+            FROM BookingCostView b
+            JOIN Booking bk ON bk.booking_id = b.booking_id
+            JOIN Client c   ON c.client_id = bk.client_id
+            JOIN Room r     ON r.room_id = bk.room_id
+            JOIN RoomType rt ON rt.room_type_id = r.room_type_id
+            WHERE bk.check_in_date >= @from AND bk.check_out_date <= @to
+            ORDER BY bk.check_in_date
+            """, conn);
+        // Проще использовать BookingCostView напрямую
+        cmd.CommandText = """
+            SELECT booking_id, 0, 0,
+                   check_in_date, check_out_date, price_per_night,
+                   client_full_name, room_number, room_type
+            FROM BookingCostView
+            WHERE check_in_date >= @from AND check_out_date <= @to
+            ORDER BY check_in_date
+            """;
+        cmd.Parameters.AddWithValue("from", from);
+        cmd.Parameters.AddWithValue("to", to);
+        await using var reader = await cmd.ExecuteReaderAsync();
+        while (await reader.ReadAsync())
+            list.Add(new Booking
+            {
+                BookingId = reader.GetInt32(0),
+                CheckInDate = reader.GetDateTime(3),
+                CheckOutDate = reader.GetDateTime(4),
+                PricePerNight = reader.GetDecimal(5),
+                ClientFullName = reader.GetString(6).Trim(),
+                RoomNumber = reader.GetInt32(7),
+                RoomType = reader.GetString(8),
+            });
+        return list;
+    }
+}

+ 4 - 0
practiceApplicaiton/Resources/AppIcon/appicon.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <rect x="0" y="0" width="456" height="456" fill="#512BD4" />
+</svg>

+ 8 - 0
practiceApplicaiton/Resources/AppIcon/appiconfg.svg

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+    <path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+    <path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+    <path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+</svg>

BIN
practiceApplicaiton/Resources/Fonts/OpenSans-Regular.ttf


BIN
practiceApplicaiton/Resources/Fonts/OpenSans-Semibold.ttf


BIN
practiceApplicaiton/Resources/Images/dotnet_bot.png


+ 15 - 0
practiceApplicaiton/Resources/Raw/AboutAssets.txt

@@ -0,0 +1,15 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories). Deployment of the asset to your application
+is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
+
+	<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
+
+These files will be deployed with your package and will be accessible using Essentials:
+
+	async Task LoadMauiAsset()
+	{
+		using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+		using var reader = new StreamReader(stream);
+
+		var contents = reader.ReadToEnd();
+	}

+ 8 - 0
practiceApplicaiton/Resources/Splash/splash.svg

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+    <path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+    <path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+    <path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
+</svg>

+ 44 - 0
practiceApplicaiton/Resources/Styles/Colors.xaml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
+
+    <!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
+
+    <Color x:Key="Primary">#512BD4</Color>
+    <Color x:Key="PrimaryDark">#ac99ea</Color>
+    <Color x:Key="PrimaryDarkText">#242424</Color>
+    <Color x:Key="Secondary">#DFD8F7</Color>
+    <Color x:Key="SecondaryDarkText">#9880e5</Color>
+    <Color x:Key="Tertiary">#2B0B98</Color>
+
+    <Color x:Key="White">White</Color>
+    <Color x:Key="Black">Black</Color>
+    <Color x:Key="Magenta">#D600AA</Color>
+    <Color x:Key="MidnightBlue">#190649</Color>
+    <Color x:Key="OffBlack">#1f1f1f</Color>
+
+    <Color x:Key="Gray100">#E1E1E1</Color>
+    <Color x:Key="Gray200">#C8C8C8</Color>
+    <Color x:Key="Gray300">#ACACAC</Color>
+    <Color x:Key="Gray400">#919191</Color>
+    <Color x:Key="Gray500">#6E6E6E</Color>
+    <Color x:Key="Gray600">#404040</Color>
+    <Color x:Key="Gray900">#212121</Color>
+    <Color x:Key="Gray950">#141414</Color>
+
+    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
+    <SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
+    <SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
+    <SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
+    <SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
+
+    <SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
+    <SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
+    <SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
+    <SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
+    <SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
+    <SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
+    <SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
+    <SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
+</ResourceDictionary>

+ 434 - 0
practiceApplicaiton/Resources/Styles/Styles.xaml

@@ -0,0 +1,434 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
+
+    <Style TargetType="ActivityIndicator">
+        <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+    </Style>
+
+    <Style TargetType="IndicatorView">
+        <Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
+        <Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
+    </Style>
+
+    <Style TargetType="Border">
+        <Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
+        <Setter Property="StrokeShape" Value="Rectangle"/>
+        <Setter Property="StrokeThickness" Value="1"/>
+    </Style>
+
+    <Style TargetType="BoxView">
+        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
+    </Style>
+
+    <Style TargetType="Button">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
+        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="BorderWidth" Value="0"/>
+        <Setter Property="CornerRadius" Value="8"/>
+        <Setter Property="Padding" Value="14,10"/>
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
+                            <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                    <VisualState x:Name="PointerOver" />
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="CheckBox">
+        <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="DatePicker">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Editor">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14" />
+        <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Entry">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14" />
+        <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="ImageButton">
+        <Setter Property="Opacity" Value="1" />
+        <Setter Property="BorderColor" Value="Transparent"/>
+        <Setter Property="BorderWidth" Value="0"/>
+        <Setter Property="CornerRadius" Value="0"/>
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="Opacity" Value="0.5" />
+                        </VisualState.Setters>
+                    </VisualState>
+                    <VisualState x:Name="PointerOver" />
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Label">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular" />
+        <Setter Property="FontSize" Value="14" />
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Label" x:Key="Headline">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
+        <Setter Property="FontSize" Value="32" />
+        <Setter Property="HorizontalOptions" Value="Center" />
+        <Setter Property="HorizontalTextAlignment" Value="Center" />
+    </Style>
+
+    <Style TargetType="Label" x:Key="SubHeadline">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
+        <Setter Property="FontSize" Value="24" />
+        <Setter Property="HorizontalOptions" Value="Center" />
+        <Setter Property="HorizontalTextAlignment" Value="Center" />
+    </Style>
+
+    <Style TargetType="Picker">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
+        <Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14" />
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                            <Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="ProgressBar">
+        <Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="RadioButton">
+        <Setter Property="BackgroundColor" Value="Transparent"/>
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="RefreshView">
+        <Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
+    </Style>
+
+    <Style TargetType="SearchBar">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
+        <Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
+        <Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular" />
+        <Setter Property="FontSize" Value="14" />
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                            <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="SearchHandler">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
+        <Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
+        <Setter Property="BackgroundColor" Value="Transparent" />
+        <Setter Property="FontFamily" Value="OpenSansRegular" />
+        <Setter Property="FontSize" Value="14" />
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                            <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Shadow">
+        <Setter Property="Radius" Value="15" />
+        <Setter Property="Opacity" Value="0.5" />
+        <Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
+        <Setter Property="Offset" Value="10,10" />
+    </Style>
+
+    <Style TargetType="Slider">
+        <Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+        <Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
+        <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
+                            <Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
+                            <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="SwipeItem">
+        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
+    </Style>
+
+    <Style TargetType="Switch">
+        <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+        <Setter Property="ThumbColor" Value="{StaticResource White}" />
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                            <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                    <VisualState x:Name="On">
+                        <VisualState.Setters>
+                            <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
+                            <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                    <VisualState x:Name="Off">
+                        <VisualState.Setters>
+                            <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+
+    <Style TargetType="TimePicker">
+        <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
+        <Setter Property="BackgroundColor" Value="Transparent"/>
+        <Setter Property="FontFamily" Value="OpenSansRegular"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="MinimumHeightRequest" Value="44"/>
+        <Setter Property="MinimumWidthRequest" Value="44"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="CommonStates">
+                    <VisualState x:Name="Normal" />
+                    <VisualState x:Name="Disabled">
+                        <VisualState.Setters>
+                            <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+  
+    <!--
+    <Style TargetType="TitleBar">
+        <Setter Property="MinimumHeightRequest" Value="32"/>
+        <Setter Property="VisualStateManager.VisualStateGroups">
+            <VisualStateGroupList>
+                <VisualStateGroup x:Name="TitleActiveStates">
+                    <VisualState x:Name="TitleBarTitleActive">
+                        <VisualState.Setters>
+                            <Setter Property="BackgroundColor" Value="Transparent" />
+                            <Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                    <VisualState x:Name="TitleBarTitleInactive">
+                        <VisualState.Setters>
+                            <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
+                            <Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
+                        </VisualState.Setters>
+                    </VisualState>
+                </VisualStateGroup>
+            </VisualStateGroupList>
+        </Setter>
+    </Style>
+    -->
+
+    <Style TargetType="Page" ApplyToDerivedTypes="True">
+        <Setter Property="Padding" Value="0"/>
+        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
+    </Style>
+
+    <Style TargetType="Shell" ApplyToDerivedTypes="True">
+        <Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
+        <Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
+        <Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
+        <Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
+        <Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
+        <Setter Property="Shell.NavBarHasShadow" Value="False" />
+        <Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
+        <Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
+        <Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
+        <Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
+    </Style>
+
+    <Style TargetType="NavigationPage">
+        <Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
+        <Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
+        <Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
+    </Style>
+
+    <Style TargetType="TabbedPage">
+        <Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
+        <Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
+        <Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
+        <Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
+    </Style>
+
+</ResourceDictionary>

+ 54 - 0
practiceApplicaiton/RoomsPage.xaml

@@ -0,0 +1,54 @@
+<?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="practiceApplicaiton.RoomsPage"
+             Title="Комнаты">
+    <Grid RowDefinitions="*,Auto" Padding="16" RowSpacing="10">
+
+        <CollectionView Grid.Row="0" x:Name="RoomsList"
+                        SelectionMode="Single" SelectionChanged="OnSelectionChanged">
+            <CollectionView.ItemTemplate>
+                <DataTemplate>
+                    <Frame Margin="0,4" Padding="12" CornerRadius="8"
+                           BackgroundColor="White" BorderColor="#E0E0E0">
+                        <Grid ColumnDefinitions="Auto,*,Auto">
+                            <!-- Номер комнаты -->
+                            <Frame Grid.Column="0" BackgroundColor="#4472C4"
+                                   CornerRadius="8" Padding="10,6" Margin="0,0,12,0">
+                                <Label Text="{Binding RoomNumber}"
+                                       TextColor="#E0E0E0" FontAttributes="Bold" FontSize="18"/>
+                            </Frame>
+                            <!-- Инфо -->
+                            <VerticalStackLayout Grid.Column="1" Spacing="3">
+                                <Label Text="{Binding TypeName}" TextColor="#4472C4"
+                                       FontAttributes="Bold" FontSize="15"/>
+                                <Label TextColor="#555">
+                                    <Label.FormattedText>
+                                        <FormattedString>
+                                            <Span Text="Вместимость: " TextColor="#4472C4"/>
+                                            <Span Text="{Binding Occupancy} " TextColor="#4472C4"/>
+                                            <Span Text=" чел." TextColor="#4472C4"/>
+                                        </FormattedString>
+                                    </Label.FormattedText>
+                                </Label>
+                            </VerticalStackLayout>
+                            <!-- Рейтинг -->
+                            <VerticalStackLayout Grid.Column="2" HorizontalOptions="End">
+                                
+                                <Label Text="{Binding Rating, StringFormat='{0:F1}'}"
+                                       FontAttributes="Bold" TextColor="#2E4057"
+                                       HorizontalOptions="Center"/>
+                            </VerticalStackLayout>
+                        </Grid>
+                    </Frame>
+                </DataTemplate>
+            </CollectionView.ItemTemplate>
+        </CollectionView>
+
+        <!-- Обновить рейтинг — доступно всем -->
+        <Button Grid.Row="1" Text="Обновить рейтинг"
+                BackgroundColor="#4472C4" TextColor="White" CornerRadius="8"
+                x:Name="EditRatingButton" IsEnabled="False"
+                Clicked="OnEditRatingClicked"/>
+    </Grid>
+</ContentPage>

+ 55 - 0
practiceApplicaiton/RoomsPage.xaml.cs

@@ -0,0 +1,55 @@
+namespace practiceApplicaiton;
+
+public partial class RoomsPage : ContentPage
+{
+    private Room? _selected;
+
+    public RoomsPage() => InitializeComponent();
+
+    protected override async void OnAppearing()
+    {
+        base.OnAppearing();
+        await LoadAsync();
+    }
+
+    private async Task LoadAsync()
+    {
+        try { RoomsList.ItemsSource = await Repository.GetRoomsAsync(); }
+        catch (Exception ex) { await DisplayAlertAsync("Ошибка", ex.Message, "OK"); }
+    }
+
+    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        _selected = e.CurrentSelection.FirstOrDefault() as Room;
+        EditRatingButton.IsEnabled = _selected != null;
+    }
+
+    private async void OnEditRatingClicked(object sender, EventArgs e)
+    {
+        if (_selected == null) return;
+
+        var result = await DisplayPromptAsync(
+            "Рейтинг комнаты",
+            $"Введите новый рейтинг для комнаты №{_selected.RoomNumber} (0.0 — 5.0):",
+            initialValue: _selected.Rating.ToString("F1"),
+            keyboard: Keyboard.Numeric);
+
+        if (result == null) return;
+
+        if (!decimal.TryParse(result.Replace(',', '.'),
+            System.Globalization.NumberStyles.Any,
+            System.Globalization.CultureInfo.InvariantCulture, out var rating)
+            || rating < 0 || rating > 5)
+        {
+            await DisplayAlertAsync("Ошибка", "Рейтинг должен быть от 0.0 до 5.0", "OK");
+            return;
+        }
+
+        try
+        {
+            await Repository.UpdateRoomRatingAsync(_selected.RoomId, rating);
+            await LoadAsync();
+        }
+        catch (Exception ex) { await DisplayAlertAsync("Ошибка", ex.Message, "OK"); }
+    }
+}

+ 74 - 0
practiceApplicaiton/practiceApplicaiton.csproj

@@ -0,0 +1,74 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<TargetFrameworks>net10.0-android</TargetFrameworks>
+		<TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('linux'))">$(TargetFrameworks);net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
+		<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
+
+		<!-- Note for MacCatalyst:
+		The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
+		When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
+		The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
+		either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
+		<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
+
+		<OutputType>Exe</OutputType>
+		<RootNamespace>practiceApplicaiton</RootNamespace>
+		<UseMaui>true</UseMaui>
+		<SingleProject>true</SingleProject>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<Nullable>enable</Nullable>
+
+		<!-- Enable XAML source generation for faster build times and improved performance.
+		     This generates C# code from XAML at compile time instead of runtime inflation.
+		     To disable, remove this line.
+		     For individual files, you can override by setting Inflator metadata:
+		       <MauiXaml Update="MyPage.xaml" Inflator="Default" /> (reverts to defaults: Runtime for Debug, XamlC for Release)
+		       <MauiXaml Update="MyPage.xaml" Inflator="Runtime" /> (force runtime inflation) -->
+		<MauiXamlInflator>SourceGen</MauiXamlInflator>
+
+		<!-- Display name -->
+		<ApplicationTitle>practiceApplicaiton</ApplicationTitle>
+
+		<!-- App Identifier -->
+		<ApplicationId>com.companyname.practiceapplicaiton</ApplicationId>
+
+		<!-- Versions -->
+		<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
+		<ApplicationVersion>1</ApplicationVersion>
+
+		<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
+		<WindowsPackageType>None</WindowsPackageType>
+
+		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
+		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
+		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
+		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
+		<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<!-- App Icon -->
+		<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
+
+		<!-- Splash Screen -->
+		<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
+
+		<!-- Images -->
+		<MauiImage Include="Resources\Images\*" />
+		<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
+		
+		<!-- Custom Fonts -->
+		<MauiFont Include="Resources\Fonts\*" />
+
+		<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
+		<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
+	</ItemGroup>
+
+	<ItemGroup>
+		<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
+		<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
+		<PackageReference Include="Npgsql" Version="10.0.3" />
+	</ItemGroup>
+
+</Project>