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
|
||||||
|
|
||||||
|
PlanTempus.Application/wwwroot/js/app.js.map
|
||||||
|
|
|
||||||
|
|
@ -88,16 +88,6 @@
|
||||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||||
</swp-toggle-slider>
|
</swp-toggle-slider>
|
||||||
</swp-toggle-row>
|
</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>
|
<swp-toggle-row>
|
||||||
<div>
|
<div>
|
||||||
<swp-toggle-label>@Model.SettingEditCalendar</swp-toggle-label>
|
<swp-toggle-label>@Model.SettingEditCalendar</swp-toggle-label>
|
||||||
|
|
@ -114,38 +104,48 @@
|
||||||
<swp-card>
|
<swp-card>
|
||||||
<swp-section-label>@Model.LabelNotifications</swp-section-label>
|
<swp-section-label>@Model.LabelNotifications</swp-section-label>
|
||||||
<swp-notification-intro>@Model.NotificationsIntro</swp-notification-intro>
|
<swp-notification-intro>@Model.NotificationsIntro</swp-notification-intro>
|
||||||
<swp-checkbox-list>
|
<swp-toggle-row>
|
||||||
<swp-checkbox-row class="checked">
|
<swp-toggle-label>@Model.SettingSmsReminders</swp-toggle-label>
|
||||||
<swp-checkbox-box>
|
<swp-toggle-slider data-value="yes">
|
||||||
<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-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||||
</swp-checkbox-box>
|
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||||
<swp-checkbox-text>@Model.NotifOnlineBooking</swp-checkbox-text>
|
</swp-toggle-slider>
|
||||||
</swp-checkbox-row>
|
</swp-toggle-row>
|
||||||
<swp-checkbox-row class="checked">
|
<swp-toggle-row>
|
||||||
<swp-checkbox-box>
|
<swp-toggle-label>@Model.NotifOnlineBooking</swp-toggle-label>
|
||||||
<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-toggle-slider data-value="yes">
|
||||||
</swp-checkbox-box>
|
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||||
<swp-checkbox-text>@Model.NotifManualBooking</swp-checkbox-text>
|
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||||
</swp-checkbox-row>
|
</swp-toggle-slider>
|
||||||
<swp-checkbox-row>
|
</swp-toggle-row>
|
||||||
<swp-checkbox-box>
|
<swp-toggle-row>
|
||||||
<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-toggle-label>@Model.NotifManualBooking</swp-toggle-label>
|
||||||
</swp-checkbox-box>
|
<swp-toggle-slider data-value="yes">
|
||||||
<swp-checkbox-text>@Model.NotifCancellation</swp-checkbox-text>
|
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||||
</swp-checkbox-row>
|
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||||
<swp-checkbox-row>
|
</swp-toggle-slider>
|
||||||
<swp-checkbox-box>
|
</swp-toggle-row>
|
||||||
<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-toggle-row>
|
||||||
</swp-checkbox-box>
|
<swp-toggle-label>@Model.NotifCancellation</swp-toggle-label>
|
||||||
<swp-checkbox-text>@Model.NotifWaitlist</swp-checkbox-text>
|
<swp-toggle-slider data-value="no">
|
||||||
</swp-checkbox-row>
|
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||||
<swp-checkbox-row class="checked">
|
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||||
<swp-checkbox-box>
|
</swp-toggle-slider>
|
||||||
<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-toggle-row>
|
||||||
</swp-checkbox-box>
|
<swp-toggle-row>
|
||||||
<swp-checkbox-text>@Model.NotifDailySummary</swp-checkbox-text>
|
<swp-toggle-label>@Model.NotifWaitlist</swp-toggle-label>
|
||||||
</swp-checkbox-row>
|
<swp-toggle-slider data-value="no">
|
||||||
</swp-checkbox-list>
|
<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>
|
</swp-card>
|
||||||
</div>
|
</div>
|
||||||
</swp-detail-grid>
|
</swp-detail-grid>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,36 @@
|
||||||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHoursViewModel
|
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHoursViewModel
|
||||||
|
|
||||||
<swp-detail-grid>
|
@{
|
||||||
<swp-card>
|
string GetBadgeClass(string status) => status switch
|
||||||
<swp-section-label>@Model.LabelWeeklySchedule</swp-section-label>
|
{
|
||||||
<swp-schedule-grid>
|
"work" => "",
|
||||||
<swp-schedule-row>
|
"off" => "off",
|
||||||
<swp-schedule-day>@Model.LabelMonday</swp-schedule-day>
|
"vacation" => "vacation",
|
||||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
"sick" => "sick",
|
||||||
</swp-schedule-row>
|
_ => "off"
|
||||||
<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-scroll>
|
||||||
<swp-schedule-row>
|
<swp-schedule-table class="hours-view">
|
||||||
<swp-schedule-day>@Model.LabelWednesday</swp-schedule-day>
|
<!-- Header row -->
|
||||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
<swp-schedule-cell class="header week-number"></swp-schedule-cell>
|
||||||
</swp-schedule-row>
|
@foreach (var dayName in Model.DayNames)
|
||||||
<swp-schedule-row>
|
{
|
||||||
<swp-schedule-day>@Model.LabelThursday</swp-schedule-day>
|
<swp-schedule-cell class="header"><swp-day-name>@dayName</swp-day-name></swp-schedule-cell>
|
||||||
<swp-schedule-time>09:00 - 19:00</swp-schedule-time>
|
}
|
||||||
</swp-schedule-row>
|
|
||||||
<swp-schedule-row>
|
<!-- Week rows -->
|
||||||
<swp-schedule-day>@Model.LabelFriday</swp-schedule-day>
|
@foreach (var week in Model.Weeks)
|
||||||
<swp-schedule-time>09:00 - 16:00</swp-schedule-time>
|
{
|
||||||
</swp-schedule-row>
|
<swp-schedule-cell class="employee week-label">
|
||||||
<swp-schedule-row class="off">
|
<swp-employee-name>Uge @week.WeekNumber</swp-employee-name>
|
||||||
<swp-schedule-day>@Model.LabelSaturday</swp-schedule-day>
|
<swp-employee-hours>@week.TotalHours @Model.LabelHours</swp-employee-hours>
|
||||||
<swp-schedule-time>Fri</swp-schedule-time>
|
</swp-schedule-cell>
|
||||||
</swp-schedule-row>
|
@foreach (var day in week.Days)
|
||||||
<swp-schedule-row class="off">
|
{
|
||||||
<swp-schedule-day>@Model.LabelSunday</swp-schedule-day>
|
<swp-schedule-cell class="day"><swp-time-badge class="@GetBadgeClass(day.Status)">@day.Display</swp-time-badge></swp-schedule-cell>
|
||||||
<swp-schedule-time>Fri</swp-schedule-time>
|
}
|
||||||
</swp-schedule-row>
|
}
|
||||||
</swp-schedule-grid>
|
</swp-schedule-table>
|
||||||
</swp-card>
|
</swp-schedule-scroll>
|
||||||
</swp-detail-grid>
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using PlanTempus.Application.Features.Localization.Services;
|
using PlanTempus.Application.Features.Localization.Services;
|
||||||
|
|
||||||
|
|
@ -6,38 +7,132 @@ namespace PlanTempus.Application.Features.Employees.Components;
|
||||||
public class EmployeeDetailHoursViewComponent : ViewComponent
|
public class EmployeeDetailHoursViewComponent : ViewComponent
|
||||||
{
|
{
|
||||||
private readonly ILocalizationService _localization;
|
private readonly ILocalizationService _localization;
|
||||||
|
private readonly IWebHostEnvironment _environment;
|
||||||
|
|
||||||
public EmployeeDetailHoursViewComponent(ILocalizationService localization)
|
public EmployeeDetailHoursViewComponent(ILocalizationService localization, IWebHostEnvironment environment)
|
||||||
{
|
{
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
_environment = environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IViewComponentResult Invoke(string key)
|
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
|
var model = new EmployeeDetailHoursViewModel
|
||||||
{
|
{
|
||||||
LabelWeeklySchedule = _localization.Get("employees.detail.hours.weekly"),
|
EmployeeId = key,
|
||||||
LabelMonday = _localization.Get("employees.detail.hours.monday"),
|
Weeks = weeks,
|
||||||
LabelTuesday = _localization.Get("employees.detail.hours.tuesday"),
|
DayNames = new[] { "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag" },
|
||||||
LabelWednesday = _localization.Get("employees.detail.hours.wednesday"),
|
LabelHours = _localization.Get("employees.detail.hours.label")
|
||||||
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")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return View(model);
|
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 class EmployeeDetailHoursViewModel
|
||||||
{
|
{
|
||||||
public required string LabelWeeklySchedule { get; init; }
|
public required string EmployeeId { get; init; }
|
||||||
public required string LabelMonday { get; init; }
|
public required List<WeekHoursData> Weeks { get; init; }
|
||||||
public required string LabelTuesday { get; init; }
|
public required string[] DayNames { get; init; }
|
||||||
public required string LabelWednesday { get; init; }
|
public required string LabelHours { get; init; }
|
||||||
public required string LabelThursday { get; init; }
|
}
|
||||||
public required string LabelFriday { get; init; }
|
|
||||||
public required string LabelSaturday { get; init; }
|
public class WeekHoursData
|
||||||
public required string LabelSunday { get; init; }
|
{
|
||||||
|
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-employee-id="@employee.EmployeeId"
|
||||||
data-date="@day.Date"
|
data-date="@day.Date"
|
||||||
data-day="@day.ShortDayName">
|
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>
|
</swp-schedule-cell>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"startDate": "2025-12-23",
|
"startDate": "2025-12-23",
|
||||||
"endDate": "2025-12-29",
|
"endDate": "2025-12-29",
|
||||||
"closedDays": ["2025-12-25"],
|
"closedDays": ["2025-12-25", "2026-01-01"],
|
||||||
"employees": [
|
"employees": [
|
||||||
{
|
{
|
||||||
"employeeId": "emp-1",
|
"employeeId": "employee-1",
|
||||||
"name": "Anna Sørensen",
|
"name": "Anna Sørensen",
|
||||||
"weeklyHours": 32,
|
"weeklyHours": 32,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
|
|
@ -16,11 +16,39 @@
|
||||||
"2025-12-26": { "status": "off" },
|
"2025-12-26": { "status": "off" },
|
||||||
"2025-12-27": { "status": "work", "start": "09:00", "end": "17:00" },
|
"2025-12-27": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||||
"2025-12-28": { "status": "work", "start": "10:00", "end": "14: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",
|
"name": "Mette Jensen",
|
||||||
"weeklyHours": 40,
|
"weeklyHours": 40,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
|
|
@ -30,11 +58,39 @@
|
||||||
"2025-12-26": { "status": "vacation" },
|
"2025-12-26": { "status": "vacation" },
|
||||||
"2025-12-27": { "status": "vacation" },
|
"2025-12-27": { "status": "vacation" },
|
||||||
"2025-12-28": { "status": "off" },
|
"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",
|
"name": "Louise Nielsen",
|
||||||
"weeklyHours": 37,
|
"weeklyHours": 37,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
|
|
@ -44,11 +100,39 @@
|
||||||
"2025-12-26": { "status": "off" },
|
"2025-12-26": { "status": "off" },
|
||||||
"2025-12-27": { "status": "work", "start": "09:00", "end": "17:00" },
|
"2025-12-27": { "status": "work", "start": "09:00", "end": "17:00" },
|
||||||
"2025-12-28": { "status": "work", "start": "09:00", "end": "14: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",
|
"name": "Katrine Pedersen",
|
||||||
"weeklyHours": 24,
|
"weeklyHours": 24,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
|
|
@ -58,11 +142,39 @@
|
||||||
"2025-12-26": { "status": "off" },
|
"2025-12-26": { "status": "off" },
|
||||||
"2025-12-27": { "status": "work", "start": "12:00", "end": "20:00" },
|
"2025-12-27": { "status": "work", "start": "12:00", "end": "20:00" },
|
||||||
"2025-12-28": { "status": "work", "start": "10:00", "end": "18: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",
|
"name": "Sofie Andersen",
|
||||||
"weeklyHours": 20,
|
"weeklyHours": 20,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
|
|
@ -72,7 +184,35 @@
|
||||||
"2025-12-26": { "status": "off" },
|
"2025-12-26": { "status": "off" },
|
||||||
"2025-12-27": { "status": "work", "start": "09:00", "end": "15:00" },
|
"2025-12-27": { "status": "work", "start": "09:00", "end": "15:00" },
|
||||||
"2025-12-28": { "status": "off" },
|
"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",
|
"rating": "rating",
|
||||||
"employedsince": "ansat siden",
|
"employedsince": "ansat siden",
|
||||||
"hours": {
|
"hours": {
|
||||||
|
"label": "timer",
|
||||||
"weekly": "Ugentlig arbejdstid",
|
"weekly": "Ugentlig arbejdstid",
|
||||||
"monday": "Mandag",
|
"monday": "Mandag",
|
||||||
"tuesday": "Tirsdag",
|
"tuesday": "Tirsdag",
|
||||||
|
|
@ -409,8 +410,8 @@
|
||||||
"desc": "Kunder kan vælge denne medarbejder"
|
"desc": "Kunder kan vælge denne medarbejder"
|
||||||
},
|
},
|
||||||
"smsreminders": {
|
"smsreminders": {
|
||||||
"label": "Modtag SMS-påmindelser",
|
"label": "Få notifikation via App'en om nye bookinger",
|
||||||
"desc": "Få besked om nye bookinger"
|
"desc": ""
|
||||||
},
|
},
|
||||||
"editcalendar": {
|
"editcalendar": {
|
||||||
"label": "Kan redigere egen kalender",
|
"label": "Kan redigere egen kalender",
|
||||||
|
|
@ -419,12 +420,12 @@
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"label": "Notifikationer",
|
"label": "Notifikationer",
|
||||||
"intro": "Vælg hvilke email-notifikationer medarbejderen skal modtage.",
|
"intro": "Vælg hvilke notifikationer der skal sendes.",
|
||||||
"onlinebooking": "Modtag email ved online booking",
|
"onlinebooking": "Email ved online booking",
|
||||||
"manualbooking": "Modtag email ved manuel booking",
|
"manualbooking": "Email ved manuel booking",
|
||||||
"cancellation": "Modtag email ved aflysning",
|
"cancellation": "Email ved aflysning",
|
||||||
"waitlist": "Modtag email ved opskrivning til venteliste",
|
"waitlist": "Email ved opskrivning til venteliste",
|
||||||
"dailysummary": "Modtag daglig oversigt over morgendagens bookinger"
|
"dailysummary": "Email med daglig oversigt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,7 @@
|
||||||
"rating": "rating",
|
"rating": "rating",
|
||||||
"employedsince": "employed since",
|
"employedsince": "employed since",
|
||||||
"hours": {
|
"hours": {
|
||||||
|
"label": "hours",
|
||||||
"weekly": "Weekly working hours",
|
"weekly": "Weekly working hours",
|
||||||
"monday": "Monday",
|
"monday": "Monday",
|
||||||
"tuesday": "Tuesday",
|
"tuesday": "Tuesday",
|
||||||
|
|
@ -409,8 +410,8 @@
|
||||||
"desc": "Customers can select this employee"
|
"desc": "Customers can select this employee"
|
||||||
},
|
},
|
||||||
"smsreminders": {
|
"smsreminders": {
|
||||||
"label": "Receive SMS reminders",
|
"label": "Get notified via the App about new bookings",
|
||||||
"desc": "Get notified about new bookings"
|
"desc": ""
|
||||||
},
|
},
|
||||||
"editcalendar": {
|
"editcalendar": {
|
||||||
"label": "Can edit own calendar",
|
"label": "Can edit own calendar",
|
||||||
|
|
@ -419,12 +420,12 @@
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"label": "Notifications",
|
"label": "Notifications",
|
||||||
"intro": "Choose which email notifications the employee should receive.",
|
"intro": "Choose which notifications to send.",
|
||||||
"onlinebooking": "Receive email for online booking",
|
"onlinebooking": "Email on online booking",
|
||||||
"manualbooking": "Receive email for manual booking",
|
"manualbooking": "Email on manual booking",
|
||||||
"cancellation": "Receive email for cancellation",
|
"cancellation": "Email on cancellation",
|
||||||
"waitlist": "Receive email for waitlist signup",
|
"waitlist": "Email on waitlist signup",
|
||||||
"dailysummary": "Receive daily summary of tomorrow's bookings"
|
"dailysummary": "Email with daily summary"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
</swp-theme-label>
|
</swp-theme-label>
|
||||||
<swp-toggle-switch id="themeToggle">
|
<swp-toggle-switch id="themeToggle">
|
||||||
<input type="checkbox" id="themeCheckbox">
|
<input type="checkbox" id="themeCheckbox">
|
||||||
<swp-toggle-slider></swp-toggle-slider>
|
<swp-toggle-track></swp-toggle-track>
|
||||||
</swp-toggle-switch>
|
</swp-toggle-switch>
|
||||||
</swp-theme-toggle>
|
</swp-theme-toggle>
|
||||||
</swp-drawer-content>
|
</swp-drawer-content>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
</swp-theme-label>
|
</swp-theme-label>
|
||||||
<swp-toggle-switch id="themeToggle">
|
<swp-toggle-switch id="themeToggle">
|
||||||
<input type="checkbox" id="themeCheckbox">
|
<input type="checkbox" id="themeCheckbox">
|
||||||
<swp-toggle-slider></swp-toggle-slider>
|
<swp-toggle-track></swp-toggle-track>
|
||||||
</swp-toggle-switch>
|
</swp-toggle-switch>
|
||||||
</swp-theme-toggle>
|
</swp-theme-toggle>
|
||||||
</swp-drawer-content>
|
</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;
|
display: inline-flex;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
border-radius: var(--radius-md);
|
border-radius: 6px;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -48,36 +48,71 @@ swp-toggle-slider::before {
|
||||||
left: 2px;
|
left: 2px;
|
||||||
width: calc(50% - 4px);
|
width: calc(50% - 4px);
|
||||||
height: calc(100% - 4px);
|
height: calc(100% - 4px);
|
||||||
background: var(--bg-green-strong);
|
background: color-mix(in srgb, var(--color-green) 18%, white);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: 4px;
|
||||||
transition: transform 200ms ease, background 200ms ease;
|
transition: transform 200ms ease, background 200ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-slider[data-value="no"]::before {
|
swp-toggle-slider[data-value="no"]::before {
|
||||||
transform: translateX(100%);
|
transform: translateX(calc(100% + 4px));
|
||||||
background: var(--bg-red-strong);
|
background: color-mix(in srgb, var(--color-red) 18%, white);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-option {
|
swp-toggle-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: var(--spacing-2) var(--spacing-5);
|
padding: 5px 16px;
|
||||||
font-size: var(--font-size-sm);
|
font-size: 12px;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: 500;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color var(--transition-fast);
|
transition: color 150ms ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-slider[data-value="yes"] swp-toggle-option:first-child {
|
swp-toggle-slider[data-value="yes"] swp-toggle-option:first-child {
|
||||||
color: var(--color-green);
|
color: var(--color-green);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-slider[data-value="no"] swp-toggle-option:last-child {
|
swp-toggle-slider[data-value="no"] swp-toggle-option:last-child {
|
||||||
color: var(--color-red);
|
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;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-slider {
|
swp-toggle-track {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|
@ -236,7 +236,7 @@ swp-toggle-slider {
|
||||||
transition: background var(--transition-fast);
|
transition: background var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-slider::before {
|
swp-toggle-track::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
|
|
@ -248,11 +248,11 @@ swp-toggle-slider::before {
|
||||||
transition: transform var(--transition-fast);
|
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);
|
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);
|
transform: translateX(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -889,11 +889,10 @@ swp-schedule-scroll {
|
||||||
overflow-x: auto;
|
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 {
|
body.schedule-drawer-open swp-tab-content[data-tab="schedule"] swp-page-container {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-right: var(--drawer-width, 420px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
|
|
@ -975,7 +974,7 @@ swp-schedule-cell.day.closed-day {
|
||||||
border-left: 2px solid #f59e0b;
|
border-left: 2px solid #f59e0b;
|
||||||
border-right: 2px solid #f59e0b;
|
border-right: 2px solid #f59e0b;
|
||||||
|
|
||||||
swp-time-display {
|
swp-time-badge {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1011,13 +1010,13 @@ swp-day-date {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time display variants */
|
/* Time display variants */
|
||||||
swp-time-display {
|
swp-time-badge {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: 500;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: var(--bg-teal-light);
|
background: color-mix(in srgb, var(--color-teal) 10%, white);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 90px;
|
min-width: 90px;
|
||||||
|
|
@ -1025,22 +1024,22 @@ swp-time-display {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-time-display.off {
|
swp-time-badge.off {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--color-text-muted);
|
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);
|
background: color-mix(in srgb, #7c3aed 12%, white);
|
||||||
color: #6d28d9;
|
color: #6d28d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-time-display.vacation {
|
swp-time-badge.vacation {
|
||||||
background: color-mix(in srgb, #f59e0b 15%, white);
|
background: color-mix(in srgb, #f59e0b 15%, white);
|
||||||
color: #b45309;
|
color: #b45309;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-time-display.sick {
|
swp-time-badge.sick {
|
||||||
background: color-mix(in srgb, #ef4444 15%, white);
|
background: color-mix(in srgb, #ef4444 15%, white);
|
||||||
color: #dc2626;
|
color: #dc2626;
|
||||||
}
|
}
|
||||||
|
|
@ -1222,38 +1221,6 @@ swp-time-range-duration {
|
||||||
white-space: nowrap;
|
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 */
|
/* Schedule drawer employee display */
|
||||||
swp-employee-display {
|
swp-employee-display {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { SearchController } from './modules/search';
|
||||||
import { LockScreenController } from './modules/lockscreen';
|
import { LockScreenController } from './modules/lockscreen';
|
||||||
import { CashController } from './modules/cash';
|
import { CashController } from './modules/cash';
|
||||||
import { EmployeesController } from './modules/employees';
|
import { EmployeesController } from './modules/employees';
|
||||||
|
import { ControlsController } from './modules/controls';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application class
|
* Main application class
|
||||||
|
|
@ -23,6 +24,7 @@ export class App {
|
||||||
readonly lockScreen: LockScreenController;
|
readonly lockScreen: LockScreenController;
|
||||||
readonly cash: CashController;
|
readonly cash: CashController;
|
||||||
readonly employees: EmployeesController;
|
readonly employees: EmployeesController;
|
||||||
|
readonly controls: ControlsController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Initialize controllers
|
// Initialize controllers
|
||||||
|
|
@ -33,6 +35,7 @@ export class App {
|
||||||
this.lockScreen = new LockScreenController(this.drawers);
|
this.lockScreen = new LockScreenController(this.drawers);
|
||||||
this.cash = new CashController();
|
this.cash = new CashController();
|
||||||
this.employees = new EmployeesController();
|
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)
|
* Open the schedule drawer (no overlay - user can still interact with table)
|
||||||
*/
|
*/
|
||||||
private openDrawer(): void {
|
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');
|
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) {
|
if (table) {
|
||||||
const rect = table.getBoundingClientRect();
|
const rect = table.getBoundingClientRect();
|
||||||
table.style.width = `${rect.width}px`;
|
table.style.width = `${rect.width}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate container med Web Animations API
|
// Tilføj klasser med det samme (maxWidth og margin ændres instant)
|
||||||
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
|
this.drawer?.classList.add('open');
|
||||||
if (container) {
|
document.body.classList.add('schedule-drawer-open');
|
||||||
const currentStyles = getComputedStyle(container);
|
|
||||||
const currentMaxWidth = currentStyles.maxWidth;
|
|
||||||
const currentMargin = currentStyles.margin;
|
|
||||||
const currentPaddingRight = currentStyles.paddingRight;
|
|
||||||
|
|
||||||
|
// Animate kun padding fra gemt værdi
|
||||||
|
if (container) {
|
||||||
container.animate([
|
container.animate([
|
||||||
{ maxWidth: currentMaxWidth, margin: currentMargin, paddingRight: currentPaddingRight },
|
{ paddingRight: startPadding },
|
||||||
{ maxWidth: 'none', margin: '0px', paddingRight: '420px' }
|
{ paddingRight: '420px' }
|
||||||
], {
|
], {
|
||||||
duration: 300,
|
duration: 200,
|
||||||
easing: 'ease',
|
easing: 'ease',
|
||||||
fill: 'forwards'
|
fill: 'forwards'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawer?.classList.add('open');
|
|
||||||
document.body.classList.add('schedule-drawer-open');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the schedule drawer
|
* Close the schedule drawer
|
||||||
*/
|
*/
|
||||||
private closeDrawer(): void {
|
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 container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
|
||||||
|
const table = document.getElementById('scheduleTable');
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
// Hent nuværende computed styles for animation
|
|
||||||
const animation = container.getAnimations()[0];
|
const animation = container.getAnimations()[0];
|
||||||
if (animation) {
|
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
|
// Ingen animation, fjern klasser og låst bredde med det samme
|
||||||
const table = document.getElementById('scheduleTable');
|
document.body.classList.remove('schedule-drawer-open');
|
||||||
if (table) {
|
if (table) {
|
||||||
table.style.width = '';
|
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 root: HTMLElement;
|
||||||
private themeOptions: NodeListOf<HTMLElement>;
|
private themeOptions: NodeListOf<HTMLElement>;
|
||||||
|
private themeCheckbox: HTMLInputElement | null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.root = document.documentElement;
|
this.root = document.documentElement;
|
||||||
this.themeOptions = document.querySelectorAll<HTMLElement>('swp-theme-option');
|
this.themeOptions = document.querySelectorAll<HTMLElement>('swp-theme-option');
|
||||||
|
this.themeCheckbox = document.getElementById('themeCheckbox') as HTMLInputElement | null;
|
||||||
|
|
||||||
this.applyTheme(this.current);
|
this.applyTheme(this.current);
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
|
|
@ -77,15 +79,19 @@ export class ThemeController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateUI(): void {
|
private updateUI(): void {
|
||||||
if (!this.themeOptions) return;
|
|
||||||
|
|
||||||
const darkActive = this.isDark;
|
const darkActive = this.isDark;
|
||||||
|
|
||||||
this.themeOptions.forEach(option => {
|
// Update theme options
|
||||||
|
this.themeOptions?.forEach(option => {
|
||||||
const theme = option.dataset.theme as Theme;
|
const theme = option.dataset.theme as Theme;
|
||||||
const isActive = (theme === 'dark' && darkActive) || (theme === 'light' && !darkActive);
|
const isActive = (theme === 'dark' && darkActive) || (theme === 'light' && !darkActive);
|
||||||
option.classList.toggle('active', isActive);
|
option.classList.toggle('active', isActive);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update checkbox (checked = dark mode)
|
||||||
|
if (this.themeCheckbox) {
|
||||||
|
this.themeCheckbox.checked = darkActive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupListeners(): void {
|
private setupListeners(): void {
|
||||||
|
|
@ -94,6 +100,11 @@ export class ThemeController {
|
||||||
option.addEventListener('click', (e) => this.handleOptionClick(e));
|
option.addEventListener('click', (e) => this.handleOptionClick(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Theme checkbox toggle
|
||||||
|
this.themeCheckbox?.addEventListener('change', () => {
|
||||||
|
this.set(this.themeCheckbox!.checked ? 'dark' : 'light');
|
||||||
|
});
|
||||||
|
|
||||||
// System theme changes
|
// System theme changes
|
||||||
window.matchMedia('(prefers-color-scheme: dark)')
|
window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
.addEventListener('change', () => this.handleSystemChange());
|
.addEventListener('change', () => this.handleSystemChange());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue