Enhances employee details with comprehensive salary and HR data

Adds detailed salary rates, commission structures, and HR-related records

Introduces new data models and view components for:
- Salary rates and supplements
- Commissions and rate configurations
- Employee HR tracking (certifications, courses, absence)

Implements dynamic rate synchronization between drawer and card views
This commit is contained in:
Janus C. H. Knudsen 2026-01-13 22:37:29 +01:00
parent 2e6207bb0b
commit f71f00099a
15 changed files with 1589 additions and 137 deletions

View file

@ -220,8 +220,62 @@ public record EmployeeDetailRecord
public required string HourlyRate { get; init; }
public required string MonthlyFixedSalary { get; init; }
// Salary - Rates
public string NormalRate { get; init; } = "131,49 kr";
public string OvertimeRate { get; init; } = "280,50 kr";
public string VacationRate { get; init; } = "140,25 kr";
// Salary - Commission
public string MinimumPerHour { get; init; } = "220 kr";
public string ServiceCommission { get; init; } = "15%";
public string ProductCommission { get; init; } = "15%";
// Salary - Supplements
public string WeekdaySupplement { get; init; } = "28,03 kr";
public string SaturdaySupplement { get; init; } = "56,02 kr";
public string SundaySupplement { get; init; } = "112,07 kr";
// Salary - Rate values (numeric only, for drawer inputs)
public string NormalRateValue { get; init; } = "131,49";
public string OvertimeRateValue { get; init; } = "280,50";
public string CourseRateValue { get; init; } = "140,25";
public string TimeOffRateValue { get; init; } = "140,25";
public string PaidLeaveRateValue { get; init; } = "140,25";
public string VacationRateValue { get; init; } = "140,25";
public string OfficeRateValue { get; init; } = "140,25";
public string ChildSickRateValue { get; init; } = "140,25";
public string ChildHospitalRateValue { get; init; } = "140,25";
public string MaternityRateValue { get; init; } = "140,25";
public string WeekdaySupplementValue { get; init; } = "28,03";
public string SaturdaySupplementValue { get; init; } = "56,02";
public string SundaySupplementValue { get; init; } = "112,07";
public string ProductCommissionValue { get; init; } = "15";
public string ServiceCommissionValue { get; init; } = "15";
// HR - Contract
public string ContractType { get; init; } = "Fastansættelse";
public string TerminationNotice { get; init; } = "1 måned";
public string ContractExpiry { get; init; } = "— (ingen udløb)";
// HR - Vacation
public string VacationEarned { get; init; } = "25 dage";
public string VacationUsed { get; init; } = "12 dage";
public string VacationRemaining { get; init; } = "13 dage";
// HR - Absence
public string SickDays2025 { get; init; } = "3 dage";
public string SickDays2024 { get; init; } = "7 dage";
public string ChildSickDays2025 { get; init; } = "1 dag";
public string MaternityLeave { get; init; } = "— (ingen planlagt)";
// Tags (certifications, specialties)
public List<EmployeeTag> Tags { get; init; } = new();
}
public record EmployeeTag(string Text, string CssClass);
// HR data records
public record CertificationRecord(string Name, string ExpiryDate, string Status, string StatusClass);
public record CourseRecord(string Name, string Provider, string Date, string? Status = null);
public record DocumentRecord(string Name, string UploadDate);
public record PlannedAbsenceRecord(string Dates, string Type, string TypeClass);

View file

@ -9,23 +9,23 @@
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelFullName</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.Name</swp-edit-value>
<input type="text" id="fullname" value="@Model.Name">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelEmail</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.Email</swp-edit-value>
<input type="text" id="email" value="@Model.Email">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelPhone</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.Phone</swp-edit-value>
<input type="text" id="phone" value="@Model.Phone">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelAddress</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.Address</swp-edit-value>
<input type="text" id="address" value="@Model.Address">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelPostalCity</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.PostalCity</swp-edit-value>
<input type="text" id="postalcity" value="@Model.PostalCity">
</swp-edit-row>
</swp-edit-section>
</swp-card>
@ -36,15 +36,15 @@
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelBirthDate</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.BirthDate</swp-edit-value>
<input type="text" id="birthdate" value="@Model.BirthDate">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelEmergencyContact</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.EmergencyContact</swp-edit-value>
<input type="text" id="emergencycontact" value="@Model.EmergencyContact">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelEmergencyPhone</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.EmergencyPhone</swp-edit-value>
<input type="text" id="emergencyphone" value="@Model.EmergencyPhone">
</swp-edit-row>
</swp-edit-section>
</swp-card>
@ -58,19 +58,19 @@
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelEmploymentDate</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.EmploymentDate</swp-edit-value>
<input type="text" id="employmentdate" value="@Model.EmploymentDate">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelPosition</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.Position</swp-edit-value>
<input type="text" id="position" value="@Model.Position">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelEmploymentType</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.EmploymentType</swp-edit-value>
<input type="text" id="employmenttype" value="@Model.EmploymentType">
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelHoursPerWeek</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.HoursPerWeek</swp-edit-value>
<input type="text" id="hoursperweek" value="@Model.HoursPerWeek">
</swp-edit-row>
</swp-edit-section>
</swp-card>

View file

@ -1,44 +1,183 @@
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHRViewModel
<swp-detail-grid>
<swp-card>
<swp-section-label>@Model.LabelDocuments</swp-section-label>
<swp-document-list>
<swp-document-item>
<i class="ph ph-file-pdf"></i>
<swp-document-name>@Model.LabelContract</swp-document-name>
<swp-document-date>15. aug 2019</swp-document-date>
</swp-document-item>
<swp-document-item>
<i class="ph ph-file-pdf"></i>
<swp-document-name>Lønaftale 2024</swp-document-name>
<swp-document-date>1. jan 2024</swp-document-date>
</swp-document-item>
</swp-document-list>
</swp-card>
<!-- Left column -->
<div>
<!-- Contract & Documents -->
<swp-card>
<swp-section-label>@Model.LabelContractDocuments</swp-section-label>
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelContractType</swp-edit-label>
<swp-edit-select>
<select>
@foreach (var option in Model.ContractTypeOptions)
{
<option selected="@(option == Model.ContractType)">@option</option>
}
</select>
</swp-edit-select>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelTerminationNotice</swp-edit-label>
<swp-edit-select>
<select>
@foreach (var option in Model.TerminationNoticeOptions)
{
<option selected="@(option == Model.TerminationNotice)">@option</option>
}
</select>
</swp-edit-select>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelContractExpiry</swp-edit-label>
<swp-edit-value>@Model.ContractExpiry</swp-edit-value>
</swp-edit-row>
</swp-edit-section>
<swp-document-list style="margin-top: var(--spacing-6);">
@foreach (var doc in Model.Documents)
{
<swp-document-item>
<i class="ph ph-file-pdf"></i>
<swp-document-info>
<swp-document-name>@doc.Name</swp-document-name>
<swp-document-meta>@doc.UploadDate</swp-document-meta>
</swp-document-info>
<swp-document-actions>
<swp-btn class="secondary sm">Vis</swp-btn>
</swp-document-actions>
</swp-document-item>
}
</swp-document-list>
<swp-add-button>+ @Model.LabelUploadDocument</swp-add-button>
</swp-card>
<!-- Planned absence -->
<swp-card>
<swp-section-label>@Model.LabelPlannedAbsence</swp-section-label>
<swp-simple-list>
@foreach (var absence in Model.PlannedAbsences)
{
<swp-simple-item>
<swp-simple-item-text>@absence.Dates</swp-simple-item-text>
<swp-status-badge class="@absence.TypeClass">@absence.Type</swp-status-badge>
</swp-simple-item>
}
</swp-simple-list>
<swp-add-button>+ @Model.LabelAddAbsence</swp-add-button>
</swp-card>
</div>
<!-- Right column -->
<div>
<!-- Certifications -->
<swp-card>
<swp-section-label>@Model.LabelCertifications</swp-section-label>
<swp-document-list>
@foreach (var cert in Model.Certifications)
{
<swp-document-item>
<i class="ph ph-certificate"></i>
<swp-document-info>
<swp-document-name>@cert.Name</swp-document-name>
<swp-document-meta>@cert.ExpiryDate</swp-document-meta>
</swp-document-info>
<swp-document-actions>
<swp-status-badge class="@cert.StatusClass">@cert.Status</swp-status-badge>
</swp-document-actions>
</swp-document-item>
}
</swp-document-list>
<swp-add-button>+ @Model.LabelAddCertification</swp-add-button>
</swp-card>
<!-- Courses -->
<swp-card>
<swp-section-label>@Model.LabelCourses</swp-section-label>
<swp-subsection>
<swp-subsection-title>@Model.LabelCompletedCourses</swp-subsection-title>
<swp-document-list>
@foreach (var course in Model.CompletedCourses)
{
<swp-document-item>
<swp-document-info>
<swp-document-name>@course.Name</swp-document-name>
<swp-document-meta>@course.Provider · @course.Date</swp-document-meta>
</swp-document-info>
</swp-document-item>
}
</swp-document-list>
</swp-subsection>
<swp-subsection>
<swp-subsection-title>@Model.LabelPlannedCourses</swp-subsection-title>
<swp-document-list>
@foreach (var course in Model.PlannedCourses)
{
<swp-document-item>
<swp-document-info>
<swp-document-name>@course.Name</swp-document-name>
<swp-document-meta>@course.Provider · @course.Date</swp-document-meta>
</swp-document-info>
@if (!string.IsNullOrEmpty(course.Status))
{
<swp-document-actions>
<swp-status-badge class="enrolled">@course.Status</swp-status-badge>
</swp-document-actions>
}
</swp-document-item>
}
</swp-document-list>
</swp-subsection>
<swp-add-button>+ @Model.LabelAddCourse</swp-add-button>
</swp-card>
</div>
</swp-detail-grid>
<!-- Vacation & Absence section -->
<swp-detail-grid style="margin-top: var(--spacing-8);">
<swp-card>
<swp-section-label>@Model.LabelVacation</swp-section-label>
<swp-section-label>@Model.LabelVacationBalance</swp-section-label>
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>Optjent ferie</swp-edit-label>
<swp-edit-value>25 dage</swp-edit-value>
<swp-edit-label>@Model.LabelVacationEarned</swp-edit-label>
<swp-edit-value>@Model.VacationEarned</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>Afholdt ferie</swp-edit-label>
<swp-edit-value>12 dage</swp-edit-value>
<swp-edit-label>@Model.LabelVacationUsed</swp-edit-label>
<swp-edit-value>@Model.VacationUsed</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>Resterende</swp-edit-label>
<swp-edit-value>13 dage</swp-edit-value>
<swp-edit-label>@Model.LabelVacationRemaining</swp-edit-label>
<swp-edit-value style="font-weight: 600; color: var(--color-teal);">@Model.VacationRemaining</swp-edit-value>
</swp-edit-row>
</swp-edit-section>
</swp-card>
<swp-card>
<swp-section-label>@Model.LabelNotes</swp-section-label>
<swp-notes-area contenteditable="true">
Ingen noter tilføjet endnu...
</swp-notes-area>
<swp-section-label>@Model.LabelAbsenceSickness</swp-section-label>
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelSickDays2025</swp-edit-label>
<swp-edit-value>@Model.SickDays2025</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelSickDays2024</swp-edit-label>
<swp-edit-value>@Model.SickDays2024</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelChildSickDays2025</swp-edit-label>
<swp-edit-value>@Model.ChildSickDays2025</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelMaternityLeave</swp-edit-label>
<swp-edit-value>@Model.MaternityLeave</swp-edit-value>
</swp-edit-row>
</swp-edit-section>
</swp-card>
</swp-detail-grid>

