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_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>
|
||||||
`;
|
`;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
Reference in New Issue
Block a user