Add brand fields for drivers and cars

This commit is contained in:
larssand
2026-03-16 19:57:56 +01:00
parent 4cabab4cda
commit baebb6ac9d

View File

@@ -86,11 +86,13 @@ const TRANSLATIONS = {
"classes.title": "Klasser",
"drivers.create": "Skapa förare",
"drivers.name_placeholder": "Förarnamn",
"drivers.brand_placeholder": "Team / märke (valfritt)",
"drivers.transponder_placeholder": "Personlig transponder (valfritt)",
"drivers.add": "Lägg till förare",
"drivers.title": "Förare",
"cars.create": "Skapa bil",
"cars.name_placeholder": "Bilnamn eller nummer",
"cars.brand_placeholder": "Märke / modell (valfritt)",
"cars.transponder_placeholder": "Bilens transponder",
"cars.add": "Lägg till bil",
"cars.title": "Bilar",
@@ -460,6 +462,7 @@ const TRANSLATIONS = {
"settings.sync_now": "Synka nu",
"settings.export_json": "Exportera JSON",
"table.name": "Namn",
"table.brand": "Märke",
"table.class": "Klass",
"table.transponder": "Transponder",
"table.delete": "Ta bort",
@@ -723,11 +726,13 @@ const TRANSLATIONS = {
"classes.title": "Classes",
"drivers.create": "Create Driver",
"drivers.name_placeholder": "Driver name",
"drivers.brand_placeholder": "Team / brand (optional)",
"drivers.transponder_placeholder": "Personal transponder (optional)",
"drivers.add": "Add Driver",
"drivers.title": "Drivers",
"cars.create": "Create Track Car",
"cars.name_placeholder": "Car name or number",
"cars.brand_placeholder": "Brand / model (optional)",
"cars.transponder_placeholder": "Car transponder",
"cars.add": "Add Car",
"cars.title": "Cars",
@@ -1097,6 +1102,7 @@ const TRANSLATIONS = {
"settings.sync_now": "Sync Now",
"settings.export_json": "Export JSON",
"table.name": "Name",
"table.brand": "Brand",
"table.class": "Class",
"table.transponder": "Transponder",
"table.delete": "Delete",
@@ -1454,6 +1460,8 @@ function seedDefaultData() {
state.settings.racePresets = [];
}
state.drivers = state.drivers.map((driver) => normalizeDriver(driver)).filter((driver) => driver.name);
state.cars = state.cars.map((car) => normalizeCar(car)).filter((car) => car.name);
state.events = state.events.map((event) => normalizeEvent(event));
state.sessions = state.sessions.map((session) => normalizeSession(session));
@@ -1600,8 +1608,8 @@ function setupLanguageControl() {
function buildPersistableState() {
return {
classes: state.classes,
drivers: state.drivers,
cars: state.cars,
drivers: state.drivers.map((driver) => normalizeDriver(driver)),
cars: state.cars.map((car) => normalizeCar(car)),
events: state.events,
sessions: state.sessions.map((session) => normalizeSession(session)),
resultsBySession: state.resultsBySession,
@@ -1755,8 +1763,8 @@ async function hydrateFromBackend() {
function applyPersistedState(persisted) {
state.classes = persisted.classes || [];
state.drivers = persisted.drivers || [];
state.cars = persisted.cars || [];
state.drivers = (persisted.drivers || []).map((driver) => normalizeDriver(driver)).filter((driver) => driver.name);
state.cars = (persisted.cars || []).map((car) => normalizeCar(car)).filter((car) => car.name);
state.events = (persisted.events || []).map((event) => normalizeEvent(event));
state.sessions = (persisted.sessions || []).map((session) => normalizeSession(session));
state.resultsBySession = persisted.resultsBySession || {};
@@ -1992,6 +2000,27 @@ function buildRaceFormatConfigFromForm(form, event) {
};
}
function normalizeDriver(driver) {
const item = driver && typeof driver === "object" ? driver : {};
return {
id: item.id || uid("driver"),
name: String(item.name || "").trim(),
classId: String(item.classId || ""),
brand: String(item.brand || "").trim(),
transponder: String(item.transponder || "").trim(),
};
}
function normalizeCar(car) {
const item = car && typeof car === "object" ? car : {};
return {
id: item.id || uid("car"),
name: String(item.name || "").trim(),
brand: String(item.brand || "").trim(),
transponder: String(item.transponder || "").trim(),
};
}
function normalizeEvent(event) {
return {
...event,
@@ -2811,9 +2840,10 @@ function renderDrivers() {
dom.view.innerHTML = `
<section class="panel">
<div class="panel-header"><h3>${t("drivers.create")}</h3></div>
<form id="driverForm" class="panel-body form-grid cols-4">
<form id="driverForm" class="panel-body form-grid cols-5">
<input required name="name" placeholder="${t("drivers.name_placeholder")}" />
<select name="classId">${classOptions}</select>
<input name="brand" placeholder="${t("drivers.brand_placeholder")}" />
<input name="transponder" placeholder="${t("drivers.transponder_placeholder")}" />
<button class="btn btn-primary" type="submit">${t("drivers.add")}</button>
</form>
@@ -2823,12 +2853,13 @@ function renderDrivers() {
<div class="panel-header"><h3>${t("drivers.title")}</h3></div>
<div class="panel-body">
${renderTable(
[t("table.name"), t("table.class"), t("table.transponder"), t("events.actions")],
[t("table.name"), t("table.class"), t("table.brand"), t("table.transponder"), t("events.actions")],
state.drivers.map(
(d) => `
<tr>
<td>${escapeHtml(d.name)}</td>
<td>${escapeHtml(getClassName(d.classId))}</td>
<td>${escapeHtml(d.brand || "-")}</td>
<td>${escapeHtml(d.transponder || "-")}</td>
<td class="actions-inline">
<button id="driver-edit-${d.id}" class="btn">${t("common.edit")}</button>
@@ -2850,7 +2881,7 @@ function renderDrivers() {
<h3>${t("common.edit")}</h3>
<button class="btn" id="driverEditCancel">${t("common.cancel")}</button>
</div>
<form id="driverEditForm" class="panel-body form-grid cols-3">
<form id="driverEditForm" class="panel-body form-grid cols-4">
<input name="name" required value="${escapeHtml(editingDriver.name)}" placeholder="${t("drivers.name_placeholder")}" />
<select name="classId">
${state.classes
@@ -2860,6 +2891,7 @@ function renderDrivers() {
)
.join("")}
</select>
<input name="brand" value="${escapeHtml(editingDriver.brand || "")}" placeholder="${t("drivers.brand_placeholder")}" />
<input name="transponder" value="${escapeHtml(editingDriver.transponder || "")}" placeholder="${t("drivers.transponder_placeholder")}" />
<p class="form-error" id="driverEditError" hidden></p>
<div class="actions-inline">
@@ -2877,12 +2909,15 @@ function renderDrivers() {
document.getElementById("driverForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
state.drivers.push({
id: uid("driver"),
name: String(form.get("name")).trim(),
classId: String(form.get("classId")),
transponder: String(form.get("transponder") || "").trim(),
});
state.drivers.push(
normalizeDriver({
id: uid("driver"),
name: String(form.get("name")).trim(),
classId: String(form.get("classId")),
brand: String(form.get("brand") || "").trim(),
transponder: String(form.get("transponder") || "").trim(),
})
);
saveState();
renderView();
});
@@ -2933,6 +2968,7 @@ function renderDrivers() {
const form = new FormData(event.currentTarget);
const cleanedName = String(form.get("name") || "").trim();
const cleanedClassId = String(form.get("classId") || "").trim();
const cleanedBrand = String(form.get("brand") || "").trim();
const cleanedTp = String(form.get("transponder") || "").trim();
if (!cleanedName) {
setFormError("driverEditError", t("validation.required_name"));
@@ -2945,6 +2981,7 @@ function renderDrivers() {
setFormError("driverEditError", "");
editingDriver.name = cleanedName;
editingDriver.classId = cleanedClassId || editingDriver.classId;
editingDriver.brand = cleanedBrand;
editingDriver.transponder = cleanedTp;
selectedDriverEditId = null;
saveState();
@@ -2957,8 +2994,9 @@ function renderCars() {
dom.view.innerHTML = `
<section class="panel">
<div class="panel-header"><h3>${t("cars.create")}</h3></div>
<form id="carForm" class="panel-body form-grid cols-3">
<form id="carForm" class="panel-body form-grid cols-4">
<input required name="name" placeholder="${t("cars.name_placeholder")}" />
<input name="brand" placeholder="${t("cars.brand_placeholder")}" />
<input required name="transponder" placeholder="${t("cars.transponder_placeholder")}" />
<button class="btn btn-primary" type="submit">${t("cars.add")}</button>
</form>
@@ -2968,11 +3006,12 @@ function renderCars() {
<div class="panel-header"><h3>${t("cars.title")}</h3></div>
<div class="panel-body">
${renderTable(
[t("table.car"), t("table.transponder"), t("events.actions")],
[t("table.car"), t("table.brand"), t("table.transponder"), t("events.actions")],
state.cars.map(
(c) => `
<tr>
<td>${escapeHtml(c.name)}</td>
<td>${escapeHtml(c.brand || "-")}</td>
<td>${escapeHtml(c.transponder)}</td>
<td class="actions-inline">
<button id="car-edit-${c.id}" class="btn">${t("common.edit")}</button>
@@ -2994,13 +3033,14 @@ function renderCars() {
<h3>${t("common.edit")}</h3>
<button class="btn" id="carEditCancel">${t("common.cancel")}</button>
</div>
<form id="carEditForm" class="panel-body form-grid cols-3">
<form id="carEditForm" class="panel-body form-grid cols-4">
<input name="name" required value="${escapeHtml(editingCar.name)}" placeholder="${t("cars.name_placeholder")}" />
<input name="brand" value="${escapeHtml(editingCar.brand || "")}" placeholder="${t("cars.brand_placeholder")}" />
<input
name="transponder"
required
value="${escapeHtml(editingCar.transponder || "")}"
placeholder="${t("cars.transponder_placeholder")}"
placeholder="${t("cars.transponder_placeholder")}"
/>
<p class="form-error" id="carEditError" hidden></p>
<div class="actions-inline">
@@ -3018,11 +3058,14 @@ function renderCars() {
document.getElementById("carForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
state.cars.push({
id: uid("car"),
name: String(form.get("name")).trim(),
transponder: String(form.get("transponder")).trim(),
});
state.cars.push(
normalizeCar({
id: uid("car"),
name: String(form.get("name")).trim(),
brand: String(form.get("brand") || "").trim(),
transponder: String(form.get("transponder")).trim(),
})
);
saveState();
renderView();
});
@@ -3072,6 +3115,7 @@ function renderCars() {
}
const form = new FormData(event.currentTarget);
const cleanedName = String(form.get("name") || "").trim();
const cleanedBrand = String(form.get("brand") || "").trim();
const cleanedTp = String(form.get("transponder") || "").trim();
if (!cleanedName) {
setFormError("carEditError", t("validation.required_name"));
@@ -3083,6 +3127,7 @@ function renderCars() {
}
setFormError("carEditError", "");
editingCar.name = cleanedName;
editingCar.brand = cleanedBrand;
editingCar.transponder = cleanedTp;
selectedCarEditId = null;
saveState();
@@ -4737,24 +4782,31 @@ function renderTiming() {
return;
}
const transponder = String(form.get("transponder") || "").trim();
const brand = String(form.get("brand") || "").trim();
if (!transponder) {
return;
}
if (quickAddDraft.type === "driver") {
if (!state.drivers.some((item) => String(item.transponder || "").trim() === transponder)) {
state.drivers.push({
id: uid("driver"),
name,
classId: String(form.get("classId") || getPreferredClassId(active)),
transponder,
});
state.drivers.push(
normalizeDriver({
id: uid("driver"),
name,
classId: String(form.get("classId") || getPreferredClassId(active)),
brand,
transponder,
})
);
}
} else if (!state.cars.some((item) => String(item.transponder || "").trim() === transponder)) {
state.cars.push({
id: uid("car"),
name,
transponder,
});
state.cars.push(
normalizeCar({
id: uid("car"),
name,
brand,
transponder,
})
);
}
quickAddDraft = null;
saveState();
@@ -5148,7 +5200,7 @@ function renderQuickAddPanel(session) {
return `
<section class="panel mt-16">
<div class="panel-header"><h3>${t(isDriver ? "timing.quick_add_driver_title" : "timing.quick_add_car_title")}</h3></div>
<form id="quickAddForm" class="panel-body form-grid cols-4">
<form id="quickAddForm" class="panel-body form-grid cols-5">
<input name="transponder" value="${escapeHtml(quickAddDraft.transponder)}" readonly />
<input
name="name"
@@ -5157,6 +5209,11 @@ function renderQuickAddPanel(session) {
placeholder="${t(isDriver ? "drivers.name_placeholder" : "cars.name_placeholder")}"
value="${escapeHtml(quickAddDraft.name || "")}"
/>
<input
name="brand"
placeholder="${t(isDriver ? "drivers.brand_placeholder" : "cars.brand_placeholder")}"
value="${escapeHtml(quickAddDraft.brand || "")}"
/>
${
isDriver
? `<select name="classId">${classOptions}</select>`