|
|
@@ -0,0 +1,848 @@
|
|
|
+using TransportMauiApp.Models;
|
|
|
+using TransportMauiApp.Services;
|
|
|
+using Microsoft.Maui.Controls.Shapes;
|
|
|
+
|
|
|
+namespace TransportMauiApp;
|
|
|
+
|
|
|
+public partial class MainPage : ContentPage
|
|
|
+{
|
|
|
+ private readonly DatabaseService _database = new();
|
|
|
+ private readonly VerticalStackLayout _content = new() { Spacing = 14 };
|
|
|
+ private readonly List<Button> _sectionButtons = [];
|
|
|
+
|
|
|
+ private UserSession? _session;
|
|
|
+ private bool CanEdit => _session?.IsAdministrator == true;
|
|
|
+
|
|
|
+ private List<Driver> _drivers = [];
|
|
|
+ private List<AppTransport> _transports = [];
|
|
|
+ private List<TransitRoute> _routes = [];
|
|
|
+ private List<TransportFlight> _flights = [];
|
|
|
+
|
|
|
+ public MainPage()
|
|
|
+ {
|
|
|
+ InitializeComponent();
|
|
|
+ Title = "Учет транспорта";
|
|
|
+ ShowLogin();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ShowLogin()
|
|
|
+ {
|
|
|
+ var loginEntry = CreateEntry("Логин");
|
|
|
+ var passwordEntry = CreateEntry("Пароль");
|
|
|
+ passwordEntry.IsPassword = true;
|
|
|
+
|
|
|
+ var statusLabel = new Label
|
|
|
+ {
|
|
|
+ TextColor = Colors.Firebrick,
|
|
|
+ FontSize = 14,
|
|
|
+ IsVisible = false
|
|
|
+ };
|
|
|
+
|
|
|
+ var loginButton = new Button
|
|
|
+ {
|
|
|
+ Text = "Войти",
|
|
|
+ HeightRequest = 46,
|
|
|
+ BackgroundColor = Color.FromArgb("#1D4ED8"),
|
|
|
+ TextColor = Colors.White,
|
|
|
+ CornerRadius = 8
|
|
|
+ };
|
|
|
+
|
|
|
+ loginButton.Clicked += async (_, _) =>
|
|
|
+ {
|
|
|
+ statusLabel.IsVisible = false;
|
|
|
+ loginButton.IsEnabled = false;
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var login = loginEntry.Text?.Trim() ?? string.Empty;
|
|
|
+ var password = passwordEntry.Text ?? string.Empty;
|
|
|
+ var session = await _database.AuthenticateAsync(login, password);
|
|
|
+
|
|
|
+ if (session is null)
|
|
|
+ {
|
|
|
+ statusLabel.Text = "Неверный логин или пароль.";
|
|
|
+ statusLabel.IsVisible = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _session = session;
|
|
|
+ await LoadLookupsAsync();
|
|
|
+ ShowWorkspace();
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ statusLabel.Text = $"Ошибка подключения: {ex.Message}";
|
|
|
+ statusLabel.IsVisible = true;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ loginButton.IsEnabled = true;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Content = new Grid
|
|
|
+ {
|
|
|
+ Padding = new Thickness(22),
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ new Border
|
|
|
+ {
|
|
|
+ StrokeThickness = 1,
|
|
|
+ Stroke = Color.FromArgb("#D7DEE8"),
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ StrokeShape = new RoundRectangle { CornerRadius = 8 },
|
|
|
+ MaximumWidthRequest = 460,
|
|
|
+ VerticalOptions = LayoutOptions.Center,
|
|
|
+ HorizontalOptions = LayoutOptions.Center,
|
|
|
+ Padding = new Thickness(24),
|
|
|
+ Content = new VerticalStackLayout
|
|
|
+ {
|
|
|
+ Spacing = 14,
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ new Label
|
|
|
+ {
|
|
|
+ Text = "Авторизация",
|
|
|
+ FontSize = 26,
|
|
|
+ FontAttributes = FontAttributes.Bold,
|
|
|
+ TextColor = Color.FromArgb("#172033")
|
|
|
+ },
|
|
|
+ loginEntry,
|
|
|
+ passwordEntry,
|
|
|
+ loginButton,
|
|
|
+ statusLabel
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ShowWorkspace()
|
|
|
+ {
|
|
|
+ _sectionButtons.Clear();
|
|
|
+ _content.Children.Clear();
|
|
|
+
|
|
|
+ var header = new Grid
|
|
|
+ {
|
|
|
+ ColumnDefinitions =
|
|
|
+ {
|
|
|
+ new ColumnDefinition(GridLength.Star),
|
|
|
+ new ColumnDefinition(GridLength.Auto)
|
|
|
+ },
|
|
|
+ RowDefinitions =
|
|
|
+ {
|
|
|
+ new RowDefinition(GridLength.Auto),
|
|
|
+ new RowDefinition(GridLength.Auto)
|
|
|
+ },
|
|
|
+ ColumnSpacing = 12
|
|
|
+ };
|
|
|
+
|
|
|
+ header.Add(new Label
|
|
|
+ {
|
|
|
+ Text = "Учет транспорта",
|
|
|
+ FontSize = 26,
|
|
|
+ FontAttributes = FontAttributes.Bold,
|
|
|
+ TextColor = Color.FromArgb("#172033")
|
|
|
+ }, 0, 0);
|
|
|
+
|
|
|
+ var logoutButton = new Button
|
|
|
+ {
|
|
|
+ Text = "Выйти",
|
|
|
+ BackgroundColor = Color.FromArgb("#EEF2F7"),
|
|
|
+ TextColor = Color.FromArgb("#172033"),
|
|
|
+ CornerRadius = 8
|
|
|
+ };
|
|
|
+ logoutButton.Clicked += (_, _) =>
|
|
|
+ {
|
|
|
+ _session = null;
|
|
|
+ ShowLogin();
|
|
|
+ };
|
|
|
+ header.Add(logoutButton, 1, 0);
|
|
|
+
|
|
|
+ header.Add(new Label
|
|
|
+ {
|
|
|
+ Text = $"Пользователь: {_session?.Login} | Роль: {_session?.DisplayRole} | Режим: {(CanEdit ? "полный доступ" : "только чтение")}",
|
|
|
+ FontSize = 14,
|
|
|
+ TextColor = Color.FromArgb("#526070")
|
|
|
+ }, 0, 1);
|
|
|
+
|
|
|
+ var tabs = new HorizontalStackLayout
|
|
|
+ {
|
|
|
+ Spacing = 8,
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ CreateSectionButton("Водители", ShowDriversSection),
|
|
|
+ CreateSectionButton("Транспорт", ShowTransportsSection),
|
|
|
+ CreateSectionButton("Маршруты", ShowRoutesSection),
|
|
|
+ CreateSectionButton("Рейсы", ShowFlightsSection),
|
|
|
+ CreateSectionButton("Информация о рейсах", ShowFlightInfoSection)
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Content = new ScrollView
|
|
|
+ {
|
|
|
+ Content = new VerticalStackLayout
|
|
|
+ {
|
|
|
+ Padding = new Thickness(20),
|
|
|
+ Spacing = 16,
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ header,
|
|
|
+ new ScrollView { Orientation = ScrollOrientation.Horizontal, Content = tabs },
|
|
|
+ _content
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ ShowDriversSection();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Button CreateSectionButton(string text, Action action)
|
|
|
+ {
|
|
|
+ var button = new Button
|
|
|
+ {
|
|
|
+ Text = text,
|
|
|
+ CornerRadius = 8,
|
|
|
+ Padding = new Thickness(14, 8),
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ BorderColor = Color.FromArgb("#D7DEE8"),
|
|
|
+ BorderWidth = 1,
|
|
|
+ TextColor = Color.FromArgb("#172033")
|
|
|
+ };
|
|
|
+
|
|
|
+ button.Clicked += (_, _) =>
|
|
|
+ {
|
|
|
+ foreach (var tab in _sectionButtons)
|
|
|
+ {
|
|
|
+ tab.BackgroundColor = Colors.White;
|
|
|
+ tab.TextColor = Color.FromArgb("#172033");
|
|
|
+ }
|
|
|
+
|
|
|
+ button.BackgroundColor = Color.FromArgb("#1D4ED8");
|
|
|
+ button.TextColor = Colors.White;
|
|
|
+ action();
|
|
|
+ };
|
|
|
+
|
|
|
+ _sectionButtons.Add(button);
|
|
|
+ return button;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task LoadLookupsAsync()
|
|
|
+ {
|
|
|
+ _drivers = [.. await _database.GetDriversAsync()];
|
|
|
+ _transports = [.. await _database.GetTransportsAsync()];
|
|
|
+ _routes = [.. await _database.GetRoutesAsync()];
|
|
|
+ _flights = [.. await _database.GetFlightsAsync()];
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void ShowDriversSection()
|
|
|
+ {
|
|
|
+ await LoadDriversSectionAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task LoadDriversSectionAsync()
|
|
|
+ {
|
|
|
+ _content.Children.Clear();
|
|
|
+ await RunSafelyAsync(async () =>
|
|
|
+ {
|
|
|
+ _drivers = [.. await _database.GetDriversAsync()];
|
|
|
+ var recordsCount = await _database.GetDriversCountAsync();
|
|
|
+ var list = CreateCollectionView(_drivers);
|
|
|
+
|
|
|
+ var nameEntry = CreateEntry("Имя");
|
|
|
+ var surnameEntry = CreateEntry("Фамилия");
|
|
|
+ var patronimicEntry = CreateEntry("Отчество");
|
|
|
+ Driver? selected = null;
|
|
|
+
|
|
|
+ list.SelectionChanged += (_, e) =>
|
|
|
+ {
|
|
|
+ selected = e.CurrentSelection.FirstOrDefault() as Driver;
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ nameEntry.Text = selected.Name;
|
|
|
+ surnameEntry.Text = selected.Surname;
|
|
|
+ patronimicEntry.Text = selected.Patronimic;
|
|
|
+ };
|
|
|
+
|
|
|
+ _content.Children.Add(CreateSectionTitle("Водители", recordsCount));
|
|
|
+ _content.Children.Add(list);
|
|
|
+ if (CanEdit)
|
|
|
+ {
|
|
|
+ _content.Children.Add(CreateForm(
|
|
|
+ [nameEntry, surnameEntry, patronimicEntry],
|
|
|
+ CreateCrudButtons(
|
|
|
+ add: async () =>
|
|
|
+ {
|
|
|
+ if (!ValidateRequired(nameEntry, surnameEntry, patronimicEntry))
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Заполните все поля.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.AddDriverAsync(new Driver
|
|
|
+ {
|
|
|
+ Name = nameEntry.Text!.Trim(),
|
|
|
+ Surname = surnameEntry.Text!.Trim(),
|
|
|
+ Patronimic = patronimicEntry.Text!.Trim()
|
|
|
+ });
|
|
|
+ ClearEntries(nameEntry, surnameEntry, patronimicEntry);
|
|
|
+ await LoadDriversSectionAsync();
|
|
|
+ },
|
|
|
+ update: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null || !ValidateRequired(nameEntry, surnameEntry, patronimicEntry))
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись и заполните все поля.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ selected.Name = nameEntry.Text!.Trim();
|
|
|
+ selected.Surname = surnameEntry.Text!.Trim();
|
|
|
+ selected.Patronimic = patronimicEntry.Text!.Trim();
|
|
|
+ await _database.UpdateDriverAsync(selected);
|
|
|
+ await LoadDriversSectionAsync();
|
|
|
+ },
|
|
|
+ delete: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.DeleteDriverAsync(selected.DriverId);
|
|
|
+ await LoadDriversSectionAsync();
|
|
|
+ })));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void ShowTransportsSection()
|
|
|
+ {
|
|
|
+ await LoadTransportsSectionAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task LoadTransportsSectionAsync()
|
|
|
+ {
|
|
|
+ _content.Children.Clear();
|
|
|
+ await RunSafelyAsync(async () =>
|
|
|
+ {
|
|
|
+ await LoadLookupsAsync();
|
|
|
+ var recordsCount = await _database.GetTransportsCountAsync();
|
|
|
+ var list = CreateCollectionView(_transports);
|
|
|
+ var branchEntry = CreateEntry("Филиал");
|
|
|
+ var modelEntry = CreateEntry("Модель");
|
|
|
+ var plateEntry = CreateEntry("Госномер");
|
|
|
+ var driverPicker = CreatePicker("Водитель", _drivers);
|
|
|
+ AppTransport? selected = null;
|
|
|
+
|
|
|
+ list.SelectionChanged += (_, e) =>
|
|
|
+ {
|
|
|
+ selected = e.CurrentSelection.FirstOrDefault() as AppTransport;
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ branchEntry.Text = selected.Branch;
|
|
|
+ modelEntry.Text = selected.Model;
|
|
|
+ plateEntry.Text = selected.LicensePlateNumber;
|
|
|
+ driverPicker.SelectedItem = _drivers.FirstOrDefault(driver => driver.DriverId == selected.DriverId);
|
|
|
+ };
|
|
|
+
|
|
|
+ _content.Children.Add(CreateSectionTitle("Транспорт", recordsCount));
|
|
|
+ _content.Children.Add(list);
|
|
|
+ if (CanEdit)
|
|
|
+ {
|
|
|
+ var assignButton = new Button
|
|
|
+ {
|
|
|
+ Text = "Назначить водителя процедурой",
|
|
|
+ BackgroundColor = Color.FromArgb("#0F766E"),
|
|
|
+ TextColor = Colors.White,
|
|
|
+ CornerRadius = 8
|
|
|
+ };
|
|
|
+ assignButton.Clicked += async (_, _) =>
|
|
|
+ {
|
|
|
+ if (selected is null || driverPicker.SelectedItem is not Driver driver)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите транспорт и водителя.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await RunSafelyAsync(async () =>
|
|
|
+ {
|
|
|
+ await _database.AssignDriverToTransportAsync(selected.TransportId, driver.DriverId);
|
|
|
+ await LoadTransportsSectionAsync();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ _content.Children.Add(CreateForm(
|
|
|
+ [branchEntry, modelEntry, plateEntry, driverPicker],
|
|
|
+ CreateCrudButtons(
|
|
|
+ add: async () =>
|
|
|
+ {
|
|
|
+ if (!ValidateRequired(branchEntry, modelEntry, plateEntry))
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Заполните филиал, модель и госномер.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.AddTransportAsync(new AppTransport
|
|
|
+ {
|
|
|
+ Branch = branchEntry.Text!.Trim(),
|
|
|
+ Model = modelEntry.Text!.Trim(),
|
|
|
+ LicensePlateNumber = plateEntry.Text!.Trim(),
|
|
|
+ DriverId = (driverPicker.SelectedItem as Driver)?.DriverId
|
|
|
+ });
|
|
|
+ await LoadTransportsSectionAsync();
|
|
|
+ },
|
|
|
+ update: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null || !ValidateRequired(branchEntry, modelEntry, plateEntry))
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись и заполните обязательные поля.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ selected.Branch = branchEntry.Text!.Trim();
|
|
|
+ selected.Model = modelEntry.Text!.Trim();
|
|
|
+ selected.LicensePlateNumber = plateEntry.Text!.Trim();
|
|
|
+ selected.DriverId = (driverPicker.SelectedItem as Driver)?.DriverId;
|
|
|
+ await _database.UpdateTransportAsync(selected);
|
|
|
+ await LoadTransportsSectionAsync();
|
|
|
+ },
|
|
|
+ delete: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.DeleteTransportAsync(selected.TransportId);
|
|
|
+ await LoadTransportsSectionAsync();
|
|
|
+ },
|
|
|
+ extra: assignButton)));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void ShowRoutesSection()
|
|
|
+ {
|
|
|
+ await LoadRoutesSectionAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task LoadRoutesSectionAsync()
|
|
|
+ {
|
|
|
+ _content.Children.Clear();
|
|
|
+ await RunSafelyAsync(async () =>
|
|
|
+ {
|
|
|
+ _routes = [.. await _database.GetRoutesAsync()];
|
|
|
+ var recordsCount = await _database.GetRoutesCountAsync();
|
|
|
+ var list = CreateCollectionView(_routes);
|
|
|
+ var routeNameEntry = CreateEntry("Название маршрута");
|
|
|
+ TransitRoute? selected = null;
|
|
|
+
|
|
|
+ list.SelectionChanged += (_, e) =>
|
|
|
+ {
|
|
|
+ selected = e.CurrentSelection.FirstOrDefault() as TransitRoute;
|
|
|
+ routeNameEntry.Text = selected?.RouteName;
|
|
|
+ };
|
|
|
+
|
|
|
+ _content.Children.Add(CreateSectionTitle("Маршруты", recordsCount));
|
|
|
+ _content.Children.Add(list);
|
|
|
+ if (CanEdit)
|
|
|
+ {
|
|
|
+ _content.Children.Add(CreateForm(
|
|
|
+ [routeNameEntry],
|
|
|
+ CreateCrudButtons(
|
|
|
+ add: async () =>
|
|
|
+ {
|
|
|
+ if (!ValidateRequired(routeNameEntry))
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Введите название маршрута.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.AddRouteAsync(new TransitRoute { RouteName = routeNameEntry.Text!.Trim() });
|
|
|
+ await LoadRoutesSectionAsync();
|
|
|
+ },
|
|
|
+ update: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null || !ValidateRequired(routeNameEntry))
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись и заполните название.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ selected.RouteName = routeNameEntry.Text!.Trim();
|
|
|
+ await _database.UpdateRouteAsync(selected);
|
|
|
+ await LoadRoutesSectionAsync();
|
|
|
+ },
|
|
|
+ delete: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.DeleteRouteAsync(selected.RouteId);
|
|
|
+ await LoadRoutesSectionAsync();
|
|
|
+ })));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void ShowFlightsSection()
|
|
|
+ {
|
|
|
+ await LoadFlightsSectionAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task LoadFlightsSectionAsync()
|
|
|
+ {
|
|
|
+ _content.Children.Clear();
|
|
|
+ await RunSafelyAsync(async () =>
|
|
|
+ {
|
|
|
+ await LoadLookupsAsync();
|
|
|
+ var recordsCount = await _database.GetFlightsCountAsync();
|
|
|
+ var list = CreateCollectionView(_flights);
|
|
|
+ var durationEntry = CreateEntry("Длительность HH:mm:ss");
|
|
|
+ var transportPicker = CreatePicker("Транспорт", _transports);
|
|
|
+ var routePicker = CreatePicker("Маршрут", _routes);
|
|
|
+ TransportFlight? selected = null;
|
|
|
+
|
|
|
+ list.SelectionChanged += (_, e) =>
|
|
|
+ {
|
|
|
+ selected = e.CurrentSelection.FirstOrDefault() as TransportFlight;
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ durationEntry.Text = selected.Duration.ToString(@"hh\:mm\:ss");
|
|
|
+ transportPicker.SelectedItem = _transports.FirstOrDefault(transport => transport.TransportId == selected.TransportId);
|
|
|
+ routePicker.SelectedItem = _routes.FirstOrDefault(route => route.RouteId == selected.RouteId);
|
|
|
+ };
|
|
|
+
|
|
|
+ _content.Children.Add(CreateSectionTitle("Рейсы", recordsCount));
|
|
|
+ _content.Children.Add(list);
|
|
|
+ if (CanEdit)
|
|
|
+ {
|
|
|
+ _content.Children.Add(CreateForm(
|
|
|
+ [durationEntry, transportPicker, routePicker],
|
|
|
+ CreateCrudButtons(
|
|
|
+ add: async () =>
|
|
|
+ {
|
|
|
+ var flight = await CreateFlightFromFormAsync(durationEntry, transportPicker, routePicker);
|
|
|
+ if (flight is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.AddFlightAsync(flight);
|
|
|
+ await LoadFlightsSectionAsync();
|
|
|
+ },
|
|
|
+ update: async () =>
|
|
|
+ {
|
|
|
+ var flight = await CreateFlightFromFormAsync(durationEntry, transportPicker, routePicker);
|
|
|
+ if (selected is null || flight is null)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите рейс и заполните поля.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ flight.FlightId = selected.FlightId;
|
|
|
+ await _database.UpdateFlightAsync(flight);
|
|
|
+ await LoadFlightsSectionAsync();
|
|
|
+ },
|
|
|
+ delete: async () =>
|
|
|
+ {
|
|
|
+ if (selected is null)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _database.DeleteFlightAsync(selected.FlightId);
|
|
|
+ await LoadFlightsSectionAsync();
|
|
|
+ })));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void ShowFlightInfoSection()
|
|
|
+ {
|
|
|
+ _content.Children.Clear();
|
|
|
+ await RunSafelyAsync(async () =>
|
|
|
+ {
|
|
|
+ await LoadLookupsAsync();
|
|
|
+ var routePicker = CreatePicker("Фильтр по маршруту", _routes);
|
|
|
+ var flightInfo = await _database.GetFlightInfoAsync();
|
|
|
+ var recordsCount = await _database.GetFlightInfoCountAsync();
|
|
|
+ var list = CreateCollectionView(flightInfo);
|
|
|
+
|
|
|
+ var allButton = new Button
|
|
|
+ {
|
|
|
+ Text = "Все рейсы",
|
|
|
+ BackgroundColor = Color.FromArgb("#1D4ED8"),
|
|
|
+ TextColor = Colors.White,
|
|
|
+ CornerRadius = 8
|
|
|
+ };
|
|
|
+ allButton.Clicked += async (_, _) =>
|
|
|
+ {
|
|
|
+ await RunSafelyAsync(async () => list.ItemsSource = await _database.GetFlightInfoAsync());
|
|
|
+ };
|
|
|
+
|
|
|
+ var byRouteButton = new Button
|
|
|
+ {
|
|
|
+ Text = "По выбранному маршруту",
|
|
|
+ BackgroundColor = Color.FromArgb("#0F766E"),
|
|
|
+ TextColor = Colors.White,
|
|
|
+ CornerRadius = 8
|
|
|
+ };
|
|
|
+ byRouteButton.Clicked += async (_, _) =>
|
|
|
+ {
|
|
|
+ if (routePicker.SelectedItem is not TransitRoute route)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите маршрут.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await RunSafelyAsync(async () => list.ItemsSource = await _database.GetFlightsByRouteAsync(route.RouteId));
|
|
|
+ };
|
|
|
+
|
|
|
+ _content.Children.Add(CreateSectionTitle("Информация о рейсах", recordsCount));
|
|
|
+ _content.Children.Add(new Border
|
|
|
+ {
|
|
|
+ StrokeThickness = 1,
|
|
|
+ Stroke = Color.FromArgb("#D7DEE8"),
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ StrokeShape = new RoundRectangle { CornerRadius = 8 },
|
|
|
+ Padding = new Thickness(12),
|
|
|
+ Content = new VerticalStackLayout
|
|
|
+ {
|
|
|
+ Spacing = 10,
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ routePicker,
|
|
|
+ new HorizontalStackLayout
|
|
|
+ {
|
|
|
+ Spacing = 8,
|
|
|
+ Children = { allButton, byRouteButton }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ _content.Children.Add(list);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task<TransportFlight?> CreateFlightFromFormAsync(Entry durationEntry, Picker transportPicker, Picker routePicker)
|
|
|
+ {
|
|
|
+ if (!TimeSpan.TryParse(durationEntry.Text, out var duration) || duration <= TimeSpan.Zero)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Длительность должна быть больше 00:00:00 в формате HH:mm:ss.", "OK");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (transportPicker.SelectedItem is not AppTransport transport || routePicker.SelectedItem is not TransitRoute route)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Проверка", "Выберите транспорт и маршрут.", "OK");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return new TransportFlight
|
|
|
+ {
|
|
|
+ Duration = duration,
|
|
|
+ TransportId = transport.TransportId,
|
|
|
+ RouteId = route.RouteId
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Border CreateSectionTitle(string text, int recordsCount)
|
|
|
+ {
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ StrokeThickness = 0,
|
|
|
+ BackgroundColor = Color.FromArgb("#E8EEF6"),
|
|
|
+ StrokeShape = new RoundRectangle { CornerRadius = 8 },
|
|
|
+ Padding = new Thickness(14, 10),
|
|
|
+ Content = new Label
|
|
|
+ {
|
|
|
+ Text = $"{text}: {recordsCount} {GetRecordsWord(recordsCount)}",
|
|
|
+ FontSize = 20,
|
|
|
+ FontAttributes = FontAttributes.Bold,
|
|
|
+ TextColor = Color.FromArgb("#172033")
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string GetRecordsWord(int count)
|
|
|
+ {
|
|
|
+ var lastTwoDigits = count % 100;
|
|
|
+ if (lastTwoDigits is >= 11 and <= 14)
|
|
|
+ {
|
|
|
+ return "записей";
|
|
|
+ }
|
|
|
+
|
|
|
+ return (count % 10) switch
|
|
|
+ {
|
|
|
+ 1 => "запись",
|
|
|
+ >= 2 and <= 4 => "записи",
|
|
|
+ _ => "записей"
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Entry CreateEntry(string placeholder)
|
|
|
+ {
|
|
|
+ return new Entry
|
|
|
+ {
|
|
|
+ Placeholder = placeholder,
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ TextColor = Color.FromArgb("#172033"),
|
|
|
+ PlaceholderColor = Color.FromArgb("#7B8794")
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Picker CreatePicker<T>(string title, IReadOnlyList<T> items)
|
|
|
+ {
|
|
|
+ return new Picker
|
|
|
+ {
|
|
|
+ Title = title,
|
|
|
+ ItemsSource = items.ToList(),
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ TextColor = Color.FromArgb("#172033"),
|
|
|
+ TitleColor = Color.FromArgb("#7B8794")
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static CollectionView CreateCollectionView<T>(IEnumerable<T> items)
|
|
|
+ {
|
|
|
+ return new CollectionView
|
|
|
+ {
|
|
|
+ ItemsSource = items.ToList(),
|
|
|
+ SelectionMode = SelectionMode.Single,
|
|
|
+ EmptyView = "Нет данных",
|
|
|
+ MinimumHeightRequest = 220,
|
|
|
+ ItemTemplate = new DataTemplate(() =>
|
|
|
+ {
|
|
|
+ var label = new Label
|
|
|
+ {
|
|
|
+ FontSize = 15,
|
|
|
+ TextColor = Color.FromArgb("#172033"),
|
|
|
+ LineBreakMode = LineBreakMode.WordWrap
|
|
|
+ };
|
|
|
+ label.SetBinding(Label.TextProperty, ".");
|
|
|
+
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ StrokeThickness = 1,
|
|
|
+ Stroke = Color.FromArgb("#D7DEE8"),
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ StrokeShape = new RoundRectangle { CornerRadius = 8 },
|
|
|
+ Margin = new Thickness(0, 0, 0, 8),
|
|
|
+ Padding = new Thickness(12),
|
|
|
+ Content = label
|
|
|
+ };
|
|
|
+ })
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Border CreateForm(IReadOnlyList<View> fields, HorizontalStackLayout buttons)
|
|
|
+ {
|
|
|
+ var stack = new VerticalStackLayout { Spacing = 10 };
|
|
|
+ foreach (var field in fields)
|
|
|
+ {
|
|
|
+ stack.Children.Add(field);
|
|
|
+ }
|
|
|
+
|
|
|
+ stack.Children.Add(buttons);
|
|
|
+
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ StrokeThickness = 1,
|
|
|
+ Stroke = Color.FromArgb("#D7DEE8"),
|
|
|
+ BackgroundColor = Colors.White,
|
|
|
+ StrokeShape = new RoundRectangle { CornerRadius = 8 },
|
|
|
+ Padding = new Thickness(12),
|
|
|
+ Content = stack
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private HorizontalStackLayout CreateCrudButtons(Func<Task> add, Func<Task> update, Func<Task> delete, Button? extra = null)
|
|
|
+ {
|
|
|
+ var layout = new HorizontalStackLayout { Spacing = 8 };
|
|
|
+ layout.Children.Add(CreateActionButton("Добавить", "#1D4ED8", add));
|
|
|
+ layout.Children.Add(CreateActionButton("Сохранить", "#0F766E", update));
|
|
|
+ layout.Children.Add(CreateActionButton("Удалить", "#B42318", delete));
|
|
|
+
|
|
|
+ if (extra is not null)
|
|
|
+ {
|
|
|
+ layout.Children.Add(extra);
|
|
|
+ }
|
|
|
+
|
|
|
+ return layout;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Button CreateActionButton(string text, string color, Func<Task> action)
|
|
|
+ {
|
|
|
+ var button = new Button
|
|
|
+ {
|
|
|
+ Text = text,
|
|
|
+ BackgroundColor = Color.FromArgb(color),
|
|
|
+ TextColor = Colors.White,
|
|
|
+ CornerRadius = 8
|
|
|
+ };
|
|
|
+
|
|
|
+ button.Clicked += async (_, _) =>
|
|
|
+ {
|
|
|
+ if (!CanEdit)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Доступ запрещен", "Менеджер может только читать данные.", "OK");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await RunSafelyAsync(action);
|
|
|
+ };
|
|
|
+
|
|
|
+ return button;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool ValidateRequired(params Entry[] entries)
|
|
|
+ {
|
|
|
+ foreach (var entry in entries)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(entry.Text))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ClearEntries(params Entry[] entries)
|
|
|
+ {
|
|
|
+ foreach (var entry in entries)
|
|
|
+ {
|
|
|
+ entry.Text = string.Empty;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task RunSafelyAsync(Func<Task> action)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ await action();
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ await DisplayAlertAsync("Ошибка", ex.Message, "OK");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|