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

@ -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; }
}