MainPage.xaml.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. using TransportMauiApp.Models;
  2. using TransportMauiApp.Services;
  3. using Microsoft.Maui.Controls.Shapes;
  4. namespace TransportMauiApp;
  5. public partial class MainPage : ContentPage
  6. {
  7. private readonly DatabaseService _database = new();
  8. private readonly VerticalStackLayout _content = new() { Spacing = 14 };
  9. private readonly List<Button> _sectionButtons = [];
  10. private UserSession? _session;
  11. private bool CanEdit => _session?.IsAdministrator == true;
  12. private List<Driver> _drivers = [];
  13. private List<AppTransport> _transports = [];
  14. private List<TransitRoute> _routes = [];
  15. private List<TransportFlight> _flights = [];
  16. public MainPage()
  17. {
  18. InitializeComponent();
  19. Title = "Учет транспорта";
  20. ShowLogin();
  21. }
  22. private void ShowLogin()
  23. {
  24. var loginEntry = CreateEntry("Логин");
  25. var passwordEntry = CreateEntry("Пароль");
  26. passwordEntry.IsPassword = true;
  27. var statusLabel = new Label
  28. {
  29. TextColor = Colors.Firebrick,
  30. FontSize = 14,
  31. IsVisible = false
  32. };
  33. var loginButton = new Button
  34. {
  35. Text = "Войти",
  36. HeightRequest = 46,
  37. BackgroundColor = Color.FromArgb("#1D4ED8"),
  38. TextColor = Colors.White,
  39. CornerRadius = 8
  40. };
  41. loginButton.Clicked += async (_, _) =>
  42. {
  43. statusLabel.IsVisible = false;
  44. loginButton.IsEnabled = false;
  45. try
  46. {
  47. var login = loginEntry.Text?.Trim() ?? string.Empty;
  48. var password = passwordEntry.Text ?? string.Empty;
  49. var session = await _database.AuthenticateAsync(login, password);
  50. if (session is null)
  51. {
  52. statusLabel.Text = "Неверный логин или пароль.";
  53. statusLabel.IsVisible = true;
  54. return;
  55. }
  56. _session = session;
  57. await LoadLookupsAsync();
  58. ShowWorkspace();
  59. }
  60. catch (Exception ex)
  61. {
  62. statusLabel.Text = $"Ошибка подключения: {ex.Message}";
  63. statusLabel.IsVisible = true;
  64. }
  65. finally
  66. {
  67. loginButton.IsEnabled = true;
  68. }
  69. };
  70. Content = new Grid
  71. {
  72. Padding = new Thickness(22),
  73. Children =
  74. {
  75. new Border
  76. {
  77. StrokeThickness = 1,
  78. Stroke = Color.FromArgb("#D7DEE8"),
  79. BackgroundColor = Colors.White,
  80. StrokeShape = new RoundRectangle { CornerRadius = 8 },
  81. MaximumWidthRequest = 460,
  82. VerticalOptions = LayoutOptions.Center,
  83. HorizontalOptions = LayoutOptions.Center,
  84. Padding = new Thickness(24),
  85. Content = new VerticalStackLayout
  86. {
  87. Spacing = 14,
  88. Children =
  89. {
  90. new Label
  91. {
  92. Text = "Авторизация",
  93. FontSize = 26,
  94. FontAttributes = FontAttributes.Bold,
  95. TextColor = Color.FromArgb("#172033")
  96. },
  97. loginEntry,
  98. passwordEntry,
  99. loginButton,
  100. statusLabel
  101. }
  102. }
  103. }
  104. }
  105. };
  106. }
  107. private void ShowWorkspace()
  108. {
  109. _sectionButtons.Clear();
  110. _content.Children.Clear();
  111. var header = new Grid
  112. {
  113. ColumnDefinitions =
  114. {
  115. new ColumnDefinition(GridLength.Star),
  116. new ColumnDefinition(GridLength.Auto)
  117. },
  118. RowDefinitions =
  119. {
  120. new RowDefinition(GridLength.Auto),
  121. new RowDefinition(GridLength.Auto)
  122. },
  123. ColumnSpacing = 12
  124. };
  125. header.Add(new Label
  126. {
  127. Text = "Учет транспорта",
  128. FontSize = 26,
  129. FontAttributes = FontAttributes.Bold,
  130. TextColor = Color.FromArgb("#172033")
  131. }, 0, 0);
  132. var logoutButton = new Button
  133. {
  134. Text = "Выйти",
  135. BackgroundColor = Color.FromArgb("#EEF2F7"),
  136. TextColor = Color.FromArgb("#172033"),
  137. CornerRadius = 8
  138. };
  139. logoutButton.Clicked += (_, _) =>
  140. {
  141. _session = null;
  142. ShowLogin();
  143. };
  144. header.Add(logoutButton, 1, 0);
  145. header.Add(new Label
  146. {
  147. Text = $"Пользователь: {_session?.Login} | Роль: {_session?.DisplayRole} | Режим: {(CanEdit ? "полный доступ" : "только чтение")}",
  148. FontSize = 14,
  149. TextColor = Color.FromArgb("#526070")
  150. }, 0, 1);
  151. var tabs = new HorizontalStackLayout
  152. {
  153. Spacing = 8,
  154. Children =
  155. {
  156. CreateSectionButton("Водители", ShowDriversSection),
  157. CreateSectionButton("Транспорт", ShowTransportsSection),
  158. CreateSectionButton("Маршруты", ShowRoutesSection),
  159. CreateSectionButton("Рейсы", ShowFlightsSection),
  160. CreateSectionButton("Информация о рейсах", ShowFlightInfoSection)
  161. }
  162. };
  163. Content = new ScrollView
  164. {
  165. Content = new VerticalStackLayout
  166. {
  167. Padding = new Thickness(20),
  168. Spacing = 16,
  169. Children =
  170. {
  171. header,
  172. new ScrollView { Orientation = ScrollOrientation.Horizontal, Content = tabs },
  173. _content
  174. }
  175. }
  176. };
  177. ShowDriversSection();
  178. }
  179. private Button CreateSectionButton(string text, Action action)
  180. {
  181. var button = new Button
  182. {
  183. Text = text,
  184. CornerRadius = 8,
  185. Padding = new Thickness(14, 8),
  186. BackgroundColor = Colors.White,
  187. BorderColor = Color.FromArgb("#D7DEE8"),
  188. BorderWidth = 1,
  189. TextColor = Color.FromArgb("#172033")
  190. };
  191. button.Clicked += (_, _) =>
  192. {
  193. foreach (var tab in _sectionButtons)
  194. {
  195. tab.BackgroundColor = Colors.White;
  196. tab.TextColor = Color.FromArgb("#172033");
  197. }
  198. button.BackgroundColor = Color.FromArgb("#1D4ED8");
  199. button.TextColor = Colors.White;
  200. action();
  201. };
  202. _sectionButtons.Add(button);
  203. return button;
  204. }
  205. private async Task LoadLookupsAsync()
  206. {
  207. _drivers = [.. await _database.GetDriversAsync()];
  208. _transports = [.. await _database.GetTransportsAsync()];
  209. _routes = [.. await _database.GetRoutesAsync()];
  210. _flights = [.. await _database.GetFlightsAsync()];
  211. }
  212. private async void ShowDriversSection()
  213. {
  214. await LoadDriversSectionAsync();
  215. }
  216. private async Task LoadDriversSectionAsync()
  217. {
  218. _content.Children.Clear();
  219. await RunSafelyAsync(async () =>
  220. {
  221. _drivers = [.. await _database.GetDriversAsync()];
  222. var recordsCount = await _database.GetDriversCountAsync();
  223. var list = CreateCollectionView(_drivers);
  224. var nameEntry = CreateEntry("Имя");
  225. var surnameEntry = CreateEntry("Фамилия");
  226. var patronimicEntry = CreateEntry("Отчество");
  227. Driver? selected = null;
  228. list.SelectionChanged += (_, e) =>
  229. {
  230. selected = e.CurrentSelection.FirstOrDefault() as Driver;
  231. if (selected is null)
  232. {
  233. return;
  234. }
  235. nameEntry.Text = selected.Name;
  236. surnameEntry.Text = selected.Surname;
  237. patronimicEntry.Text = selected.Patronimic;
  238. };
  239. _content.Children.Add(CreateSectionTitle("Водители", recordsCount));
  240. _content.Children.Add(list);
  241. if (CanEdit)
  242. {
  243. _content.Children.Add(CreateForm(
  244. [nameEntry, surnameEntry, patronimicEntry],
  245. CreateCrudButtons(
  246. add: async () =>
  247. {
  248. if (!ValidateRequired(nameEntry, surnameEntry, patronimicEntry))
  249. {
  250. await DisplayAlertAsync("Проверка", "Заполните все поля.", "OK");
  251. return;
  252. }
  253. await _database.AddDriverAsync(new Driver
  254. {
  255. Name = nameEntry.Text!.Trim(),
  256. Surname = surnameEntry.Text!.Trim(),
  257. Patronimic = patronimicEntry.Text!.Trim()
  258. });
  259. ClearEntries(nameEntry, surnameEntry, patronimicEntry);
  260. await LoadDriversSectionAsync();
  261. },
  262. update: async () =>
  263. {
  264. if (selected is null || !ValidateRequired(nameEntry, surnameEntry, patronimicEntry))
  265. {
  266. await DisplayAlertAsync("Проверка", "Выберите запись и заполните все поля.", "OK");
  267. return;
  268. }
  269. selected.Name = nameEntry.Text!.Trim();
  270. selected.Surname = surnameEntry.Text!.Trim();
  271. selected.Patronimic = patronimicEntry.Text!.Trim();
  272. await _database.UpdateDriverAsync(selected);
  273. await LoadDriversSectionAsync();
  274. },
  275. delete: async () =>
  276. {
  277. if (selected is null)
  278. {
  279. await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
  280. return;
  281. }
  282. await _database.DeleteDriverAsync(selected.DriverId);
  283. await LoadDriversSectionAsync();
  284. })));
  285. }
  286. });
  287. }
  288. private async void ShowTransportsSection()
  289. {
  290. await LoadTransportsSectionAsync();
  291. }
  292. private async Task LoadTransportsSectionAsync()
  293. {
  294. _content.Children.Clear();
  295. await RunSafelyAsync(async () =>
  296. {
  297. await LoadLookupsAsync();
  298. var recordsCount = await _database.GetTransportsCountAsync();
  299. var list = CreateCollectionView(_transports);
  300. var branchEntry = CreateEntry("Филиал");
  301. var modelEntry = CreateEntry("Модель");
  302. var plateEntry = CreateEntry("Госномер");
  303. var driverPicker = CreatePicker("Водитель", _drivers);
  304. AppTransport? selected = null;
  305. list.SelectionChanged += (_, e) =>
  306. {
  307. selected = e.CurrentSelection.FirstOrDefault() as AppTransport;
  308. if (selected is null)
  309. {
  310. return;
  311. }
  312. branchEntry.Text = selected.Branch;
  313. modelEntry.Text = selected.Model;
  314. plateEntry.Text = selected.LicensePlateNumber;
  315. driverPicker.SelectedItem = _drivers.FirstOrDefault(driver => driver.DriverId == selected.DriverId);
  316. };
  317. _content.Children.Add(CreateSectionTitle("Транспорт", recordsCount));
  318. _content.Children.Add(list);
  319. if (CanEdit)
  320. {
  321. var assignButton = new Button
  322. {
  323. Text = "Назначить водителя процедурой",
  324. BackgroundColor = Color.FromArgb("#0F766E"),
  325. TextColor = Colors.White,
  326. CornerRadius = 8
  327. };
  328. assignButton.Clicked += async (_, _) =>
  329. {
  330. if (selected is null || driverPicker.SelectedItem is not Driver driver)
  331. {
  332. await DisplayAlertAsync("Проверка", "Выберите транспорт и водителя.", "OK");
  333. return;
  334. }
  335. await RunSafelyAsync(async () =>
  336. {
  337. await _database.AssignDriverToTransportAsync(selected.TransportId, driver.DriverId);
  338. await LoadTransportsSectionAsync();
  339. });
  340. };
  341. _content.Children.Add(CreateForm(
  342. [branchEntry, modelEntry, plateEntry, driverPicker],
  343. CreateCrudButtons(
  344. add: async () =>
  345. {
  346. if (!ValidateRequired(branchEntry, modelEntry, plateEntry))
  347. {
  348. await DisplayAlertAsync("Проверка", "Заполните филиал, модель и госномер.", "OK");
  349. return;
  350. }
  351. await _database.AddTransportAsync(new AppTransport
  352. {
  353. Branch = branchEntry.Text!.Trim(),
  354. Model = modelEntry.Text!.Trim(),
  355. LicensePlateNumber = plateEntry.Text!.Trim(),
  356. DriverId = (driverPicker.SelectedItem as Driver)?.DriverId
  357. });
  358. await LoadTransportsSectionAsync();
  359. },
  360. update: async () =>
  361. {
  362. if (selected is null || !ValidateRequired(branchEntry, modelEntry, plateEntry))
  363. {
  364. await DisplayAlertAsync("Проверка", "Выберите запись и заполните обязательные поля.", "OK");
  365. return;
  366. }
  367. selected.Branch = branchEntry.Text!.Trim();
  368. selected.Model = modelEntry.Text!.Trim();
  369. selected.LicensePlateNumber = plateEntry.Text!.Trim();
  370. selected.DriverId = (driverPicker.SelectedItem as Driver)?.DriverId;
  371. await _database.UpdateTransportAsync(selected);
  372. await LoadTransportsSectionAsync();
  373. },
  374. delete: async () =>
  375. {
  376. if (selected is null)
  377. {
  378. await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
  379. return;
  380. }
  381. await _database.DeleteTransportAsync(selected.TransportId);
  382. await LoadTransportsSectionAsync();
  383. },
  384. extra: assignButton)));
  385. }
  386. });
  387. }
  388. private async void ShowRoutesSection()
  389. {
  390. await LoadRoutesSectionAsync();
  391. }
  392. private async Task LoadRoutesSectionAsync()
  393. {
  394. _content.Children.Clear();
  395. await RunSafelyAsync(async () =>
  396. {
  397. _routes = [.. await _database.GetRoutesAsync()];
  398. var recordsCount = await _database.GetRoutesCountAsync();
  399. var list = CreateCollectionView(_routes);
  400. var routeNameEntry = CreateEntry("Название маршрута");
  401. TransitRoute? selected = null;
  402. list.SelectionChanged += (_, e) =>
  403. {
  404. selected = e.CurrentSelection.FirstOrDefault() as TransitRoute;
  405. routeNameEntry.Text = selected?.RouteName;
  406. };
  407. _content.Children.Add(CreateSectionTitle("Маршруты", recordsCount));
  408. _content.Children.Add(list);
  409. if (CanEdit)
  410. {
  411. _content.Children.Add(CreateForm(
  412. [routeNameEntry],
  413. CreateCrudButtons(
  414. add: async () =>
  415. {
  416. if (!ValidateRequired(routeNameEntry))
  417. {
  418. await DisplayAlertAsync("Проверка", "Введите название маршрута.", "OK");
  419. return;
  420. }
  421. await _database.AddRouteAsync(new TransitRoute { RouteName = routeNameEntry.Text!.Trim() });
  422. await LoadRoutesSectionAsync();
  423. },
  424. update: async () =>
  425. {
  426. if (selected is null || !ValidateRequired(routeNameEntry))
  427. {
  428. await DisplayAlertAsync("Проверка", "Выберите запись и заполните название.", "OK");
  429. return;
  430. }
  431. selected.RouteName = routeNameEntry.Text!.Trim();
  432. await _database.UpdateRouteAsync(selected);
  433. await LoadRoutesSectionAsync();
  434. },
  435. delete: async () =>
  436. {
  437. if (selected is null)
  438. {
  439. await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
  440. return;
  441. }
  442. await _database.DeleteRouteAsync(selected.RouteId);
  443. await LoadRoutesSectionAsync();
  444. })));
  445. }
  446. });
  447. }
  448. private async void ShowFlightsSection()
  449. {
  450. await LoadFlightsSectionAsync();
  451. }
  452. private async Task LoadFlightsSectionAsync()
  453. {
  454. _content.Children.Clear();
  455. await RunSafelyAsync(async () =>
  456. {
  457. await LoadLookupsAsync();
  458. var recordsCount = await _database.GetFlightsCountAsync();
  459. var list = CreateCollectionView(_flights);
  460. var durationEntry = CreateEntry("Длительность HH:mm:ss");
  461. var transportPicker = CreatePicker("Транспорт", _transports);
  462. var routePicker = CreatePicker("Маршрут", _routes);
  463. TransportFlight? selected = null;
  464. list.SelectionChanged += (_, e) =>
  465. {
  466. selected = e.CurrentSelection.FirstOrDefault() as TransportFlight;
  467. if (selected is null)
  468. {
  469. return;
  470. }
  471. durationEntry.Text = selected.Duration.ToString(@"hh\:mm\:ss");
  472. transportPicker.SelectedItem = _transports.FirstOrDefault(transport => transport.TransportId == selected.TransportId);
  473. routePicker.SelectedItem = _routes.FirstOrDefault(route => route.RouteId == selected.RouteId);
  474. };
  475. _content.Children.Add(CreateSectionTitle("Рейсы", recordsCount));
  476. _content.Children.Add(list);
  477. if (CanEdit)
  478. {
  479. _content.Children.Add(CreateForm(
  480. [durationEntry, transportPicker, routePicker],
  481. CreateCrudButtons(
  482. add: async () =>
  483. {
  484. var flight = await CreateFlightFromFormAsync(durationEntry, transportPicker, routePicker);
  485. if (flight is null)
  486. {
  487. return;
  488. }
  489. await _database.AddFlightAsync(flight);
  490. await LoadFlightsSectionAsync();
  491. },
  492. update: async () =>
  493. {
  494. var flight = await CreateFlightFromFormAsync(durationEntry, transportPicker, routePicker);
  495. if (selected is null || flight is null)
  496. {
  497. await DisplayAlertAsync("Проверка", "Выберите рейс и заполните поля.", "OK");
  498. return;
  499. }
  500. flight.FlightId = selected.FlightId;
  501. await _database.UpdateFlightAsync(flight);
  502. await LoadFlightsSectionAsync();
  503. },
  504. delete: async () =>
  505. {
  506. if (selected is null)
  507. {
  508. await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
  509. return;
  510. }
  511. await _database.DeleteFlightAsync(selected.FlightId);
  512. await LoadFlightsSectionAsync();
  513. })));
  514. }
  515. });
  516. }
  517. private async void ShowFlightInfoSection()
  518. {
  519. _content.Children.Clear();
  520. await RunSafelyAsync(async () =>
  521. {
  522. await LoadLookupsAsync();
  523. var routePicker = CreatePicker("Фильтр по маршруту", _routes);
  524. var flightInfo = await _database.GetFlightInfoAsync();
  525. var recordsCount = await _database.GetFlightInfoCountAsync();
  526. var list = CreateCollectionView(flightInfo);
  527. var allButton = new Button
  528. {
  529. Text = "Все рейсы",
  530. BackgroundColor = Color.FromArgb("#1D4ED8"),
  531. TextColor = Colors.White,
  532. CornerRadius = 8
  533. };
  534. allButton.Clicked += async (_, _) =>
  535. {
  536. await RunSafelyAsync(async () => list.ItemsSource = await _database.GetFlightInfoAsync());
  537. };
  538. var byRouteButton = new Button
  539. {
  540. Text = "По выбранному маршруту",
  541. BackgroundColor = Color.FromArgb("#0F766E"),
  542. TextColor = Colors.White,
  543. CornerRadius = 8
  544. };
  545. byRouteButton.Clicked += async (_, _) =>
  546. {
  547. if (routePicker.SelectedItem is not TransitRoute route)
  548. {
  549. await DisplayAlertAsync("Проверка", "Выберите маршрут.", "OK");
  550. return;
  551. }
  552. await RunSafelyAsync(async () => list.ItemsSource = await _database.GetFlightsByRouteAsync(route.RouteId));
  553. };
  554. _content.Children.Add(CreateSectionTitle("Информация о рейсах", recordsCount));
  555. _content.Children.Add(new Border
  556. {
  557. StrokeThickness = 1,
  558. Stroke = Color.FromArgb("#D7DEE8"),
  559. BackgroundColor = Colors.White,
  560. StrokeShape = new RoundRectangle { CornerRadius = 8 },
  561. Padding = new Thickness(12),
  562. Content = new VerticalStackLayout
  563. {
  564. Spacing = 10,
  565. Children =
  566. {
  567. routePicker,
  568. new HorizontalStackLayout
  569. {
  570. Spacing = 8,
  571. Children = { allButton, byRouteButton }
  572. }
  573. }
  574. }
  575. });
  576. _content.Children.Add(list);
  577. });
  578. }
  579. private async Task<TransportFlight?> CreateFlightFromFormAsync(Entry durationEntry, Picker transportPicker, Picker routePicker)
  580. {
  581. if (!TimeSpan.TryParse(durationEntry.Text, out var duration) || duration <= TimeSpan.Zero)
  582. {
  583. await DisplayAlertAsync("Проверка", "Длительность должна быть больше 00:00:00 в формате HH:mm:ss.", "OK");
  584. return null;
  585. }
  586. if (transportPicker.SelectedItem is not AppTransport transport || routePicker.SelectedItem is not TransitRoute route)
  587. {
  588. await DisplayAlertAsync("Проверка", "Выберите транспорт и маршрут.", "OK");
  589. return null;
  590. }
  591. return new TransportFlight
  592. {
  593. Duration = duration,
  594. TransportId = transport.TransportId,
  595. RouteId = route.RouteId
  596. };
  597. }
  598. private static Border CreateSectionTitle(string text, int recordsCount)
  599. {
  600. return new Border
  601. {
  602. StrokeThickness = 0,
  603. BackgroundColor = Color.FromArgb("#E8EEF6"),
  604. StrokeShape = new RoundRectangle { CornerRadius = 8 },
  605. Padding = new Thickness(14, 10),
  606. Content = new Label
  607. {
  608. Text = $"{text}: {recordsCount} {GetRecordsWord(recordsCount)}",
  609. FontSize = 20,
  610. FontAttributes = FontAttributes.Bold,
  611. TextColor = Color.FromArgb("#172033")
  612. }
  613. };
  614. }
  615. private static string GetRecordsWord(int count)
  616. {
  617. var lastTwoDigits = count % 100;
  618. if (lastTwoDigits is >= 11 and <= 14)
  619. {
  620. return "записей";
  621. }
  622. return (count % 10) switch
  623. {
  624. 1 => "запись",
  625. >= 2 and <= 4 => "записи",
  626. _ => "записей"
  627. };
  628. }
  629. private static Entry CreateEntry(string placeholder)
  630. {
  631. return new Entry
  632. {
  633. Placeholder = placeholder,
  634. BackgroundColor = Colors.White,
  635. TextColor = Color.FromArgb("#172033"),
  636. PlaceholderColor = Color.FromArgb("#7B8794")
  637. };
  638. }
  639. private static Picker CreatePicker<T>(string title, IReadOnlyList<T> items)
  640. {
  641. return new Picker
  642. {
  643. Title = title,
  644. ItemsSource = items.ToList(),
  645. BackgroundColor = Colors.White,
  646. TextColor = Color.FromArgb("#172033"),
  647. TitleColor = Color.FromArgb("#7B8794")
  648. };
  649. }
  650. private static CollectionView CreateCollectionView<T>(IEnumerable<T> items)
  651. {
  652. return new CollectionView
  653. {
  654. ItemsSource = items.ToList(),
  655. SelectionMode = SelectionMode.Single,
  656. EmptyView = "Нет данных",
  657. MinimumHeightRequest = 220,
  658. ItemTemplate = new DataTemplate(() =>
  659. {
  660. var label = new Label
  661. {
  662. FontSize = 15,
  663. TextColor = Color.FromArgb("#172033"),
  664. LineBreakMode = LineBreakMode.WordWrap
  665. };
  666. label.SetBinding(Label.TextProperty, ".");
  667. return new Border
  668. {
  669. StrokeThickness = 1,
  670. Stroke = Color.FromArgb("#D7DEE8"),
  671. BackgroundColor = Colors.White,
  672. StrokeShape = new RoundRectangle { CornerRadius = 8 },
  673. Margin = new Thickness(0, 0, 0, 8),
  674. Padding = new Thickness(12),
  675. Content = label
  676. };
  677. })
  678. };
  679. }
  680. private static Border CreateForm(IReadOnlyList<View> fields, HorizontalStackLayout buttons)
  681. {
  682. var stack = new VerticalStackLayout { Spacing = 10 };
  683. foreach (var field in fields)
  684. {
  685. stack.Children.Add(field);
  686. }
  687. stack.Children.Add(buttons);
  688. return new Border
  689. {
  690. StrokeThickness = 1,
  691. Stroke = Color.FromArgb("#D7DEE8"),
  692. BackgroundColor = Colors.White,
  693. StrokeShape = new RoundRectangle { CornerRadius = 8 },
  694. Padding = new Thickness(12),
  695. Content = stack
  696. };
  697. }
  698. private HorizontalStackLayout CreateCrudButtons(Func<Task> add, Func<Task> update, Func<Task> delete, Button? extra = null)
  699. {
  700. var layout = new HorizontalStackLayout { Spacing = 8 };
  701. layout.Children.Add(CreateActionButton("Добавить", "#1D4ED8", add));
  702. layout.Children.Add(CreateActionButton("Сохранить", "#0F766E", update));
  703. layout.Children.Add(CreateActionButton("Удалить", "#B42318", delete));
  704. if (extra is not null)
  705. {
  706. layout.Children.Add(extra);
  707. }
  708. return layout;
  709. }
  710. private Button CreateActionButton(string text, string color, Func<Task> action)
  711. {
  712. var button = new Button
  713. {
  714. Text = text,
  715. BackgroundColor = Color.FromArgb(color),
  716. TextColor = Colors.White,
  717. CornerRadius = 8
  718. };
  719. button.Clicked += async (_, _) =>
  720. {
  721. if (!CanEdit)
  722. {
  723. await DisplayAlertAsync("Доступ запрещен", "Менеджер может только читать данные.", "OK");
  724. return;
  725. }
  726. await RunSafelyAsync(action);
  727. };
  728. return button;
  729. }
  730. private static bool ValidateRequired(params Entry[] entries)
  731. {
  732. foreach (var entry in entries)
  733. {
  734. if (string.IsNullOrWhiteSpace(entry.Text))
  735. {
  736. return false;
  737. }
  738. }
  739. return true;
  740. }
  741. private static void ClearEntries(params Entry[] entries)
  742. {
  743. foreach (var entry in entries)
  744. {
  745. entry.Text = string.Empty;
  746. }
  747. }
  748. private async Task RunSafelyAsync(Func<Task> action)
  749. {
  750. try
  751. {
  752. await action();
  753. }
  754. catch (Exception ex)
  755. {
  756. await DisplayAlertAsync("Ошибка", ex.Message, "OK");
  757. }
  758. }
  759. }