diff --git a/Directory.Packages.props b/Directory.Packages.props index 04460a3..d52b22e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,6 +23,7 @@ + \ No newline at end of file diff --git a/src/Somnia.GuestLog/Components/Controls/ConfirmDeleteMsgBox.razor b/src/Somnia.GuestLog/Components/Controls/ConfirmDeleteMsgBox.razor new file mode 100644 index 0000000..7cdec14 --- /dev/null +++ b/src/Somnia.GuestLog/Components/Controls/ConfirmDeleteMsgBox.razor @@ -0,0 +1,18 @@ + + + @LanguageLocale.general_confirmDeleteMsg + + + + @LanguageLocale.general_btnDelete + + + + +@code { + public MudMessageBox MsgBox => _msgBox; + + MudMessageBox _msgBox = null!; + +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor b/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor index 6fa0c10..f8491b0 100644 --- a/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor +++ b/src/Somnia.GuestLog/Components/Forms/StayEntryForm.razor @@ -1,5 +1,5 @@ +@using System.Diagnostics @using FluentValidation; -@using Microsoft.AspNetCore.SignalR @using Somnia.GuestLog.Models.DTOs; @using Somnia.GuestLog.Services; @using Somnia.GuestLog.Validators; @@ -143,7 +143,7 @@ { await _form.ValidateAsync(); - if (Model.GuestDocuments.Count == 0) + if (Model.GuestDocuments is null || Model.GuestDocuments.Count == 0) { snackbar.Add("At least 1 guest is required!", MudBlazor.Severity.Warning); return false; @@ -198,12 +198,21 @@ } //Check if this guest is already added. - bool alreadyAdded = Model.GuestDocuments.Any(e => e.DocumentType == resultData.DocumentType - && e.DocumentNum == resultData.DocumentNum); - if (alreadyAdded) + if (Model.GuestDocuments is not null && Model.GuestDocuments.Count > 0) { - snackbar.Add("GuestDocument already added!", MudBlazor.Severity.Warning); - return; + bool alreadyAdded = Model.GuestDocuments.Any(e => e.DocumentType == resultData.DocumentType + && e.DocumentNum == resultData.DocumentNum); + if (alreadyAdded) + { + snackbar.Add("GuestDocument already added!", MudBlazor.Severity.Warning); + return; + } + } + + //Init List. + if (Model.GuestDocuments is null) + { + Model.GuestDocuments = []; } Model.GuestDocuments.Add(resultData); @@ -211,8 +220,12 @@ private void BtnDeleteGuest(string docType, string docNum) { + Debug.Assert(Model.GuestDocuments is not null); + //Filter out given GuestDocument from model. - Model.GuestDocuments = Model.GuestDocuments.Where(e => !(e.DocumentType == docType && e.DocumentNum == docNum)).ToList(); + Model.GuestDocuments = Model.GuestDocuments + .Where(e => !(e.DocumentType == docType && e.DocumentNum == docNum)) + .ToList(); } #endregion diff --git a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor index 68eb8cb..762a045 100644 --- a/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor +++ b/src/Somnia.GuestLog/Components/Pages/GuestDocuments/GuestDocList.razor @@ -69,17 +69,7 @@ - - - @LanguageLocale.general_confirmDeleteMsg - - - - @LanguageLocale.general_btnDelete - - - + @code { @@ -92,7 +82,7 @@ private GuestDocFilterDTO docFilter = new GuestDocFilterDTO(); private MudDataGrid _dataGrid = null!; - private MudMessageBox _msgBoxConfirmDelete = null!; + private ConfirmDeleteMsgBox _msgBoxConfirmDelete = null!; protected override async Task OnInitializedAsync() @@ -145,7 +135,7 @@ private async Task BtnDeleteItem(GuestDocumentDTO item) { - bool? confirmDelete = await _msgBoxConfirmDelete.ShowAsync(); + bool? confirmDelete = await _msgBoxConfirmDelete.MsgBox.ShowAsync(); if (confirmDelete is null || confirmDelete.Value == false) { diff --git a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor new file mode 100644 index 0000000..fed7887 --- /dev/null +++ b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryDetail.razor @@ -0,0 +1,70 @@ +@attribute [Route($"{GuestConsts.Page_StayEntry_Detail}")] + +@using Somnia.GuestLog.Models.DTOs; +@using Somnia.GuestLog.Services; + +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@inject NavigationManager Navigation +@inject IGuestDocumentService guestDocService +@inject IStayEntryService stayService + + +Somnia - Guest Docs - Details + + + Stay Entry - "@StayId" + + + @LanguageLocale.general_btnEdit + + + @LanguageLocale.general_btnReturn + + + +@if (stayModel is not null) +{ + +} + + +@code { + + [Parameter] + public long StayId { get; set; } + + StayEntryDTO? stayModel = null; + + protected override async Task OnInitializedAsync() + { + @* if (StayId is default) + { + Snackbar.Add("No Id provided!!", Severity.Error); + Navigation.NotFound(); + return; + } *@ + + //Get model from db. + stayModel = await stayService.GetDtoByIdAsync(StayId, queryConfig: new(IncludeGuests: true)); + if (stayModel is null) + { + Snackbar.Add($"Stay not found with ID: '{StayId}'", Severity.Error); + Navigation.NotFound(); + return; + } + } + + private void BtnEdit() + { + Snackbar.Add("Not implemented yet!", Severity.Info); + } + + private void BtnReturn() + { + Navigation.NavigateTo(GuestConsts.Page_StayEntry_List); + } + +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor index ea39c8c..7fed61c 100644 --- a/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor +++ b/src/Somnia.GuestLog/Components/Pages/StayEntries/StayEntryList.razor @@ -43,7 +43,7 @@ - @* - *@ + + + @code { private bool _dataLoading = true; @@ -67,6 +69,7 @@ private IEnumerable stayEntryList = []; private MudDataGrid _dataGrid = null!; + private ConfirmDeleteMsgBox _confirmDeleteMsgBox = null!; protected override async Task OnInitializedAsync() @@ -102,6 +105,34 @@ Navigation.NavigateTo(GuestConsts.Page_StayEntry_New); } + + private async Task BtnViewDetails(StayEntryDTO item) + { + string itemId = $"{item.Id ?? -1}"; + string uri = GuestConsts.Page_StayEntry_Detail.Replace(GuestConsts.Page_StayEntry_Detail_Param_Id, itemId); + Navigation.NavigateTo(uri); + } + + private async Task BtnDeleteItem(StayEntryDTO item) + { + bool? confirmDelete = await _confirmDeleteMsgBox.MsgBox.ShowAsync(); + + if (confirmDelete is null || confirmDelete.Value == false) + { + return; + } + + bool deleted = await StayEntryService.DeleteEntryAsync(item); + if (!deleted) + { + Snackbar.Add($"Error deleting Stay {item.Id}", Severity.Error); + return; + } + + Snackbar.Add($"Stay {item.Id} deleted!", Severity.Success); + await LoadDataAsync(); + } + #endregion } \ No newline at end of file diff --git a/src/Somnia.GuestLog/GuestConsts.cs b/src/Somnia.GuestLog/GuestConsts.cs index dea0f0b..ed23428 100644 --- a/src/Somnia.GuestLog/GuestConsts.cs +++ b/src/Somnia.GuestLog/GuestConsts.cs @@ -10,6 +10,8 @@ public static class GuestConsts public const string Page_StayEntry_List = "/stayEntries"; public const string Page_StayEntry_New = "/stayEntries/new"; + public const string Page_StayEntry_Detail = $"/stayEntries/d/{Page_StayEntry_Detail_Param_Id}"; + public const string Page_StayEntry_Detail_Param_Id = "{StayId:long}"; public const string DataList_DocumentTypes = "DocumentType"; diff --git a/src/Somnia.GuestLog/Models/DTOs/StayEntryDTO.cs b/src/Somnia.GuestLog/Models/DTOs/StayEntryDTO.cs index 94fa01e..90a42aa 100644 --- a/src/Somnia.GuestLog/Models/DTOs/StayEntryDTO.cs +++ b/src/Somnia.GuestLog/Models/DTOs/StayEntryDTO.cs @@ -12,7 +12,7 @@ public class StayEntryDTO public string? PaymentMethod { get; set; } - public List GuestDocuments { get; set; } = []; + public List? GuestDocuments { get; set; } = null; public DateTime? CreateTime { get; set; } diff --git a/src/Somnia.GuestLog/Models/EntityMapping.cs b/src/Somnia.GuestLog/Models/EntityMapping.cs index 332d749..6b0dfee 100644 --- a/src/Somnia.GuestLog/Models/EntityMapping.cs +++ b/src/Somnia.GuestLog/Models/EntityMapping.cs @@ -33,7 +33,7 @@ internal static class EntityMapping }; } - public static StayEntryDTO MapStayEntryEntityToDTO(StayEntry entity) + public static StayEntryDTO MapStayEntryEntityToDTO(StayEntry entity, bool includeGuests) { return new() { @@ -45,7 +45,7 @@ internal static class EntityMapping PaymentMethod = entity.PaymentMethod, CreateTime = entity.CreateTime, UpdateTime = entity.UpdateTime, - GuestDocuments = [.. entity.GuestDocuments.Select(e => MapGuestDocumentEntityToDTO(e))], + GuestDocuments = includeGuests ? [.. entity.GuestDocuments.Select(e => MapGuestDocumentEntityToDTO(e))] : null, }; } diff --git a/src/Somnia.GuestLog/Models/QueryConfigs/StayEntryQueryConfig.cs b/src/Somnia.GuestLog/Models/QueryConfigs/StayEntryQueryConfig.cs new file mode 100644 index 0000000..e40eefe --- /dev/null +++ b/src/Somnia.GuestLog/Models/QueryConfigs/StayEntryQueryConfig.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using Somnia.GuestLog.Models.Entities; + +namespace Somnia.GuestLog.Models.QueryConfigs; + +public record StayEntryQueryConfig(bool IncludeGuests = false, bool UseSplitQueries = true, bool DisableTracking = false) +{ + public readonly static StayEntryQueryConfig DefaultQueryConfig = new(); +} + + +public static class StayEntryQueryExtensions +{ + public static IQueryable AddQueryConfig(this IQueryable query, StayEntryQueryConfig? config) + { + config ??= StayEntryQueryConfig.DefaultQueryConfig; + + if (config.IncludeGuests) + { + query = query.Include(e => e.GuestDocuments); + } + + if (config.UseSplitQueries) + { + query = query.AsSplitQuery(); + } + if (config.DisableTracking) + { + query = query.AsNoTracking(); + } + + return query; + } +} diff --git a/src/Somnia.GuestLog/Program.cs b/src/Somnia.GuestLog/Program.cs index 18efcf9..846b599 100644 --- a/src/Somnia.GuestLog/Program.cs +++ b/src/Somnia.GuestLog/Program.cs @@ -1,6 +1,7 @@ using FluentValidation; using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using MudBlazor.Services; using Somnia.GuestLog.Components; using Somnia.GuestLog.Infrastructure; @@ -17,7 +18,17 @@ var connectionStringGuestLog = builder.Configuration.GetConnectionString("DbSomn ?? throw new InvalidOperationException("Connection string 'DbSomniaGuestLog' not found."); builder.Services.AddDbContextFactory(options => { - options.UseNpgsql(connectionStringGuestLog); + options.UseNpgsql(connectionStringGuestLog) + .UseLazyLoadingProxies() + .ConfigureWarnings(w => w.Log((CoreEventId.NavigationLazyLoading, LogLevel.Warning))); + + // The following options help with debugging, but should + // be changed or removed for production. + if (builder.Environment.IsDevelopment()) + { + options.EnableSensitiveDataLogging() + .EnableDetailedErrors(); + } }); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); diff --git a/src/Somnia.GuestLog/Services/IStayEntryService.cs b/src/Somnia.GuestLog/Services/IStayEntryService.cs index 2cc113f..2c63954 100644 --- a/src/Somnia.GuestLog/Services/IStayEntryService.cs +++ b/src/Somnia.GuestLog/Services/IStayEntryService.cs @@ -1,4 +1,5 @@ using Somnia.GuestLog.Models.DTOs; +using Somnia.GuestLog.Models.QueryConfigs; namespace Somnia.GuestLog.Services; @@ -9,11 +10,11 @@ public interface IStayEntryService public Task DeleteEntryAsync(StayEntryDTO dto); public Task DeleteEntryAsync(long id); - public Task GetDtoByIdAsync(long id); - public Task> GetDtoListAsync(); + public Task GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null); + public Task> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null); public Task IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime); - public Task> GetStaysWithGuest(string docType, string docNum); + public Task> GetStaysWithGuest(string docType, string docNum, StayEntryQueryConfig? queryConfig = null); } \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayEntryService.cs b/src/Somnia.GuestLog/Services/StayEntryService.cs index fcd46da..daeed77 100644 --- a/src/Somnia.GuestLog/Services/StayEntryService.cs +++ b/src/Somnia.GuestLog/Services/StayEntryService.cs @@ -4,6 +4,7 @@ using Somnia.GuestLog.Infrastructure; using Somnia.GuestLog.Models; using Somnia.GuestLog.Models.DTOs; using Somnia.GuestLog.Models.Entities; +using Somnia.GuestLog.Models.QueryConfigs; namespace Somnia.GuestLog.Services; @@ -41,7 +42,7 @@ public class StayEntryService( using var dbContext = contextFactory.CreateDbContext(); //Check and add GuestDocuments - foreach (GuestDocumentDTO guestDto in dto.GuestDocuments) + foreach (GuestDocumentDTO guestDto in dto.GuestDocuments!) { //Get it from db. GuestDocument? dbGuest = await dbContext.GuestDocuments.FindAsync(guestDto.DocumentType, guestDto.DocumentNum) @@ -111,21 +112,27 @@ public class StayEntryService( } } - public async Task GetDtoByIdAsync(long id) + public async Task GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); - StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id); - return dbItem is not null ? GetDtoFromDb(dbItem) : null; + StayEntry? dbItem = await dbContext.StayEntries + .Where(e => e.Id == id) + .AddQueryConfig(queryConfig) + .FirstOrDefaultAsync(); + + return dbItem is not null ? GetDtoFromDb(dbItem, queryConfig) : null; } - public async Task> GetDtoListAsync() + public async Task> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); + var dbList = await dbContext.StayEntries + .AddQueryConfig(queryConfig) .OrderByDescending(e => e.UpdateTime) .ToListAsync(); - return dbList.Select(GetDtoFromDb); + return dbList.Select(e => GetDtoFromDb(e, queryConfig)); } @@ -133,29 +140,33 @@ public class StayEntryService( { using var dbContext = contextFactory.CreateDbContext(); - bool rs = await dbContext.StayEntries.AnyAsync( - s => s.DateIn < endTime && s.DateOut > startTime - && s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum)); + bool rs = await dbContext.StayEntries + .AnyAsync(s => s.DateIn < endTime && s.DateOut > startTime + && s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum) + ); return rs; } - public async Task> GetStaysWithGuest(string docType, string docNum) + public async Task> GetStaysWithGuest(string docType, string docNum, StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); - var rs = await dbContext.StayEntries.Where( - s => s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum)) + var rs = await dbContext.StayEntries + .Where(s => s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum)) + .AddQueryConfig(queryConfig) .ToListAsync(); - return rs.Select(GetDtoFromDb); + return rs.Select(e => GetDtoFromDb(e, queryConfig)); } #region Internal Methods - private static StayEntryDTO GetDtoFromDb(StayEntry dbItem) + private static StayEntryDTO GetDtoFromDb(StayEntry dbItem, StayEntryQueryConfig? queryConfig) { - return EntityMapping.MapStayEntryEntityToDTO(dbItem); + queryConfig ??= StayEntryQueryConfig.DefaultQueryConfig; + + return EntityMapping.MapStayEntryEntityToDTO(dbItem, queryConfig.IncludeGuests); } #endregion diff --git a/src/Somnia.GuestLog/Somnia.GuestLog.csproj b/src/Somnia.GuestLog/Somnia.GuestLog.csproj index 7c1e4ab..9dca5d8 100644 --- a/src/Somnia.GuestLog/Somnia.GuestLog.csproj +++ b/src/Somnia.GuestLog/Somnia.GuestLog.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Somnia.GuestLog/Validators/StayEntryDTOValidator.cs b/src/Somnia.GuestLog/Validators/StayEntryDTOValidator.cs index 678bcf3..4d1f10a 100644 --- a/src/Somnia.GuestLog/Validators/StayEntryDTOValidator.cs +++ b/src/Somnia.GuestLog/Validators/StayEntryDTOValidator.cs @@ -26,6 +26,7 @@ public class StayEntryDTOValidator : ValueValidator .Must(val => val != null && dataListService.IsValueInList(GuestConsts.DataList_PaymentMethods, val)); RuleFor(e => e.GuestDocuments) + .NotNull() .NotEmpty(); } }