diff --git a/GSS2/ViewModels/AmineContent/BrandCalibrationViewModel.cs b/GSS2/ViewModels/AmineContent/BrandCalibrationViewModel.cs new file mode 100644 index 0000000..10438f6 --- /dev/null +++ b/GSS2/ViewModels/AmineContent/BrandCalibrationViewModel.cs @@ -0,0 +1,366 @@ +using System.Collections.ObjectModel; + +using Microsoft.Extensions.Logging; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +using GSS2.Core.Analysis.AmineContent.Database; + +using GSS2.UI.Core.ViewModels; +using GSS2.UI.Core.Services; + +namespace GSS2.ViewModels.AmineContent; + +public partial class BrandCalibrationViewModel : ViewModelBase +{ + /// + /// Внутренняя модель элементов меню + /// + public partial class MenuItemViewModel : ViewModelBase + { + [ObservableProperty] public partial int Id { get; set; } + [ObservableProperty] public partial string BrandName { get; set; } + [ObservableProperty] public partial string MixtureName { get; set; } + + public MenuItemViewModel(int id, string brandName, string mixtureName) + { + Id = id; + BrandName = brandName; + MixtureName = mixtureName; + } + } + + /// + /// Логгер + /// + private readonly ILogger _logger; + /// + /// КОнтекст базы данных + /// + private readonly Context _context; + /// + /// ViewLocator для получения других View по их моделям + /// + private readonly DependencyInjectionViewLocator _viewLocator; + /// + /// Сервис навигации между экранами + /// + private readonly NavigationService _navigation; + + /// + /// Меню открыто + /// + [ObservableProperty] public partial bool MenuOpened { get; set; } = false; + /// + /// Элементы меню + /// + [ObservableProperty] public partial ObservableCollection MenuItems { get; set; } + /// + /// Текущий выбранный элемент меню. Равен -1 если элемент не выбран + /// + [ObservableProperty] public partial int MenuSelectedIndex { get; set; } + /// + /// выбранного элемента + /// + [ObservableProperty] public partial int SelectedId { get; set; } + /// + /// выбранного элемента + /// + [ObservableProperty] public partial string SelectedUuid { get; set; } + /// + /// выбранного элемента + /// + [ObservableProperty] public partial string SelectedBrandName { get; set; } + /// + /// выбранного элемента + /// + [ObservableProperty] public partial string SelectedMixtureName { get; set; } + /// + /// выбранного элемента + /// + [ObservableProperty] public partial double SelectedMixtureNormalRate { get; set; } + /// + /// Можно ли производить перемещение между экранами + /// + [ObservableProperty] public partial bool CanScroll { get; set; } + + public BrandCalibrationViewModel( + ILogger logger, + Context context, + DependencyInjectionViewLocator viewLocator, + NavigationService navigation + ) + { + // Устанавливаем readonly поля + _logger = logger; + _context = context; + _viewLocator = viewLocator; + _navigation = navigation; + _logger.LogInformation("Инициализация"); + + // Заполняем свойства пустыми значениями + MenuOpened = new(); + MenuItems = new(); + MenuSelectedIndex = -1; + CanScroll = true; + SelectedId = 0; + SelectedUuid = Guid.NewGuid().ToString(); + SelectedBrandName = ""; + SelectedMixtureName = ""; + SelectedMixtureNormalRate = -1; + + // Получаем записи из базы данных + MenuItems = new ObservableCollection(_context.BrandRecords.Select(r => new MenuItemViewModel(r.Id, r.BrandName, r.MixtureName))); + if (MenuItems.Count() > 0) // Если записи есть, + MenuSelectedIndex = 0; // Выбираем первую запись + else // Если записей нет, + MenuSelectedIndex = -1; // Устанавливаем, что запись не выбрана + + _logger.LogInformation("Инициализировано"); + } + + /// + /// Вызывается при изменении + /// + partial void OnMenuSelectedIndexChanged(int value) + { + // Если значение выходит за верхнюю границу списка элементов меню, то логгируем как ошибку + if (value >= MenuItems.Count()) + _logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(MenuSelectedIndex), MenuSelectedIndex, nameof(MenuItems), MenuItems.Count()); + // Если значение больше или равно 0, то значит выбран один из элементов меню + if (value >= 0 && value < MenuItems.Count()) + { + _logger.LogTrace("Выбран элемент из списка: {}", value); + // Находим запись в базе данных + var selectedRecord = _context.BrandRecords.Find(MenuItems[value].Id); + // Если запись найдена, то заполняем поля из базы данных + if (selectedRecord is not null) + { + SelectedId = selectedRecord.Id; + SelectedUuid = selectedRecord.Uuid; + SelectedBrandName = selectedRecord.BrandName; + SelectedMixtureName = selectedRecord.MixtureName; + SelectedMixtureNormalRate = selectedRecord.MixtureNormalRate; + return; + } + // Если значение не найдено в базе данных + else + { + // Логгируем как ошибку + _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", MenuItems[value].Id, nameof(_context.BrandRecords)); + var nonExistingIndex = MenuSelectedIndex; // Сохраняем несуществующий индекс + // Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями + MenuSelectedIndex = -1; + // Удаляем несуществующий элемент из списка + MenuItems.RemoveAt(nonExistingIndex); + return; + } + } + + // Если элемент не выбран, заполняем поля пустыми значениями + _logger.LogTrace("Выбран новый элемент"); + SelectedId = 0; + SelectedUuid = Guid.NewGuid().ToString(); + SelectedBrandName = ""; + SelectedMixtureName = ""; + SelectedMixtureNormalRate = -1; + } + + /// + /// Вызывается при изменении + /// + partial void OnMenuOpenedChanged(bool value) + { + CanScroll = !value; + } + + + // Команды управления меню + /// + /// Открыть меню + /// + [RelayCommand] private void OpenMenu() => MenuOpened = true; + /// + /// Закрыть меню + /// + [RelayCommand] private void CloseMenu() => MenuOpened = false; + /// + /// Переключить видимость меню + /// + [RelayCommand] private void ToggleMenu() => MenuOpened = !MenuOpened; + + /// + /// Создать новую запись + /// + [RelayCommand] + private void CreateRecord() + { + _logger.LogInformation("Создание новой записи"); + // Если уже создана новая запись, то MenuSelectedIndex может быть равен -1 и + // при простом присвоении MenuSelectedIndex = -1 событие OnMenuSelectedIndexChanged не вызовется, + // потому что событие срабатывает только при изменении значения, а значение было -1 и привилось ему -1, + // поэтому если SelectedRecord != -1, то нужно явно вызвать метод OnMenuSelectedIndexChanged + if (MenuSelectedIndex != -1) + MenuSelectedIndex = -1; + else + OnMenuSelectedIndexChanged(-1); + } + + /// + /// Удалить выбранную запись из базы данных или очистить значения, если запись не выбрана + /// + [RelayCommand] + private void DeleteRecord() + { + try + { + _logger.LogTrace("Удаление записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), SelectedId, nameof(BrandRecord.BrandName), SelectedBrandName, nameof(BrandRecord.MixtureName), SelectedMixtureName); + // Если запись не выбрана, то просто создаём новую запись + if (MenuSelectedIndex < 0) + { + _logger.LogTrace("Запись не выбрана, создаём новую"); + CreateRecord(); + return; + } + + // Создаём модель для экрана подтверждения + var confirmViewModel = new DeleteConfirmViewModel(_navigation) + { + Question = "Вы уверены, что хотите удалить запись?" + }; + + // Создаём коллбек который вызовется при экрана подтверждения + void OnConfirmCloses(object? sender, bool result) + { + // Если пользователь отменил удаление, то выходим из коллбека + if (!result) + { + _logger.LogTrace("Пользователь отменил удаление"); + return; + } + + // Если запись не выбрана, то просто создаём новую запись + if (MenuSelectedIndex < 0) + { + _logger.LogTrace("Запись не выбрана, создаём новую"); + CreateRecord(); + return; + } + + // Сохраняем индекс удалённый записи и находим её в базе данных + var deletedMenuIndex = MenuSelectedIndex; + var deletedRecordId = SelectedId; + var deletedRecord = _context.BrandRecords.Find(deletedRecordId); + // Создаём новую запись + CreateRecord(); + + // Удаляем запись из списка + MenuItems.RemoveAt(deletedMenuIndex); + // Если удалённая запись не найдена в базе данных, то логгируем как ошибку + if (deletedRecord is null) + { + _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.BrandRecords)); + return; + } + + // Удаляем запись из базы данных и сохраняем изменения в базе данных + _context.BrandRecords.Remove(deletedRecord); + _context.SaveChanges(); + _logger.LogTrace("Запись удалена"); + + confirmViewModel.Closes -= OnConfirmCloses; // Отписываемся от события закрытия экрана подтверждения + } + + // Создаём View экрана подтверждения + var confirmView = _viewLocator.Build(confirmViewModel); + // Если View не найден, то логгируем как ошибку + if (confirmView is null) + { + _logger.LogError("Не удалось найти View для экрана подтверждения"); + return; + } + + _logger.LogTrace("Задаём вопрос"); + // Подписываемся на событие закрытия экрана подтверждения + confirmViewModel.Closes += OnConfirmCloses; + // Преходим на экран подтверждения, при закрытии он сам вернёт экран обратно на текущий + _navigation.NavigateTo(confirmView); + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение при удалении записи"); + } + } + + /// + /// Сохранить текущие значения в новую запись или перезаписать значения в выдранной записи + /// + [RelayCommand] + private void SaveRecord() + { + try + { + _logger.LogTrace("Сохранение записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), SelectedId, nameof(BrandRecord.BrandName), SelectedBrandName, nameof(BrandRecord.MixtureName), SelectedMixtureName); + // Если запись не выбрана + if (MenuSelectedIndex < 0) + { + _logger.LogTrace("Создаём новую запись"); + // Создаём новую запись + var newRecord = new BrandRecord( + uuid: SelectedUuid, + brandName: SelectedBrandName, + mixtureName: SelectedMixtureName, + mixtureNormalRate: SelectedMixtureNormalRate + ); + + // При добавляем запись в базу данных + var attachedRecord = _context.BrandRecords.Add(newRecord); + + // Сохраняем изменения в базе данных + _context.SaveChanges(); + _logger.LogTrace("Запись сохранена"); + + // Добавляем новый элемент в меню + MenuItems.Add(new MenuItemViewModel(attachedRecord.Entity.Id, attachedRecord.Entity.BrandName, attachedRecord.Entity.MixtureName)); + // Выбираем последний элемент из меню + MenuSelectedIndex = MenuItems.Count() - 1; + } + // Если запись выбрана + else + { + // Находим запись в базе данных + var attachedRecord = _context.BrandRecords.Find(SelectedId); + // Если запись не найдена, то логгируем как ошибку + if (attachedRecord is null) + { + _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SelectedId, nameof(_context.BrandRecords)); + return; + } + + _logger.LogTrace("Записываем изменения в базу данных"); + // Если в данных есть изменения, то записываем их + if (attachedRecord.BrandName != SelectedBrandName) + attachedRecord.BrandName = SelectedBrandName; + if (attachedRecord.MixtureName != SelectedMixtureName) + attachedRecord.MixtureName = SelectedMixtureName; + if (attachedRecord.MixtureNormalRate != SelectedMixtureNormalRate) + attachedRecord.MixtureNormalRate = SelectedMixtureNormalRate; + + // Записываем изменения в меню + _logger.LogTrace("Записываем изменения в меню"); + MenuItems[MenuSelectedIndex].BrandName = SelectedBrandName; + MenuItems[MenuSelectedIndex].MixtureName = SelectedMixtureName; + + // Сохраняем изменения в базе данных + _context.SaveChanges(); + _logger.LogTrace("Запись сохранена"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение при сохранении записи"); + } + } + +} diff --git a/GSS2/Views/AmineContent/BrandCalibrationView.axaml b/GSS2/Views/AmineContent/BrandCalibrationView.axaml new file mode 100644 index 0000000..0b2b582 --- /dev/null +++ b/GSS2/Views/AmineContent/BrandCalibrationView.axaml @@ -0,0 +1,329 @@ + + + + + + + + 5 + + + + + + + 5 + + + + + + + + + + + + + + + 5 + + + + + + 5 + + + + + + + + + + + + + 5 + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GSS2/Views/AmineContent/BrandCalibrationView.axaml.cs b/GSS2/Views/AmineContent/BrandCalibrationView.axaml.cs new file mode 100644 index 0000000..d9dff9d --- /dev/null +++ b/GSS2/Views/AmineContent/BrandCalibrationView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace GSS2.Views.AmineContent; + +public partial class BrandCalibrationView : UserControl +{ + public BrandCalibrationView() + { + InitializeComponent(); + } +}