fixa adda drivers
This commit is contained in:
179
src/app.js
179
src/app.js
@@ -251,6 +251,12 @@ const TRANSLATIONS = {
|
||||
"timing.no_session_selected": "Ingen session vald.",
|
||||
"timing.no_passings": "Inga passeringar registrerade.",
|
||||
"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_speaker_overlay": "Speaker overlay",
|
||||
"timing.open_results_overlay": "Result overlay",
|
||||
@@ -361,6 +367,8 @@ const TRANSLATIONS = {
|
||||
"table.ahead_gap": "Gap fram",
|
||||
"table.own_delta": "Eget delta",
|
||||
"common.delete": "Ta bort",
|
||||
"common.cancel": "Avbryt",
|
||||
"common.save": "Spara",
|
||||
"common.edit": "Redigera",
|
||||
"common.unknown_driver": "Okänd förare",
|
||||
"common.unknown_car": "Okänd bil",
|
||||
@@ -389,8 +397,10 @@ const TRANSLATIONS = {
|
||||
"validation.invalid_date": "Datum måste vara i format YYYY-MM-DD.",
|
||||
"edit.class_name": "Redigera klassnamn",
|
||||
"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.car_name": "Redigera bilnamn",
|
||||
"edit.new_car_name": "Namn på ny bil",
|
||||
"edit.car_transponder": "Redigera bilens transponder",
|
||||
"edit.event_name": "Redigera eventnamn",
|
||||
"edit.event_date": "Redigera eventdatum (YYYY-MM-DD)",
|
||||
@@ -712,6 +722,12 @@ const TRANSLATIONS = {
|
||||
"timing.no_session_selected": "No session selected.",
|
||||
"timing.no_passings": "No passings recorded.",
|
||||
"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_speaker_overlay": "Speaker overlay",
|
||||
"timing.open_results_overlay": "Results overlay",
|
||||
@@ -822,6 +838,8 @@ const TRANSLATIONS = {
|
||||
"table.ahead_gap": "Gap ahead",
|
||||
"table.own_delta": "Own delta",
|
||||
"common.delete": "Delete",
|
||||
"common.cancel": "Cancel",
|
||||
"common.save": "Save",
|
||||
"common.edit": "Edit",
|
||||
"common.unknown_driver": "Unknown Driver",
|
||||
"common.unknown_car": "Unknown Car",
|
||||
@@ -850,8 +868,10 @@ const TRANSLATIONS = {
|
||||
"validation.invalid_date": "Date must be in YYYY-MM-DD format.",
|
||||
"edit.class_name": "Edit class name",
|
||||
"edit.driver_name": "Edit driver name",
|
||||
"edit.new_driver_name": "New driver name",
|
||||
"edit.driver_transponder": "Edit personal transponder (can be empty)",
|
||||
"edit.car_name": "Edit car name",
|
||||
"edit.new_car_name": "New car name",
|
||||
"edit.car_transponder": "Edit car transponder",
|
||||
"edit.event_name": "Edit event name",
|
||||
"edit.event_date": "Edit event date (YYYY-MM-DD)",
|
||||
@@ -954,6 +974,7 @@ let appVersionPollTimer = null;
|
||||
let baselineAppVersion = "";
|
||||
let selectedLeaderboardKey = null;
|
||||
let selectedGridSessionId = null;
|
||||
let quickAddDraft = null;
|
||||
let overlaySyncTimer = null;
|
||||
let overlayRotationTimer = null;
|
||||
let overlayRotationIndex = 0;
|
||||
@@ -3174,6 +3195,8 @@ function renderTiming() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
${active ? renderQuickAddPanel(active) : ""}
|
||||
|
||||
<section class="panel mt-16">
|
||||
<div class="panel-header"><h3>${t("timing.speaker_panel")}</h3></div>
|
||||
<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", () => {
|
||||
selectedLeaderboardKey = null;
|
||||
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() {
|
||||
dom.view.innerHTML = `
|
||||
<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) {
|
||||
const passings = getCompetitorPassings(session, row);
|
||||
return `
|
||||
@@ -3708,6 +3881,7 @@ function renderLeaderboardModal(session, row) {
|
||||
<div class="panel-body">
|
||||
<p><strong>${escapeHtml(row.driverName)}</strong> • ${escapeHtml(row.carName)}</p>
|
||||
<p>${t("table.transponder")}: ${escapeHtml(row.transponder)}</p>
|
||||
${renderQuickAddActions(session, row.transponder, "leaderboardModal")}
|
||||
<p>${t("table.laps")}: ${row.laps}</p>
|
||||
<p>${t("timing.total_time")}: ${escapeHtml(row.resultDisplay)}</p>
|
||||
<p>${t("table.best_lap")}: ${formatLap(row.bestLapMs)}</p>
|
||||
@@ -3843,8 +4017,8 @@ function renderRecentPassings(session) {
|
||||
}
|
||||
|
||||
return renderTable(
|
||||
[t("table.time"), t("table.transponder"), t("table.driver"), t("table.car"), t("table.loop"), t("table.strength")],
|
||||
items.map((p) => {
|
||||
[t("table.time"), t("table.transponder"), t("table.driver"), t("table.car"), t("table.loop"), t("table.strength"), ""],
|
||||
items.map((p, index) => {
|
||||
return `
|
||||
<tr>
|
||||
<td>${new Date(p.timestamp).toLocaleTimeString()}</td>
|
||||
@@ -3853,6 +4027,7 @@ function renderRecentPassings(session) {
|
||||
<td>${escapeHtml(p.carName || "-")}</td>
|
||||
<td>${escapeHtml(p.loopId || "-")}</td>
|
||||
<td>${p.strength ?? "-"}</td>
|
||||
<td>${renderQuickAddActions(session, p.transponder, `recentPassing-${index}`)}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
|
||||
@@ -439,6 +439,16 @@ select:focus {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-add-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.quick-add-spacer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.check-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user