From 3214cbdc169e9224f19756847bc867c9b38c1c47 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Wed, 14 Jan 2026 22:47:40 +0100 Subject: [PATCH] Adds employee work schedule component Introduces a new work schedule feature for managing employee shifts and schedules Implements interactive schedule view with: - Week-based schedule grid - Shift status tracking (work, vacation, sick, off) - Editable time ranges - Repeat shift functionality Enhances employee management with dynamic scheduling capabilities --- .workbench/drawer-worktime.png | Bin 0 -> 68468 bytes .workbench/{image.png => invoice.png} | Bin .../EmployeeWorkSchedule/Default.cshtml | 179 +++++ .../EmployeeWorkScheduleViewComponent.cs | 158 +++++ .../Employees/Data/workScheduleMock.json | 79 +++ .../Features/Employees/Pages/Index.cshtml | 11 + .../Localization/Translations/da.json | 21 + .../Localization/Translations/en.json | 21 + .../wwwroot/css/employees.css | 525 ++++++++++++++ .../wwwroot/ts/modules/employees.ts | 645 ++++++++++++++++++ .../wwwroot/ts/types/WorkSchedule.ts | 30 + 11 files changed, 1669 insertions(+) create mode 100644 .workbench/drawer-worktime.png rename .workbench/{image.png => invoice.png} (100%) create mode 100644 PlanTempus.Application/Features/Employees/Components/EmployeeWorkSchedule/Default.cshtml create mode 100644 PlanTempus.Application/Features/Employees/Components/EmployeeWorkSchedule/EmployeeWorkScheduleViewComponent.cs create mode 100644 PlanTempus.Application/Features/Employees/Data/workScheduleMock.json create mode 100644 PlanTempus.Application/wwwroot/ts/types/WorkSchedule.ts diff --git a/.workbench/drawer-worktime.png b/.workbench/drawer-worktime.png new file mode 100644 index 0000000000000000000000000000000000000000..bf1df848f3196a540a4775c2fe61262e13c2d3c9 GIT binary patch literal 68468 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

  • aB;?xw9%OmL6!#%OUmN;Of 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%0DDuAJvljbc`dL_6S@{(!y%zkN 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 $"{shift.Start} - {shift.End}", + "vacation" => Model.LabelVacation, + "sick" => Model.LabelSick, + _ => "—" + }; + } + + string GetTimeClass(ShiftData? shift) + { + if (shift == null) return "off"; + return shift.Status switch + { + "work" => "", + "vacation" => "vacation", + "sick" => "sick", + _ => "off" + }; + } + + bool IsClosed(string date) => Model.ClosedDays.Contains(date); +} + + + + + + + @Model.ButtonEdit + + + + + + + @Model.LabelWeek @Model.WeekNumber + @foreach (var day in Model.Days) + { + + @day.DayName + @day.DisplayDate + + } + + + @foreach (var employee in Model.Employees) + { + + @employee.Name + @employee.WeeklyHours @Model.LabelHours + + + @foreach (var day in Model.Days) + { + var shift = employee.Schedule.GetValueOrDefault(day.Date); + var isClosed = IsClosed(day.Date); + + @GetTimeDisplay(shift) + + } + } + + + + +
    + + @Model.LabelEditShift + + + + + + + + + Medarbejder + + ? + Vælg celle... + + + + + Dato + + + + + + + + @Model.LabelStatus + + @Model.LabelWork + @Model.LabelOff + @Model.LabelVacation + @Model.LabelSick + + + + + + @Model.LabelTimeRange + + + + + + + + + 09:00 – 17:00 + 8 timer + + + + + + + @Model.LabelNote (valgfrit) + + + + + + + + @Model.LabelType + + @Model.LabelSingle + @Model.LabelRepeat + + + + + + + @Model.LabelRepeatInterval + + + + + + Gentagelser bruger valgt dato som startuge. + + + @Model.LabelRepeatEnd (valgfrit) + + + + + @Model.LabelWeekday (auto) + + + + + + + @Model.LabelCancel + @Model.LabelSave + +
    diff --git a/PlanTempus.Application/Features/Employees/Components/EmployeeWorkSchedule/EmployeeWorkScheduleViewComponent.cs b/PlanTempus.Application/Features/Employees/Components/EmployeeWorkSchedule/EmployeeWorkScheduleViewComponent.cs new file mode 100644 index 0000000..f6ea737 --- /dev/null +++ b/PlanTempus.Application/Features/Employees/Components/EmployeeWorkSchedule/EmployeeWorkScheduleViewComponent.cs @@ -0,0 +1,158 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using PlanTempus.Application.Features.Localization.Services; + +namespace PlanTempus.Application.Features.Employees.Components; + +public class EmployeeWorkScheduleViewComponent : ViewComponent +{ + private readonly ILocalizationService _localization; + private readonly IWebHostEnvironment _environment; + + public EmployeeWorkScheduleViewComponent(ILocalizationService localization, IWebHostEnvironment environment) + { + _localization = localization; + _environment = environment; + } + + public IViewComponentResult Invoke(string key) + { + var weekSchedule = LoadMockData(); + var days = GenerateDays(weekSchedule.StartDate); + + var model = new EmployeeWorkScheduleViewModel + { + WeekNumber = weekSchedule.WeekNumber, + Year = weekSchedule.Year, + StartDate = weekSchedule.StartDate, + EndDate = weekSchedule.EndDate, + ClosedDays = weekSchedule.ClosedDays, + Employees = weekSchedule.Employees, + Days = days, + + // Labels + LabelWeek = _localization.Get("employees.detail.schedule.week"), + LabelHours = _localization.Get("employees.detail.schedule.hours"), + LabelEditShift = _localization.Get("employees.detail.schedule.editShift"), + LabelStatus = _localization.Get("employees.detail.schedule.status"), + LabelWork = _localization.Get("employees.detail.schedule.work"), + LabelOff = _localization.Get("employees.detail.schedule.off"), + LabelVacation = _localization.Get("employees.detail.schedule.vacation"), + LabelSick = _localization.Get("employees.detail.schedule.sick"), + LabelTimeRange = _localization.Get("employees.detail.schedule.timeRange"), + LabelNote = _localization.Get("employees.detail.schedule.note"), + LabelType = _localization.Get("employees.detail.schedule.type"), + LabelSingle = _localization.Get("employees.detail.schedule.single"), + LabelRepeat = _localization.Get("employees.detail.schedule.repeat"), + LabelRepeatInterval = _localization.Get("employees.detail.schedule.repeatInterval"), + LabelRepeatEnd = _localization.Get("employees.detail.schedule.repeatEnd"), + LabelWeekday = _localization.Get("employees.detail.schedule.weekday"), + LabelEdit = _localization.Get("common.edit"), + LabelSave = _localization.Get("common.save"), + LabelCancel = _localization.Get("common.cancel"), + ButtonEdit = _localization.Get("employees.detail.schedule.buttonEdit"), + ButtonDone = _localization.Get("employees.detail.schedule.buttonDone") + }; + + 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(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + })!; + } + + private List GenerateDays(string startDate) + { + var start = DateTime.Parse(startDate); + var dayNames = new[] { "Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag" }; + var shortDayNames = new[] { "Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør" }; + + var days = new List(); + for (int i = 0; i < 7; i++) + { + var date = start.AddDays(i); + days.Add(new DayInfo + { + Date = date.ToString("yyyy-MM-dd"), + DayName = dayNames[(int)date.DayOfWeek], + ShortDayName = shortDayNames[(int)date.DayOfWeek], + DisplayDate = date.ToString("dd/MM") + }); + } + return days; + } +} + +public class EmployeeWorkScheduleViewModel +{ + public int WeekNumber { get; init; } + public int Year { get; init; } + public required string StartDate { get; init; } + public required string EndDate { get; init; } + public required List ClosedDays { get; init; } + public required List Employees { get; init; } + public required List Days { get; init; } + + // Labels + public required string LabelWeek { get; init; } + public required string LabelHours { get; init; } + public required string LabelEditShift { get; init; } + public required string LabelStatus { get; init; } + public required string LabelWork { get; init; } + public required string LabelOff { get; init; } + public required string LabelVacation { get; init; } + public required string LabelSick { get; init; } + public required string LabelTimeRange { get; init; } + public required string LabelNote { get; init; } + public required string LabelType { get; init; } + public required string LabelSingle { get; init; } + public required string LabelRepeat { get; init; } + public required string LabelRepeatInterval { get; init; } + public required string LabelRepeatEnd { get; init; } + public required string LabelWeekday { get; init; } + public required string LabelEdit { get; init; } + public required string LabelSave { get; init; } + public required string LabelCancel { get; init; } + public required string ButtonEdit { get; init; } + public required string ButtonDone { get; init; } +} + +public class DayInfo +{ + public required string Date { get; init; } + public required string DayName { get; init; } + public required string ShortDayName { get; init; } + public required string DisplayDate { get; init; } +} + +public class WeekScheduleData +{ + public int WeekNumber { get; init; } + public int Year { get; init; } + public required string StartDate { get; init; } + public required string EndDate { get; init; } + public required List ClosedDays { get; init; } + public required List Employees { get; init; } +} + +public class EmployeeScheduleData +{ + public required string EmployeeId { get; init; } + public required string Name { get; init; } + public int WeeklyHours { get; init; } + public required Dictionary Schedule { get; init; } +} + +public class ShiftData +{ + public required string Status { get; init; } + public string? Start { get; init; } + public string? End { get; init; } + public string? Note { get; init; } +} diff --git a/PlanTempus.Application/Features/Employees/Data/workScheduleMock.json b/PlanTempus.Application/Features/Employees/Data/workScheduleMock.json new file mode 100644 index 0000000..9995b5e --- /dev/null +++ b/PlanTempus.Application/Features/Employees/Data/workScheduleMock.json @@ -0,0 +1,79 @@ +{ + "weekNumber": 52, + "year": 2025, + "startDate": "2025-12-23", + "endDate": "2025-12-29", + "closedDays": ["2025-12-25"], + "employees": [ + { + "employeeId": "emp-1", + "name": "Anna Sørensen", + "weeklyHours": 32, + "schedule": { + "2025-12-23": { "status": "work", "start": "09:00", "end": "17:00" }, + "2025-12-24": { "status": "work", "start": "09:00", "end": "13:00" }, + "2025-12-25": { "status": "off" }, + "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" } + } + }, + { + "employeeId": "emp-2", + "name": "Mette Jensen", + "weeklyHours": 40, + "schedule": { + "2025-12-23": { "status": "work", "start": "10:00", "end": "18:00" }, + "2025-12-24": { "status": "work", "start": "10:00", "end": "18:00" }, + "2025-12-25": { "status": "vacation" }, + "2025-12-26": { "status": "vacation" }, + "2025-12-27": { "status": "vacation" }, + "2025-12-28": { "status": "off" }, + "2025-12-29": { "status": "off" } + } + }, + { + "employeeId": "emp-3", + "name": "Louise Nielsen", + "weeklyHours": 37, + "schedule": { + "2025-12-23": { "status": "work", "start": "09:00", "end": "17:00" }, + "2025-12-24": { "status": "work", "start": "09:00", "end": "17:00" }, + "2025-12-25": { "status": "off" }, + "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" } + } + }, + { + "employeeId": "emp-4", + "name": "Katrine Pedersen", + "weeklyHours": 24, + "schedule": { + "2025-12-23": { "status": "work", "start": "12:00", "end": "20:00" }, + "2025-12-24": { "status": "off" }, + "2025-12-25": { "status": "off" }, + "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" } + } + }, + { + "employeeId": "emp-5", + "name": "Sofie Andersen", + "weeklyHours": 20, + "schedule": { + "2025-12-23": { "status": "sick" }, + "2025-12-24": { "status": "work", "start": "09:00", "end": "15:00" }, + "2025-12-25": { "status": "off" }, + "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" } + } + } + ] +} diff --git a/PlanTempus.Application/Features/Employees/Pages/Index.cshtml b/PlanTempus.Application/Features/Employees/Pages/Index.cshtml index 5481d20..45db507 100644 --- a/PlanTempus.Application/Features/Employees/Pages/Index.cshtml +++ b/PlanTempus.Application/Features/Employees/Pages/Index.cshtml @@ -35,6 +35,10 @@ Roller + + + Vagtplan + @@ -51,6 +55,13 @@ @await Component.InvokeAsync("PermissionsMatrix", "default") + + + + + @await Component.InvokeAsync("EmployeeWorkSchedule", "default") + + diff --git a/PlanTempus.Application/Features/Localization/Translations/da.json b/PlanTempus.Application/Features/Localization/Translations/da.json index 8c294f4..1cdf225 100644 --- a/PlanTempus.Application/Features/Localization/Translations/da.json +++ b/PlanTempus.Application/Features/Localization/Translations/da.json @@ -271,11 +271,32 @@ "tabs": { "general": "Generelt", "hours": "Arbejdstid", + "schedule": "Vagtplan", "services": "Services", "salary": "Løn", "hr": "HR", "stats": "Statistik" }, + "schedule": { + "week": "Uge", + "hours": "timer", + "editShift": "Redigér vagt", + "status": "Status", + "work": "Arbejde", + "off": "Fri", + "vacation": "Ferie", + "sick": "Syg", + "timeRange": "Tidsrum", + "note": "Note", + "type": "Type", + "single": "Enkelt", + "repeat": "Gentagelse", + "repeatInterval": "Gentag", + "repeatEnd": "Slutdato", + "weekday": "Ugedag", + "buttonEdit": "Rediger", + "buttonDone": "Færdig" + }, "contact": "Kontaktoplysninger", "personal": "Personlige oplysninger", "employment": "Ansættelse", diff --git a/PlanTempus.Application/Features/Localization/Translations/en.json b/PlanTempus.Application/Features/Localization/Translations/en.json index 796b6ca..5dc90f0 100644 --- a/PlanTempus.Application/Features/Localization/Translations/en.json +++ b/PlanTempus.Application/Features/Localization/Translations/en.json @@ -271,11 +271,32 @@ "tabs": { "general": "General", "hours": "Working hours", + "schedule": "Schedule", "services": "Services", "salary": "Salary", "hr": "HR", "stats": "Statistics" }, + "schedule": { + "week": "Week", + "hours": "hours", + "editShift": "Edit shift", + "status": "Status", + "work": "Work", + "off": "Off", + "vacation": "Vacation", + "sick": "Sick", + "timeRange": "Time range", + "note": "Note", + "type": "Type", + "single": "Single", + "repeat": "Repeat", + "repeatInterval": "Repeat", + "repeatEnd": "End date", + "weekday": "Weekday", + "buttonEdit": "Edit", + "buttonDone": "Done" + }, "contact": "Contact information", "personal": "Personal information", "employment": "Employment", diff --git a/PlanTempus.Application/wwwroot/css/employees.css b/PlanTempus.Application/wwwroot/css/employees.css index fd49f6b..46c5202 100644 --- a/PlanTempus.Application/wwwroot/css/employees.css +++ b/PlanTempus.Application/wwwroot/css/employees.css @@ -879,6 +879,531 @@ swp-data-row.focus-highlight { } } +/* =========================================== + WORK SCHEDULE TABLE + =========================================== */ +swp-schedule-table { + display: grid; + grid-template-columns: 180px repeat(7, minmax(100px, 1fr)); + border-radius: var(--radius-md); + overflow: hidden; + border: 1px solid var(--color-border); + background: var(--color-surface); +} + +swp-schedule-cell { + display: flex; + flex-direction: column; + justify-content: center; + padding: 12px 16px; + min-height: 60px; + background: var(--color-surface); + border-right: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border); + user-select: none; +} + +/* Last column: no right border */ +swp-schedule-cell:nth-child(8n) { + border-right: none; +} + +/* Last row: no bottom border */ +swp-schedule-cell:nth-last-child(-n+8) { + border-bottom: none; +} + +swp-schedule-cell.header { + background: var(--color-background-alt); + font-weight: var(--font-weight-medium); + font-size: 13px; + color: var(--color-text-secondary); + min-height: 48px; + text-align: center; + align-items: center; +} + +swp-schedule-cell.header.week-number { + font-size: 15px; + font-weight: var(--font-weight-semibold); + color: var(--color-text); +} + +swp-schedule-cell.header.closed { + background: color-mix(in srgb, #f59e0b 10%, var(--color-background-alt)); + border-top: 2px solid #f59e0b; + border-left: 2px solid #f59e0b; + border-right: 2px solid #f59e0b; + border-bottom: none; + + swp-day-name { + color: #d97706; + } +} + +swp-schedule-cell.employee { + align-items: flex-start; + gap: 2px; +} + +swp-schedule-cell.day { + align-items: center; + text-align: center; + position: relative; +} + +swp-schedule-cell.day.closed-day { + background: color-mix(in srgb, #f59e0b 6%, var(--color-surface)); + border-left: 2px solid #f59e0b; + border-right: 2px solid #f59e0b; + + swp-time-display { + opacity: 0.5; + } +} + +/* Last cell in closed column gets bottom border */ +swp-schedule-cell.day.closed-day:nth-last-child(-n+8) { + border-bottom: 2px solid #f59e0b; +} + +/* Schedule employee info */ +swp-schedule-cell swp-employee-name { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +swp-schedule-cell swp-employee-hours { + font-family: var(--font-mono); + font-size: 12px; + color: var(--color-text-muted); +} + +/* Day header */ +swp-day-name { + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +swp-day-date { + font-size: 12px; + color: var(--color-text-muted); + font-weight: var(--font-weight-normal); +} + +/* Time display variants */ +swp-time-display { + font-family: var(--font-mono); + font-size: 12px; + font-weight: var(--font-weight-medium); + padding: 4px 8px; + border-radius: 4px; + background: var(--bg-teal-light); + color: var(--color-text); + white-space: nowrap; + min-width: 90px; + text-align: center; + display: inline-block; +} + +swp-time-display.off { + background: transparent; + color: var(--color-text-muted); +} + +swp-time-display.off.off-override { + background: color-mix(in srgb, #7c3aed 12%, white); + color: #6d28d9; +} + +swp-time-display.vacation { + background: color-mix(in srgb, #f59e0b 15%, white); + color: #b45309; +} + +swp-time-display.sick { + background: color-mix(in srgb, #ef4444 15%, white); + color: #dc2626; +} + +/* Edit mode */ +body.schedule-edit-mode swp-schedule-cell.day { + cursor: pointer; +} + +body.schedule-edit-mode swp-schedule-cell.day:hover { + background: var(--color-background-alt); +} + +body.schedule-edit-mode swp-schedule-cell.day.selected { + background: color-mix(in srgb, var(--color-teal) 12%, white); + border: 2px solid var(--color-teal); + margin: -1px; + padding: 11px 15px; +} + +body.schedule-edit-mode swp-schedule-cell.header:not(.week-number) { + cursor: pointer; +} + +body.schedule-edit-mode swp-schedule-cell.header:not(.week-number):hover { + background: var(--color-border); +} + +/* Status options in drawer */ +swp-status-options { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +swp-status-option { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: all var(--transition-fast); + font-size: 13px; + font-weight: var(--font-weight-medium); + background: var(--color-background-alt); + color: var(--color-text-secondary); + + &::before { + content: ''; + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; + } + + &[data-status="work"] { + --status-color: var(--color-teal); + } + &[data-status="off"] { + --status-color: #7c3aed; + } + &[data-status="vacation"] { + --status-color: #f59e0b; + } + &[data-status="sick"] { + --status-color: #e53935; + } + + &::before { + background: var(--status-color); + } + + &:hover { + background: var(--color-border); + } + + &.selected { + background: color-mix(in srgb, var(--status-color) 15%, white); + color: var(--status-color); + } +} + +/* Time range slider */ +swp-time-range { + display: flex; + align-items: center; + gap: 12px; +} + +swp-time-range-slider { + position: relative; + flex: 1; + height: 20px; + display: flex; + align-items: center; +} + +swp-time-range-track { + position: absolute; + width: 100%; + height: 4px; + background: var(--color-border); + border-radius: 2px; +} + +swp-time-range-fill { + position: absolute; + height: 4px; + background: var(--color-teal); + border-radius: 2px; + cursor: grab; + + &:active { + cursor: grabbing; + } +} + +swp-time-range-slider input[type="range"] { + position: absolute; + width: 100%; + height: 4px; + -webkit-appearance: none; + appearance: none; + background: transparent; + pointer-events: none; + margin: 0; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + background: var(--color-teal); + border: 2px solid white; + border-radius: 50%; + cursor: pointer; + pointer-events: auto; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); + } + + &::-moz-range-thumb { + width: 14px; + height: 14px; + background: var(--color-teal); + border: 2px solid white; + border-radius: 50%; + cursor: pointer; + pointer-events: auto; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); + } +} + +swp-time-range-label { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + min-width: 100px; + text-align: center; + background: var(--color-background-alt); + padding: 6px 12px; + border-radius: 4px; +} + +swp-time-range-times { + font-size: 13px; + font-family: var(--font-mono); + font-weight: var(--font-weight-medium); + color: var(--color-text); + white-space: nowrap; +} + +swp-time-range-duration { + font-size: 11px; + font-family: var(--font-mono); + color: var(--color-text-secondary); + 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 { + display: flex; + align-items: center; + gap: 10px; + + swp-employee-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, var(--color-teal) 0%, #00695c 100%); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: var(--font-weight-semibold); + font-size: 12px; + flex-shrink: 0; + } + + &.empty swp-employee-avatar { + background: var(--color-border); + color: var(--color-text-muted); + } + + &.multi swp-employee-avatar { + background: var(--color-text-muted); + } +} + +/* =========================================== + SCHEDULE DRAWER (matches POC exactly) + =========================================== */ + +/* Drawer header with background */ +#schedule-drawer swp-drawer-header { + background: var(--color-background-alt); + padding: 20px 24px; +} + +#schedule-drawer swp-drawer-title { + font-size: 18px; +} + +/* Drawer body/content */ +#schedule-drawer swp-drawer-body { + padding: 24px; +} + +/* Form row layout */ +#schedule-drawer swp-form-row { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 16px; +} + +/* Form labels - uppercase style from POC */ +#schedule-drawer swp-form-label { + font-size: 11px; + font-weight: 400; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + + .optional, + .auto { + font-weight: 400; + text-transform: none; + color: var(--color-text-muted); + } +} + +/* Form value (read-only display) */ +#schedule-drawer swp-form-value { + font-size: 15px; + font-weight: 500; + color: var(--color-text); +} + +/* Form divider */ +#schedule-drawer swp-form-divider { + display: block; + height: 1px; + background: var(--color-border); + margin: 20px 0; +} + +/* Form hint text */ +#schedule-drawer swp-form-hint { + display: block; + font-size: 12px; + color: var(--color-text-muted); + margin: -8px 0 16px 0; + line-height: 1.4; +} + +/* Form group - gray card background from POC */ +#schedule-drawer swp-form-group { + display: block; + padding: 16px; + background: var(--color-background-alt); + border-radius: 8px; + margin-top: 16px; + + swp-form-row:last-child { + margin-bottom: 0; + } +} + +/* Form select wrapper */ +#schedule-drawer swp-form-select { + display: block; + + select { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + font-size: 14px; + font-family: var(--font-family); + color: var(--color-text); + background: var(--color-surface); + cursor: pointer; + + &:focus { + outline: none; + border-color: var(--color-teal); + } + } +} + +/* Text inputs in drawer */ +#schedule-drawer input[type="text"], +#schedule-drawer input[type="date"] { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + font-size: 14px; + font-family: var(--font-family); + color: var(--color-text); + background: var(--color-surface); + + &::placeholder { + color: var(--color-text-muted); + } + + &:focus { + outline: none; + border-color: var(--color-teal); + } +} + +/* Drawer footer with background */ +#schedule-drawer swp-drawer-footer { + display: flex; + gap: 12px; + padding: 20px 24px; + border-top: 1px solid var(--color-border); + background: var(--color-background-alt); + + swp-btn { + flex: 1; + } +} + /* =========================================== RESPONSIVE =========================================== */ diff --git a/PlanTempus.Application/wwwroot/ts/modules/employees.ts b/PlanTempus.Application/wwwroot/ts/modules/employees.ts index 7ee10c6..f763390 100644 --- a/PlanTempus.Application/wwwroot/ts/modules/employees.ts +++ b/PlanTempus.Application/wwwroot/ts/modules/employees.ts @@ -8,6 +8,7 @@ export class EmployeesController { private ratesSync: RatesSyncController | null = null; + private scheduleController: ScheduleController | null = null; private listView: HTMLElement | null = null; private detailView: HTMLElement | null = null; @@ -25,6 +26,7 @@ export class EmployeesController { this.setupHistoryNavigation(); this.restoreStateFromUrl(); this.ratesSync = new RatesSyncController(); + this.scheduleController = new ScheduleController(); } /** @@ -397,3 +399,646 @@ class RatesSyncController { }); } } + +/** + * Schedule Controller + * + * Handles work schedule (vagtplan) functionality: + * - Edit mode toggle + * - Cell selection (single, ctrl+click, shift+click) + * - Drawer interaction + * - Time range slider + * - Status options + */ +class ScheduleController { + private isEditMode = false; + private selectedCells: HTMLElement[] = []; + private anchorCell: HTMLElement | null = null; + private drawer: HTMLElement | null = null; + private editBtn: HTMLElement | null = null; + private scheduleTable: HTMLElement | null = null; + + // Time range constants + private readonly TIME_RANGE_MAX = 60; // 15 hours (06:00-21:00) * 4 intervals + + constructor() { + this.drawer = document.getElementById('schedule-drawer'); + this.editBtn = document.getElementById('scheduleEditBtn'); + this.scheduleTable = document.getElementById('scheduleTable'); + + if (!this.scheduleTable) return; + + this.setupEditModeToggle(); + this.setupCellSelection(); + this.setupStatusOptions(); + this.setupTypeToggle(); + this.setupTimeRangeSlider(); + this.setupDrawerSave(); + } + + /** + * Setup edit mode toggle button + */ + private setupEditModeToggle(): void { + this.editBtn?.addEventListener('click', () => { + this.isEditMode = !this.isEditMode; + document.body.classList.toggle('schedule-edit-mode', this.isEditMode); + + if (this.editBtn) { + const icon = this.editBtn.querySelector('i'); + const text = this.editBtn.childNodes[this.editBtn.childNodes.length - 1]; + + if (this.isEditMode) { + icon?.classList.replace('ph-pencil-simple', 'ph-check'); + if (text && text.nodeType === Node.TEXT_NODE) { + text.textContent = ' Færdig'; + } + this.openDrawer(); + this.showEmptyState(); + } else { + icon?.classList.replace('ph-check', 'ph-pencil-simple'); + if (text && text.nodeType === Node.TEXT_NODE) { + text.textContent = ' Rediger'; + } + this.closeDrawer(); + this.clearSelection(); + } + } + }); + } + + /** + * Setup cell click selection + */ + private setupCellSelection(): void { + if (!this.scheduleTable) return; + + const dayCells = this.scheduleTable.querySelectorAll('swp-schedule-cell.day'); + + dayCells.forEach(cell => { + // Double-click to enter edit mode + cell.addEventListener('dblclick', () => { + if (!this.isEditMode) { + this.isEditMode = true; + document.body.classList.add('schedule-edit-mode'); + + if (this.editBtn) { + const icon = this.editBtn.querySelector('i'); + const text = this.editBtn.childNodes[this.editBtn.childNodes.length - 1]; + icon?.classList.replace('ph-pencil-simple', 'ph-check'); + if (text && text.nodeType === Node.TEXT_NODE) { + text.textContent = ' Færdig'; + } + } + + this.openDrawer(); + this.clearSelection(); + cell.classList.add('selected'); + this.selectedCells = [cell]; + this.anchorCell = cell; + this.updateDrawerFields(); + } + }); + + // Click selection in edit mode + cell.addEventListener('click', (e: MouseEvent) => { + if (!this.isEditMode) return; + + if (e.shiftKey && this.anchorCell) { + // Shift+click: range selection + this.selectRange(this.anchorCell, cell); + } else if (e.ctrlKey || e.metaKey) { + // Ctrl/Cmd+click: toggle selection + if (cell.classList.contains('selected')) { + cell.classList.remove('selected'); + this.selectedCells = this.selectedCells.filter(c => c !== cell); + } else { + cell.classList.add('selected'); + this.selectedCells.push(cell); + this.anchorCell = cell; + } + } else { + // Single click: replace selection + this.clearSelection(); + cell.classList.add('selected'); + this.selectedCells = [cell]; + this.anchorCell = cell; + } + + this.updateDrawerFields(); + }); + }); + } + + /** + * Select a range of cells between anchor and target + */ + private selectRange(anchor: HTMLElement, target: HTMLElement): void { + if (!this.scheduleTable) return; + + const allDayCells = Array.from(this.scheduleTable.querySelectorAll('swp-schedule-cell.day')); + const anchorIdx = allDayCells.indexOf(anchor); + const targetIdx = allDayCells.indexOf(target); + + // Calculate grid positions (7 columns for days) + const anchorRow = Math.floor(anchorIdx / 7); + const anchorCol = anchorIdx % 7; + const targetRow = Math.floor(targetIdx / 7); + const targetCol = targetIdx % 7; + + const minRow = Math.min(anchorRow, targetRow); + const maxRow = Math.max(anchorRow, targetRow); + const minCol = Math.min(anchorCol, targetCol); + const maxCol = Math.max(anchorCol, targetCol); + + this.clearSelection(); + + allDayCells.forEach((c, idx) => { + const row = Math.floor(idx / 7); + const col = idx % 7; + if (row >= minRow && row <= maxRow && col >= minCol && col <= maxCol) { + c.classList.add('selected'); + this.selectedCells.push(c); + } + }); + } + + /** + * Clear all selected cells + */ + private clearSelection(): void { + this.selectedCells.forEach(c => c.classList.remove('selected')); + this.selectedCells = []; + } + + /** + * Get initials from name + */ + private getInitials(name: string): string { + return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); + } + + /** + * Show empty state in drawer + */ + private showEmptyState(): void { + const employeeDisplay = document.getElementById('scheduleFieldEmployee'); + const avatar = document.getElementById('scheduleFieldAvatar'); + const employeeName = document.getElementById('scheduleFieldEmployeeName'); + const dateField = document.getElementById('scheduleFieldDate'); + const weekdayField = document.getElementById('scheduleFieldWeekday'); + const drawerBody = this.drawer?.querySelector('swp-drawer-body') as HTMLElement; + const drawerFooter = this.drawer?.querySelector('swp-drawer-footer') as HTMLElement; + + if (employeeDisplay) employeeDisplay.classList.add('empty'); + if (employeeDisplay) employeeDisplay.classList.remove('multi'); + if (avatar) avatar.textContent = '?'; + if (employeeName) employeeName.textContent = 'Vælg celle...'; + if (dateField) dateField.textContent = '—'; + if (weekdayField) weekdayField.textContent = '—'; + if (drawerBody) { + drawerBody.style.opacity = '0.5'; + drawerBody.style.pointerEvents = 'none'; + } + if (drawerFooter) drawerFooter.style.display = 'none'; + } + + /** + * Show edit state in drawer + */ + private showEditState(): void { + const drawerBody = this.drawer?.querySelector('swp-drawer-body') as HTMLElement; + const drawerFooter = this.drawer?.querySelector('swp-drawer-footer') as HTMLElement; + + if (drawerBody) { + drawerBody.style.opacity = '1'; + drawerBody.style.pointerEvents = 'auto'; + } + if (drawerFooter) drawerFooter.style.display = 'flex'; + } + + /** + * Update drawer fields based on selected cells + */ + private updateDrawerFields(): void { + if (this.selectedCells.length === 0) { + this.showEmptyState(); + return; + } + + this.showEditState(); + + const employeeDisplay = document.getElementById('scheduleFieldEmployee'); + const avatar = document.getElementById('scheduleFieldAvatar'); + const employeeName = document.getElementById('scheduleFieldEmployeeName'); + const dateField = document.getElementById('scheduleFieldDate'); + const weekdayField = document.getElementById('scheduleFieldWeekday'); + + employeeDisplay?.classList.remove('empty', 'multi'); + + if (this.selectedCells.length === 1) { + const cell = this.selectedCells[0]; + const name = cell.dataset.employee || ''; + const date = cell.dataset.date || ''; + const dayName = this.getDayName(cell.dataset.day || ''); + + if (employeeName) employeeName.textContent = name; + if (avatar) avatar.textContent = this.getInitials(name); + if (dateField) dateField.textContent = this.formatDate(date); + if (weekdayField) weekdayField.textContent = dayName; + + this.prefillFormFromCell(cell); + } else { + const employees = [...new Set(this.selectedCells.map(c => c.dataset.employee))]; + const days = [...new Set(this.selectedCells.map(c => c.dataset.day))]; + + if (employees.length === 1) { + if (employeeName) employeeName.textContent = employees[0] || ''; + if (avatar) avatar.textContent = this.getInitials(employees[0] || ''); + if (dateField) dateField.textContent = `${this.selectedCells.length} dage valgt`; + } else { + if (employeeName) employeeName.textContent = `${this.selectedCells.length} valgt`; + if (avatar) avatar.textContent = String(this.selectedCells.length); + employeeDisplay?.classList.add('multi'); + if (dateField) dateField.textContent = `${employees.length} medarbejdere, ${days.length} dage`; + } + + if (days.length === 1) { + if (weekdayField) weekdayField.textContent = this.getDayName(days[0] || ''); + } else { + if (weekdayField) weekdayField.textContent = 'Flere dage'; + } + + this.resetFormToDefault(); + } + } + + /** + * Format ISO date to display format + */ + private formatDate(isoDate: string): string { + const date = new Date(isoDate); + return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`; + } + + /** + * Get full day name from short name + */ + private getDayName(shortName: string): string { + const dayMap: Record = { + 'Man': 'Mandag', 'Tir': 'Tirsdag', 'Ons': 'Onsdag', + 'Tor': 'Torsdag', 'Fre': 'Fredag', 'Lør': 'Lørdag', 'Søn': 'Søndag' + }; + return dayMap[shortName] || shortName; + } + + /** + * Prefill form from cell data + */ + private prefillFormFromCell(cell: HTMLElement): void { + const timeDisplay = cell.querySelector('swp-time-display'); + if (!timeDisplay) return; + + let status = 'work'; + if (timeDisplay.classList.contains('off')) status = 'off'; + else if (timeDisplay.classList.contains('vacation')) status = 'vacation'; + else if (timeDisplay.classList.contains('sick')) status = 'sick'; + + // Update status options + document.querySelectorAll('#scheduleStatusOptions swp-status-option').forEach(opt => { + opt.classList.toggle('selected', (opt as HTMLElement).dataset.status === status); + }); + + // Show/hide time row + const timeRow = document.getElementById('scheduleTimeRow'); + if (timeRow) timeRow.style.display = status === 'work' ? 'flex' : 'none'; + + // Parse time if work status + if (status === 'work') { + const timeText = timeDisplay.textContent?.trim() || ''; + const timeMatch = timeText.match(/(\d{2}:\d{2})\s*-\s*(\d{2}:\d{2})/); + if (timeMatch) { + this.setTimeRange(timeMatch[1], timeMatch[2]); + } + } + + // Reset type to template + document.querySelectorAll('#scheduleTypeOptions swp-toggle-option').forEach(opt => { + opt.classList.toggle('selected', (opt as HTMLElement).dataset.value === 'template'); + }); + const repeatGroup = document.getElementById('scheduleRepeatGroup'); + if (repeatGroup) repeatGroup.style.display = 'block'; + } + + /** + * Reset form to default values + */ + private resetFormToDefault(): void { + document.querySelectorAll('#scheduleStatusOptions swp-status-option').forEach(opt => { + opt.classList.toggle('selected', (opt as HTMLElement).dataset.status === 'work'); + }); + + const timeRow = document.getElementById('scheduleTimeRow'); + if (timeRow) timeRow.style.display = 'flex'; + + this.setTimeRange('09:00', '17:00'); + + document.querySelectorAll('#scheduleTypeOptions swp-toggle-option').forEach(opt => { + opt.classList.toggle('selected', (opt as HTMLElement).dataset.value === 'template'); + }); + + const repeatGroup = document.getElementById('scheduleRepeatGroup'); + if (repeatGroup) repeatGroup.style.display = 'block'; + + const noteField = document.getElementById('scheduleFieldNote') as HTMLInputElement; + if (noteField) noteField.value = ''; + } + + /** + * Setup status options click handlers + */ + private setupStatusOptions(): void { + document.querySelectorAll('#scheduleStatusOptions swp-status-option').forEach(option => { + option.addEventListener('click', () => { + document.querySelectorAll('#scheduleStatusOptions swp-status-option').forEach(o => + o.classList.remove('selected') + ); + option.classList.add('selected'); + + const status = (option as HTMLElement).dataset.status; + const timeRow = document.getElementById('scheduleTimeRow'); + if (timeRow) timeRow.style.display = status === 'work' ? 'flex' : 'none'; + + this.updateSelectedCellsStatus(); + }); + }); + } + + /** + * Setup type toggle (single/repeat) + */ + private setupTypeToggle(): void { + document.querySelectorAll('#scheduleTypeOptions swp-toggle-option').forEach(option => { + option.addEventListener('click', () => { + document.querySelectorAll('#scheduleTypeOptions swp-toggle-option').forEach(o => + o.classList.remove('selected') + ); + option.classList.add('selected'); + + const isTemplate = (option as HTMLElement).dataset.value === 'template'; + const repeatGroup = document.getElementById('scheduleRepeatGroup'); + if (repeatGroup) repeatGroup.style.display = isTemplate ? 'block' : 'none'; + }); + }); + } + + /** + * Convert slider value to time string + */ + private valueToTime(value: number): string { + const totalMinutes = (value * 15) + (6 * 60); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; + } + + /** + * Convert time string to slider value + */ + private timeToValue(timeStr: string): number { + const [hours, minutes] = timeStr.split(':').map(Number); + const totalMinutes = hours * 60 + minutes; + return Math.round((totalMinutes - 6 * 60) / 15); + } + + /** + * Set time range slider values + */ + private setTimeRange(startTime: string, endTime: string): void { + const timeRange = document.getElementById('scheduleTimeRange'); + if (!timeRange) return; + + const startInput = timeRange.querySelector('.range-start'); + const endInput = timeRange.querySelector('.range-end'); + + if (startInput) startInput.value = String(this.timeToValue(startTime)); + if (endInput) endInput.value = String(this.timeToValue(endTime)); + + this.updateTimeRangeDisplay(timeRange); + } + + /** + * Update time range display + */ + private updateTimeRangeDisplay(container: HTMLElement): void { + const startInput = container.querySelector('.range-start'); + const endInput = container.querySelector('.range-end'); + const fill = container.querySelector('swp-time-range-fill'); + const timesEl = container.querySelector('swp-time-range-times'); + const durationEl = container.querySelector('swp-time-range-duration'); + + if (!startInput || !endInput) return; + + let startVal = parseInt(startInput.value); + let endVal = parseInt(endInput.value); + + // Ensure start doesn't exceed end + if (startVal > endVal) { + if (startInput === document.activeElement) { + startInput.value = String(endVal); + startVal = endVal; + } else { + endInput.value = String(startVal); + endVal = startVal; + } + } + + // Update fill bar + if (fill) { + const startPercent = (startVal / this.TIME_RANGE_MAX) * 100; + const endPercent = (endVal / this.TIME_RANGE_MAX) * 100; + fill.style.left = startPercent + '%'; + fill.style.width = (endPercent - startPercent) + '%'; + } + + // Calculate duration + const durationMinutes = (endVal - startVal) * 15; + const durationHours = durationMinutes / 60; + const durationText = durationHours % 1 === 0 + ? `${durationHours} timer` + : `${durationHours.toFixed(1).replace('.', ',')} timer`; + + if (timesEl) timesEl.textContent = `${this.valueToTime(startVal)} – ${this.valueToTime(endVal)}`; + if (durationEl) durationEl.textContent = durationText; + } + + /** + * Setup time range slider + */ + private setupTimeRangeSlider(): void { + const timeRange = document.getElementById('scheduleTimeRange'); + if (!timeRange) return; + + const startInput = timeRange.querySelector('.range-start'); + const endInput = timeRange.querySelector('.range-end'); + const fill = timeRange.querySelector('swp-time-range-fill'); + const track = timeRange.querySelector('swp-time-range-track'); + + this.updateTimeRangeDisplay(timeRange); + + startInput?.addEventListener('input', () => { + this.updateTimeRangeDisplay(timeRange); + this.updateSelectedCellsTime(); + }); + + endInput?.addEventListener('input', () => { + this.updateTimeRangeDisplay(timeRange); + this.updateSelectedCellsTime(); + }); + + // Drag fill bar to move entire range + if (fill && track && startInput && endInput) { + let isDragging = false; + let dragStartX = 0; + let dragStartValues = { start: 0, end: 0 }; + + fill.addEventListener('mousedown', (e: MouseEvent) => { + isDragging = true; + dragStartX = e.clientX; + dragStartValues.start = parseInt(startInput.value); + dragStartValues.end = parseInt(endInput.value); + e.preventDefault(); + }); + + document.addEventListener('mousemove', (e: MouseEvent) => { + if (!isDragging) return; + + const sliderWidth = track.offsetWidth; + const deltaX = e.clientX - dragStartX; + const deltaValue = Math.round((deltaX / sliderWidth) * this.TIME_RANGE_MAX); + + const duration = dragStartValues.end - dragStartValues.start; + let newStart = dragStartValues.start + deltaValue; + let newEnd = dragStartValues.end + deltaValue; + + if (newStart < 0) { + newStart = 0; + newEnd = duration; + } + if (newEnd > this.TIME_RANGE_MAX) { + newEnd = this.TIME_RANGE_MAX; + newStart = this.TIME_RANGE_MAX - duration; + } + + startInput.value = String(newStart); + endInput.value = String(newEnd); + this.updateTimeRangeDisplay(timeRange); + this.updateSelectedCellsTime(); + }); + + document.addEventListener('mouseup', () => { + isDragging = false; + }); + } + } + + /** + * Update selected cells with current time + */ + private updateSelectedCellsTime(): void { + const selectedStatus = document.querySelector('#scheduleStatusOptions swp-status-option.selected') as HTMLElement; + const status = selectedStatus?.dataset.status || 'work'; + + if (status !== 'work') return; + + const timeRange = document.getElementById('scheduleTimeRange'); + if (!timeRange) return; + + const startInput = timeRange.querySelector('.range-start'); + const endInput = timeRange.querySelector('.range-end'); + if (!startInput || !endInput) return; + + const startTime = this.valueToTime(parseInt(startInput.value)); + const endTime = this.valueToTime(parseInt(endInput.value)); + const formattedTime = `${startTime} - ${endTime}`; + + this.selectedCells.forEach(cell => { + const timeDisplay = cell.querySelector('swp-time-display'); + if (timeDisplay && !timeDisplay.classList.contains('off') && + !timeDisplay.classList.contains('vacation') && + !timeDisplay.classList.contains('sick')) { + timeDisplay.textContent = formattedTime; + } + }); + } + + /** + * Update selected cells with current status + */ + private updateSelectedCellsStatus(): void { + const selectedStatus = document.querySelector('#scheduleStatusOptions swp-status-option.selected') as HTMLElement; + const status = selectedStatus?.dataset.status || 'work'; + + const timeRange = document.getElementById('scheduleTimeRange'); + const startInput = timeRange?.querySelector('.range-start'); + const endInput = timeRange?.querySelector('.range-end'); + + const startTime = startInput ? this.valueToTime(parseInt(startInput.value)) : '09:00'; + const endTime = endInput ? this.valueToTime(parseInt(endInput.value)) : '17:00'; + + this.selectedCells.forEach(cell => { + const timeDisplay = cell.querySelector('swp-time-display'); + if (!timeDisplay) return; + + timeDisplay.classList.remove('off', 'off-override', 'vacation', 'sick'); + + switch (status) { + case 'work': + timeDisplay.textContent = `${startTime} - ${endTime}`; + break; + case 'off': + timeDisplay.classList.add('off'); + timeDisplay.textContent = '—'; + break; + case 'vacation': + timeDisplay.classList.add('vacation'); + timeDisplay.textContent = 'Ferie'; + break; + case 'sick': + timeDisplay.classList.add('sick'); + timeDisplay.textContent = 'Syg'; + break; + } + }); + } + + /** + * Setup drawer save button + */ + private setupDrawerSave(): void { + const saveBtn = document.getElementById('scheduleDrawerSave'); + saveBtn?.addEventListener('click', () => { + // Changes are already applied in real-time + this.clearSelection(); + this.showEmptyState(); + }); + } + + /** + * Open the schedule drawer + */ + private openDrawer(): void { + this.drawer?.classList.add('open'); + document.getElementById('drawerOverlay')?.classList.add('active'); + document.body.style.overflow = 'hidden'; + } + + /** + * Close the schedule drawer + */ + private closeDrawer(): void { + this.drawer?.classList.remove('open'); + document.getElementById('drawerOverlay')?.classList.remove('active'); + document.body.style.overflow = ''; + } +} diff --git a/PlanTempus.Application/wwwroot/ts/types/WorkSchedule.ts b/PlanTempus.Application/wwwroot/ts/types/WorkSchedule.ts new file mode 100644 index 0000000..bf53852 --- /dev/null +++ b/PlanTempus.Application/wwwroot/ts/types/WorkSchedule.ts @@ -0,0 +1,30 @@ +/** + * Work Schedule Types + * + * Types for employee work schedule (arbejdstidsplan) + */ + +export type ShiftStatus = 'work' | 'off' | 'vacation' | 'sick'; + +export interface WorkScheduleShift { + status: ShiftStatus; + start?: string; // "09:00" - only when status=work + end?: string; // "17:00" - only when status=work + note?: string; +} + +export interface EmployeeSchedule { + employeeId: string; + name: string; + weeklyHours: number; + schedule: Record; // key = ISO date "2025-12-23" +} + +export interface WeekSchedule { + weekNumber: number; + year: number; + startDate: string; // ISO date "2025-12-23" + endDate: string; // ISO date "2025-12-29" + closedDays: string[]; // ["2025-12-25"] + employees: EmployeeSchedule[]; +}