feat: add WorkUnitEditorModal component for managing work units

- Implemented WorkUnitEditorModal.razor for creating and editing work units.
- Added necessary services and parameters for data handling.
- Included computed values for calculated hours, gross income, and net income.
- Enhanced UI with modal structure and styling.

fix: update _Imports.razor to include Shared components

- Added reference to WorkUnitEditorModal in _Imports.razor for accessibility.

feat: extend CalendarEventDocument with StartDate and EndDate properties

- Updated CalendarEventDocument.cs to include StartDate and EndDate for better event management.

feat: create CalendarEventFormatter for event description formatting

- Introduced CalendarEventFormatter.cs to handle display logic for calendar events.

fix: enhance CouchbaseLiteWorkDayService for calendar event management

- Updated methods to handle new StartDate and EndDate properties in calendar events.
- Improved event saving and deletion logic.

test: add Playwright tests for date locale functionality

- Created date-locale.spec.ts to verify date picker behavior and formatting.

style: enhance app.css with modal and date input styles

- Added styles for calendar modal, date input, and related components for improved UI.
This commit is contained in:
Marco 2026-04-22 11:07:30 +02:00
commit bc28d869eb
14 changed files with 1638 additions and 150 deletions

View file

@ -20,8 +20,14 @@ else
{
<div class="row g-3">
<div class="col-12 col-md-6 col-lg-4">
<label class="form-label">Date</label>
<input type="date" class="form-control" value="@selectedDate.ToString("yyyy-MM-dd")" @onchange="OnDateChanged" disabled="@isExistingEvent" />
<label class="form-label">Start Date</label>
<LocalizedDateInput InputId="calendar-event-date" TestId="calendar-event-start-date" Value="@selectedDate" ValueChanged="OnDateChangedAsync" />
</div>
<div class="col-12 col-md-6 col-lg-4">
<label class="form-label">End Date</label>
<LocalizedDateInput InputId="calendar-event-end-date" TestId="calendar-event-end-date" Value="@endDate" ValueChanged="OnEndDateChangedAsync" AllowEmpty="true" />
<div class="form-text">Optional. Leave empty for a single-day event.</div>
</div>
<div class="col-12 col-md-6 col-lg-4">
@ -29,24 +35,24 @@ else
<select class="form-select" @bind="eventType">
@foreach (var item in Enum.GetValues<CalendarEventType>())
{
<option value="@item">@item</option>
<option value="@item">@CalendarEventFormatter.GetEventTypeName(item)</option>
}
</select>
</div>
<div class="col-12 col-lg-8">
<label class="form-label">Description</label>
<input class="form-control" @bind="description" maxlength="120" />
<label class="form-label">Title</label>
<input class="form-control" @bind="description" maxlength="120" placeholder="Optional" />
</div>
<div class="col-12 col-md-6 col-lg-4">
<label class="form-label">Start Time</label>
<input type="time" class="form-control" value="@startTimeStr" @onchange="OnStartTimeChanged" />
<input type="time" lang="it-IT" class="form-control" value="@startTimeStr" @onchange="OnStartTimeChanged" />
</div>
<div class="col-12 col-md-6 col-lg-4">
<label class="form-label">End Time</label>
<input type="time" class="form-control" value="@endTimeStr" @onchange="OnEndTimeChanged" />
<input type="time" lang="it-IT" class="form-control" value="@endTimeStr" @onchange="OnEndTimeChanged" />
</div>
<div class="col-12 col-md-6 col-lg-4">
@ -78,9 +84,10 @@ else
private DateOnly selectedDate = DateOnly.FromDateTime(DateTime.Today);
private string eventId = string.Empty;
private CalendarEventType eventType = CalendarEventType.Generic;
private string description = "Calendar entry";
private string description = string.Empty;
private string? startTimeStr;
private string? endTimeStr;
private DateOnly? endDate;
private string? statusMessage;
protected override async Task OnInitializedAsync()
@ -106,10 +113,12 @@ else
if (existing is not null)
{
eventId = existing.Id;
selectedDate = existing.StartDate == default ? selectedDate : existing.StartDate;
eventType = existing.EventType;
description = existing.Description;
startTimeStr = existing.StartTime?.ToString("HH:mm");
endTimeStr = existing.EndTime?.ToString("HH:mm");
endDate = existing.EndDate;
isExistingEvent = true;
}
else
@ -123,23 +132,31 @@ else
{
eventId = string.Empty;
eventType = CalendarEventType.Generic;
description = "Calendar entry";
description = string.Empty;
startTimeStr = null;
endTimeStr = null;
endDate = null;
isExistingEvent = false;
}
private Task OnDateChanged(ChangeEventArgs e)
private Task OnDateChangedAsync(DateOnly? value)
{
if (DateOnly.TryParse(e.Value?.ToString(), out var parsed))
if (value.HasValue)
{
selectedDate = parsed;
selectedDate = value.Value;
statusMessage = null;
}
return Task.CompletedTask;
}
private Task OnEndDateChangedAsync(DateOnly? value)
{
endDate = value;
statusMessage = null;
return Task.CompletedTask;
}
private Task OnStartTimeChanged(ChangeEventArgs e)
{
startTimeStr = e.Value?.ToString();
@ -159,6 +176,8 @@ else
var calendarEvent = new CalendarEventDocument
{
Id = eventId,
StartDate = selectedDate,
EndDate = endDate,
EventType = eventType,
Description = description,
StartTime = ParseTime(startTimeStr),
@ -180,7 +199,8 @@ else
return;
}
var confirmed = await JS.InvokeAsync<bool>("confirm", $"Delete calendar event '{description}' on {selectedDate:dddd d MMMM}?\nThis cannot be undone.");
var eventName = CalendarEventFormatter.GetDisplayDescription(eventType, description);
var confirmed = await JS.InvokeAsync<bool>("confirm", $"Delete calendar event '{eventName}' starting on {FormatDisplayDate(selectedDate)}?\nThis cannot be undone.");
if (!confirmed)
{
return;
@ -229,4 +249,9 @@ else
? parsed
: null;
}
private static string FormatDisplayDate(DateOnly date)
{
return date.ToString("dddd dd/MM/yyyy");
}
}