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.
This commit is contained in:
2026-08-23 21:57:26 +02:00
parent 0de4e1ba10
commit 511bd023f2
22 changed files with 239 additions and 80 deletions
@@ -1,9 +1,10 @@
@using Somnia.GuestLog.Models.DTOs; @using Somnia.GuestLog.Models.DTOs;
@using Somnia.GuestLog.Services
@using Somnia.GuestLog.Services.AddressQuery; @using Somnia.GuestLog.Services.AddressQuery;
@inject ILogger<AddressQueryDialog> logger; @inject ILogger<AddressQueryDialog> logger;
@inject IAddressQueryFactory addressQueryFactory; @inject IAddressQueryFactory addressQueryFactory;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
<MudDialog @ref="@DialogRef"> <MudDialog @ref="@DialogRef">
<TitleContent> <TitleContent>
@@ -112,7 +113,7 @@
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "SearchAddress returned an error!"); 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) if (SelectedAddressResult is null)
{ {
Snackbar.Add("Select an address", Severity.Warning); userMessage.ShowMessage("Select an address", MessageKind.Warning);
return; return;
} }
@@ -2,7 +2,7 @@
@using Somnia.GuestLog.Services @using Somnia.GuestLog.Services
@inject ILogger<AddressQueryDialog> logger; @inject ILogger<AddressQueryDialog> logger;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IGuestDocumentService guestDocumentService; @inject IGuestDocumentService guestDocumentService;
@@ -108,7 +108,7 @@
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "GuestDocument Search returned an error!"); 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) if (filter.IsEmpty)
{ {
Snackbar.Add("Add some search!", Severity.Warning); userMessage.ShowMessage("Search can't be empty!", MessageKind.Warning);
return; return;
} }
@@ -131,7 +131,7 @@
{ {
if (SelectedResult is null) if (SelectedResult is null)
{ {
Snackbar.Add("Select a guest document", Severity.Warning); userMessage.ShowMessage("A guest document must be selected", MessageKind.Warning);
return; return;
} }
@@ -7,7 +7,7 @@
@inject IDataListService dataListService; @inject IDataListService dataListService;
@inject IValidator<StayEntryDTO> stayValidator; @inject IValidator<StayEntryDTO> stayValidator;
@inject IDialogService dialogService; @inject IDialogService dialogService;
@inject ISnackbar snackbar @inject IUserMessageService userMessage
<MudForm @ref="@_form" Model="@Model" Validation="@((stayValidator as StayEntryDTOValidator)!.ValidateValue)" <MudForm @ref="@_form" Model="@Model" Validation="@((stayValidator as StayEntryDTOValidator)!.ValidateValue)"
@@ -149,7 +149,7 @@
if (Model.GuestDocuments is null || Model.GuestDocuments.Count == 0) if (Model.GuestDocuments is null || Model.GuestDocuments.Count == 0)
{ {
snackbar.Add("At least 1 guest is required!", MudBlazor.Severity.Warning); userMessage.ShowMessage("At least 1 guest is required!", MessageKind.Warning);
return false; return false;
} }
@@ -205,7 +205,7 @@
if (resultData is null) if (resultData is null)
{ {
snackbar.Add("Error! No Guest Document selected!", MudBlazor.Severity.Error); userMessage.ShowMessage("Error! No Guest Document selected!", MessageKind.Error);
return; return;
} }
@@ -216,7 +216,7 @@
&& e.DocumentNum == resultData.DocumentNum); && e.DocumentNum == resultData.DocumentNum);
if (alreadyAdded) if (alreadyAdded)
{ {
snackbar.Add("GuestDocument already added!", MudBlazor.Severity.Warning); userMessage.ShowMessage("GuestDocument already added!", MessageKind.Warning);
return; return;
} }
} }
@@ -1,11 +1,12 @@
@page "/debug/addrSearch" @page "/debug/addrSearch"
@using Somnia.GuestLog.Models.DTOs; @using Somnia.GuestLog.Models.DTOs;
@using Somnia.GuestLog.Services;
@using Somnia.GuestLog.Services.AddressQuery; @using Somnia.GuestLog.Services.AddressQuery;
@inject ILogger<AddressQuery> logger; @inject ILogger<AddressQuery> logger;
@inject IAddressQueryFactory addressQueryFactory; @inject IAddressQueryFactory addressQueryFactory;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
<PageTitle>DEBUG - Address Query</PageTitle> <PageTitle>DEBUG - Address Query</PageTitle>
@@ -89,7 +90,7 @@
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "SearchAddress returned an error!"); 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);
} }
} }
@@ -1,8 +1,10 @@
@page "/debug/empty" @page "/debug/empty"
@using Somnia.GuestLog.Services
@inject ILogger<Empty> logger; @inject ILogger<Empty> logger;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
<PageTitle>DEBUG - Empty</PageTitle> <PageTitle>DEBUG - Empty</PageTitle>
@@ -4,7 +4,7 @@
@using Somnia.GuestLog.Services; @using Somnia.GuestLog.Services;
@using Microsoft.AspNetCore.WebUtilities; @using Microsoft.AspNetCore.WebUtilities;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IGuestDocumentService guestDocService @inject IGuestDocumentService guestDocService
@@ -92,7 +92,7 @@
{ {
if (docId is null) if (docId is null)
{ {
Snackbar.Add("No GuestDocument provided!!", Severity.Error); userMessage.ShowMessage("No GuestDocument provided!!", MessageKind.Error);
Navigation.NotFound(); Navigation.NotFound();
return; return;
} }
@@ -100,7 +100,7 @@
GuestDocIdDTO? docIdModel = GuestDocIdDTO.DecodeFromString(docId); GuestDocIdDTO? docIdModel = GuestDocIdDTO.DecodeFromString(docId);
if (docIdModel is null) 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(); Navigation.NotFound();
return; return;
} }
@@ -109,7 +109,7 @@
documentModel = await guestDocService.GetDtoByIdAsync(docIdModel.DocumentType, docIdModel.DocumentNum); documentModel = await guestDocService.GetDtoByIdAsync(docIdModel.DocumentType, docIdModel.DocumentNum);
if (documentModel is null) 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(); Navigation.NotFound();
return; return;
} }
@@ -138,7 +138,7 @@
private void BtnEdit() private void BtnEdit()
{ {
Snackbar.Add("Not implemented yet!", Severity.Info); userMessage.ShowMessage("Not implemented yet!", MessageKind.Info);
} }
private void BtnReturn() private void BtnReturn()
@@ -5,7 +5,7 @@
@inject IGuestDocumentService GuestDocService @inject IGuestDocumentService GuestDocService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
@inject IDataListService dataListService; @inject IDataListService dataListService;
@@ -101,7 +101,7 @@
if (showMsg) if (showMsg)
{ {
Snackbar.Add("Data loaded from DB", Severity.Info, o => o.VisibleStateDuration = 1000); userMessage.ShowMessage("Data loaded from DB", MessageKind.Info);
} }
_dataLoading = false; _dataLoading = false;
} }
@@ -142,14 +142,15 @@
return; return;
} }
bool deleted = await GuestDocService.DeleteDocumentAsync(item); Result deleteResult = await GuestDocService.DeleteDocumentAsync(item);
if (!deleted) 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; return;
} }
Snackbar.Add($"Document {item.DocumentType}-{item.DocumentNum} deleted!", Severity.Success); userMessage.ShowMessage($"Document {item.DocumentType}-{item.DocumentNum} deleted!", MessageKind.Success);
await LoadDataAsync(); await LoadDataAsync();
} }
@@ -4,7 +4,7 @@
@using Somnia.GuestLog.Services; @using Somnia.GuestLog.Services;
@using Somnia.GuestLog.Validators; @using Somnia.GuestLog.Validators;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IGuestDocumentService guestDocService @inject IGuestDocumentService guestDocService
@@ -129,26 +129,25 @@
bool isFormValid = await _form.ValidateForm(); bool isFormValid = await _form.ValidateForm();
if (!isFormValid) if (!isFormValid)
{ {
Snackbar.Add("Invalid Form!", Severity.Error); userMessage.ShowMessage("Invalid Form!", MessageKind.Warning);
return; return;
} }
GuestDocumentDTO model = _form.Model; GuestDocumentDTO model = _form.Model;
Snackbar.Add($"Document '{model.DocumentNum}' creating...", Severity.Info);
//Show Loading dialog... //Show Loading dialog...
var loadingDialog = await DialogService.ShowAsync<LoadingDialog>(); var loadingDialog = await DialogService.ShowAsync<LoadingDialog>();
bool isCreated = await guestDocService.CreateDocumentAsync(model); Result createResult = await guestDocService.CreateDocumentAsync(model);
if (!isCreated) 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(); loadingDialog.Close();
return; return;
} }
Snackbar.Add($"Document '{model.DocumentNum}' Created!", Severity.Success); userMessage.ShowMessage($"Document '{model.DocumentNum}' Created!", MessageKind.Success);
Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List); Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List);
} }
@@ -156,7 +155,7 @@
{ {
if (currentPageMode != ePageMode.alreadyExistDoc) if (currentPageMode != ePageMode.alreadyExistDoc)
{ {
Snackbar.Add("Invalid operation!", Severity.Error); userMessage.ShowMessage("Invalid operation!", MessageKind.Error);
return; return;
} }
@@ -3,7 +3,7 @@
@using Somnia.GuestLog.Models.DTOs; @using Somnia.GuestLog.Models.DTOs;
@using Somnia.GuestLog.Services; @using Somnia.GuestLog.Services;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IGuestDocumentService guestDocService @inject IGuestDocumentService guestDocService
@@ -54,7 +54,7 @@
stayModel = await stayService.GetDtoByIdAsync(StayId, queryConfig: new(IncludeGuests: true)); stayModel = await stayService.GetDtoByIdAsync(StayId, queryConfig: new(IncludeGuests: true));
if (stayModel is null) 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(); Navigation.NotFound();
return; return;
} }
@@ -62,7 +62,7 @@
private void BtnEdit() private void BtnEdit()
{ {
Snackbar.Add("Not implemented yet!", Severity.Info); userMessage.ShowMessage("Not implemented yet!", MessageKind.Info);
} }
private void BtnReturn() private void BtnReturn()
@@ -5,7 +5,7 @@
@inject IStayEntryService StayEntryService @inject IStayEntryService StayEntryService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
@inject IDataListService dataListService; @inject IDataListService dataListService;
@@ -96,7 +96,7 @@
if (showMsg) if (showMsg)
{ {
Snackbar.Add("Data loaded from DB", Severity.Info, o => o.VisibleStateDuration = 1000); userMessage.ShowMessage("Data loaded from DB", MessageKind.Info);
} }
_dataLoading = false; _dataLoading = false;
} }
@@ -131,14 +131,15 @@
return; return;
} }
bool deleted = await StayEntryService.DeleteEntryAsync(item); Result deleteResult = await StayEntryService.DeleteEntryAsync(item);
if (!deleted) if (!deleteResult.IsSuccess)
{ {
Snackbar.Add($"Error deleting Stay {item.Id}", Severity.Error); string errorMsg = $"Error deleting Stay {item.Id}";
userMessage.ShowResultWithMessage(deleteResult, errorMsg);
return; return;
} }
Snackbar.Add($"Stay {item.Id} deleted!", Severity.Success); userMessage.ShowMessage($"Stay {item.Id} deleted!", MessageKind.Success);
await LoadDataAsync(); await LoadDataAsync();
} }
@@ -4,7 +4,7 @@
@using Somnia.GuestLog.Services; @using Somnia.GuestLog.Services;
@inject ISnackbar Snackbar @inject IUserMessageService userMessage
@inject IDialogService DialogService @inject IDialogService DialogService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IStayEntryService stayService @inject IStayEntryService stayService
@@ -45,22 +45,22 @@
bool isFormValid = await _form.ValidateForm(); bool isFormValid = await _form.ValidateForm();
if (!isFormValid) if (!isFormValid)
{ {
Snackbar.Add("Invalid Form!", Severity.Warning); userMessage.ShowMessage("Invalid Form!", MessageKind.Warning);
return; return;
} }
var loadingDialog = await DialogService.ShowAsync<LoadingDialog>(); var loadingDialog = await DialogService.ShowAsync<LoadingDialog>();
StayEntryDTO model = _form.GetModel(); StayEntryDTO model = _form.GetModel();
bool isCreated = await stayService.CreateEntryAsync(model); Result createResult = await stayService.CreateEntryAsync(model);
if (!isCreated) if (!createResult.IsSuccess)
{ {
Snackbar.Add($"Error creating StayEntry", Severity.Error); userMessage.ShowResultWithMessage(createResult, $"Error creating StayEntry");
loadingDialog.Close(); loadingDialog.Close();
return; return;
} }
Snackbar.Add($"StayEntry Created!", Severity.Success); userMessage.ShowMessage($"StayEntry Created!", MessageKind.Success);
Navigation.NavigateTo(GuestConsts.Page_StayEntry_List); Navigation.NavigateTo(GuestConsts.Page_StayEntry_List);
} }
@@ -1,5 +1,6 @@
@using System.Net.Http @using System.Net.Http
@using System.Net.Http.Json @using System.Net.Http.Json
@using System.Linq;
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@@ -15,4 +16,4 @@
@using Somnia.GuestLog.Components.Forms @using Somnia.GuestLog.Components.Forms
@using Somnia.GuestLog.Components.Controls @using Somnia.GuestLog.Components.Controls
@using Somnia.GuestLog.Resources @using Somnia.GuestLog.Resources
@using System.Linq; @using MessageKind = Somnia.GuestLog.Services.IUserMessageService.MessageKind;
+24
View File
@@ -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<T>(bool IsSuccess, T? Value, string ResultCode, params object[]? Context)
{
public static Result<T> Ok(T value) => new(IsSuccess: true, value, ResultCode: Models.ResultCode.Ok, Context: null);
public static Result<T> Fail(string resultCode) => new(IsSuccess: false, Value: default, resultCode, Context: null);
public static Result<T> FailWithContext(string resultCode, params object[] context)
=> new(IsSuccess: false, Value: default, resultCode, context);
public bool IsUnknownError => !IsSuccess && ResultCode == Models.ResultCode.UnknownError;
}
+11
View File
@@ -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";
}
+2
View File
@@ -59,6 +59,8 @@ builder.Services.AddSingleton<IGuestDocumentService, GuestDocumentService>();
builder.Services.AddSingleton<IStayEntryService, StayEntryService>(); builder.Services.AddSingleton<IStayEntryService, StayEntryService>();
builder.Services.AddSingleton<IDataListService, StaticDataListService>(); builder.Services.AddSingleton<IDataListService, StaticDataListService>();
builder.Services.AddScoped<IUserMessageService, UserMessageService>();
// Add Factories (and it's services) // Add Factories (and it's services)
builder.Services.AddFactoryDI<IAddressQueryFactory, ConfigFileAddressQueryFactory>(); builder.Services.AddFactoryDI<IAddressQueryFactory, ConfigFileAddressQueryFactory>();
@@ -176,6 +176,16 @@
<data name="general.address.country" xml:space="preserve"><value>Country</value></data> <data name="general.address.country" xml:space="preserve"><value>Country</value></data>
<data name="general.address.zipCode" xml:space="preserve"><value>Zip Code</value></data> <data name="general.address.zipCode" xml:space="preserve"><value>Zip Code</value></data>
<!-- Common codes -->
<data name="resultCode.Ok" xml:space="preserve"><value>OK</value></data>
<data name="resultCode.UnknownError" xml:space="preserve"><value>Unknown Error</value></data>
<data name="resultCode.FailedValidation" xml:space="preserve"><value>Failed entity validation</value></data>
<data name="resultCode.NotFound" xml:space="preserve"><value>Not found</value></data>
<!-- Specific codes -->
<data name="resultCode.GuestCurrentlyInStay" xml:space="preserve"><value>Guest ('{0}') is already in one stay during the period: {1}</value></data>
<data name="navMenu.GuestDocumentList" xml:space="preserve"><value>Guests</value></data> <data name="navMenu.GuestDocumentList" xml:space="preserve"><value>Guests</value></data>
<data name="navMenu.StayEntryList" xml:space="preserve"><value>Stays</value></data> <data name="navMenu.StayEntryList" xml:space="preserve"><value>Stays</value></data>
@@ -14,7 +14,7 @@ public class GuestDocumentService(
IValidator<GuestDocumentDTO> dtoValidator IValidator<GuestDocumentDTO> dtoValidator
) : IGuestDocumentService ) : IGuestDocumentService
{ {
public async Task<bool> CreateDocumentAsync(GuestDocumentDTO dto) public async Task<Result> CreateDocumentAsync(GuestDocumentDTO dto)
{ {
ArgumentNullException.ThrowIfNull(dto); ArgumentNullException.ThrowIfNull(dto);
@@ -22,7 +22,7 @@ public class GuestDocumentService(
if (!valResult.IsValid) if (!valResult.IsValid)
{ {
logger.LogWarning("GuestDocumentService - CreateDocument: Dto validation failed. Fix the errors: {errorList}", valResult.Errors); 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. //Dto must be valid, so null warings shouldn't matter.
@@ -63,21 +63,21 @@ public class GuestDocumentService(
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
logger.LogInformation("GuestDocumentService - CreateDocument: GuestDocument Created: '{id}'", dbItem.GetFullKey()); logger.LogInformation("GuestDocumentService - CreateDocument: GuestDocument Created: '{id}'", dbItem.GetFullKey());
return true; return Result.Ok();
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "GuestDocumentService - CreateDocument: Error creating GuestDocument: '{id}'", dbItem.GetFullKey()); logger.LogError(ex, "GuestDocumentService - CreateDocument: Error creating GuestDocument: '{id}'", dbItem.GetFullKey());
return false; return Result.FailUnknownError();
} }
} }
public Task<bool> UpdateDocumentAsync(GuestDocumentDTO dto) public Task<Result> UpdateDocumentAsync(GuestDocumentDTO dto)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<bool> DeleteDocumentAsync(GuestDocumentDTO dto) public Task<Result> DeleteDocumentAsync(GuestDocumentDTO dto)
{ {
if (dto.DocumentType is null || dto.DocumentNum is null) if (dto.DocumentType is null || dto.DocumentNum is null)
{ {
@@ -87,14 +87,14 @@ public class GuestDocumentService(
return DeleteDocumentAsync(dto.DocumentType, dto.DocumentNum); return DeleteDocumentAsync(dto.DocumentType, dto.DocumentNum);
} }
public async Task<bool> DeleteDocumentAsync(string docType, string docNum) public async Task<Result> DeleteDocumentAsync(string docType, string docNum)
{ {
using var dbContext = contextFactory.CreateDbContext(); using var dbContext = contextFactory.CreateDbContext();
GuestDocument? dbItem = await dbContext.GuestDocuments.FindAsync(docType, docNum); GuestDocument? dbItem = await dbContext.GuestDocuments.FindAsync(docType, docNum);
if (dbItem is null) if (dbItem is null)
{ {
logger.LogWarning("GuestDocumentService - DeleteDocument: Error deleting GuestDocument. Entity not found with key: '{key}-{key2}'", docType, docNum); logger.LogWarning("GuestDocumentService - DeleteDocument: Error deleting GuestDocument. Entity not found with key: '{key}-{key2}'", docType, docNum);
return false; return Result.Fail(ResultCode.NotFound);
} }
try try
@@ -103,12 +103,12 @@ public class GuestDocumentService(
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
logger.LogInformation("GuestDocumentService - DeleteDocument: GuestDocument deleted: '{id}'", dbItem.GetFullKey()); logger.LogInformation("GuestDocumentService - DeleteDocument: GuestDocument deleted: '{id}'", dbItem.GetFullKey());
return true; return Result.Ok();
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "GuestDocumentService - CreateDocument: Error deleting GuestDocument: '{id}'", dbItem.GetFullKey()); logger.LogError(ex, "GuestDocumentService - CreateDocument: Error deleting GuestDocument: '{id}'", dbItem.GetFullKey());
return false; return Result.FailUnknownError();
} }
} }
@@ -4,10 +4,10 @@ namespace Somnia.GuestLog.Services;
public interface IGuestDocumentService public interface IGuestDocumentService
{ {
public Task<bool> CreateDocumentAsync(GuestDocumentDTO dto); public Task<Result> CreateDocumentAsync(GuestDocumentDTO dto);
public Task<bool> UpdateDocumentAsync(GuestDocumentDTO dto); public Task<Result> UpdateDocumentAsync(GuestDocumentDTO dto);
public Task<bool> DeleteDocumentAsync(GuestDocumentDTO dto); public Task<Result> DeleteDocumentAsync(GuestDocumentDTO dto);
public Task<bool> DeleteDocumentAsync(string docType, string docNum); public Task<Result> DeleteDocumentAsync(string docType, string docNum);
public Task<GuestDocumentDTO?> GetDtoByIdAsync(string docType, string docNum); public Task<GuestDocumentDTO?> GetDtoByIdAsync(string docType, string docNum);
public Task<IEnumerable<GuestDocumentDTO>> GetDtoListByFilterAsync(GuestDocFilterDTO filter, int limit = 0); public Task<IEnumerable<GuestDocumentDTO>> GetDtoListByFilterAsync(GuestDocFilterDTO filter, int limit = 0);
@@ -5,10 +5,10 @@ namespace Somnia.GuestLog.Services;
public interface IStayEntryService public interface IStayEntryService
{ {
public Task<bool> CreateEntryAsync(StayEntryDTO dto); public Task<Result> CreateEntryAsync(StayEntryDTO dto);
public Task<bool> UpdateEntryAsync(StayEntryDTO updatedDto); public Task<Result> UpdateEntryAsync(StayEntryDTO updatedDto);
public Task<bool> DeleteEntryAsync(StayEntryDTO dto); public Task<Result> DeleteEntryAsync(StayEntryDTO dto);
public Task<bool> DeleteEntryAsync(long id); public Task<Result> DeleteEntryAsync(long id);
public Task<StayEntryDTO?> GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null); public Task<StayEntryDTO?> GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null);
public Task<IEnumerable<StayEntryDTO>> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null); public Task<IEnumerable<StayEntryDTO>> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null);
@@ -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
}
}
@@ -15,7 +15,7 @@ public class StayEntryService(
IValidator<StayEntryDTO> dtoValidator IValidator<StayEntryDTO> dtoValidator
) : IStayEntryService ) : IStayEntryService
{ {
public async Task<bool> CreateEntryAsync(StayEntryDTO dto) public async Task<Result> CreateEntryAsync(StayEntryDTO dto)
{ {
ArgumentNullException.ThrowIfNull(dto); ArgumentNullException.ThrowIfNull(dto);
@@ -23,7 +23,7 @@ public class StayEntryService(
if (!valResult.IsValid) if (!valResult.IsValid)
{ {
logger.LogWarning("StayEntryService - CreateEntryAsync: Dto validation failed. Fix the errors: {errorList}", valResult.Errors); logger.LogWarning("StayEntryService - CreateEntryAsync: Dto validation failed. Fix the errors: {errorList}", valResult.Errors);
return false; return Result.Fail(ResultCode.FailedValidation);
} }
DateTime createTime = DateTime.UtcNow; DateTime createTime = DateTime.UtcNow;
@@ -53,7 +53,8 @@ public class StayEntryService(
bool isInOtherStay = await this.IsGuestInStayInPeriod(dbGuest.DocumentType, dbGuest.DocumentNum, dbStay.DateIn, dbStay.DateOut); bool isInOtherStay = await this.IsGuestInStayInPeriod(dbGuest.DocumentType, dbGuest.DocumentNum, dbStay.DateIn, dbStay.DateOut);
if (isInOtherStay) 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. //Add dbGuest to dbStay.
@@ -64,38 +65,38 @@ public class StayEntryService(
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
logger.LogInformation("StayEntryService - CreateEntryAsync: StayEntry Created: '{id}'", dbStay.GetFullKey()); logger.LogInformation("StayEntryService - CreateEntryAsync: StayEntry Created: '{id}'", dbStay.GetFullKey());
return true; return Result.Ok();
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "StayEntryService - CreateEntryAsync: Error creating StayEntry"); logger.LogError(ex, "StayEntryService - CreateEntryAsync: Error creating StayEntry");
return false; return Result.FailUnknownError();
} }
} }
public Task<bool> UpdateEntryAsync(StayEntryDTO updatedDto) public Task<Result> UpdateEntryAsync(StayEntryDTO updatedDto)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<bool> DeleteEntryAsync(StayEntryDTO dto) public async Task<Result> DeleteEntryAsync(StayEntryDTO dto)
{ {
if (dto.Id is null) if (dto.Id is null)
{ {
throw new InvalidOperationException("StayEntryService - DeleteEntry: A DTO without key can't be deleted!"); 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<bool> DeleteEntryAsync(long id) public async Task<Result> DeleteEntryAsync(long id)
{ {
using var dbContext = contextFactory.CreateDbContext(); using var dbContext = contextFactory.CreateDbContext();
StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id); StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id);
if (dbItem is null) if (dbItem is null)
{ {
logger.LogWarning("StayEntryService - DeleteEntry: Error deleting StayEntry. Entity not found with key: '{key}'", id); logger.LogWarning("StayEntryService - DeleteEntry: Error deleting StayEntry. Entity not found with key: '{key}'", id);
return false; return Result.Fail(ResultCode.NotFound);
} }
try try
@@ -104,12 +105,12 @@ public class StayEntryService(
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
logger.LogInformation("StayEntryService - DeleteEntry: StayEntry deleted: '{id}'", dbItem.GetFullKey()); logger.LogInformation("StayEntryService - DeleteEntry: StayEntry deleted: '{id}'", dbItem.GetFullKey());
return true; return Result.Ok();
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "StayEntryService - DeleteEntry: Error deleting StayEntry: '{id}'", dbItem.GetFullKey()); logger.LogError(ex, "StayEntryService - DeleteEntry: Error deleting StayEntry: '{id}'", dbItem.GetFullKey());
return false; return Result.FailUnknownError();
} }
} }
@@ -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<LanguageLocale> _localizer
{
get
{
return serviceProvider.GetRequiredService<IStringLocalizer<LanguageLocale>>();
}
}
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
}