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

@ -890,6 +890,7 @@
// wwwroot/ts/modules/employees.ts
var EmployeesController = class {
constructor() {
this.ratesSync = null;
this.listView = null;
this.detailView = null;
this.listView = document.getElementById("employees-list-view");
@ -901,6 +902,7 @@
this.setupBackNavigation();
this.setupHistoryNavigation();
this.restoreStateFromUrl();
this.ratesSync = new RatesSyncController();
}
/**
* Setup popstate listener for browser back/forward
@ -1041,6 +1043,93 @@
}
}
};
var RatesSyncController = class {
constructor() {
this.drawer = null;
this.drawer = document.getElementById("rates-drawer");
if (!this.drawer) return;
this.setupCheckboxListeners();
this.setupInputListeners();
}
/**
* Extract rate key from checkbox ID (e.g., "rate-normal-enabled" "normal")
*/
extractRateKey(checkboxId) {
const match = checkboxId.match(/^rate-(.+)-enabled$/);
return match ? match[1] : null;
}
/**
* Setup checkbox change listeners in drawer
*/
setupCheckboxListeners() {
if (!this.drawer) return;
this.drawer.addEventListener("change", (e) => {
const target = e.target;
if (target.type !== "checkbox" || !target.id) return;
const rateKey = this.extractRateKey(target.id);
if (!rateKey) return;
const isChecked = target.checked;
const row = target.closest("swp-data-row");
if (!row) return;
const label = row.querySelector("swp-data-label");
const input = row.querySelector("swp-data-input");
if (label) label.classList.toggle("disabled", !isChecked);
if (input) input.classList.toggle("disabled", !isChecked);
this.toggleCardRow(rateKey, isChecked);
if (isChecked) {
const textInput = document.getElementById(`rate-${rateKey}`);
if (textInput) {
this.syncValueToCard(rateKey, textInput.value);
}
}
});
}
/**
* Setup input change listeners in drawer
*/
setupInputListeners() {
if (!this.drawer) return;
this.drawer.addEventListener("input", (e) => {
const target = e.target;
if (target.type !== "text" || !target.id) return;
const match = target.id.match(/^rate-(.+)$/);
if (!match) return;
const rateKey = match[1];
if (rateKey.endsWith("-enabled")) return;
this.syncValueToCard(rateKey, target.value);
});
}
/**
* Toggle card row visibility by ID
*/
toggleCardRow(rateKey, visible) {
const cardRow = document.getElementById(`card-${rateKey}`);
if (cardRow) {
cardRow.style.display = visible ? "" : "none";
}
}
/**
* Format number with 2 decimals using Danish locale (comma as decimal separator)
*/
formatNumber(value) {
const normalized = value.replace(",", ".");
const num = parseFloat(normalized);
if (isNaN(num)) return value;
return num.toFixed(2).replace(".", ",");
}
/**
* Sync value from drawer to card by ID
*/
syncValueToCard(rateKey, value) {
const cardInput = document.getElementById(`value-${rateKey}`);
if (!cardInput) return;
const textInput = document.getElementById(`rate-${rateKey}`);
const inputContainer = textInput?.closest("swp-data-input");
const unit = inputContainer?.textContent?.trim().replace(value, "").trim() || "kr";
const formattedValue = this.formatNumber(value);
cardInput.value = `${formattedValue} ${unit}`;
}
};
// wwwroot/ts/app.ts
var App = class {