This commit is contained in:
Claudio Schaad 2025-09-06 16:46:17 +02:00
parent 419dd4453f
commit f3389936b5
4 changed files with 33 additions and 38 deletions

View file

@ -7,20 +7,10 @@
<button class="btn month-btn @(_isCurrentMonth ? "" : "selected")" @onclick="SelectNextMonth" disabled="@_isLoadingInitial">@MonthAndYear.NextMonthName</button> <button class="btn month-btn @(_isCurrentMonth ? "" : "selected")" @onclick="SelectNextMonth" disabled="@_isLoadingInitial">@MonthAndYear.NextMonthName</button>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button class="btn export-btn" @onclick="ExportToIcs" disabled="@_isLoadingInitial"><span class="oi oi-calendar" aria-hidden="true"></span> Export to ICS</button> <button class="btn export-btn" @onclick="ExportToIcs" disabled="@(_isLoadingInitial || _isSyncing)"><span class="oi oi-calendar" aria-hidden="true"></span> Export to ICS</button>
<button class="btn export-btn" @onclick="SyncToGoogleCalendar" disabled="@(_isLoadingInitial || _isSyncing)"> <button class="btn export-btn" @onclick="SyncToGoogleCalendar" disabled="@(_isLoadingInitial || _isSyncing)"><span class="oi oi-cloud-upload" aria-hidden="true"></span> Sync to Google Calendar</button>
@if (_isSyncing) <button class="btn export-btn" @onclick="ExportToPdf" disabled="@(_isLoadingInitial || _isSyncing)"><span class="oi oi-document" aria-hidden="true"></span>Export to PDF</button>
{ <button class="btn config-btn" @onclick="ShowConfiguration" disabled="@(_isLoadingInitial || _isSyncing)"><span class="oi oi-cog" aria-hidden="true"></span> Configuration</button>
<span class="loading-spinner">⏳</span>
}
else
{
<span class="oi oi-cloud-upload" aria-hidden="true"></span>
}
Sync to Google Calendar
</button>
<button class="btn export-btn" @onclick="ExportToPdf" disabled="@_isLoadingInitial"><span class="oi oi-document" aria-hidden="true"></span>Export to PDF</button>
<button class="btn config-btn" @onclick="ShowConfiguration" disabled="@_isLoadingInitial"><span class="oi oi-cog" aria-hidden="true"></span> Configuration</button>
</div> </div>
</div> </div>
@ -92,6 +82,15 @@
</div> </div>
</div> </div>
} }
else if (_isSyncing)
{
<div class="loading-overlay">
<div class="loading-content">
<div class="loading-spinner">⏳</div>
<p>Syncing with google calendar...</p>
</div>
</div>
}
else else
{ {

View file

@ -5,6 +5,7 @@ namespace ShiftScheduler.Services
{ {
public interface IConfigurationService public interface IConfigurationService
{ {
string GetTimeZone();
ApplicationConfiguration GetConfiguration(); ApplicationConfiguration GetConfiguration();
void UpdateConfiguration(ApplicationConfiguration configuration); void UpdateConfiguration(ApplicationConfiguration configuration);
Task<string> ExportConfigurationAsync(); Task<string> ExportConfigurationAsync();
@ -29,7 +30,12 @@ namespace ShiftScheduler.Services
private readonly string _shiftsFilePath; private readonly string _shiftsFilePath;
private readonly string _transportFilePath; private readonly string _transportFilePath;
readonly TimeZoneInfo localTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Zurich"); private readonly TimeZoneInfo localTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Zurich");
public string GetTimeZone()
{
return localTimeZone.Id;
}
public ConfigurationService(ApplicationConfiguration initialConfiguration) public ConfigurationService(ApplicationConfiguration initialConfiguration)
{ {

View file

@ -5,7 +5,6 @@ using Google.Apis.Services;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using ShiftScheduler.Shared; using ShiftScheduler.Shared;
using System.Net;
namespace ShiftScheduler.Services; namespace ShiftScheduler.Services;
@ -15,17 +14,9 @@ public interface IGoogleCalendarService
Task SyncShiftsToCalendarAsync(string calendarId, List<ShiftWithTransport> shifts); Task SyncShiftsToCalendarAsync(string calendarId, List<ShiftWithTransport> shifts);
} }
public class GoogleCalendarService : IGoogleCalendarService public class GoogleCalendarService(IHttpContextAccessor httpContextAccessor, IConfigurationService configurationService)
: IGoogleCalendarService
{ {
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IcsExportService _icsExportService;
public GoogleCalendarService(IHttpContextAccessor httpContextAccessor, IcsExportService icsExportService)
{
_httpContextAccessor = httpContextAccessor;
_icsExportService = icsExportService;
}
public async Task<List<CalendarListEntry>> GetCalendarsAsync() public async Task<List<CalendarListEntry>> GetCalendarsAsync()
{ {
var service = await CreateCalendarServiceAsync(); var service = await CreateCalendarServiceAsync();
@ -110,25 +101,25 @@ public class GoogleCalendarService : IGoogleCalendarService
// Create morning event if it exists // Create morning event if it exists
if (!string.IsNullOrEmpty(shift.MorningTime)) if (!string.IsNullOrEmpty(shift.MorningTime))
{ {
var morningEvent = CreateEventFromShift(shift, date, shift.MorningTime, "Morning", shiftWithTransport.MorningTransport); var morningEvent = CreateEventFromShift(shift, date, shift.MorningTime, shiftWithTransport.MorningTransport);
await ExecuteWithRetryAsync(async () => await service.Events.Insert(morningEvent, calendarId).ExecuteAsync()); await ExecuteWithRetryAsync(async () => await service.Events.Insert(morningEvent, calendarId).ExecuteAsync());
} }
// Create afternoon event if it exists // Create afternoon event if it exists
if (!string.IsNullOrEmpty(shift.AfternoonTime)) if (!string.IsNullOrEmpty(shift.AfternoonTime))
{ {
var afternoonEvent = CreateEventFromShift(shift, date, shift.AfternoonTime, "Afternoon", shiftWithTransport.AfternoonTransport); var afternoonEvent = CreateEventFromShift(shift, date, shift.AfternoonTime, shiftWithTransport.AfternoonTransport);
await ExecuteWithRetryAsync(async () => await service.Events.Insert(afternoonEvent, calendarId).ExecuteAsync()); await ExecuteWithRetryAsync(async () => await service.Events.Insert(afternoonEvent, calendarId).ExecuteAsync());
} }
} }
private static Event CreateEventFromShift(Shift shift, DateTime date, string timeRange, string period, TransportConnection? transport) private Event CreateEventFromShift(Shift shift, DateTime date, string timeRange, TransportConnection? transport)
{ {
var times = timeRange.Split('-'); var times = timeRange.Split('-');
var startTime = date.Add(TimeSpan.Parse(times[0])); var startTime = date.Add(TimeSpan.Parse(times[0]));
var endTime = date.Add(TimeSpan.Parse(times[1])); var endTime = date.Add(TimeSpan.Parse(times[1]));
var summary = $"{shift.Name} ({period})"; var summary = $"{shift.Name}";
var description = ""; var description = "";
if (transport != null) if (transport != null)
@ -144,20 +135,19 @@ public class GoogleCalendarService : IGoogleCalendarService
Start = new EventDateTime Start = new EventDateTime
{ {
DateTimeDateTimeOffset = startTime, DateTimeDateTimeOffset = startTime,
TimeZone = "Europe/Zurich" TimeZone = configurationService.GetTimeZone()
}, },
End = new EventDateTime End = new EventDateTime
{ {
DateTimeDateTimeOffset = endTime, DateTimeDateTimeOffset = endTime,
TimeZone = "Europe/Zurich" TimeZone = configurationService.GetTimeZone()
}, },
ExtendedProperties = new Event.ExtendedPropertiesData ExtendedProperties = new Event.ExtendedPropertiesData
{ {
Private__ = new Dictionary<string, string> Private__ = new Dictionary<string, string>
{ {
["shiftSchedulerEvent"] = "true", ["shiftSchedulerEvent"] = "true",
["shiftName"] = shift.Name, ["shiftName"] = shift.Name
["period"] = period.ToLower()
} }
} }
}; };
@ -167,12 +157,12 @@ public class GoogleCalendarService : IGoogleCalendarService
{ {
var departure = transport.DepartureTime.ToString("HH:mm"); var departure = transport.DepartureTime.ToString("HH:mm");
var arrival = transport.ArrivalTime.ToString("HH:mm"); var arrival = transport.ArrivalTime.ToString("HH:mm");
return $"{departure} → {arrival}"; return $"{transport.Platform}: {departure} → {arrival} ({transport.Duration.TotalMinutes} Minutes)";
} }
private async Task<CalendarService> CreateCalendarServiceAsync() private async Task<CalendarService> CreateCalendarServiceAsync()
{ {
var httpContext = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException("No HTTP context available"); var httpContext = httpContextAccessor.HttpContext ?? throw new InvalidOperationException("No HTTP context available");
var accessToken = await httpContext.GetTokenAsync("access_token"); var accessToken = await httpContext.GetTokenAsync("access_token");
if (string.IsNullOrEmpty(accessToken)) if (string.IsNullOrEmpty(accessToken))

View file

@ -23,7 +23,7 @@ namespace ShiftScheduler.Services
if (!string.IsNullOrEmpty(shift.MorningTime)) if (!string.IsNullOrEmpty(shift.MorningTime))
{ {
var times = shift.MorningTime.Split('-'); var times = shift.MorningTime.Split('-');
var summary = $"{shift.Name} (Morning)"; var summary = $"{shift.Name}";
var description = ""; var description = "";
if (shiftWithTransport.MorningTransport != null) if (shiftWithTransport.MorningTransport != null)
@ -44,7 +44,7 @@ namespace ShiftScheduler.Services
if (!string.IsNullOrEmpty(shift.AfternoonTime)) if (!string.IsNullOrEmpty(shift.AfternoonTime))
{ {
var times = shift.AfternoonTime.Split('-'); var times = shift.AfternoonTime.Split('-');
var summary = $"{shift.Name} (Afternoon)"; var summary = $"{shift.Name}";
var description = ""; var description = "";
if (shiftWithTransport.AfternoonTransport != null) if (shiftWithTransport.AfternoonTransport != null)