feat: Add Grid View and Monthly Summary pages with workday management
Some checks failed
Publish Container / publish (push) Failing after 1m2s
Some checks failed
Publish Container / publish (push) Failing after 1m2s
- Implement GridView.razor for displaying a tabular view of workdays in the current month. - Create MonthlySummary.razor to show a summary of worked hours, income, and day types for the selected month. - Introduce WorkDayEditor.razor for adding and editing workday entries with detailed calculations. - Update Home.razor to include links to the new Grid View and Monthly Summary pages. - Add IWorkDayService interface and CouchbaseLiteWorkDayService implementation for managing workday data. - Define domain models: WorkDayDocument, MonthlySummaryModel, and CoeffSnapshotDocument for data structure. - Enhance CouchbaseLiteDatabaseProvider to include a collection for workdays. - Update app settings and services to support new features. - Add CSS styles for calendar view and table formatting.
This commit is contained in:
parent
6e3371514e
commit
3ccce7e8a6
17 changed files with 1257 additions and 18 deletions
164
Components/Pages/GridView.razor
Normal file
164
Components/Pages/GridView.razor
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
@page "/grid"
|
||||
@page "/grid/{YearMonth}"
|
||||
@attribute [Authorize]
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject IWorkDayService WorkDayService
|
||||
@inject IItalianFestivitySource FestivitySource
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>Grid View</PageTitle>
|
||||
|
||||
<h1>Grid View</h1>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="PreviousMonth">« Prev</button>
|
||||
<h2 class="h5 mb-0">@currentDate.ToString("MMMM yyyy")</h2>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="NextMonth">Next »</button>
|
||||
</div>
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Day</th>
|
||||
<th>Type</th>
|
||||
<th>Start</th>
|
||||
<th>Projected</th>
|
||||
<th>Actual</th>
|
||||
<th class="text-end">Worked</th>
|
||||
<th class="text-end">Extra</th>
|
||||
<th class="text-end">Off</th>
|
||||
<th class="text-end">Gross €</th>
|
||||
<th class="text-end">Net €</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var row in calendarDays)
|
||||
{
|
||||
<tr class="@GetRowClass(row)">
|
||||
<td>@row.Date.ToString("dd")</td>
|
||||
<td>@row.Date.ToString("ddd")</td>
|
||||
@if (row.Entry is not null)
|
||||
{
|
||||
<td>@row.Entry.DayType</td>
|
||||
<td>@(row.Entry.StartTime?.ToString("HH:mm") ?? "")</td>
|
||||
<td>@(row.Entry.ProjectedExitTime?.ToString("HH:mm") ?? "")</td>
|
||||
<td>@(row.Entry.ActualExitTime?.ToString("HH:mm") ?? "")</td>
|
||||
<td class="text-end">@row.Entry.WorkedHoursFinal.ToString("N2")</td>
|
||||
<td class="text-end">@FormatDelta(row.Entry.ExtraHoursDelta)</td>
|
||||
<td class="text-end">@row.Entry.HoursOff.ToString("N2")</td>
|
||||
<td class="text-end">@row.Entry.GrossIncome.ToString("N2")</td>
|
||||
<td class="text-end">@row.Entry.NetIncome.ToString("N2")</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td colspan="9" class="text-muted">—</td>
|
||||
}
|
||||
<td>
|
||||
<a href="workday/@row.Date.ToString("yyyy-MM-dd")" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string? YearMonth { get; set; }
|
||||
|
||||
private DateOnly currentDate;
|
||||
private bool loading = true;
|
||||
private List<CalendarDayRow> calendarDays = [];
|
||||
private IReadOnlyCollection<DateOnly> festivities = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(YearMonth) && DateTime.TryParseExact(YearMonth, "yyyy-MM", null, System.Globalization.DateTimeStyles.None, out var parsed))
|
||||
{
|
||||
currentDate = new DateOnly(parsed.Year, parsed.Month, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentDate = new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1);
|
||||
}
|
||||
|
||||
await LoadMonth();
|
||||
}
|
||||
|
||||
private async Task LoadMonth()
|
||||
{
|
||||
loading = true;
|
||||
festivities = FestivitySource.GetFestivities(currentDate.Year);
|
||||
|
||||
var from = currentDate;
|
||||
var to = currentDate.AddMonths(1).AddDays(-1);
|
||||
var entries = await WorkDayService.GetRangeAsync(from, to);
|
||||
var lookup = entries.ToDictionary(e => e.Date);
|
||||
|
||||
calendarDays = [];
|
||||
for (var d = from; d <= to; d = d.AddDays(1))
|
||||
{
|
||||
calendarDays.Add(new CalendarDayRow
|
||||
{
|
||||
Date = d,
|
||||
IsWeekend = d.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday,
|
||||
IsFestivity = festivities.Contains(d),
|
||||
Entry = lookup.GetValueOrDefault(d)
|
||||
});
|
||||
}
|
||||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
private async Task PreviousMonth()
|
||||
{
|
||||
currentDate = currentDate.AddMonths(-1);
|
||||
await LoadMonth();
|
||||
}
|
||||
|
||||
private async Task NextMonth()
|
||||
{
|
||||
currentDate = currentDate.AddMonths(1);
|
||||
await LoadMonth();
|
||||
}
|
||||
|
||||
private string GetRowClass(CalendarDayRow row)
|
||||
{
|
||||
if (row.IsWeekend || row.IsFestivity) return "table-danger";
|
||||
if (row.Entry is null) return "";
|
||||
return row.Entry.DayType switch
|
||||
{
|
||||
DayType.Closure => "table-warning",
|
||||
DayType.Illness => "table-info",
|
||||
DayType.DayOff => "table-secondary",
|
||||
DayType.Holiday => "table-success",
|
||||
DayType.Home => "table-light",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatDelta(decimal d) => d switch
|
||||
{
|
||||
> 0 => $"+{d:N2}",
|
||||
< 0 => d.ToString("N2"),
|
||||
_ => "—"
|
||||
};
|
||||
|
||||
private sealed class CalendarDayRow
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
public bool IsWeekend { get; set; }
|
||||
public bool IsFestivity { get; set; }
|
||||
public WorkDayDocument? Entry { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue