Refactors employee details and UI controls
Enhances employee hours view with dynamic weekly schedule rendering Updates toggle slider and theme switch components with improved interactions Adds more flexible notification and settings configurations for employees Improves user experience by streamlining UI controls and schedule display
This commit is contained in:
parent
6746e876d7
commit
545d6606a6
18 changed files with 506 additions and 206 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -368,3 +368,5 @@ PlanTempus.Application/tmpclaude*
|
|||
|
||||
|
||||
PlanTempus.Application/wwwroot/js/app.js
|
||||
|
||||
PlanTempus.Application/wwwroot/js/app.js.map
|
||||
|
|
|
|||
|
|
@ -88,16 +88,6 @@
|
|||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<div>
|
||||
<swp-toggle-label>@Model.SettingSmsReminders</swp-toggle-label>
|
||||
<swp-toggle-description>@Model.SettingSmsRemindersDesc</swp-toggle-description>
|
||||
</div>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<div>
|
||||
<swp-toggle-label>@Model.SettingEditCalendar</swp-toggle-label>
|
||||
|
|
@ -114,38 +104,48 @@
|
|||
<swp-card>
|
||||
<swp-section-label>@Model.LabelNotifications</swp-section-label>
|
||||
<swp-notification-intro>@Model.NotificationsIntro</swp-notification-intro>
|
||||
<swp-checkbox-list>
|
||||
<swp-checkbox-row class="checked">
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifOnlineBooking</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row class="checked">
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifManualBooking</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row>
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifCancellation</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row>
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifWaitlist</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row class="checked">
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifDailySummary</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
</swp-checkbox-list>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.SettingSmsReminders</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.NotifOnlineBooking</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.NotifManualBooking</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.NotifCancellation</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="no">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.NotifWaitlist</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="no">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<swp-toggle-label>@Model.NotifDailySummary</swp-toggle-label>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
</swp-card>
|
||||
</div>
|
||||
</swp-detail-grid>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,36 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHoursViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelWeeklySchedule</swp-section-label>
|
||||
<swp-schedule-grid>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelMonday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelTuesday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelWednesday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelThursday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 19:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelFriday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 16:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row class="off">
|
||||
<swp-schedule-day>@Model.LabelSaturday</swp-schedule-day>
|
||||
<swp-schedule-time>Fri</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row class="off">
|
||||
<swp-schedule-day>@Model.LabelSunday</swp-schedule-day>
|
||||
<swp-schedule-time>Fri</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
</swp-schedule-grid>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
@{
|
||||
string GetBadgeClass(string status) => status switch
|
||||
{
|
||||
"work" => "",
|
||||
"off" => "off",
|
||||
"vacation" => "vacation",
|
||||
"sick" => "sick",
|
||||
_ => "off"
|
||||
};
|
||||
}
|
||||
|
||||
<swp-schedule-scroll>
|
||||
<swp-schedule-table class="hours-view">
|
||||
<!-- Header row -->
|
||||
<swp-schedule-cell class="header week-number"></swp-schedule-cell>
|
||||
@foreach (var dayName in Model.DayNames)
|
||||
{
|
||||
<swp-schedule-cell class="header"><swp-day-name>@dayName</swp-day-name></swp-schedule-cell>
|
||||
}
|
||||
|
||||
<!-- Week rows -->
|
||||
@foreach (var week in Model.Weeks)
|
||||
{
|
||||
<swp-schedule-cell class="employee week-label">
|
||||
<swp-employee-name>Uge @week.WeekNumber</swp-employee-name>
|
||||
<swp-employee-hours>@week.TotalHours @Model.LabelHours</swp-employee-hours>
|
||||
</swp-schedule-cell>
|
||||
@foreach (var day in week.Days)
|
||||
{
|
||||
<swp-schedule-cell class="day"><swp-time-badge class="@GetBadgeClass(day.Status)">@day.Display</swp-time-badge></swp-schedule-cell>
|
||||
}
|
||||
}
|
||||
</swp-schedule-table>
|
||||
</swp-schedule-scroll>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
|
|
@ -6,38 +7,132 @@ namespace PlanTempus.Application.Features.Employees.Components;
|
|||
public class EmployeeDetailHoursViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public EmployeeDetailHoursViewComponent(ILocalizationService localization)
|
||||
public EmployeeDetailHoursViewComponent(ILocalizationService localization, IWebHostEnvironment environment)
|
||||
{
|
||||
_localization = localization;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var weekSchedule = LoadMockData();
|
||||
var employee = weekSchedule.Employees.FirstOrDefault(e => e.EmployeeId == key);
|
||||
var weeks = GenerateWeeks(weekSchedule, employee);
|
||||
|
||||
var model = new EmployeeDetailHoursViewModel
|
||||
{
|
||||
LabelWeeklySchedule = _localization.Get("employees.detail.hours.weekly"),
|
||||
LabelMonday = _localization.Get("employees.detail.hours.monday"),
|
||||
LabelTuesday = _localization.Get("employees.detail.hours.tuesday"),
|
||||
LabelWednesday = _localization.Get("employees.detail.hours.wednesday"),
|
||||
LabelThursday = _localization.Get("employees.detail.hours.thursday"),
|
||||
LabelFriday = _localization.Get("employees.detail.hours.friday"),
|
||||
LabelSaturday = _localization.Get("employees.detail.hours.saturday"),
|
||||
LabelSunday = _localization.Get("employees.detail.hours.sunday")
|
||||
EmployeeId = key,
|
||||
Weeks = weeks,
|
||||
DayNames = new[] { "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag" },
|
||||
LabelHours = _localization.Get("employees.detail.hours.label")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private WeekScheduleData LoadMockData()
|
||||
{
|
||||
var jsonPath = Path.Combine(_environment.ContentRootPath, "Features", "Employees", "Data", "workScheduleMock.json");
|
||||
var json = System.IO.File.ReadAllText(jsonPath);
|
||||
return JsonSerializer.Deserialize<WeekScheduleData>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
})!;
|
||||
}
|
||||
|
||||
private List<WeekHoursData> GenerateWeeks(WeekScheduleData weekSchedule, EmployeeScheduleData? employee)
|
||||
{
|
||||
var weeks = new List<WeekHoursData>();
|
||||
var startDate = DateTime.Parse(weekSchedule.StartDate);
|
||||
|
||||
// Generate 6 weeks of data (current week + 5 more)
|
||||
for (int w = 0; w < 6; w++)
|
||||
{
|
||||
var weekStart = startDate.AddDays(w * 7);
|
||||
var weekNumber = GetWeekNumber(weekStart);
|
||||
var days = new List<DayHoursData>();
|
||||
var totalMinutes = 0;
|
||||
|
||||
for (int d = 0; d < 7; d++)
|
||||
{
|
||||
var date = weekStart.AddDays(d);
|
||||
var dateKey = date.ToString("yyyy-MM-dd");
|
||||
var shift = employee?.Schedule.GetValueOrDefault(dateKey);
|
||||
|
||||
string status = "off";
|
||||
string display = "—";
|
||||
|
||||
if (shift != null)
|
||||
{
|
||||
status = shift.Status;
|
||||
if (shift.Status == "work" && shift.Start != null && shift.End != null)
|
||||
{
|
||||
display = $"{shift.Start} - {shift.End}";
|
||||
totalMinutes += CalculateMinutes(shift.Start, shift.End);
|
||||
}
|
||||
else if (shift.Status == "vacation")
|
||||
{
|
||||
display = "Ferie";
|
||||
}
|
||||
else if (shift.Status == "sick")
|
||||
{
|
||||
display = "Syg";
|
||||
}
|
||||
}
|
||||
|
||||
days.Add(new DayHoursData
|
||||
{
|
||||
Date = dateKey,
|
||||
Status = status,
|
||||
Display = display
|
||||
});
|
||||
}
|
||||
|
||||
weeks.Add(new WeekHoursData
|
||||
{
|
||||
WeekNumber = weekNumber,
|
||||
TotalHours = totalMinutes / 60,
|
||||
Days = days
|
||||
});
|
||||
}
|
||||
|
||||
return weeks;
|
||||
}
|
||||
|
||||
private int GetWeekNumber(DateTime date)
|
||||
{
|
||||
var cal = System.Globalization.CultureInfo.CurrentCulture.Calendar;
|
||||
return cal.GetWeekOfYear(date, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
||||
}
|
||||
|
||||
private int CalculateMinutes(string start, string end)
|
||||
{
|
||||
var startTime = TimeSpan.Parse(start);
|
||||
var endTime = TimeSpan.Parse(end);
|
||||
return (int)(endTime - startTime).TotalMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailHoursViewModel
|
||||
{
|
||||
public required string LabelWeeklySchedule { get; init; }
|
||||
public required string LabelMonday { get; init; }
|
||||
public required string LabelTuesday { get; init; }
|
||||
public required string LabelWednesday { get; init; }
|
||||
public required string LabelThursday { get; init; }
|
||||
public required string LabelFriday { get; init; }
|
||||
public required string LabelSaturday { get; init; }
|
||||
public required string LabelSunday { get; init; }
|
||||
public required string EmployeeId { get; init; }
|
||||
public required List<WeekHoursData> Weeks { get; init; }
|
||||
public required string[] DayNames { get; init; }
|
||||
public required string LabelHours { get; init; }
|
||||
}
|
||||
|
||||
public class WeekHoursData
|
||||
{
|
||||
public int WeekNumber { get; init; }
|
||||
public int TotalHours { get; init; }
|
||||
public required List<DayHoursData> Days { get; init; }
|
||||
}
|
||||
|
||||
public class DayHoursData
|
||||
{
|
||||
public required string Date { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public required string Display { get; init; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
data-employee-id="@employee.EmployeeId"
|
||||
data-date="@day.Date"
|
||||
data-day="@day.ShortDayName">
|
||||
<swp-time-display class="@GetTimeClass(shift)">@GetTimeDisplay(shift)</swp-time-display>
|
||||
<swp-time-badge class="@GetTimeClass(shift)">@GetTimeDisplay(shift)</swp-time-badge>
|
||||
</swp-schedule-cell>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
"year": 2025,
|
||||
"startDate": "2025-12-23",
|
||||
"endDate": "2025-12-29",
|
||||
"closedDays": ["2025-12-25"],
|
||||
"closedDays": ["2025-12-25", "2026-01-01"],
|
||||
"employees": [
|
||||
{
|
||||
"employeeId": "emp-1",
|
||||
"employeeId": "employee-1",
|
||||
"name": "Anna Sørensen",
|
||||
"weeklyHours": 32,
|
||||
"schedule": {
|
||||
|
|
@ -16,11 +16,39 @@
|
|||
"2025-12-26": { "status": "off" },
|
||||
"2025-12-27": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2025-12-28": { "status": "work", "start": "10:00", "end": "14:00" },
|
||||
"2025-12-29": { "status": "off" }
|
||||
"2025-12-29": { "status": "off" },
|
||||
"2025-12-30": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2025-12-31": { "status": "work", "start": "09:00", "end": "14:00" },
|
||||
"2026-01-01": { "status": "off" },
|
||||
"2026-01-02": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-03": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-04": { "status": "work", "start": "10:00", "end": "14:00" },
|
||||
"2026-01-05": { "status": "off" },
|
||||
"2026-01-06": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-07": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-08": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-09": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-10": { "status": "off" },
|
||||
"2026-01-11": { "status": "off" },
|
||||
"2026-01-12": { "status": "off" },
|
||||
"2026-01-13": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-14": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-15": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-16": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-17": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-18": { "status": "off" },
|
||||
"2026-01-19": { "status": "off" },
|
||||
"2026-01-20": { "status": "vacation" },
|
||||
"2026-01-21": { "status": "vacation" },
|
||||
"2026-01-22": { "status": "vacation" },
|
||||
"2026-01-23": { "status": "vacation" },
|
||||
"2026-01-24": { "status": "vacation" },
|
||||
"2026-01-25": { "status": "off" },
|
||||
"2026-01-26": { "status": "off" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"employeeId": "emp-2",
|
||||
"employeeId": "employee-2",
|
||||
"name": "Mette Jensen",
|
||||
"weeklyHours": 40,
|
||||
"schedule": {
|
||||
|
|
@ -30,11 +58,39 @@
|
|||
"2025-12-26": { "status": "vacation" },
|
||||
"2025-12-27": { "status": "vacation" },
|
||||
"2025-12-28": { "status": "off" },
|
||||
"2025-12-29": { "status": "off" }
|
||||
"2025-12-29": { "status": "off" },
|
||||
"2025-12-30": { "status": "vacation" },
|
||||
"2025-12-31": { "status": "vacation" },
|
||||
"2026-01-01": { "status": "off" },
|
||||
"2026-01-02": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-03": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-04": { "status": "off" },
|
||||
"2026-01-05": { "status": "off" },
|
||||
"2026-01-06": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-07": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-08": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-09": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-10": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-11": { "status": "off" },
|
||||
"2026-01-12": { "status": "off" },
|
||||
"2026-01-13": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-14": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-15": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-16": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-17": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-18": { "status": "off" },
|
||||
"2026-01-19": { "status": "off" },
|
||||
"2026-01-20": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-21": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-22": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-23": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-24": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-25": { "status": "off" },
|
||||
"2026-01-26": { "status": "off" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"employeeId": "emp-3",
|
||||
"employeeId": "employee-3",
|
||||
"name": "Louise Nielsen",
|
||||
"weeklyHours": 37,
|
||||
"schedule": {
|
||||
|
|
@ -44,11 +100,39 @@
|
|||
"2025-12-26": { "status": "off" },
|
||||
"2025-12-27": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2025-12-28": { "status": "work", "start": "09:00", "end": "14:00" },
|
||||
"2025-12-29": { "status": "off" }
|
||||
"2025-12-29": { "status": "off" },
|
||||
"2025-12-30": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2025-12-31": { "status": "work", "start": "09:00", "end": "13:00" },
|
||||
"2026-01-01": { "status": "off" },
|
||||
"2026-01-02": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-03": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-04": { "status": "off" },
|
||||
"2026-01-05": { "status": "off" },
|
||||
"2026-01-06": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-07": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-08": { "status": "sick" },
|
||||
"2026-01-09": { "status": "sick" },
|
||||
"2026-01-10": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-11": { "status": "off" },
|
||||
"2026-01-12": { "status": "off" },
|
||||
"2026-01-13": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-14": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-15": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-16": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-17": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-18": { "status": "off" },
|
||||
"2026-01-19": { "status": "off" },
|
||||
"2026-01-20": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-21": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-22": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-23": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-24": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||
"2026-01-25": { "status": "off" },
|
||||
"2026-01-26": { "status": "off" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"employeeId": "emp-4",
|
||||
"employeeId": "employee-4",
|
||||
"name": "Katrine Pedersen",
|
||||
"weeklyHours": 24,
|
||||
"schedule": {
|
||||
|
|
@ -58,11 +142,39 @@
|
|||
"2025-12-26": { "status": "off" },
|
||||
"2025-12-27": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2025-12-28": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2025-12-29": { "status": "off" }
|
||||
"2025-12-29": { "status": "off" },
|
||||
"2025-12-30": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2025-12-31": { "status": "off" },
|
||||
"2026-01-01": { "status": "off" },
|
||||
"2026-01-02": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-03": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-04": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-05": { "status": "off" },
|
||||
"2026-01-06": { "status": "off" },
|
||||
"2026-01-07": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-08": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-09": { "status": "off" },
|
||||
"2026-01-10": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-11": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-12": { "status": "off" },
|
||||
"2026-01-13": { "status": "off" },
|
||||
"2026-01-14": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-15": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-16": { "status": "off" },
|
||||
"2026-01-17": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-18": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-19": { "status": "off" },
|
||||
"2026-01-20": { "status": "off" },
|
||||
"2026-01-21": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-22": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-23": { "status": "off" },
|
||||
"2026-01-24": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||
"2026-01-25": { "status": "work", "start": "10:00", "end": "18:00" },
|
||||
"2026-01-26": { "status": "off" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"employeeId": "emp-5",
|
||||
"employeeId": "employee-5",
|
||||
"name": "Sofie Andersen",
|
||||
"weeklyHours": 20,
|
||||
"schedule": {
|
||||
|
|
@ -72,7 +184,35 @@
|
|||
"2025-12-26": { "status": "off" },
|
||||
"2025-12-27": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2025-12-28": { "status": "off" },
|
||||
"2025-12-29": { "status": "off" }
|
||||
"2025-12-29": { "status": "off" },
|
||||
"2025-12-30": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2025-12-31": { "status": "off" },
|
||||
"2026-01-01": { "status": "off" },
|
||||
"2026-01-02": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-03": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-04": { "status": "off" },
|
||||
"2026-01-05": { "status": "off" },
|
||||
"2026-01-06": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-07": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-08": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-09": { "status": "off" },
|
||||
"2026-01-10": { "status": "off" },
|
||||
"2026-01-11": { "status": "off" },
|
||||
"2026-01-12": { "status": "off" },
|
||||
"2026-01-13": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-14": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-15": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-16": { "status": "off" },
|
||||
"2026-01-17": { "status": "off" },
|
||||
"2026-01-18": { "status": "off" },
|
||||
"2026-01-19": { "status": "off" },
|
||||
"2026-01-20": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-21": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-22": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||
"2026-01-23": { "status": "off" },
|
||||
"2026-01-24": { "status": "off" },
|
||||
"2026-01-25": { "status": "off" },
|
||||
"2026-01-26": { "status": "off" }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@
|
|||
"rating": "rating",
|
||||
"employedsince": "ansat siden",
|
||||
"hours": {
|
||||
"label": "timer",
|
||||
"weekly": "Ugentlig arbejdstid",
|
||||
"monday": "Mandag",
|
||||
"tuesday": "Tirsdag",
|
||||
|
|
@ -409,8 +410,8 @@
|
|||
"desc": "Kunder kan vælge denne medarbejder"
|
||||
},
|
||||
"smsreminders": {
|
||||
"label": "Modtag SMS-påmindelser",
|
||||
"desc": "Få besked om nye bookinger"
|
||||
"label": "Få notifikation via App'en om nye bookinger",
|
||||
"desc": ""
|
||||
},
|
||||
"editcalendar": {
|
||||
"label": "Kan redigere egen kalender",
|
||||
|
|
@ -419,12 +420,12 @@
|
|||
},
|
||||
"notifications": {
|
||||
"label": "Notifikationer",
|
||||
"intro": "Vælg hvilke email-notifikationer medarbejderen skal modtage.",
|
||||
"onlinebooking": "Modtag email ved online booking",
|
||||
"manualbooking": "Modtag email ved manuel booking",
|
||||
"cancellation": "Modtag email ved aflysning",
|
||||
"waitlist": "Modtag email ved opskrivning til venteliste",
|
||||
"dailysummary": "Modtag daglig oversigt over morgendagens bookinger"
|
||||
"intro": "Vælg hvilke notifikationer der skal sendes.",
|
||||
"onlinebooking": "Email ved online booking",
|
||||
"manualbooking": "Email ved manuel booking",
|
||||
"cancellation": "Email ved aflysning",
|
||||
"waitlist": "Email ved opskrivning til venteliste",
|
||||
"dailysummary": "Email med daglig oversigt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@
|
|||
"rating": "rating",
|
||||
"employedsince": "employed since",
|
||||
"hours": {
|
||||
"label": "hours",
|
||||
"weekly": "Weekly working hours",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
|
|
@ -409,8 +410,8 @@
|
|||
"desc": "Customers can select this employee"
|
||||
},
|
||||
"smsreminders": {
|
||||
"label": "Receive SMS reminders",
|
||||
"desc": "Get notified about new bookings"
|
||||
"label": "Get notified via the App about new bookings",
|
||||
"desc": ""
|
||||
},
|
||||
"editcalendar": {
|
||||
"label": "Can edit own calendar",
|
||||
|
|
@ -419,12 +420,12 @@
|
|||
},
|
||||
"notifications": {
|
||||
"label": "Notifications",
|
||||
"intro": "Choose which email notifications the employee should receive.",
|
||||
"onlinebooking": "Receive email for online booking",
|
||||
"manualbooking": "Receive email for manual booking",
|
||||
"cancellation": "Receive email for cancellation",
|
||||
"waitlist": "Receive email for waitlist signup",
|
||||
"dailysummary": "Receive daily summary of tomorrow's bookings"
|
||||
"intro": "Choose which notifications to send.",
|
||||
"onlinebooking": "Email on online booking",
|
||||
"manualbooking": "Email on manual booking",
|
||||
"cancellation": "Email on cancellation",
|
||||
"waitlist": "Email on waitlist signup",
|
||||
"dailysummary": "Email with daily summary"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
</swp-theme-label>
|
||||
<swp-toggle-switch id="themeToggle">
|
||||
<input type="checkbox" id="themeCheckbox">
|
||||
<swp-toggle-slider></swp-toggle-slider>
|
||||
<swp-toggle-track></swp-toggle-track>
|
||||
</swp-toggle-switch>
|
||||
</swp-theme-toggle>
|
||||
</swp-drawer-content>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
</swp-theme-label>
|
||||
<swp-toggle-switch id="themeToggle">
|
||||
<input type="checkbox" id="themeCheckbox">
|
||||
<swp-toggle-slider></swp-toggle-slider>
|
||||
<swp-toggle-track></swp-toggle-track>
|
||||
</swp-toggle-switch>
|
||||
</swp-theme-toggle>
|
||||
</swp-drawer-content>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
/c/Users/Janus Knudsen/source/swp-repos/PlanTempus/PlanTempus.Application
|
||||
|
|
@ -35,7 +35,7 @@ swp-toggle-slider {
|
|||
display: inline-flex;
|
||||
width: fit-content;
|
||||
background: var(--color-background);
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
|
@ -48,36 +48,71 @@ swp-toggle-slider::before {
|
|||
left: 2px;
|
||||
width: calc(50% - 4px);
|
||||
height: calc(100% - 4px);
|
||||
background: var(--bg-green-strong);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--color-green) 18%, white);
|
||||
border-radius: 4px;
|
||||
transition: transform 200ms ease, background 200ms ease;
|
||||
}
|
||||
|
||||
swp-toggle-slider[data-value="no"]::before {
|
||||
transform: translateX(100%);
|
||||
background: var(--bg-red-strong);
|
||||
transform: translateX(calc(100% + 4px));
|
||||
background: color-mix(in srgb, var(--color-red) 18%, white);
|
||||
}
|
||||
|
||||
swp-toggle-option {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: var(--spacing-2) var(--spacing-5);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
padding: 5px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast);
|
||||
transition: color 150ms ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
swp-toggle-slider[data-value="yes"] swp-toggle-option:first-child {
|
||||
color: var(--color-green);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
swp-toggle-slider[data-value="no"] swp-toggle-option:last-child {
|
||||
color: var(--color-red);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
TOGGLE OPTIONS (Tab-style selector)
|
||||
=========================================== */
|
||||
swp-toggle-options {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
|
||||
swp-toggle-option {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
text-align: center;
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
background: var(--color-surface);
|
||||
border-right: 1px solid var(--color-border);
|
||||
transition: all var(--transition-fast);
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-alt);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--color-teal);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ swp-toggle-switch input {
|
|||
height: 0;
|
||||
}
|
||||
|
||||
swp-toggle-slider {
|
||||
swp-toggle-track {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
inset: 0;
|
||||
|
|
@ -236,7 +236,7 @@ swp-toggle-slider {
|
|||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
swp-toggle-slider::before {
|
||||
swp-toggle-track::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
|
|
@ -248,11 +248,11 @@ swp-toggle-slider::before {
|
|||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
swp-toggle-switch input:checked + swp-toggle-slider {
|
||||
swp-toggle-switch input:checked + swp-toggle-track {
|
||||
background: var(--color-teal);
|
||||
}
|
||||
|
||||
swp-toggle-switch input:checked + swp-toggle-slider::before {
|
||||
swp-toggle-switch input:checked + swp-toggle-track::before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -889,11 +889,10 @@ swp-schedule-scroll {
|
|||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Når drawer er åben: page-container styles (animation via JS) */
|
||||
/* Når drawer er åben: page-container styles (padding animeres via JS) */
|
||||
body.schedule-drawer-open swp-tab-content[data-tab="schedule"] swp-page-container {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding-right: var(--drawer-width, 420px);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
|
|
@ -975,7 +974,7 @@ swp-schedule-cell.day.closed-day {
|
|||
border-left: 2px solid #f59e0b;
|
||||
border-right: 2px solid #f59e0b;
|
||||
|
||||
swp-time-display {
|
||||
swp-time-badge {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
|
@ -1011,13 +1010,13 @@ swp-day-date {
|
|||
}
|
||||
|
||||
/* Time display variants */
|
||||
swp-time-display {
|
||||
swp-time-badge {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-weight: 500;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-teal-light);
|
||||
background: color-mix(in srgb, var(--color-teal) 10%, white);
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
min-width: 90px;
|
||||
|
|
@ -1025,22 +1024,22 @@ swp-time-display {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
swp-time-display.off {
|
||||
swp-time-badge.off {
|
||||
background: transparent;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
swp-time-display.off.off-override {
|
||||
swp-time-badge.off.off-override {
|
||||
background: color-mix(in srgb, #7c3aed 12%, white);
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
swp-time-display.vacation {
|
||||
swp-time-badge.vacation {
|
||||
background: color-mix(in srgb, #f59e0b 15%, white);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
swp-time-display.sick {
|
||||
swp-time-badge.sick {
|
||||
background: color-mix(in srgb, #ef4444 15%, white);
|
||||
color: #dc2626;
|
||||
}
|
||||
|
|
@ -1222,38 +1221,6 @@ swp-time-range-duration {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Toggle options (Enkelt/Gentagelse) */
|
||||
swp-toggle-options {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-toggle-option {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
text-align: center;
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
background: var(--color-surface);
|
||||
border-right: 1px solid var(--color-border);
|
||||
transition: all var(--transition-fast);
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-alt);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--color-teal);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
/* Schedule drawer employee display */
|
||||
swp-employee-display {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { SearchController } from './modules/search';
|
|||
import { LockScreenController } from './modules/lockscreen';
|
||||
import { CashController } from './modules/cash';
|
||||
import { EmployeesController } from './modules/employees';
|
||||
import { ControlsController } from './modules/controls';
|
||||
|
||||
/**
|
||||
* Main application class
|
||||
|
|
@ -23,6 +24,7 @@ export class App {
|
|||
readonly lockScreen: LockScreenController;
|
||||
readonly cash: CashController;
|
||||
readonly employees: EmployeesController;
|
||||
readonly controls: ControlsController;
|
||||
|
||||
constructor() {
|
||||
// Initialize controllers
|
||||
|
|
@ -33,6 +35,7 @@ export class App {
|
|||
this.lockScreen = new LockScreenController(this.drawers);
|
||||
this.cash = new CashController();
|
||||
this.employees = new EmployeesController();
|
||||
this.controls = new ControlsController();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
36
PlanTempus.Application/wwwroot/ts/modules/controls.ts
Normal file
36
PlanTempus.Application/wwwroot/ts/modules/controls.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Controls Module
|
||||
*
|
||||
* Handles generic UI controls functionality:
|
||||
* - Toggle sliders (Ja/Nej switches)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller for generic UI controls
|
||||
*/
|
||||
export class ControlsController {
|
||||
constructor() {
|
||||
this.initToggleSliders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all toggle sliders on the page
|
||||
* Toggle slider: Ja/Nej button switch with data-value attribute
|
||||
* Clicking anywhere on the slider toggles the value
|
||||
*/
|
||||
private initToggleSliders(): void {
|
||||
document.querySelectorAll('swp-toggle-slider').forEach(slider => {
|
||||
slider.addEventListener('click', () => {
|
||||
const el = slider as HTMLElement;
|
||||
const newValue = el.dataset.value === 'yes' ? 'no' : 'yes';
|
||||
el.dataset.value = newValue;
|
||||
|
||||
// Dispatch custom event for listeners
|
||||
slider.dispatchEvent(new CustomEvent('toggle', {
|
||||
bubbles: true,
|
||||
detail: { value: newValue }
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1028,55 +1028,66 @@ class ScheduleController {
|
|||
* Open the schedule drawer (no overlay - user can still interact with table)
|
||||
*/
|
||||
private openDrawer(): void {
|
||||
// Lås tabelbredde før drawer åbner for at undgå "hop"
|
||||
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
|
||||
const table = document.getElementById('scheduleTable');
|
||||
|
||||
// Gem nuværende padding FØR klasser tilføjes
|
||||
const startPadding = container ? getComputedStyle(container).paddingRight : '0px';
|
||||
|
||||
// Lås tabelbredde før drawer åbner for at undgå "hop"
|
||||
if (table) {
|
||||
const rect = table.getBoundingClientRect();
|
||||
table.style.width = `${rect.width}px`;
|
||||
}
|
||||
|
||||
// Animate container med Web Animations API
|
||||
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
|
||||
if (container) {
|
||||
const currentStyles = getComputedStyle(container);
|
||||
const currentMaxWidth = currentStyles.maxWidth;
|
||||
const currentMargin = currentStyles.margin;
|
||||
const currentPaddingRight = currentStyles.paddingRight;
|
||||
// Tilføj klasser med det samme (maxWidth og margin ændres instant)
|
||||
this.drawer?.classList.add('open');
|
||||
document.body.classList.add('schedule-drawer-open');
|
||||
|
||||
// Animate kun padding fra gemt værdi
|
||||
if (container) {
|
||||
container.animate([
|
||||
{ maxWidth: currentMaxWidth, margin: currentMargin, paddingRight: currentPaddingRight },
|
||||
{ maxWidth: 'none', margin: '0px', paddingRight: '420px' }
|
||||
{ paddingRight: startPadding },
|
||||
{ paddingRight: '420px' }
|
||||
], {
|
||||
duration: 300,
|
||||
duration: 200,
|
||||
easing: 'ease',
|
||||
fill: 'forwards'
|
||||
});
|
||||
}
|
||||
|
||||
this.drawer?.classList.add('open');
|
||||
document.body.classList.add('schedule-drawer-open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the schedule drawer
|
||||
*/
|
||||
private closeDrawer(): void {
|
||||
// Luk drawer med det samme (visuelt)
|
||||
this.drawer?.classList.remove('open');
|
||||
|
||||
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
|
||||
const table = document.getElementById('scheduleTable');
|
||||
|
||||
if (container) {
|
||||
// Hent nuværende computed styles for animation
|
||||
const animation = container.getAnimations()[0];
|
||||
if (animation) {
|
||||
animation.cancel();
|
||||
// Afspil animationen baglæns
|
||||
animation.reverse();
|
||||
animation.onfinish = () => {
|
||||
animation.cancel();
|
||||
// Fjern klasser og låst bredde når animation er færdig
|
||||
document.body.classList.remove('schedule-drawer-open');
|
||||
if (table) {
|
||||
table.style.width = '';
|
||||
}
|
||||
};
|
||||
return; // Exit early - cleanup happens in onfinish
|
||||
}
|
||||
}
|
||||
|
||||
// Fjern låst bredde så tabellen kan tilpasse sig igen
|
||||
const table = document.getElementById('scheduleTable');
|
||||
// Ingen animation, fjern klasser og låst bredde med det samme
|
||||
document.body.classList.remove('schedule-drawer-open');
|
||||
if (table) {
|
||||
table.style.width = '';
|
||||
}
|
||||
|
||||
this.drawer?.classList.remove('open');
|
||||
document.body.classList.remove('schedule-drawer-open');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ export class ThemeController {
|
|||
|
||||
private root: HTMLElement;
|
||||
private themeOptions: NodeListOf<HTMLElement>;
|
||||
private themeCheckbox: HTMLInputElement | null;
|
||||
|
||||
constructor() {
|
||||
this.root = document.documentElement;
|
||||
this.themeOptions = document.querySelectorAll<HTMLElement>('swp-theme-option');
|
||||
this.themeCheckbox = document.getElementById('themeCheckbox') as HTMLInputElement | null;
|
||||
|
||||
this.applyTheme(this.current);
|
||||
this.updateUI();
|
||||
|
|
@ -77,15 +79,19 @@ export class ThemeController {
|
|||
}
|
||||
|
||||
private updateUI(): void {
|
||||
if (!this.themeOptions) return;
|
||||
|
||||
const darkActive = this.isDark;
|
||||
|
||||
this.themeOptions.forEach(option => {
|
||||
// Update theme options
|
||||
this.themeOptions?.forEach(option => {
|
||||
const theme = option.dataset.theme as Theme;
|
||||
const isActive = (theme === 'dark' && darkActive) || (theme === 'light' && !darkActive);
|
||||
option.classList.toggle('active', isActive);
|
||||
});
|
||||
|
||||
// Update checkbox (checked = dark mode)
|
||||
if (this.themeCheckbox) {
|
||||
this.themeCheckbox.checked = darkActive;
|
||||
}
|
||||
}
|
||||
|
||||
private setupListeners(): void {
|
||||
|
|
@ -94,6 +100,11 @@ export class ThemeController {
|
|||
option.addEventListener('click', (e) => this.handleOptionClick(e));
|
||||
});
|
||||
|
||||
// Theme checkbox toggle
|
||||
this.themeCheckbox?.addEventListener('change', () => {
|
||||
this.set(this.themeCheckbox!.checked ? 'dark' : 'light');
|
||||
});
|
||||
|
||||
// System theme changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)')
|
||||
.addEventListener('change', () => this.handleSystemChange());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue