fixa adda drivers

This commit is contained in:
larssand
2026-03-14 20:17:28 +01:00
parent 3119d98bb6
commit da1e60fe52
2 changed files with 187 additions and 2 deletions

View File

@@ -251,6 +251,12 @@ const TRANSLATIONS = {
"timing.no_session_selected": "Ingen session vald.", "timing.no_session_selected": "Ingen session vald.",
"timing.no_passings": "Inga passeringar registrerade.", "timing.no_passings": "Inga passeringar registrerade.",
"timing.details": "Detaljer", "timing.details": "Detaljer",
"timing.add_driver": "Lägg till förare",
"timing.add_car": "Lägg till bil",
"timing.quick_add_hint": "Snabbregistrera transponder",
"timing.quick_add_title": "Snabbregistrering",
"timing.quick_add_driver_title": "Lägg till förare från transponder",
"timing.quick_add_car_title": "Lägg till bil från transponder",
"timing.open_overlay": "Öppna overlay", "timing.open_overlay": "Öppna overlay",
"timing.open_speaker_overlay": "Speaker overlay", "timing.open_speaker_overlay": "Speaker overlay",
"timing.open_results_overlay": "Result overlay", "timing.open_results_overlay": "Result overlay",
@@ -361,6 +367,8 @@ const TRANSLATIONS = {
"table.ahead_gap": "Gap fram", "table.ahead_gap": "Gap fram",
"table.own_delta": "Eget delta", "table.own_delta": "Eget delta",
"common.delete": "Ta bort", "common.delete": "Ta bort",
"common.cancel": "Avbryt",
"common.save": "Spara",
"common.edit": "Redigera", "common.edit": "Redigera",
"common.unknown_driver": "Okänd förare", "common.unknown_driver": "Okänd förare",
"common.unknown_car": "Okänd bil", "common.unknown_car": "Okänd bil",
@@ -389,8 +397,10 @@ const TRANSLATIONS = {
"validation.invalid_date": "Datum måste vara i format YYYY-MM-DD.", "validation.invalid_date": "Datum måste vara i format YYYY-MM-DD.",
"edit.class_name": "Redigera klassnamn", "edit.class_name": "Redigera klassnamn",
"edit.driver_name": "Redigera förarnamn", "edit.driver_name": "Redigera förarnamn",
"edit.new_driver_name": "Namn på ny förare",
"edit.driver_transponder": "Redigera personlig transponder (kan vara tom)", "edit.driver_transponder": "Redigera personlig transponder (kan vara tom)",
"edit.car_name": "Redigera bilnamn", "edit.car_name": "Redigera bilnamn",
"edit.new_car_name": "Namn på ny bil",
"edit.car_transponder": "Redigera bilens transponder", "edit.car_transponder": "Redigera bilens transponder",
"edit.event_name": "Redigera eventnamn", "edit.event_name": "Redigera eventnamn",
"edit.event_date": "Redigera eventdatum (YYYY-MM-DD)", "edit.event_date": "Redigera eventdatum (YYYY-MM-DD)",
@@ -712,6 +722,12 @@ const TRANSLATIONS = {
"timing.no_session_selected": "No session selected.", "timing.no_session_selected": "No session selected.",
"timing.no_passings": "No passings recorded.", "timing.no_passings": "No passings recorded.",
"timing.details": "Details", "timing.details": "Details",
"timing.add_driver": "Add driver",
"timing.add_car": "Add car",
"timing.quick_add_hint": "Quick-register transponder",
"timing.quick_add_title": "Quick add",
"timing.quick_add_driver_title": "Add driver from transponder",
"timing.quick_add_car_title": "Add car from transponder",
"timing.open_overlay": "Open overlay", "timing.open_overlay": "Open overlay",
"timing.open_speaker_overlay": "Speaker overlay", "timing.open_speaker_overlay": "Speaker overlay",
"timing.open_results_overlay": "Results overlay", "timing.open_results_overlay": "Results overlay",
@@ -822,6 +838,8 @@ const TRANSLATIONS = {
"table.ahead_gap": "Gap ahead", "table.ahead_gap": "Gap ahead",
"table.own_delta": "Own delta", "table.own_delta": "Own delta",
"common.delete": "Delete", "common.delete": "Delete",
"common.cancel": "Cancel",
"common.save": "Save",
"common.edit": "Edit", "common.edit": "Edit",
"common.unknown_driver": "Unknown Driver", "common.unknown_driver": "Unknown Driver",
"common.unknown_car": "Unknown Car", "common.unknown_car": "Unknown Car",
@@ -850,8 +868,10 @@ const TRANSLATIONS = {
"validation.invalid_date": "Date must be in YYYY-MM-DD format.", "validation.invalid_date": "Date must be in YYYY-MM-DD format.",
"edit.class_name": "Edit class name", "edit.class_name": "Edit class name",
"edit.driver_name": "Edit driver name", "edit.driver_name": "Edit driver name",
"edit.new_driver_name": "New driver name",
"edit.driver_transponder": "Edit personal transponder (can be empty)", "edit.driver_transponder": "Edit personal transponder (can be empty)",
"edit.car_name": "Edit car name", "edit.car_name": "Edit car name",
"edit.new_car_name": "New car name",
"edit.car_transponder": "Edit car transponder", "edit.car_transponder": "Edit car transponder",
"edit.event_name": "Edit event name", "edit.event_name": "Edit event name",
"edit.event_date": "Edit event date (YYYY-MM-DD)", "edit.event_date": "Edit event date (YYYY-MM-DD)",
@@ -954,6 +974,7 @@ let appVersionPollTimer = null;
let baselineAppVersion = ""; let baselineAppVersion = "";
let selectedLeaderboardKey = null; let selectedLeaderboardKey = null;
let selectedGridSessionId = null; let selectedGridSessionId = null;
let quickAddDraft = null;
let overlaySyncTimer = null; let overlaySyncTimer = null;
let overlayRotationTimer = null; let overlayRotationTimer = null;
let overlayRotationIndex = 0; let overlayRotationIndex = 0;
@@ -3174,6 +3195,8 @@ function renderTiming() {
</div> </div>
</section> </section>
${active ? renderQuickAddPanel(active) : ""}
<section class="panel mt-16"> <section class="panel mt-16">
<div class="panel-header"><h3>${t("timing.speaker_panel")}</h3></div> <div class="panel-header"><h3>${t("timing.speaker_panel")}</h3></div>
<div class="panel-body"> <div class="panel-body">
@@ -3224,6 +3247,59 @@ function renderTiming() {
}); });
}); });
if (active && selectedRow) {
bindQuickAddActions(active, selectedRow.transponder, "leaderboardModal");
}
if (active) {
ensureSessionResult(active.id)
.passings.slice(-20)
.reverse()
.forEach((passing, index) => {
bindQuickAddActions(active, passing.transponder, `recentPassing-${index}`);
});
}
document.getElementById("quickAddCancel")?.addEventListener("click", () => {
quickAddDraft = null;
renderView();
});
document.getElementById("quickAddForm")?.addEventListener("submit", (event) => {
event.preventDefault();
if (!active || !quickAddDraft) {
return;
}
const form = new FormData(event.currentTarget);
const name = String(form.get("name") || "").trim();
if (!name) {
return;
}
const transponder = String(form.get("transponder") || "").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,
});
}
} else if (!state.cars.some((item) => String(item.transponder || "").trim() === transponder)) {
state.cars.push({
id: uid("car"),
name,
transponder,
});
}
quickAddDraft = null;
saveState();
renderView();
});
document.getElementById("leaderboardModalClose")?.addEventListener("click", () => { document.getElementById("leaderboardModalClose")?.addEventListener("click", () => {
selectedLeaderboardKey = null; selectedLeaderboardKey = null;
renderView(); renderView();
@@ -3346,6 +3422,42 @@ function renderSpeakerToggle(settingKey, labelKey) {
`; `;
} }
function renderQuickAddPanel(session) {
if (!quickAddDraft || !quickAddDraft.transponder) {
return "";
}
const classOptions = state.classes
.map(
(item) => `<option value="${item.id}" ${item.id === (quickAddDraft.classId || getPreferredClassId(session)) ? "selected" : ""}>${escapeHtml(item.name)}</option>`
)
.join("");
const isDriver = quickAddDraft.type === "driver";
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">
<input name="transponder" value="${escapeHtml(quickAddDraft.transponder)}" readonly />
<input
name="name"
required
autofocus
placeholder="${t(isDriver ? "drivers.name_placeholder" : "cars.name_placeholder")}"
value="${escapeHtml(quickAddDraft.name || "")}"
/>
${
isDriver
? `<select name="classId">${classOptions}</select>`
: `<div class="hint quick-add-spacer">${t("timing.quick_add_hint")}</div>`
}
<div class="actions-inline">
<button class="btn btn-primary" type="submit">${t("common.save")}</button>
<button class="btn" id="quickAddCancel" type="button">${t("common.cancel")}</button>
</div>
</form>
</section>
`;
}
function renderGuide() { function renderGuide() {
dom.view.innerHTML = ` dom.view.innerHTML = `
<section class="panel"> <section class="panel">
@@ -3696,6 +3808,67 @@ function renderOverlaySidePanel(panel) {
`; `;
} }
function getQuickAddState(transponder) {
const normalized = String(transponder || "").trim();
const driver = state.drivers.find((item) => String(item.transponder || "").trim() === normalized) || null;
const car = state.cars.find((item) => String(item.transponder || "").trim() === normalized) || null;
return {
transponder: normalized,
hasDriver: Boolean(driver),
hasCar: Boolean(car),
};
}
function getPreferredClassId(session) {
const event = state.events.find((item) => item.id === session?.eventId);
if (event?.classId) {
return event.classId;
}
return state.classes[0]?.id || "";
}
function beginQuickAddDraft(session, type, transponder) {
const normalized = String(transponder || "").trim();
if (!normalized) {
return;
}
if (type === "driver" && state.drivers.some((item) => String(item.transponder || "").trim() === normalized)) {
return;
}
if (type === "car" && state.cars.some((item) => String(item.transponder || "").trim() === normalized)) {
return;
}
quickAddDraft = {
type,
transponder: normalized,
classId: getPreferredClassId(session),
name: type === "driver" ? normalized : `Car ${normalized}`,
};
renderView();
}
function renderQuickAddActions(session, transponder, idPrefix) {
const quickState = getQuickAddState(transponder);
if (!quickState.transponder || (quickState.hasDriver && quickState.hasCar)) {
return "";
}
return `
<div class="actions-inline quick-add-actions">
${!quickState.hasDriver ? `<button class="btn btn-mini" id="${idPrefix}-add-driver">${t("timing.add_driver")}</button>` : ""}
${!quickState.hasCar ? `<button class="btn btn-mini" id="${idPrefix}-add-car">${t("timing.add_car")}</button>` : ""}
</div>
`;
}
function bindQuickAddActions(session, transponder, idPrefix) {
document.getElementById(`${idPrefix}-add-driver`)?.addEventListener("click", () => {
beginQuickAddDraft(session, "driver", transponder);
});
document.getElementById(`${idPrefix}-add-car`)?.addEventListener("click", () => {
beginQuickAddDraft(session, "car", transponder);
});
}
function renderLeaderboardModal(session, row) { function renderLeaderboardModal(session, row) {
const passings = getCompetitorPassings(session, row); const passings = getCompetitorPassings(session, row);
return ` return `
@@ -3708,6 +3881,7 @@ function renderLeaderboardModal(session, row) {
<div class="panel-body"> <div class="panel-body">
<p><strong>${escapeHtml(row.driverName)}</strong> • ${escapeHtml(row.carName)}</p> <p><strong>${escapeHtml(row.driverName)}</strong> • ${escapeHtml(row.carName)}</p>
<p>${t("table.transponder")}: ${escapeHtml(row.transponder)}</p> <p>${t("table.transponder")}: ${escapeHtml(row.transponder)}</p>
${renderQuickAddActions(session, row.transponder, "leaderboardModal")}
<p>${t("table.laps")}: ${row.laps}</p> <p>${t("table.laps")}: ${row.laps}</p>
<p>${t("timing.total_time")}: ${escapeHtml(row.resultDisplay)}</p> <p>${t("timing.total_time")}: ${escapeHtml(row.resultDisplay)}</p>
<p>${t("table.best_lap")}: ${formatLap(row.bestLapMs)}</p> <p>${t("table.best_lap")}: ${formatLap(row.bestLapMs)}</p>
@@ -3843,8 +4017,8 @@ function renderRecentPassings(session) {
} }
return renderTable( return renderTable(
[t("table.time"), t("table.transponder"), t("table.driver"), t("table.car"), t("table.loop"), t("table.strength")], [t("table.time"), t("table.transponder"), t("table.driver"), t("table.car"), t("table.loop"), t("table.strength"), ""],
items.map((p) => { items.map((p, index) => {
return ` return `
<tr> <tr>
<td>${new Date(p.timestamp).toLocaleTimeString()}</td> <td>${new Date(p.timestamp).toLocaleTimeString()}</td>
@@ -3853,6 +4027,7 @@ function renderRecentPassings(session) {
<td>${escapeHtml(p.carName || "-")}</td> <td>${escapeHtml(p.carName || "-")}</td>
<td>${escapeHtml(p.loopId || "-")}</td> <td>${escapeHtml(p.loopId || "-")}</td>
<td>${p.strength ?? "-"}</td> <td>${p.strength ?? "-"}</td>
<td>${renderQuickAddActions(session, p.transponder, `recentPassing-${index}`)}</td>
</tr> </tr>
`; `;
}) })

View File

@@ -439,6 +439,16 @@ select:focus {
flex-wrap: wrap; flex-wrap: wrap;
} }
.quick-add-actions {
justify-content: flex-end;
}
.quick-add-spacer {
display: flex;
align-items: center;
min-height: 42px;
}
.check-grid { .check-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));