GuestLog - General Improvements.

- GuestDocument details - Added navigation to details for stays.
- StayEntry details - Added dialog to see details of GuestDocuments.
- StayEntry List - Added filter for result table.
- StayEntry New - Show suggested guests on GuestSearchDialog so no searchs are required in some cases.
- NavMenu - Removed default navigation links.
This commit is contained in:
2026-08-18 22:17:16 +02:00
parent 340bc583b7
commit 0de4e1ba10
12 changed files with 227 additions and 36 deletions
@@ -34,6 +34,14 @@
@if (!isLoading) @if (!isLoading)
{ {
@* Only show on first load *@
@if (loadCount == 1)
{
<MudText Align="Align.Center" GutterBottom>
@LanguageLocale.control_GuestDocumentSearchDialog_txtSuggestedGuests
</MudText>
}
<MudList T="GuestDocumentDTO" @bind-SelectedValue="SelectedResult" <MudList T="GuestDocumentDTO" @bind-SelectedValue="SelectedResult"
SelectionMode="SelectionMode.SingleSelection" Color="Color.Info"> SelectionMode="SelectionMode.SingleSelection" Color="Color.Info">
@foreach (GuestDocumentDTO result in results) @foreach (GuestDocumentDTO result in results)
@@ -74,10 +82,39 @@
private GuestDocumentDTO? SelectedResult { get; set; } private GuestDocumentDTO? SelectedResult { get; set; }
private bool isLoading = false; private bool isLoading = false;
private int loadCount = 0;
protected override async Task OnInitializedAsync()
{
//Load last 5 guests.
await LoadGuests(limit: 5);
}
private void Cancel() => MudDialog.Cancel(); private void Cancel() => MudDialog.Cancel();
private async Task LoadGuests(int limit = 25)
{
try
{
isLoading = true;
results = await guestDocumentService.GetDtoListByFilterAsync(filter);
isLoading = false;
loadCount++;
}
catch (Exception ex)
{
logger.LogError(ex, "GuestDocument Search returned an error!");
Snackbar.Add($"Error searching: {ex.Message}", Severity.Error);
}
}
#region Event Handlers
private async Task BtnSearchGuestDocuments() private async Task BtnSearchGuestDocuments()
{ {
if (filter.IsEmpty) if (filter.IsEmpty)
@@ -86,17 +123,8 @@
return; return;
} }
try //Load unlimited guests using current filter.
{ await LoadGuests();
isLoading = true;
results = await guestDocumentService.GetDtoListByFilterAsync(filter);
isLoading = false;
}
catch (Exception ex)
{
logger.LogError(ex, "GuestDocument Search returned an error!");
Snackbar.Add($"Error searching: {ex.Message}", Severity.Error);
}
} }
private async Task btnSelectGuest() private async Task btnSelectGuest()
@@ -114,4 +142,7 @@
MudDialog.Close(dialogResult); MudDialog.Close(dialogResult);
return; return;
} }
#endregion
} }
@@ -0,0 +1,44 @@
@using System.ComponentModel
@using Somnia.GuestLog.Models.DTOs
@using Somnia.GuestLog.Services
@inject IDataListService dataListService;
<MudForm Model="@Filter" IsTouchedChanged="@(() => FilterChanged.InvokeAsync(Filter))">
<MudStack Row Breakpoint="Breakpoint.LgAndDown" StretchItems="StretchItems.All">
<MudStack Row style="margin-block: 0.2em;">
<MudDatePicker @bind-Date="Filter.DateInStart" For="@(() => Filter.DateInStart)"
Label="@LanguageLocale.general_dateStart" OpenTo="OpenTo.Date" Editable Required="@DateRequired" />
<MudDatePicker @bind-Date="Filter.DateInEnd" For="@(() => Filter.DateInEnd)"
Label="@LanguageLocale.general_dateEnd" OpenTo="OpenTo.Date" Editable Required="@DateRequired" />
</MudStack>
<MudStack Row style="margin-block: 0.2em;">
<MudTextField @bind-Value="Filter.RoomNumber" For="@(() => Filter.RoomNumber)"
Label="@LanguageLocale.dto_StayEntry_RoomNumber" FullWidth Clearable />
<MudSelect @bind-Value="Filter.PaymentMethod" For="@(() => Filter.PaymentMethod)"
Label="@LanguageLocale.dto_StayEntry_PaymentMethod" FullWidth Clearable>
@foreach (string paymentType in
dataListService.GetDataListByName(GuestConsts.DataList_PaymentMethods))
{
<MudSelectItem Value="paymentType">
@dataListService.GetLocalizedValue(GuestConsts.DataList_PaymentMethods, paymentType)
</MudSelectItem>
}
</MudSelect>
</MudStack>
</MudStack>
</MudForm>
@code {
[Parameter, Bindable(true)]
public StayEntryFilterDTO Filter { get; set; } = new();
[Parameter]
public EventCallback<StayEntryFilterDTO> FilterChanged { get; set; }
[Parameter]
public bool DateRequired { get; set; } = true;
}
@@ -69,10 +69,7 @@
<MudTh>@LanguageLocale.dto_GuestDocument_Name</MudTh> <MudTh>@LanguageLocale.dto_GuestDocument_Name</MudTh>
<MudTh>@LanguageLocale.dto_GuestDocument_Surname</MudTh> <MudTh>@LanguageLocale.dto_GuestDocument_Surname</MudTh>
<MudTh>@LanguageLocale.dto_GuestDocument_Surname2</MudTh> <MudTh>@LanguageLocale.dto_GuestDocument_Surname2</MudTh>
@if (!ReadOnly) <MudTh>@LanguageLocale.general_actions</MudTh>
{
<MudTh>@LanguageLocale.general_actions</MudTh>
}
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd DataLabel="@LanguageLocale.dto_GuestDocument_DocumentType"> <MudTd DataLabel="@LanguageLocale.dto_GuestDocument_DocumentType">
@@ -97,6 +94,13 @@
OnClick="@(() => BtnDeleteGuest(context.DocumentType!, context.DocumentNum!))" /> OnClick="@(() => BtnDeleteGuest(context.DocumentType!, context.DocumentNum!))" />
</MudTd> </MudTd>
} }
@if (ReadOnly)
{
<MudTd DataLabel="@LanguageLocale.general_actions">
<MudIconButton Icon="@Icons.Material.Filled.Info"
OnClick="@(() => BtnViewGuestDetails(context))" />
</MudTd>
}
</RowTemplate> </RowTemplate>
</MudTable> </MudTable>
@@ -181,7 +185,15 @@
private async Task BtnAddGuest() private async Task BtnAddGuest()
{ {
var dialog = await dialogService.ShowAsync<GuestDocumentSearchDialog>(); DialogOptions dialogOptions = new()
{
MaxWidth = MaxWidth.Medium,
FullWidth = true,
BackdropClick = false,
CloseOnEscapeKey = true,
};
var dialog = await dialogService.ShowAsync<GuestDocumentSearchDialog>(title: "", options: dialogOptions);
DialogResult? result = await dialog.Result; DialogResult? result = await dialog.Result;
if (result is null || result.Canceled) if (result is null || result.Canceled)
@@ -228,6 +240,21 @@
.ToList(); .ToList();
} }
private async Task BtnViewGuestDetails(GuestDocumentDTO item)
{
var parameters =
new DialogParameters<GuestDocDetailsDialog> { { x => x.DocumentDTO, item } };
var options = new DialogOptions()
{
FullScreen = true,
CloseButton = true,
CloseOnEscapeKey = true,
};
await dialogService.ShowAsync<GuestDocDetailsDialog>("GuestDoc Details", parameters, options);
}
#endregion #endregion
} }
@@ -1,12 +1,16 @@
<MudNavMenu>
<MudNavMenu>
<MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink> <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
@*
<MudNavLink Href="counter" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Add">Counter</MudNavLink> <MudNavLink Href="counter" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Add">Counter</MudNavLink>
<MudNavLink Href="weather" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">Weather</MudNavLink> <MudNavLink Href="weather" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">Weather</MudNavLink>
*@
<MudNavLink Href="@GuestConsts.Page_GuestDocs_List" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.DocumentScanner">Guests</MudNavLink> <MudNavLink Href="@GuestConsts.Page_GuestDocs_List" Match="NavLinkMatch.Prefix"
<MudNavLink Href="@GuestConsts.Page_StayEntry_List" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Book">Stays</MudNavLink> Icon="@Icons.Material.Filled.DocumentScanner">
@LanguageLocale.navMenu_GuestDocumentList
</MudNavLink>
<MudNavLink Href="@GuestConsts.Page_StayEntry_List" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Book">
@LanguageLocale.navMenu_StayEntryList
</MudNavLink>
</MudNavMenu> </MudNavMenu>
@@ -2,6 +2,7 @@
@using Somnia.GuestLog.Models.DTOs; @using Somnia.GuestLog.Models.DTOs;
@using Somnia.GuestLog.Services; @using Somnia.GuestLog.Services;
@using Microsoft.AspNetCore.WebUtilities;
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject IDialogService DialogService @inject IDialogService DialogService
@@ -42,6 +43,7 @@
<MudTh>@LanguageLocale.dto_StayEntry_DateOut</MudTh> <MudTh>@LanguageLocale.dto_StayEntry_DateOut</MudTh>
<MudTh>@LanguageLocale.dto_StayEntry_RoomCount</MudTh> <MudTh>@LanguageLocale.dto_StayEntry_RoomCount</MudTh>
<MudTh>@LanguageLocale.dto_StayEntry_RoomNumber</MudTh> <MudTh>@LanguageLocale.dto_StayEntry_RoomNumber</MudTh>
<MudTh>@LanguageLocale.general_actions</MudTh>
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd DataLabel="@LanguageLocale.dto_StayEntry_DateIn"> <MudTd DataLabel="@LanguageLocale.dto_StayEntry_DateIn">
@@ -56,6 +58,9 @@
<MudTd DataLabel="@LanguageLocale.dto_StayEntry_RoomNumber"> <MudTd DataLabel="@LanguageLocale.dto_StayEntry_RoomNumber">
@context.RoomNumber @context.RoomNumber
</MudTd> </MudTd>
<MudTd DataLabel="@LanguageLocale.general_actions">
<MudIconButton Icon="@Icons.Material.Filled.Info" OnClick="@(() => BtnViewStayDetails(context))" />
</MudTd>
</RowTemplate> </RowTemplate>
<NoRecordsContent> <NoRecordsContent>
<MudText>@LanguageLocale.general_noResults</MudText> <MudText>@LanguageLocale.general_noResults</MudText>
@@ -110,16 +115,6 @@
} }
} }
private void BtnEdit()
{
Snackbar.Add("Not implemented yet!", Severity.Info);
}
private void BtnReturn()
{
Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List);
}
private async Task LoadTabGuestStays() private async Task LoadTabGuestStays()
{ {
//Only load when there is no data. //Only load when there is no data.
@@ -137,4 +132,33 @@
guestStayList = await stayService.GetStaysWithGuest(documentModel.DocumentType, documentModel.DocumentNum); guestStayList = await stayService.GetStaysWithGuest(documentModel.DocumentType, documentModel.DocumentNum);
guestStaysLoading = false; guestStaysLoading = false;
} }
#region Event Handlers
private void BtnEdit()
{
Snackbar.Add("Not implemented yet!", Severity.Info);
}
private void BtnReturn()
{
Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List);
}
private void BtnViewStayDetails(StayEntryDTO item)
{
string itemId = $"{item.Id ?? -1}";
string uri = GuestConsts.Page_StayEntry_Detail.Replace(GuestConsts.Page_StayEntry_Detail_Param_Id, itemId);
string relativeUri = Navigation.ToBaseRelativePath(Navigation.Uri);
uri = QueryHelpers.AddQueryString(uri, [
new KeyValuePair<string, string?>(GuestConsts.QueryParam_ReturnUrl, relativeUri)
]);
Navigation.NavigateTo(uri);
}
#endregion
} }
@@ -36,6 +36,9 @@
[Parameter] [Parameter]
public long StayId { get; set; } public long StayId { get; set; }
[SupplyParameterFromQuery(Name = GuestConsts.QueryParam_ReturnUrl)]
public string? ReturnUrl { get; set; }
StayEntryDTO? stayModel = null; StayEntryDTO? stayModel = null;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -64,7 +67,8 @@
private void BtnReturn() private void BtnReturn()
{ {
Navigation.NavigateTo(GuestConsts.Page_StayEntry_List); string uri = ReturnUrl ?? GuestConsts.Page_StayEntry_List;
Navigation.NavigateTo(uri);
} }
} }
@@ -24,6 +24,10 @@
</MudButton> </MudButton>
</MudStack> </MudStack>
<MudPaper Class="pa-4 my-4" Elevation="2" Height="100%">
<StayEntryFilterForm @bind-Filter="stayFilter" />
</MudPaper>
<MudDataGrid @ref="_dataGrid" T="StayEntryDTO" Items="@stayEntryList" Loading="@_dataLoading" ReadOnly="true" <MudDataGrid @ref="_dataGrid" T="StayEntryDTO" Items="@stayEntryList" Loading="@_dataLoading" ReadOnly="true"
Elevation="2"> Elevation="2">
<ToolBarContent> <ToolBarContent>
@@ -66,6 +70,12 @@
private bool _showAllData = false; private bool _showAllData = false;
private const int resultLimit = 200; private const int resultLimit = 200;
private StayEntryFilterDTO stayFilter = new StayEntryFilterDTO()
{
DateInStart = DateTime.Today.AddDays(-14),
DateInEnd = DateTime.Today,
};
private IEnumerable<StayEntryDTO> stayEntryList = []; private IEnumerable<StayEntryDTO> stayEntryList = [];
private MudDataGrid<StayEntryDTO> _dataGrid = null!; private MudDataGrid<StayEntryDTO> _dataGrid = null!;
@@ -82,8 +92,7 @@
_dataLoading = true; _dataLoading = true;
int dataLimit = _showAllData ? 0 : resultLimit; int dataLimit = _showAllData ? 0 : resultLimit;
stayEntryList = await StayEntryService.GetDtoListAsync(); stayEntryList = await StayEntryService.GetDtoListByFilterAsync(filter: stayFilter, limit: dataLimit);
//stayEntryList = await StayEntryService.GetDtoListAsync(filter: filter, limit: dataLimit);
if (showMsg) if (showMsg)
{ {
+3
View File
@@ -14,6 +14,9 @@ public static class GuestConsts
public const string Page_StayEntry_Detail_Param_Id = "{StayId:long}"; public const string Page_StayEntry_Detail_Param_Id = "{StayId:long}";
public const string QueryParam_ReturnUrl = "returnUrl";
public const string DataList_DocumentTypes = "DocumentType"; public const string DataList_DocumentTypes = "DocumentType";
public const string DataList_GenderOptions = "GenderOption"; public const string DataList_GenderOptions = "GenderOption";
public const string DataList_CountryCodes = "CountryCode"; public const string DataList_CountryCodes = "CountryCode";
@@ -0,0 +1,10 @@
namespace Somnia.GuestLog.Models.DTOs;
public class StayEntryFilterDTO
{
public DateTime? DateInStart { get; set; }
public DateTime? DateInEnd { get; set; }
public string? RoomNumber { get; set; }
public string? PaymentMethod { get; set; }
}
@@ -166,6 +166,8 @@
<data name="general.noResults" xml:space="preserve"><value>No Results!</value></data> <data name="general.noResults" xml:space="preserve"><value>No Results!</value></data>
<data name="general.showAllElements" xml:space="preserve"><value>Show all elements</value></data> <data name="general.showAllElements" xml:space="preserve"><value>Show all elements</value></data>
<data name="general.wildcardHelperText" xml:space="preserve"><value>Use '%' as wildcard</value></data> <data name="general.wildcardHelperText" xml:space="preserve"><value>Use '%' as wildcard</value></data>
<data name="general.dateStart" xml:space="preserve"><value>Date Start</value></data>
<data name="general.dateEnd" xml:space="preserve"><value>Date End</value></data>
<data name="general.address.address" xml:space="preserve"><value>Address</value></data> <data name="general.address.address" xml:space="preserve"><value>Address</value></data>
<data name="general.address.city" xml:space="preserve"><value>City</value></data> <data name="general.address.city" xml:space="preserve"><value>City</value></data>
@@ -174,6 +176,10 @@
<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>
<data name="navMenu.GuestDocumentList" xml:space="preserve"><value>Guests</value></data>
<data name="navMenu.StayEntryList" xml:space="preserve"><value>Stays</value></data>
<data name="page.GuestDocList.pageTitle" xml:space="preserve"><value>Guest Documents</value></data> <data name="page.GuestDocList.pageTitle" xml:space="preserve"><value>Guest Documents</value></data>
<data name="page.GuestDocList.documentList" xml:space="preserve"><value>Document List</value></data> <data name="page.GuestDocList.documentList" xml:space="preserve"><value>Document List</value></data>
<data name="page.GuestDocList.btnNewGuest" xml:space="preserve"><value>New Guest</value></data> <data name="page.GuestDocList.btnNewGuest" xml:space="preserve"><value>New Guest</value></data>
@@ -204,5 +210,6 @@
<data name="control.GuestDocumentSearchDialog.dialogTitle" xml:space="preserve"><value>Search Guest Document</value></data> <data name="control.GuestDocumentSearchDialog.dialogTitle" xml:space="preserve"><value>Search Guest Document</value></data>
<data name="control.GuestDocumentSearchDialog.btnSelectDocument" xml:space="preserve"><value>Use Guest</value></data> <data name="control.GuestDocumentSearchDialog.btnSelectDocument" xml:space="preserve"><value>Use Guest</value></data>
<data name="control.GuestDocumentSearchDialog.txtSuggestedGuests" xml:space="preserve"><value>Suggested Guests</value></data>
</root> </root>
@@ -12,6 +12,7 @@ public interface IStayEntryService
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);
public Task<IEnumerable<StayEntryDTO>> GetDtoListByFilterAsync(StayEntryFilterDTO filter, int limit = 0, StayEntryQueryConfig? queryConfig = null);
public Task<bool> IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime); public Task<bool> IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime);
@@ -5,6 +5,7 @@ using Somnia.GuestLog.Models;
using Somnia.GuestLog.Models.DTOs; using Somnia.GuestLog.Models.DTOs;
using Somnia.GuestLog.Models.Entities; using Somnia.GuestLog.Models.Entities;
using Somnia.GuestLog.Models.QueryConfigs; using Somnia.GuestLog.Models.QueryConfigs;
using Somnia.GuestLog.Utils;
namespace Somnia.GuestLog.Services; namespace Somnia.GuestLog.Services;
@@ -135,6 +136,32 @@ public class StayEntryService(
return dbList.Select(e => GetDtoFromDb(e, queryConfig)); return dbList.Select(e => GetDtoFromDb(e, queryConfig));
} }
public async Task<IEnumerable<StayEntryDTO>> GetDtoListByFilterAsync(StayEntryFilterDTO filter, int limit = 0, StayEntryQueryConfig? queryConfig = null)
{
using var dbContext = contextFactory.CreateDbContext();
var query = dbContext.StayEntries
.AddQueryConfig(queryConfig)
.AsQueryable();
query = query.WhereIf(filter.DateInStart is not null && filter.DateInEnd is not null,
e => e.DateIn >= filter.DateInStart!.Value.ToUniversalTime()
&& e.DateIn < filter.DateInEnd!.Value.ToUniversalTime());
query = query.WhereIf(!string.IsNullOrEmpty(filter.RoomNumber),
e => EF.Functions.Like(e.RoomNumber, $"%{filter.RoomNumber}%"));
query = query.WhereIf(!string.IsNullOrEmpty(filter.PaymentMethod),
e => e.PaymentMethod == filter.PaymentMethod);
if (limit > 0)
{
query = query.Take(limit);
}
var dbList = await query.ToListAsync();
return dbList.Select(x => GetDtoFromDb(x, queryConfig));
}
public async Task<bool> IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime) public async Task<bool> IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime)
{ {