From 511bd023f2e2c3008e8b766d072b70d39680ef86534542f1cddde922a558a0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= <93217193+Daniel-Garmig@users.noreply.github.com> Date: Sun, 23 Aug 2026 21:57:26 +0200 Subject: [PATCH] GuestLog - User message improvements - Added UserMessageService to have common methods for showing messages and errors to the user. - Added Result record so services can return status on operation error (so it can be shown in the UI messages). - Updated services to use the new Result record. - Updated pages to use the new IUserMessageService. --- .../Controls/AddressQueryDialog.razor | 7 +- .../Controls/GuestDocumentSearchDialog.razor | 8 +- .../Components/Forms/StayEntryForm.razor | 8 +- .../Pages/DebugPages/AddressQuery.razor | 5 +- .../Components/Pages/DebugPages/Empty.razor | 4 +- .../Pages/GuestDocuments/GuestDocDetail.razor | 10 +-- .../Pages/GuestDocuments/GuestDocList.razor | 13 +-- .../Pages/GuestDocuments/GuestDocNew.razor | 17 ++-- .../Pages/StayEntries/StayEntryDetail.razor | 6 +- .../Pages/StayEntries/StayEntryList.razor | 13 +-- .../Pages/StayEntries/StayEntryNew.razor | 12 +-- src/Somnia.GuestLog/Components/_Imports.razor | 3 +- src/Somnia.GuestLog/Models/DTOs/Result.cs | 24 +++++ src/Somnia.GuestLog/Models/ResultCode.cs | 11 +++ src/Somnia.GuestLog/Program.cs | 2 + .../Resources/LanguageLocale.resx | 10 +++ .../Services/GuestDocumentService.cs | 20 ++--- .../Services/IGuestDocumentService.cs | 8 +- .../Services/IStayEntryService.cs | 8 +- .../Services/IUserMessageService.cs | 17 ++++ .../Services/StayEntryService.cs | 25 +++--- .../Services/UserMessageService.cs | 88 +++++++++++++++++++ 22 files changed, 239 insertions(+), 80 deletions(-) create mode 100644 src/Somnia.GuestLog/Models/DTOs/Result.cs create mode 100644 src/Somnia.GuestLog/Models/ResultCode.cs create mode 100644 src/Somnia.GuestLog/Services/IUserMessageService.cs create mode 100644 src/Somnia.GuestLog/Services/UserMessageService.cs diff --git a/src/Somnia.GuestLog/Components/Controls/AddressQueryDialog.razor b/src/Somnia.GuestLog/Components/Controls/AddressQueryDialog.razor index c40679c..7f8b2b7 100644 --- a/src/Somnia.GuestLog/Components/Controls/AddressQueryDialog.razor +++ b/src/Somnia.GuestLog/Components/Controls/AddressQueryDialog.razor @@ -1,9 +1,10 @@ @using Somnia.GuestLog.Models.DTOs; +@using Somnia.GuestLog.Services @using Somnia.GuestLog.Services.AddressQuery; @inject ILogger logger; @inject IAddressQueryFactory addressQueryFactory; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @@ -112,7 +113,7 @@ catch (Exception ex) { logger.LogError(ex, "SearchAddress returned an error!"); - Snackbar.Add($"Error searching address: {ex.Message}", Severity.Error); + userMessage.ShowMessage($"Error searching address: {ex.Message}", MessageKind.Error); } } @@ -120,7 +121,7 @@ { if (SelectedAddressResult is null) { - Snackbar.Add("Select an address", Severity.Warning); + userMessage.ShowMessage("Select an address", MessageKind.Warning); return; } diff --git a/src/Somnia.GuestLog/Components/Controls/GuestDocumentSearchDialog.razor b/src/Somnia.GuestLog/Components/Controls/GuestDocumentSearchDialog.razor index 29c03d3..c567f70 100644 --- a/src/Somnia.GuestLog/Components/Controls/GuestDocumentSearchDialog.razor +++ b/src/Somnia.GuestLog/Components/Controls/GuestDocumentSearchDialog.razor @@ -2,7 +2,7 @@ @using Somnia.GuestLog.Services @inject ILogger logger; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IGuestDocumentService guestDocumentService; @@ -108,7 +108,7 @@ catch (Exception ex) { logger.LogError(ex, "GuestDocument Search returned an error!"); - Snackbar.Add($"Error searching: {ex.Message}", Severity.Error); + userMessage.ShowMessage($"Error searching: {ex.Message}", MessageKind.Error); } } @@ -119,7 +119,7 @@ { if (filter.IsEmpty) { - Snackbar.Add("Add some search!", Severity.Warning); + userMessage.ShowMessage("Search can't be empty!", MessageKind.Warning); return; } @@ -131,7 +131,7 @@ { if (SelectedResult is null) { - Snackbar.Add("Select a guest document", Severity.Warning); + userMessage.ShowMessage("A guest document must be selected", MessageKind.Warning); return; } diff --git a/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor b/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor index 991ef81..52f416c 100644 --- a/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor +++ b/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor @@ -7,7 +7,7 @@ @inject IDataListService dataListService; @inject IValidator stayValidator; @inject IDialogService dialogService; -@inject ISnackbar snackbar +@inject IUserMessageService userMessage logger; @inject IAddressQueryFactory addressQueryFactory; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService DEBUG - Address Query @@ -89,7 +90,7 @@ catch (Exception ex) { logger.LogError(ex, "SearchAddress returned an error!"); - Snackbar.Add($"Error searching address: {ex.Message}", Severity.Error); + userMessage.ShowMessage($"Error searching address: {ex.Message}", MessageKind.Error); } } diff --git a/src/Somnia.GuestLog/Components/Pages/DebugPages/Empty.razor b/src/Somnia.GuestLog/Components/Pages/DebugPages/Empty.razor index ca51ccb..b488d2b 100644 --- a/src/Somnia.GuestLog/Components/Pages/DebugPages/Empty.razor +++ b/src/Somnia.GuestLog/Components/Pages/DebugPages/Empty.razor @@ -1,8 +1,10 @@ @page "/debug/empty" +@using Somnia.GuestLog.Services + @inject ILogger logger; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService DEBUG - Empty diff --git a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocDetail.razor b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocDetail.razor index 940f0db..42f494f 100644 --- a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocDetail.razor +++ b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocDetail.razor @@ -4,7 +4,7 @@ @using Somnia.GuestLog.Services; @using Microsoft.AspNetCore.WebUtilities; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService @inject NavigationManager Navigation @inject IGuestDocumentService guestDocService @@ -92,7 +92,7 @@ { if (docId is null) { - Snackbar.Add("No GuestDocument provided!!", Severity.Error); + userMessage.ShowMessage("No GuestDocument provided!!", MessageKind.Error); Navigation.NotFound(); return; } @@ -100,7 +100,7 @@ GuestDocIdDTO? docIdModel = GuestDocIdDTO.DecodeFromString(docId); if (docIdModel is null) { - Snackbar.Add($"Provided GuestDocId can't be decoded: '{docId}'", Severity.Error); + userMessage.ShowMessage($"Provided GuestDocId can't be decoded: '{docId}'", MessageKind.Error); Navigation.NotFound(); return; } @@ -109,7 +109,7 @@ documentModel = await guestDocService.GetDtoByIdAsync(docIdModel.DocumentType, docIdModel.DocumentNum); if (documentModel is null) { - Snackbar.Add($"GuestDocument not found with ID: '{docId}'", Severity.Error); + userMessage.ShowMessage($"GuestDocument not found with ID: '{docId}'", MessageKind.Error); Navigation.NotFound(); return; } @@ -138,7 +138,7 @@ private void BtnEdit() { - Snackbar.Add("Not implemented yet!", Severity.Info); + userMessage.ShowMessage("Not implemented yet!", MessageKind.Info); } private void BtnReturn() diff --git a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor index 762a045..e19881d 100644 --- a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor +++ b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor @@ -5,7 +5,7 @@ @inject IGuestDocumentService GuestDocService @inject NavigationManager Navigation -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService @inject IDataListService dataListService; @@ -101,7 +101,7 @@ if (showMsg) { - Snackbar.Add("Data loaded from DB", Severity.Info, o => o.VisibleStateDuration = 1000); + userMessage.ShowMessage("Data loaded from DB", MessageKind.Info); } _dataLoading = false; } @@ -142,14 +142,15 @@ return; } - bool deleted = await GuestDocService.DeleteDocumentAsync(item); - if (!deleted) + Result deleteResult = await GuestDocService.DeleteDocumentAsync(item); + if (!deleteResult.IsSuccess) { - Snackbar.Add($"Error deleting document {item.DocumentType}-{item.DocumentNum}", Severity.Error); + string errorMsg = $"Error deleting document {item.DocumentType}-{item.DocumentNum}"; + userMessage.ShowResultWithMessage(deleteResult, errorMsg); return; } - Snackbar.Add($"Document {item.DocumentType}-{item.DocumentNum} deleted!", Severity.Success); + userMessage.ShowMessage($"Document {item.DocumentType}-{item.DocumentNum} deleted!", MessageKind.Success); await LoadDataAsync(); } diff --git a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocNew.razor b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocNew.razor index 9ba6115..b865f97 100644 --- a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocNew.razor +++ b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocNew.razor @@ -4,7 +4,7 @@ @using Somnia.GuestLog.Services; @using Somnia.GuestLog.Validators; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService @inject NavigationManager Navigation @inject IGuestDocumentService guestDocService @@ -129,26 +129,25 @@ bool isFormValid = await _form.ValidateForm(); if (!isFormValid) { - Snackbar.Add("Invalid Form!", Severity.Error); + userMessage.ShowMessage("Invalid Form!", MessageKind.Warning); return; } GuestDocumentDTO model = _form.Model; - Snackbar.Add($"Document '{model.DocumentNum}' creating...", Severity.Info); - //Show Loading dialog... var loadingDialog = await DialogService.ShowAsync(); - bool isCreated = await guestDocService.CreateDocumentAsync(model); - if (!isCreated) + Result createResult = await guestDocService.CreateDocumentAsync(model); + if (!createResult.IsSuccess) { - Snackbar.Add($"Error creating Document '{model.DocumentNum}'", Severity.Error); + string errorMsg = $"Error creating Document '{model.DocumentNum}'"; + userMessage.ShowResultWithMessage(createResult, errorMsg); loadingDialog.Close(); return; } - Snackbar.Add($"Document '{model.DocumentNum}' Created!", Severity.Success); + userMessage.ShowMessage($"Document '{model.DocumentNum}' Created!", MessageKind.Success); Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List); } @@ -156,7 +155,7 @@ { if (currentPageMode != ePageMode.alreadyExistDoc) { - Snackbar.Add("Invalid operation!", Severity.Error); + userMessage.ShowMessage("Invalid operation!", MessageKind.Error); return; } diff --git a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor index a1df66e..41bc2ff 100644 --- a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor +++ b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor @@ -3,7 +3,7 @@ @using Somnia.GuestLog.Models.DTOs; @using Somnia.GuestLog.Services; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService @inject NavigationManager Navigation @inject IGuestDocumentService guestDocService @@ -54,7 +54,7 @@ stayModel = await stayService.GetDtoByIdAsync(StayId, queryConfig: new(IncludeGuests: true)); if (stayModel is null) { - Snackbar.Add($"Stay not found with ID: '{StayId}'", Severity.Error); + userMessage.ShowMessage($"Stay not found with ID: '{StayId}'", MessageKind.Error); Navigation.NotFound(); return; } @@ -62,7 +62,7 @@ private void BtnEdit() { - Snackbar.Add("Not implemented yet!", Severity.Info); + userMessage.ShowMessage("Not implemented yet!", MessageKind.Info); } private void BtnReturn() diff --git a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor index af4ecda..cf2900f 100644 --- a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor +++ b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor @@ -5,7 +5,7 @@ @inject IStayEntryService StayEntryService @inject NavigationManager Navigation -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService @inject IDataListService dataListService; @@ -96,7 +96,7 @@ if (showMsg) { - Snackbar.Add("Data loaded from DB", Severity.Info, o => o.VisibleStateDuration = 1000); + userMessage.ShowMessage("Data loaded from DB", MessageKind.Info); } _dataLoading = false; } @@ -131,14 +131,15 @@ return; } - bool deleted = await StayEntryService.DeleteEntryAsync(item); - if (!deleted) + Result deleteResult = await StayEntryService.DeleteEntryAsync(item); + if (!deleteResult.IsSuccess) { - Snackbar.Add($"Error deleting Stay {item.Id}", Severity.Error); + string errorMsg = $"Error deleting Stay {item.Id}"; + userMessage.ShowResultWithMessage(deleteResult, errorMsg); return; } - Snackbar.Add($"Stay {item.Id} deleted!", Severity.Success); + userMessage.ShowMessage($"Stay {item.Id} deleted!", MessageKind.Success); await LoadDataAsync(); } diff --git a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryNew.razor b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryNew.razor index 959b2d9..78f130e 100644 --- a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryNew.razor +++ b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryNew.razor @@ -4,7 +4,7 @@ @using Somnia.GuestLog.Services; -@inject ISnackbar Snackbar +@inject IUserMessageService userMessage @inject IDialogService DialogService @inject NavigationManager Navigation @inject IStayEntryService stayService @@ -45,22 +45,22 @@ bool isFormValid = await _form.ValidateForm(); if (!isFormValid) { - Snackbar.Add("Invalid Form!", Severity.Warning); + userMessage.ShowMessage("Invalid Form!", MessageKind.Warning); return; } var loadingDialog = await DialogService.ShowAsync(); StayEntryDTO model = _form.GetModel(); - bool isCreated = await stayService.CreateEntryAsync(model); - if (!isCreated) + Result createResult = await stayService.CreateEntryAsync(model); + if (!createResult.IsSuccess) { - Snackbar.Add($"Error creating StayEntry", Severity.Error); + userMessage.ShowResultWithMessage(createResult, $"Error creating StayEntry"); loadingDialog.Close(); return; } - Snackbar.Add($"StayEntry Created!", Severity.Success); + userMessage.ShowMessage($"StayEntry Created!", MessageKind.Success); Navigation.NavigateTo(GuestConsts.Page_StayEntry_List); } diff --git a/src/Somnia.GuestLog/Components/_Imports.razor b/src/Somnia.GuestLog/Components/_Imports.razor index 829502f..57f2108 100644 --- a/src/Somnia.GuestLog/Components/_Imports.razor +++ b/src/Somnia.GuestLog/Components/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using System.Net.Http.Json +@using System.Linq; @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @@ -15,4 +16,4 @@ @using Somnia.GuestLog.Components.Forms @using Somnia.GuestLog.Components.Controls @using Somnia.GuestLog.Resources -@using System.Linq; \ No newline at end of file +@using MessageKind = Somnia.GuestLog.Services.IUserMessageService.MessageKind; \ No newline at end of file diff --git a/src/Somnia.GuestLog/Models/DTOs/Result.cs b/src/Somnia.GuestLog/Models/DTOs/Result.cs new file mode 100644 index 0000000..20451b9 --- /dev/null +++ b/src/Somnia.GuestLog/Models/DTOs/Result.cs @@ -0,0 +1,24 @@ +namespace Somnia.GuestLog.Models.DTOs; + +public sealed record Result(bool IsSuccess, string ResultCode, params object[]? Context) +{ + public static Result Ok() => new(IsSuccess: true, ResultCode: Models.ResultCode.Ok, Context: null); + + public static Result Fail(string resultCode) => new(IsSuccess: false, resultCode, Context: null); + public static Result FailWithContext(string resultCode, params object[] context) + => new(IsSuccess: false, resultCode, context); + public static Result FailUnknownError() + => new(IsSuccess: false, ResultCode: Models.ResultCode.UnknownError, Context: null); + + public bool IsUnknownError => !IsSuccess && ResultCode == Models.ResultCode.UnknownError; +} + +public sealed record Result(bool IsSuccess, T? Value, string ResultCode, params object[]? Context) +{ + public static Result Ok(T value) => new(IsSuccess: true, value, ResultCode: Models.ResultCode.Ok, Context: null); + public static Result Fail(string resultCode) => new(IsSuccess: false, Value: default, resultCode, Context: null); + public static Result FailWithContext(string resultCode, params object[] context) + => new(IsSuccess: false, Value: default, resultCode, context); + + public bool IsUnknownError => !IsSuccess && ResultCode == Models.ResultCode.UnknownError; +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Models/ResultCode.cs b/src/Somnia.GuestLog/Models/ResultCode.cs new file mode 100644 index 0000000..ea54801 --- /dev/null +++ b/src/Somnia.GuestLog/Models/ResultCode.cs @@ -0,0 +1,11 @@ +namespace Somnia.GuestLog.Models; + +public static class ResultCode +{ + public const string Ok = "Ok"; + public const string UnknownError = "UnknownError"; + public const string FailedValidation = "FailedValidation"; + public const string NotFound = "NotFound"; + + public const string GuestCurrentlyInStay = "GuestCurrentlyInStay"; +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Program.cs b/src/Somnia.GuestLog/Program.cs index 846b599..b03c5c9 100644 --- a/src/Somnia.GuestLog/Program.cs +++ b/src/Somnia.GuestLog/Program.cs @@ -59,6 +59,8 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddScoped(); + // Add Factories (and it's services) builder.Services.AddFactoryDI(); diff --git a/src/Somnia.GuestLog/Resources/LanguageLocale.resx b/src/Somnia.GuestLog/Resources/LanguageLocale.resx index b17142a..064c0b7 100644 --- a/src/Somnia.GuestLog/Resources/LanguageLocale.resx +++ b/src/Somnia.GuestLog/Resources/LanguageLocale.resx @@ -176,6 +176,16 @@ Country Zip Code + + + OK + Unknown Error + Failed entity validation + Not found + + Guest ('{0}') is already in one stay during the period: {1} + + Guests Stays diff --git a/src/Somnia.GuestLog/Services/GuestDocumentService.cs b/src/Somnia.GuestLog/Services/GuestDocumentService.cs index 978b832..b2408c3 100644 --- a/src/Somnia.GuestLog/Services/GuestDocumentService.cs +++ b/src/Somnia.GuestLog/Services/GuestDocumentService.cs @@ -14,7 +14,7 @@ public class GuestDocumentService( IValidator dtoValidator ) : IGuestDocumentService { - public async Task CreateDocumentAsync(GuestDocumentDTO dto) + public async Task CreateDocumentAsync(GuestDocumentDTO dto) { ArgumentNullException.ThrowIfNull(dto); @@ -22,7 +22,7 @@ public class GuestDocumentService( if (!valResult.IsValid) { logger.LogWarning("GuestDocumentService - CreateDocument: Dto validation failed. Fix the errors: {errorList}", valResult.Errors); - return false; + return Result.Fail(ResultCode.FailedValidation); } //Dto must be valid, so null warings shouldn't matter. @@ -63,21 +63,21 @@ public class GuestDocumentService( await dbContext.SaveChangesAsync(); logger.LogInformation("GuestDocumentService - CreateDocument: GuestDocument Created: '{id}'", dbItem.GetFullKey()); - return true; + return Result.Ok(); } catch (Exception ex) { logger.LogError(ex, "GuestDocumentService - CreateDocument: Error creating GuestDocument: '{id}'", dbItem.GetFullKey()); - return false; + return Result.FailUnknownError(); } } - public Task UpdateDocumentAsync(GuestDocumentDTO dto) + public Task UpdateDocumentAsync(GuestDocumentDTO dto) { throw new NotImplementedException(); } - public Task DeleteDocumentAsync(GuestDocumentDTO dto) + public Task DeleteDocumentAsync(GuestDocumentDTO dto) { if (dto.DocumentType is null || dto.DocumentNum is null) { @@ -87,14 +87,14 @@ public class GuestDocumentService( return DeleteDocumentAsync(dto.DocumentType, dto.DocumentNum); } - public async Task DeleteDocumentAsync(string docType, string docNum) + public async Task DeleteDocumentAsync(string docType, string docNum) { using var dbContext = contextFactory.CreateDbContext(); GuestDocument? dbItem = await dbContext.GuestDocuments.FindAsync(docType, docNum); if (dbItem is null) { logger.LogWarning("GuestDocumentService - DeleteDocument: Error deleting GuestDocument. Entity not found with key: '{key}-{key2}'", docType, docNum); - return false; + return Result.Fail(ResultCode.NotFound); } try @@ -103,12 +103,12 @@ public class GuestDocumentService( await dbContext.SaveChangesAsync(); logger.LogInformation("GuestDocumentService - DeleteDocument: GuestDocument deleted: '{id}'", dbItem.GetFullKey()); - return true; + return Result.Ok(); } catch (Exception ex) { logger.LogError(ex, "GuestDocumentService - CreateDocument: Error deleting GuestDocument: '{id}'", dbItem.GetFullKey()); - return false; + return Result.FailUnknownError(); } } diff --git a/src/Somnia.GuestLog/Services/IGuestDocumentService.cs b/src/Somnia.GuestLog/Services/IGuestDocumentService.cs index 1e17df8..89d11cb 100644 --- a/src/Somnia.GuestLog/Services/IGuestDocumentService.cs +++ b/src/Somnia.GuestLog/Services/IGuestDocumentService.cs @@ -4,10 +4,10 @@ namespace Somnia.GuestLog.Services; public interface IGuestDocumentService { - public Task CreateDocumentAsync(GuestDocumentDTO dto); - public Task UpdateDocumentAsync(GuestDocumentDTO dto); - public Task DeleteDocumentAsync(GuestDocumentDTO dto); - public Task DeleteDocumentAsync(string docType, string docNum); + public Task CreateDocumentAsync(GuestDocumentDTO dto); + public Task UpdateDocumentAsync(GuestDocumentDTO dto); + public Task DeleteDocumentAsync(GuestDocumentDTO dto); + public Task DeleteDocumentAsync(string docType, string docNum); public Task GetDtoByIdAsync(string docType, string docNum); public Task> GetDtoListByFilterAsync(GuestDocFilterDTO filter, int limit = 0); diff --git a/src/Somnia.GuestLog/Services/IStayEntryService.cs b/src/Somnia.GuestLog/Services/IStayEntryService.cs index e3f2068..a7f2eea 100644 --- a/src/Somnia.GuestLog/Services/IStayEntryService.cs +++ b/src/Somnia.GuestLog/Services/IStayEntryService.cs @@ -5,10 +5,10 @@ namespace Somnia.GuestLog.Services; public interface IStayEntryService { - public Task CreateEntryAsync(StayEntryDTO dto); - public Task UpdateEntryAsync(StayEntryDTO updatedDto); - public Task DeleteEntryAsync(StayEntryDTO dto); - public Task DeleteEntryAsync(long id); + public Task CreateEntryAsync(StayEntryDTO dto); + public Task UpdateEntryAsync(StayEntryDTO updatedDto); + public Task DeleteEntryAsync(StayEntryDTO dto); + public Task DeleteEntryAsync(long id); public Task GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null); public Task> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null); diff --git a/src/Somnia.GuestLog/Services/IUserMessageService.cs b/src/Somnia.GuestLog/Services/IUserMessageService.cs new file mode 100644 index 0000000..5a5b9b4 --- /dev/null +++ b/src/Somnia.GuestLog/Services/IUserMessageService.cs @@ -0,0 +1,17 @@ +using Somnia.GuestLog.Models.DTOs; + +namespace Somnia.GuestLog.Services; + +public interface IUserMessageService +{ + public void ShowMessage(string text, MessageKind kind, params object[] parameters); + + public void ShowResult(Result resultDto); + public void ShowResultWithMessage(Result resultDto, string message); + + + public enum MessageKind + { + Info, Warning, Error, Success, Normal + } +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayEntryService.cs b/src/Somnia.GuestLog/Services/StayEntryService.cs index dccba19..efaff58 100644 --- a/src/Somnia.GuestLog/Services/StayEntryService.cs +++ b/src/Somnia.GuestLog/Services/StayEntryService.cs @@ -15,7 +15,7 @@ public class StayEntryService( IValidator dtoValidator ) : IStayEntryService { - public async Task CreateEntryAsync(StayEntryDTO dto) + public async Task CreateEntryAsync(StayEntryDTO dto) { ArgumentNullException.ThrowIfNull(dto); @@ -23,7 +23,7 @@ public class StayEntryService( if (!valResult.IsValid) { logger.LogWarning("StayEntryService - CreateEntryAsync: Dto validation failed. Fix the errors: {errorList}", valResult.Errors); - return false; + return Result.Fail(ResultCode.FailedValidation); } DateTime createTime = DateTime.UtcNow; @@ -53,7 +53,8 @@ public class StayEntryService( bool isInOtherStay = await this.IsGuestInStayInPeriod(dbGuest.DocumentType, dbGuest.DocumentNum, dbStay.DateIn, dbStay.DateOut); if (isInOtherStay) { - throw new InvalidOperationException($"Guest ('{dbGuest.GetFullKey()}') is already in a stay during the period: {dbStay.DateIn}-{dbStay.DateOut}"); + string period = $"{dbStay.DateIn}-{dbStay.DateOut}"; + return Result.FailWithContext(ResultCode.GuestCurrentlyInStay, dbGuest.GetFullKey(), period); } //Add dbGuest to dbStay. @@ -64,38 +65,38 @@ public class StayEntryService( await dbContext.SaveChangesAsync(); logger.LogInformation("StayEntryService - CreateEntryAsync: StayEntry Created: '{id}'", dbStay.GetFullKey()); - return true; + return Result.Ok(); } catch (Exception ex) { logger.LogError(ex, "StayEntryService - CreateEntryAsync: Error creating StayEntry"); - return false; + return Result.FailUnknownError(); } } - public Task UpdateEntryAsync(StayEntryDTO updatedDto) + public Task UpdateEntryAsync(StayEntryDTO updatedDto) { throw new NotImplementedException(); } - public Task DeleteEntryAsync(StayEntryDTO dto) + public async Task DeleteEntryAsync(StayEntryDTO dto) { if (dto.Id is null) { throw new InvalidOperationException("StayEntryService - DeleteEntry: A DTO without key can't be deleted!"); } - return DeleteEntryAsync(dto.Id.Value); + return await DeleteEntryAsync(dto.Id.Value); } - public async Task DeleteEntryAsync(long id) + public async Task DeleteEntryAsync(long id) { using var dbContext = contextFactory.CreateDbContext(); StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id); if (dbItem is null) { logger.LogWarning("StayEntryService - DeleteEntry: Error deleting StayEntry. Entity not found with key: '{key}'", id); - return false; + return Result.Fail(ResultCode.NotFound); } try @@ -104,12 +105,12 @@ public class StayEntryService( await dbContext.SaveChangesAsync(); logger.LogInformation("StayEntryService - DeleteEntry: StayEntry deleted: '{id}'", dbItem.GetFullKey()); - return true; + return Result.Ok(); } catch (Exception ex) { logger.LogError(ex, "StayEntryService - DeleteEntry: Error deleting StayEntry: '{id}'", dbItem.GetFullKey()); - return false; + return Result.FailUnknownError(); } } diff --git a/src/Somnia.GuestLog/Services/UserMessageService.cs b/src/Somnia.GuestLog/Services/UserMessageService.cs new file mode 100644 index 0000000..22d8ede --- /dev/null +++ b/src/Somnia.GuestLog/Services/UserMessageService.cs @@ -0,0 +1,88 @@ +using Microsoft.Extensions.Localization; +using MudBlazor; +using Somnia.GuestLog.Models; +using Somnia.GuestLog.Models.DTOs; +using Somnia.GuestLog.Resources; +using MessageKind = Somnia.GuestLog.Services.IUserMessageService.MessageKind; + +namespace Somnia.GuestLog.Services; + +public class UserMessageService( + IServiceProvider serviceProvider, + ISnackbar snackbar +) : IUserMessageService +{ + private IStringLocalizer _localizer + { + get + { + return serviceProvider.GetRequiredService>(); + } + } + + + public void ShowMessage(string text, MessageKind kind, params object[] parameters) + { + string formatedMsg = GetFormatedMessage(text, parameters); + ShowSnackbar(formatedMsg, kind); + } + + + public void ShowResult(Result resultDto) + { + ShowResultWithMessage(resultDto, message: null); + } + + public void ShowResultWithMessage(Result resultDto, string? message) + { + string resultMsg = GetMsgForResultCode(resultDto.ResultCode); + resultMsg = GetFormatedMessage(resultMsg, resultDto.Context); + + string finalMsg = message is not null ? $"{message.Trim().TrimEnd(':')}: {resultMsg}" : resultMsg; + + //Show success when OK, error for UnknownErrors and warning otherwise. + MessageKind kind = resultDto switch + { + { IsSuccess: true } => MessageKind.Success, + { ResultCode: ResultCode.UnknownError } => MessageKind.Error, + _ => MessageKind.Warning + }; + + ShowSnackbar(finalMsg, kind); + } + + + #region Internal Methods + + private void ShowSnackbar(string msg, MessageKind kind) + { + snackbar.Add(message: msg, severity: GetSnackbarSeveryForMessageKind(kind)); + } + + private string GetMsgForResultCode(string resultCode) + { + return _localizer[$"resultCode.{resultCode}"]; + } + + private static string GetFormatedMessage(string template, params object[]? parameters) + { + if (parameters is null || parameters.Length == 0) + { + return template; + } + + return string.Format(template, parameters); + } + + private static MudBlazor.Severity GetSnackbarSeveryForMessageKind(MessageKind kind) => kind switch + { + MessageKind.Info => MudBlazor.Severity.Info, + MessageKind.Warning => MudBlazor.Severity.Warning, + MessageKind.Error => MudBlazor.Severity.Error, + MessageKind.Success => MudBlazor.Severity.Success, + MessageKind.Normal => MudBlazor.Severity.Normal, + _ => throw new ArgumentOutOfRangeException(nameof(kind), $"Not expected MessageKind value: {kind}") + }; + + #endregion +} \ No newline at end of file