View file

@ -14,13 +14,95 @@ public class EmployeeDetailHRViewComponent : ViewComponent
public IViewComponentResult Invoke(string key)
{
var employee = EmployeeDetailCatalog.Get(key);
var model = new EmployeeDetailHRViewModel
{
LabelDocuments = _localization.Get("employees.detail.hr.documents"),
LabelContract = _localization.Get("employees.detail.hr.contract"),
LabelVacation = _localization.Get("employees.detail.hr.vacation"),
LabelSickLeave = _localization.Get("employees.detail.hr.sickleave"),
LabelNotes = _localization.Get("employees.detail.hr.notes")
// Contract data
ContractType = employee.ContractType,
TerminationNotice = employee.TerminationNotice,
ContractExpiry = employee.ContractExpiry,
// Vacation data
VacationEarned = employee.VacationEarned,
VacationUsed = employee.VacationUsed,
VacationRemaining = employee.VacationRemaining,
// Absence data
SickDays2025 = employee.SickDays2025,
SickDays2024 = employee.SickDays2024,
ChildSickDays2025 = employee.ChildSickDays2025,
MaternityLeave = employee.MaternityLeave,
// Labels - Contract & Documents
LabelContractDocuments = _localization.Get("employees.detail.hr.contractdocuments"),
LabelContractType = _localization.Get("employees.detail.hr.contracttype"),
LabelTerminationNotice = _localization.Get("employees.detail.hr.terminationnotice"),
LabelContractExpiry = _localization.Get("employees.detail.hr.contractexpiry"),
LabelUploadDocument = _localization.Get("employees.detail.hr.uploaddocument"),
// Labels - Certifications
LabelCertifications = _localization.Get("employees.detail.hr.certifications"),
LabelAddCertification = _localization.Get("employees.detail.hr.addcertification"),
// Labels - Courses
LabelCourses = _localization.Get("employees.detail.hr.courses"),
LabelCompletedCourses = _localization.Get("employees.detail.hr.completedcourses"),
LabelPlannedCourses = _localization.Get("employees.detail.hr.plannedcourses"),
LabelAddCourse = _localization.Get("employees.detail.hr.addcourse"),
// Labels - Vacation
LabelVacationBalance = _localization.Get("employees.detail.hr.vacationbalance"),
LabelVacationEarned = _localization.Get("employees.detail.hr.vacationearned"),
LabelVacationUsed = _localization.Get("employees.detail.hr.vacationused"),
LabelVacationRemaining = _localization.Get("employees.detail.hr.vacationremaining"),
// Labels - Absence
LabelAbsenceSickness = _localization.Get("employees.detail.hr.absencesickness"),
LabelSickDays2025 = _localization.Get("employees.detail.hr.sickdays2025"),
LabelSickDays2024 = _localization.Get("employees.detail.hr.sickdays2024"),
LabelChildSickDays2025 = _localization.Get("employees.detail.hr.childsickdays2025"),
LabelMaternityLeave = _localization.Get("employees.detail.hr.maternityleave"),
// Labels - Planned absence
LabelPlannedAbsence = _localization.Get("employees.detail.hr.plannedabsence"),
LabelAddAbsence = _localization.Get("employees.detail.hr.addabsence"),
// Contract type options
ContractTypeOptions = new List<string> { "Fastansættelse", "Tidsbegrænset", "Freelance", "Elev/Lærling" },
TerminationNoticeOptions = new List<string> { "14 dage", "1 måned", "3 måneder" },
// Mock data - Documents
Documents = new List<DocumentRecord>
{
new("Ansættelseskontrakt.pdf", "Uploadet 1. aug 2019"),
new("Tillæg - Lønforhøjelse 2023.pdf", "Uploadet 15. jan 2023")
},
// Mock data - Certifications
Certifications = new List<CertificationRecord>
{
new("Balayage Specialist", "Udløber: 15. juni 2026", "Gyldig", "valid"),
new("Farvecertificering (Wella)", "Udløber: 1. marts 2025", "Udløber snart", "expiring")
},
// Mock data - Courses
CompletedCourses = new List<CourseRecord>
{
new("Avanceret balayage teknikker", "Wella Academy", "Marts 2024"),
new("Kundeservice & mersalg", "SalonUp", "November 2023")
},
PlannedCourses = new List<CourseRecord>
{
new("Olaplex certificering", "Olaplex DK", "15. februar 2026", "Tilmeldt")
},
// Mock data - Planned absence
PlannedAbsences = new List<PlannedAbsenceRecord>
{
new("23. dec 2. jan 2026", "Ferie", "ferie"),
new("14. feb 2025", "Fri", "fri")
}
};
return View(model);
@ -29,9 +111,64 @@ public class EmployeeDetailHRViewComponent : ViewComponent
public class EmployeeDetailHRViewModel
{
public required string LabelDocuments { get; init; }
public required string LabelContract { get; init; }
public required string LabelVacation { get; init; }
public required string LabelSickLeave { get; init; }
public required string LabelNotes { get; init; }
// Contract data
public required string ContractType { get; init; }
public required string TerminationNotice { get; init; }
public required string ContractExpiry { get; init; }
// Vacation data
public required string VacationEarned { get; init; }
public required string VacationUsed { get; init; }
public required string VacationRemaining { get; init; }
// Absence data
public required string SickDays2025 { get; init; }
public required string SickDays2024 { get; init; }
public required string ChildSickDays2025 { get; init; }
public required string MaternityLeave { get; init; }
// Labels - Contract & Documents
public required string LabelContractDocuments { get; init; }
public required string LabelContractType { get; init; }
public required string LabelTerminationNotice { get; init; }
public required string LabelContractExpiry { get; init; }
public required string LabelUploadDocument { get; init; }
// Labels - Certifications
public required string LabelCertifications { get; init; }
public required string LabelAddCertification { get; init; }
// Labels - Courses
public required string LabelCourses { get; init; }
public required string LabelCompletedCourses { get; init; }
public required string LabelPlannedCourses { get; init; }
public required string LabelAddCourse { get; init; }
// Labels - Vacation
public required string LabelVacationBalance { get; init; }
public required string LabelVacationEarned { get; init; }
public required string LabelVacationUsed { get; init; }
public required string LabelVacationRemaining { get; init; }
// Labels - Absence
public required string LabelAbsenceSickness { get; init; }
public required string LabelSickDays2025 { get; init; }
public required string LabelSickDays2024 { get; init; }
public required string LabelChildSickDays2025 { get; init; }
public required string LabelMaternityLeave { get; init; }
// Labels - Planned absence
public required string LabelPlannedAbsence { get; init; }
public required string LabelAddAbsence { get; init; }
// Contract type options
public List<string> ContractTypeOptions { get; init; } = new();
public List<string> TerminationNoticeOptions { get; init; } = new();
// Data collections
public List<DocumentRecord> Documents { get; init; } = new();
public List<CertificationRecord> Certifications { get; init; } = new();
public List<CourseRecord> CompletedCourses { get; init; } = new();
public List<CourseRecord> PlannedCourses { get; init; } = new();
public List<PlannedAbsenceRecord> PlannedAbsences { get; init; } = new();
}

View file

@ -1,39 +1,215 @@
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailSalaryViewModel
<swp-detail-grid>
<swp-card>
<swp-section-label>@Model.LabelPaymentInfo</swp-section-label>
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelBankAccount</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.BankAccount</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelTaxCard</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.TaxCard</swp-edit-value>
</swp-edit-row>
</swp-edit-section>
</swp-card>
<div>
<!-- Satser (Grundsatser) -->
<swp-card>
<swp-section-header>
<swp-section-label>@Model.LabelRates</swp-section-label>
<swp-section-action data-drawer-trigger="rates-drawer">@Model.LabelEdit</swp-section-action>
</swp-section-header>
<swp-edit-section>
<swp-edit-row id="card-normal">
<swp-edit-label>@Model.LabelNormalRate</swp-edit-label>
<input type="text" id="value-normal" data-type="number" value="@Model.NormalRate" readonly>
</swp-edit-row>
<swp-edit-row id="card-overtime">
<swp-edit-label>@Model.LabelOvertimeRate</swp-edit-label>
<input type="text" id="value-overtime" data-type="number" value="@Model.OvertimeRate" readonly>
</swp-edit-row>
<swp-edit-row id="card-course" style="display: none;">
<swp-edit-label>@Model.LabelCourseRate</swp-edit-label>
<input type="text" id="value-course" data-type="number" value="" readonly>
</swp-edit-row>
<swp-edit-row id="card-timeoff" style="display: none;">
<swp-edit-label>@Model.LabelTimeOffRate</swp-edit-label>
<input type="text" id="value-timeoff" data-type="number" value="" readonly>
</swp-edit-row>
<swp-edit-row id="card-paidleave" style="display: none;">
<swp-edit-label>@Model.LabelPaidLeaveRate</swp-edit-label>
<input type="text" id="value-paidleave" data-type="number" value="" readonly>
</swp-edit-row>
<swp-edit-row id="card-vacation">
<swp-edit-label>@Model.LabelVacationRate</swp-edit-label>
<input type="text" id="value-vacation" data-type="number" value="@Model.VacationRateValue kr" readonly>
</swp-edit-row>
<swp-edit-row id="card-office" style="display: none;">
<swp-edit-label>@Model.LabelOfficeRate</swp-edit-label>
<input type="text" id="value-office" data-type="number" value="" readonly>
</swp-edit-row>
<swp-edit-row id="card-childsick" style="display: none;">
<swp-edit-label>@Model.LabelChildSickRate</swp-edit-label>
<input type="text" id="value-childsick" data-type="number" value="" readonly>
</swp-edit-row>
<swp-edit-row id="card-childhospital" style="display: none;">
<swp-edit-label>@Model.LabelChildHospitalRate</swp-edit-label>
<input type="text" id="value-childhospital" data-type="number" value="" readonly>
</swp-edit-row>
<swp-edit-row id="card-maternity" style="display: none;">
<swp-edit-label>@Model.LabelMaternityRate</swp-edit-label>
<input type="text" id="value-maternity" data-type="number" value="" readonly>
</swp-edit-row>
</swp-edit-section>
</swp-card>
<!-- Tillæg -->
<swp-card>
<swp-section-label>@Model.LabelSupplements</swp-section-label>
<swp-edit-section>
<swp-edit-row id="card-weekday">
<swp-edit-label>@Model.LabelWeekdaySupplement</swp-edit-label>
<input type="text" id="value-weekday" data-type="number" value="@Model.WeekdaySupplement" readonly>
</swp-edit-row>
<swp-edit-row id="card-saturday">
<swp-edit-label>@Model.LabelSaturdaySupplement</swp-edit-label>
<input type="text" id="value-saturday" data-type="number" value="@Model.SaturdaySupplement" readonly>
</swp-edit-row>
<swp-edit-row id="card-sunday">
<swp-edit-label>@Model.LabelSundaySupplement</swp-edit-label>
<input type="text" id="value-sunday" data-type="number" value="@Model.SundaySupplement" readonly>
</swp-edit-row>
</swp-edit-section>
</swp-card>
<!-- Provision -->
<swp-card>
<swp-section-label>@Model.LabelCommission</swp-section-label>
<swp-edit-section>
<swp-edit-row id="card-productcommission">
<swp-edit-label>@Model.LabelProductCommission</swp-edit-label>
<input type="text" id="value-productcommission" data-type="number" value="@Model.ProductCommission" readonly>
</swp-edit-row>
<swp-edit-row id="card-servicecommission">
<swp-edit-label>@Model.LabelServiceCommission</swp-edit-label>
<input type="text" id="value-servicecommission" data-type="number" value="@Model.ServiceCommission" readonly>
</swp-edit-row>
</swp-edit-section>
</swp-card>
</div>
<swp-card>
<swp-section-label>@Model.LabelSalarySettings</swp-section-label>
<swp-edit-section>
<swp-edit-row>
<swp-edit-label>@Model.LabelHourlyRate</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.HourlyRate</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelMonthlyFixed</swp-edit-label>
<swp-edit-value contenteditable="true">@Model.MonthlyFixedSalary</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelCommission</swp-edit-label>
<swp-edit-value contenteditable="true">10%</swp-edit-value>
</swp-edit-row>
<swp-edit-row>
<swp-edit-label>@Model.LabelProductCommission</swp-edit-label>
<swp-edit-value contenteditable="true">5%</swp-edit-value>
</swp-edit-row>
</swp-edit-section>
<swp-section-label>@Model.LabelSalaryHistory</swp-section-label>
<swp-salary-table>
<swp-salary-table-header>
<swp-salary-table-cell>@Model.LabelPeriod</swp-salary-table-cell>
<swp-salary-table-cell>@Model.LabelGrossSalary</swp-salary-table-cell>
<swp-salary-table-cell></swp-salary-table-cell>
</swp-salary-table-header>
<swp-salary-table-body>
@foreach (var item in Model.SalaryHistory)
{
<swp-salary-table-row>
<swp-salary-table-cell>@item.Period</swp-salary-table-cell>
<swp-salary-table-cell class="mono">@item.GrossSalary</swp-salary-table-cell>
<swp-salary-table-cell><i class="ph ph-caret-right"></i></swp-salary-table-cell>
</swp-salary-table-row>
}
</swp-salary-table-body>
</swp-salary-table>
</swp-card>
</swp-detail-grid>
<!-- Rates drawer -->
<div id="rates-drawer" data-drawer="lg">
<swp-drawer-header>
<swp-drawer-title>@Model.LabelRatesDrawerTitle</swp-drawer-title>
<swp-drawer-close data-drawer-close>
<i class="ph ph-x"></i>
</swp-drawer-close>
</swp-drawer-header>
<swp-drawer-body class="rates-content">
<!-- Grundsatser -->
<swp-section-label>@Model.LabelBaseRates</swp-section-label>
<swp-data-table>
<swp-data-row>
<input type="checkbox" id="rate-normal-enabled" checked>
<swp-data-label>@Model.LabelNormalRate</swp-data-label>
<swp-data-input><input type="text" id="rate-normal" value="@Model.NormalRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-overtime-enabled" checked>
<swp-data-label>@Model.LabelOvertimeRate</swp-data-label>
<swp-data-input><input type="text" id="rate-overtime" value="@Model.OvertimeRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-course-enabled">
<swp-data-label class="disabled">@Model.LabelCourseRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-course" value="@Model.CourseRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-timeoff-enabled">
<swp-data-label class="disabled">@Model.LabelTimeOffRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-timeoff" value="@Model.TimeOffRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-paidleave-enabled">
<swp-data-label class="disabled">@Model.LabelPaidLeaveRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-paidleave" value="@Model.PaidLeaveRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-vacation-enabled" checked>
<swp-data-label>@Model.LabelVacationRate</swp-data-label>
<swp-data-input><input type="text" id="rate-vacation" value="@Model.VacationRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-office-enabled">
<swp-data-label class="disabled">@Model.LabelOfficeRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-office" value="@Model.OfficeRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-childsick-enabled">
<swp-data-label class="disabled">@Model.LabelChildSickRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-childsick" value="@Model.ChildSickRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-childhospital-enabled">
<swp-data-label class="disabled">@Model.LabelChildHospitalRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-childhospital" value="@Model.ChildHospitalRateValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-maternity-enabled">
<swp-data-label class="disabled">@Model.LabelMaternityRate</swp-data-label>
<swp-data-input class="disabled"><input type="text" id="rate-maternity" value="@Model.MaternityRateValue"> kr</swp-data-input>
</swp-data-row>
</swp-data-table>
<!-- Tillæg -->
<swp-data-section>
<swp-section-label>@Model.LabelSupplements</swp-section-label>
<swp-data-table>
<swp-data-row>
<input type="checkbox" id="rate-weekday-enabled" checked>
<swp-data-label>@Model.LabelWeekdaySupplementFull</swp-data-label>
<swp-data-input><input type="text" id="rate-weekday" value="@Model.WeekdaySupplementValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-saturday-enabled" checked>
<swp-data-label>@Model.LabelSaturdaySupplementFull</swp-data-label>
<swp-data-input><input type="text" id="rate-saturday" value="@Model.SaturdaySupplementValue"> kr</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-sunday-enabled" checked>
<swp-data-label>@Model.LabelSundaySupplement</swp-data-label>
<swp-data-input><input type="text" id="rate-sunday" value="@Model.SundaySupplementValue"> kr</swp-data-input>
</swp-data-row>
</swp-data-table>
</swp-data-section>
<!-- Provision -->
<swp-data-section>
<swp-section-label>@Model.LabelCommission</swp-section-label>
<swp-data-table>
<swp-data-row>
<input type="checkbox" id="rate-productcommission-enabled" checked>
<swp-data-label>@Model.LabelProductCommissionFull</swp-data-label>
<swp-data-input><input type="text" id="rate-productcommission" value="@Model.ProductCommissionValue"> %</swp-data-input>
</swp-data-row>
<swp-data-row>
<input type="checkbox" id="rate-servicecommission-enabled" checked>
<swp-data-label>@Model.LabelServiceCommissionFull</swp-data-label>
<swp-data-input><input type="text" id="rate-servicecommission" value="@Model.ServiceCommissionValue"> %</swp-data-input>
</swp-data-row>
</swp-data-table>
</swp-data-section>
</swp-drawer-body>
</div>

View file

@ -18,18 +18,86 @@ public class EmployeeDetailSalaryViewComponent : ViewComponent
var model = new EmployeeDetailSalaryViewModel
{
// Data
BankAccount = employee.BankAccount,
TaxCard = employee.TaxCard,
HourlyRate = employee.HourlyRate,
MonthlyFixedSalary = employee.MonthlyFixedSalary,
LabelPaymentInfo = _localization.Get("employees.detail.salary.paymentinfo"),
LabelBankAccount = _localization.Get("employees.detail.salary.bankaccount"),
LabelTaxCard = _localization.Get("employees.detail.salary.taxcard"),
LabelSalarySettings = _localization.Get("employees.detail.salary.settings"),
LabelHourlyRate = _localization.Get("employees.detail.salary.hourlyrate"),
LabelMonthlyFixed = _localization.Get("employees.detail.salary.monthlyfixed"),
// Rates
NormalRate = employee.NormalRate,
OvertimeRate = employee.OvertimeRate,
VacationRate = employee.VacationRate,
// Commission
MinimumPerHour = employee.MinimumPerHour,
ServiceCommission = employee.ServiceCommission,
ProductCommission = employee.ProductCommission,
// Supplements
WeekdaySupplement = employee.WeekdaySupplement,
SaturdaySupplement = employee.SaturdaySupplement,
SundaySupplement = employee.SundaySupplement,
// Labels
LabelRates = _localization.Get("employees.detail.salary.rates"),
LabelNormalRate = _localization.Get("employees.detail.salary.normalrate"),
LabelOvertimeRate = _localization.Get("employees.detail.salary.overtimerate"),
LabelVacationRate = _localization.Get("employees.detail.salary.vacationrate"),
LabelProvision = _localization.Get("employees.detail.salary.provision"),
LabelMinimumPerHour = _localization.Get("employees.detail.salary.minimumperhour"),
LabelServiceCommission = _localization.Get("employees.detail.salary.servicecommission"),
LabelProductCommission = _localization.Get("employees.detail.salary.productcommission"),
LabelSupplements = _localization.Get("employees.detail.salary.supplements"),
LabelWeekdaySupplement = _localization.Get("employees.detail.salary.weekdaysupplement"),
LabelSaturdaySupplement = _localization.Get("employees.detail.salary.saturdaysupplement"),
LabelSundaySupplement = _localization.Get("employees.detail.salary.sundaysupplement"),
LabelSalaryHistory = _localization.Get("employees.detail.salary.history"),
LabelPeriod = _localization.Get("employees.detail.salary.period"),
LabelGrossSalary = _localization.Get("employees.detail.salary.grosssalary"),
LabelView = _localization.Get("employees.detail.salary.view"),
LabelEdit = _localization.Get("common.edit"),
LabelRatesDrawerTitle = _localization.Get("employees.detail.salary.ratesdrawertitle"),
LabelBaseRates = _localization.Get("employees.detail.salary.baserates"),
LabelCourseRate = _localization.Get("employees.detail.salary.courserate"),
LabelTimeOffRate = _localization.Get("employees.detail.salary.timeoffrate"),
LabelPaidLeaveRate = _localization.Get("employees.detail.salary.paidleaverate"),
LabelOfficeRate = _localization.Get("employees.detail.salary.officerate"),
LabelChildSickRate = _localization.Get("employees.detail.salary.childsickrate"),
LabelChildHospitalRate = _localization.Get("employees.detail.salary.childhospitalrate"),
LabelMaternityRate = _localization.Get("employees.detail.salary.maternityrate"),
LabelWeekdaySupplementFull = _localization.Get("employees.detail.salary.weekdaysupplementfull"),
LabelSaturdaySupplementFull = _localization.Get("employees.detail.salary.saturdaysupplementfull"),
LabelCommission = _localization.Get("employees.detail.salary.commission"),
LabelProductCommission = _localization.Get("employees.detail.salary.productcommission")
LabelProductCommissionFull = _localization.Get("employees.detail.salary.productcommissionfull"),
LabelServiceCommissionFull = _localization.Get("employees.detail.salary.servicecommissionfull"),
// Rate values (numeric only for drawer inputs)
NormalRateValue = employee.NormalRateValue,
OvertimeRateValue = employee.OvertimeRateValue,
CourseRateValue = employee.CourseRateValue,
TimeOffRateValue = employee.TimeOffRateValue,
PaidLeaveRateValue = employee.PaidLeaveRateValue,
VacationRateValue = employee.VacationRateValue,
OfficeRateValue = employee.OfficeRateValue,
ChildSickRateValue = employee.ChildSickRateValue,
ChildHospitalRateValue = employee.ChildHospitalRateValue,
MaternityRateValue = employee.MaternityRateValue,
WeekdaySupplementValue = employee.WeekdaySupplementValue,
SaturdaySupplementValue = employee.SaturdaySupplementValue,
SundaySupplementValue = employee.SundaySupplementValue,
ProductCommissionValue = employee.ProductCommissionValue,
ServiceCommissionValue = employee.ServiceCommissionValue,
// Mock salary history
SalaryHistory = new List<SalaryHistoryItem>
{
new() { Period = "Januar 2026", GrossSalary = "34.063,50 kr" },
new() { Period = "December 2025", GrossSalary = "31.845,00 kr" },
new() { Period = "November 2025", GrossSalary = "33.290,25 kr" },
new() { Period = "Oktober 2025", GrossSalary = "32.156,75 kr" },
new() { Period = "September 2025", GrossSalary = "34.520,00 kr" }
}
};
return View(model);
@ -38,16 +106,83 @@ public class EmployeeDetailSalaryViewComponent : ViewComponent
public class EmployeeDetailSalaryViewModel
{
// Data
public required string BankAccount { get; init; }
public required string TaxCard { get; init; }
public required string HourlyRate { get; init; }
public required string MonthlyFixedSalary { get; init; }
public required string LabelPaymentInfo { get; init; }
public required string LabelBankAccount { get; init; }
public required string LabelTaxCard { get; init; }
public required string LabelSalarySettings { get; init; }
public required string LabelHourlyRate { get; init; }
public required string LabelMonthlyFixed { get; init; }
public required string LabelCommission { get; init; }
// Rates
public required string NormalRate { get; init; }
public required string OvertimeRate { get; init; }
public required string VacationRate { get; init; }
// Commission
public required string MinimumPerHour { get; init; }
public required string ServiceCommission { get; init; }
public required string ProductCommission { get; init; }
// Supplements
public required string WeekdaySupplement { get; init; }
public required string SaturdaySupplement { get; init; }
public required string SundaySupplement { get; init; }
// Labels
public required string LabelRates { get; init; }
public required string LabelNormalRate { get; init; }
public required string LabelOvertimeRate { get; init; }
public required string LabelVacationRate { get; init; }
public required string LabelProvision { get; init; }
public required string LabelMinimumPerHour { get; init; }
public required string LabelServiceCommission { get; init; }
public required string LabelProductCommission { get; init; }
public required string LabelSupplements { get; init; }
public required string LabelWeekdaySupplement { get; init; }
public required string LabelSaturdaySupplement { get; init; }
public required string LabelSundaySupplement { get; init; }
public required string LabelSalaryHistory { get; init; }
public required string LabelPeriod { get; init; }
public required string LabelGrossSalary { get; init; }
public required string LabelView { get; init; }
public required string LabelEdit { get; init; }
public required string LabelRatesDrawerTitle { get; init; }
public required string LabelBaseRates { get; init; }
public required string LabelCourseRate { get; init; }
public required string LabelTimeOffRate { get; init; }
public required string LabelPaidLeaveRate { get; init; }
public required string LabelOfficeRate { get; init; }
public required string LabelChildSickRate { get; init; }
public required string LabelChildHospitalRate { get; init; }
public required string LabelMaternityRate { get; init; }
public required string LabelWeekdaySupplementFull { get; init; }
public required string LabelSaturdaySupplementFull { get; init; }
public required string LabelCommission { get; init; }
public required string LabelProductCommissionFull { get; init; }
public required string LabelServiceCommissionFull { get; init; }
// Rate values (for drawer inputs)
public required string NormalRateValue { get; init; }
public required string OvertimeRateValue { get; init; }
public required string CourseRateValue { get; init; }
public required string TimeOffRateValue { get; init; }
public required string PaidLeaveRateValue { get; init; }
public required string VacationRateValue { get; init; }
public required string OfficeRateValue { get; init; }
public required string ChildSickRateValue { get; init; }
public required string ChildHospitalRateValue { get; init; }
public required string MaternityRateValue { get; init; }
public required string WeekdaySupplementValue { get; init; }
public required string SaturdaySupplementValue { get; init; }
public required string SundaySupplementValue { get; init; }
public required string ProductCommissionValue { get; init; }
public required string ServiceCommissionValue { get; init; }
// Salary History (mock data)
public List<SalaryHistoryItem> SalaryHistory { get; init; } = new();
}
public class SalaryHistoryItem
{
public required string Period { get; init; }
public required string GrossSalary { get; init; }
}