UI improvements: Add month to title, remove sidebar, move buttons to top navigation, show shift times (#14)
* Initial plan * Complete UI improvements: add month to title, remove sidebar, move buttons to top, show shift times, add loading spinner Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com> * Move action buttons to top navigation level as requested Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com> * Some ui changes --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com> Co-authored-by: Claudio Schaad <c.schaad@pog.ch>
This commit is contained in:
parent
b41d8d6b36
commit
45fa364368
8 changed files with 103 additions and 143 deletions
|
|
@ -1,6 +1,24 @@
|
||||||
@page "/"
|
@page "/"
|
||||||
|
|
||||||
<h3>Shift Scheduler</h3>
|
<div class="top-nav-buttons">
|
||||||
|
<button class="reset-btn" @onclick="ResetSchedule" disabled="@_isLoadingInitial">Reset Schedule</button>
|
||||||
|
<button class="btn export-btn" @onclick="ExportToIcs" disabled="@_isLoadingInitial">Export to ICS</button>
|
||||||
|
<button class="btn export-btn" @onclick="ExportToPdf" disabled="@_isLoadingInitial">Export to PDF</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Shift Scheduler - @CurrentMonthYear</h3>
|
||||||
|
|
||||||
|
@if (_isLoadingInitial)
|
||||||
|
{
|
||||||
|
<div class="loading-overlay">
|
||||||
|
<div class="loading-content">
|
||||||
|
<div class="loading-spinner">⏳</div>
|
||||||
|
<p>Loading shifts and transport data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
<div class="calendar-grid">
|
<div class="calendar-grid">
|
||||||
@foreach (var day in DaysInMonth)
|
@foreach (var day in DaysInMonth)
|
||||||
|
|
@ -31,6 +49,9 @@
|
||||||
</div>
|
</div>
|
||||||
@if (SelectedSchedule.ContainsKey(day))
|
@if (SelectedSchedule.ContainsKey(day))
|
||||||
{
|
{
|
||||||
|
<div class="shift-times">
|
||||||
|
<small>@GetShiftTimes(day)</small>
|
||||||
|
</div>
|
||||||
<div class="transport-info">
|
<div class="transport-info">
|
||||||
<small>@GetTransportSummary(day)</small>
|
<small>@GetTransportSummary(day)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -38,12 +59,4 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<div class="reset-section">
|
|
||||||
<button class="reset-btn" @onclick="ResetSchedule">Reset Schedule</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="export-buttons">
|
|
||||||
<button class="btn export-btn" @onclick="ExportToIcs">Export to ICS</button>
|
|
||||||
<button class="btn export-btn" @onclick="ExportToPdf">Export to PDF</button>
|
|
||||||
</div>
|
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,14 @@ namespace ShiftScheduler.Client.Pages
|
||||||
private int EditMonth { get; set; }
|
private int EditMonth { get; set; }
|
||||||
private int EditYear { get; set; }
|
private int EditYear { get; set; }
|
||||||
private bool _isLoadingTransport = false;
|
private bool _isLoadingTransport = false;
|
||||||
|
private bool _isLoadingInitial = false;
|
||||||
|
|
||||||
|
private string CurrentMonthYear => new DateTime(EditYear, EditMonth, 1).ToString("MMMM yyyy");
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
_isLoadingInitial = true;
|
||||||
|
|
||||||
(EditMonth, EditYear) = GetNextMonthAndYear();
|
(EditMonth, EditYear) = GetNextMonthAndYear();
|
||||||
|
|
||||||
DaysInMonth = Enumerable.Range(1, DateTime.DaysInMonth(EditYear, EditMonth))
|
DaysInMonth = Enumerable.Range(1, DateTime.DaysInMonth(EditYear, EditMonth))
|
||||||
|
|
@ -31,6 +36,9 @@ namespace ShiftScheduler.Client.Pages
|
||||||
Shifts = await HttpClient.GetFromJsonAsync<List<Shift>>("api/shift/shifts") ?? new();
|
Shifts = await HttpClient.GetFromJsonAsync<List<Shift>>("api/shift/shifts") ?? new();
|
||||||
|
|
||||||
await LoadScheduleFromStorage();
|
await LoadScheduleFromStorage();
|
||||||
|
|
||||||
|
_isLoadingInitial = false;
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private (int nextMonth, int year) GetNextMonthAndYear()
|
private (int nextMonth, int year) GetNextMonthAndYear()
|
||||||
|
|
@ -230,5 +238,24 @@ namespace ShiftScheduler.Client.Pages
|
||||||
|
|
||||||
return string.Join(" | ", summaries);
|
return string.Join(" | ", summaries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method to get shift times for display
|
||||||
|
public string GetShiftTimes(DateTime date)
|
||||||
|
{
|
||||||
|
if (!SelectedSchedule.TryGetValue(date, out var shiftName))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var shift = Shifts.FirstOrDefault(s => s.Name == shiftName);
|
||||||
|
if (shift == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var times = new List<string>();
|
||||||
|
if (!string.IsNullOrEmpty(shift.MorningTime))
|
||||||
|
times.Add(shift.MorningTime);
|
||||||
|
if (!string.IsNullOrEmpty(shift.AfternoonTime))
|
||||||
|
times.Add(shift.AfternoonTime);
|
||||||
|
|
||||||
|
return string.Join(" | ", times);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,43 @@
|
||||||
|
/* Top navigation buttons */
|
||||||
|
.top-nav-buttons {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 24px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 14px 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading overlay */
|
||||||
|
.loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-content {
|
||||||
|
text-align: center;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-content .loading-spinner {
|
||||||
|
font-size: 2rem;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Calendar container */
|
/* Calendar container */
|
||||||
.calendar-grid {
|
.calendar-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
@ -154,6 +194,18 @@
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shift times styling */
|
||||||
|
.shift-times {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: #f0f8e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #2d5016;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* Loading spinner */
|
/* Loading spinner */
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,7 @@
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="sidebar">
|
|
||||||
<NavMenu />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="top-row px-4">
|
|
||||||
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<article class="content px-4">
|
<article class="content px-4">
|
||||||
@Body
|
@Body
|
||||||
</article>
|
</article>
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,6 @@ main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
background-color: #f7f7f7;
|
background-color: #f7f7f7;
|
||||||
border-bottom: 1px solid #d6d5d5;
|
border-bottom: 1px solid #d6d5d5;
|
||||||
|
|
@ -52,14 +48,7 @@ main {
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
@media (min-width: 641px) {
|
||||||
.page {
|
.page {
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 250px;
|
|
||||||
height: 100vh;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<div class="top-row ps-3 navbar navbar-dark">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="">ShiftScheduler</a>
|
|
||||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
|
||||||
<nav class="flex-column">
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
|
||||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private bool collapseNavMenu = true;
|
|
||||||
|
|
||||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
|
||||||
|
|
||||||
private void ToggleNavMenu()
|
|
||||||
{
|
|
||||||
collapseNavMenu = !collapseNavMenu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
.navbar-toggler {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row {
|
|
||||||
height: 3.5rem;
|
|
||||||
background-color: rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.oi {
|
|
||||||
width: 2rem;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
vertical-align: text-top;
|
|
||||||
top: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item:first-of-type {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item:last-of-type {
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item ::deep a {
|
|
||||||
color: #d7d7d7;
|
|
||||||
border-radius: 4px;
|
|
||||||
height: 3rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item ::deep a.active {
|
|
||||||
background-color: rgba(255,255,255,0.25);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item ::deep a:hover {
|
|
||||||
background-color: rgba(255,255,255,0.1);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
|
||||||
.navbar-toggler {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse {
|
|
||||||
/* Never collapse the sidebar for wide screens */
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-scrollable {
|
|
||||||
/* Allow sidebar to scroll for tall menus */
|
|
||||||
height: calc(100vh - 3.5rem);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<div class="alert alert-secondary mt-4">
|
|
||||||
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
|
|
||||||
<strong>@Title</strong>
|
|
||||||
|
|
||||||
<span class="text-nowrap">
|
|
||||||
Please take our
|
|
||||||
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
|
|
||||||
</span>
|
|
||||||
and tell us what you think.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// Demonstrates how a parent component can supply parameters
|
|
||||||
[Parameter]
|
|
||||||
public string? Title { get; set; }
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue