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>
This commit is contained in:
copilot-swe-agent[bot] 2025-08-20 06:19:20 +00:00
parent 0c84183389
commit b6b31dc937
7 changed files with 101 additions and 123 deletions

View file

@ -1,6 +1,24 @@
@page "/" @page "/"
<h3>Shift Scheduler</h3> <div class="header-buttons">
<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>
<button class="reset-btn" @onclick="ResetSchedule" disabled="@_isLoadingInitial">Reset Schedule</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>

View file

@ -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()
@ -231,5 +239,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);
}
} }
} }

View file

@ -1,3 +1,41 @@
/* Header buttons */
.header-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-bottom: 20px;
padding: 10px 0;
border-bottom: 1px solid #ddd;
}
/* 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 +192,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;

View file

@ -1,10 +1,6 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<div class="page"> <div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main> <main>
<div class="top-row px-4"> <div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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;
}
}