From 61008eac0a6b4a88a2482e12c731250297cf1a98 Mon Sep 17 00:00:00 2001 From: larssand Date: Mon, 16 Mar 2026 20:07:29 +0100 Subject: [PATCH] Add brand filtering for drivers and cars --- README.md | 2 ++ README.sv.md | 2 ++ src/app.js | 56 ++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1cddc20..6a6c3f7 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ JMK RB RaceController is an RC timing and race-control system with support for s - bundled binaries from `AMMC/windows64`, `AMMC/linux_x86-64`, `AMMC/apple_m` - Editing in UI: - classes, event names/dates, drivers, and cars can be edited directly + - drivers and cars support optional `brand` fields for team, sponsor, manufacturer, or model data + - `Drivers` and `Cars` can be filtered by brand directly in the UI - Live race control: - countdown during timed sessions - auto-finish at end of session time with `Race is finished` diff --git a/README.sv.md b/README.sv.md index 82d87cf..200b828 100644 --- a/README.sv.md +++ b/README.sv.md @@ -77,6 +77,8 @@ RC timing app med sponsor-eventflöde (delade bilar/transpondrar mellan olika he - läser bundlade binärer från `AMMC/windows64`, `AMMC/linux_x86-64`, `AMMC/apple_m` - Redigering i UI: - Klasser, eventnamn/datum, förare och bilar kan redigeras direkt + - förare och bilar har valfria brandfält för team, sponsor, märke eller modell + - `Förare` och `Bilar` kan filtreras direkt på brand i UI - Live race-kontroll: - Nedräkning under pågående session - Auto-finish vid tidslut med status `Race is finished` diff --git a/src/app.js b/src/app.js index 01274dc..0bb3f64 100644 --- a/src/app.js +++ b/src/app.js @@ -87,12 +87,14 @@ const TRANSLATIONS = { "drivers.create": "Skapa förare", "drivers.name_placeholder": "Förarnamn", "drivers.brand_placeholder": "Team / märke (valfritt)", + "drivers.brand_filter_placeholder": "Filtrera på team / märke", "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.brand_filter_placeholder": "Filtrera på märke / modell", "cars.transponder_placeholder": "Bilens transponder", "cars.add": "Lägg till bil", "cars.title": "Bilar", @@ -539,14 +541,14 @@ const TRANSLATIONS = { "guide.title": "Guide och dokumentation", "guide.intro": "Här finns steg-för-steg för sponsor-event (10 personer, 4 bilar), vanligt race, samt AMMC-installation på Windows/Linux. Guiden beskriver också var AMMC faktiskt körs i Managed AMMC-läget.", "guide.sponsor_title": "Skapa Sponsor Event: 10 personer, 4 bilar", - "guide.sponsor_1": "1. Lägg upp 4 bilar i sidan Bilar med unikt transponder-ID.", - "guide.sponsor_2": "2. Lägg upp 10 förare i sidan Förare.", + "guide.sponsor_1": "1. Lägg upp 4 bilar i sidan Bilar med unikt transponder-ID. Lägg gärna också in märke/modell i brandfältet.", + "guide.sponsor_2": "2. Lägg upp 10 förare i sidan Förare. Du kan också spara team/märke i brandfältet och filtrera listan på det senare.", "guide.sponsor_3": "3. Skapa event med läge Sponsor Event.", "guide.sponsor_4": "4. Klicka Hantera på eventet och skapa rundor (kval/heat/final).", "guide.sponsor_5": "5. Tilldela 4 förare till 4 bilar i Heat 1, byt förare till Heat 2/3 osv.", "guide.sponsor_6": "6. I Tidtagning: välj session, Sätt aktiv, Starta, Stoppa.", "guide.race_title": "Skapa vanligt race (förare har egna transpondrar)", - "guide.race_1": "1. Lägg in förare med personlig transponder.", + "guide.race_1": "1. Lägg in förare med personlig transponder. Brandfältet kan användas för team, sponsor eller bilmärke.", "guide.race_2": "2. Skapa event med läge Race.", "guide.race_3": "3. Klicka Hantera på racet och markera exakt vilka förare som ska få vara med i just detta race.", "guide.race_4": "4. Gå igenom Raceformat och välj antal kvalrundor, förare per heat, tider, starttyp och hur finaler ska seedas.", @@ -727,12 +729,14 @@ const TRANSLATIONS = { "drivers.create": "Create Driver", "drivers.name_placeholder": "Driver name", "drivers.brand_placeholder": "Team / brand (optional)", + "drivers.brand_filter_placeholder": "Filter by team / brand", "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.brand_filter_placeholder": "Filter by brand / model", "cars.transponder_placeholder": "Car transponder", "cars.add": "Add Car", "cars.title": "Cars", @@ -1179,14 +1183,14 @@ const TRANSLATIONS = { "guide.title": "Guide and Documentation", "guide.intro": "Step-by-step setup for sponsor events (10 drivers, 4 cars), normal race mode, and AMMC on Windows/Linux. The guide also explains where Managed AMMC actually runs.", "guide.sponsor_title": "Create Sponsor Event: 10 drivers, 4 cars", - "guide.sponsor_1": "1. Add 4 cars in Cars with unique transponder IDs.", - "guide.sponsor_2": "2. Add 10 drivers in Drivers.", + "guide.sponsor_1": "1. Add 4 cars in Cars with unique transponder IDs. You can also store brand/model in the brand field.", + "guide.sponsor_2": "2. Add 10 drivers in Drivers. You can also store team/brand in the brand field and filter by it later.", "guide.sponsor_3": "3. Create event in Track Event mode.", "guide.sponsor_4": "4. Click Manage and create rounds (qualification/heat/final).", "guide.sponsor_5": "5. Assign 4 drivers to 4 cars in Heat 1, rotate drivers for Heat 2/3, then finals.", "guide.sponsor_6": "6. In Timing: select session, Set Active, Start, Stop.", "guide.race_title": "Create regular race (driver transponders)", - "guide.race_1": "1. Add drivers with personal transponder IDs.", + "guide.race_1": "1. Add drivers with personal transponder IDs. The brand field can be used for team, sponsor or car brand.", "guide.race_2": "2. Create event in Race mode.", "guide.race_3": "3. Click Manage on the race and select exactly which drivers should be valid for this race.", "guide.race_4": "4. Go through Race Format and choose qualifying rounds, drivers per heat, times, start mode and how finals should be seeded.", @@ -1323,6 +1327,8 @@ let selectedJudgeKey = null; let judgingCompetitorFilter = "all"; let judgingLogFilter = "all"; let quickAddDraft = null; +let driverBrandFilter = ""; +let carBrandFilter = ""; let overlaySyncTimer = null; let overlayRotationTimer = null; let overlayLiveRefreshTimer = null; @@ -2835,6 +2841,11 @@ function renderDrivers() { const classOptions = state.classes .map((c) => ``) .join(""); + const filteredDrivers = state.drivers.filter((driver) => + String(driver.brand || "") + .toLowerCase() + .includes(driverBrandFilter.trim().toLowerCase()) + ); const editingDriver = state.drivers.find((driver) => driver.id === selectedDriverEditId) || null; dom.view.innerHTML = ` @@ -2851,10 +2862,13 @@ function renderDrivers() {

${t("drivers.title")}

+
+ +
${renderTable( [t("table.name"), t("table.class"), t("table.brand"), t("table.transponder"), t("events.actions")], - state.drivers.map( + filteredDrivers.map( (d) => ` ${escapeHtml(d.name)} @@ -2922,6 +2936,15 @@ function renderDrivers() { renderView(); }); + document.getElementById("driverBrandFilter")?.addEventListener("input", (event) => { + const input = event.currentTarget; + if (!(input instanceof HTMLInputElement)) { + return; + } + driverBrandFilter = input.value; + renderDrivers(); + }); + state.drivers.forEach((d) => { document.getElementById(`driver-edit-${d.id}`)?.addEventListener("click", () => { selectedDriverEditId = d.id; @@ -2990,6 +3013,11 @@ function renderDrivers() { } function renderCars() { + const filteredCars = state.cars.filter((car) => + String(car.brand || "") + .toLowerCase() + .includes(carBrandFilter.trim().toLowerCase()) + ); const editingCar = state.cars.find((car) => car.id === selectedCarEditId) || null; dom.view.innerHTML = `
@@ -3004,10 +3032,13 @@ function renderCars() {

${t("cars.title")}

+
+ +
${renderTable( [t("table.car"), t("table.brand"), t("table.transponder"), t("events.actions")], - state.cars.map( + filteredCars.map( (c) => ` ${escapeHtml(c.name)} @@ -3070,6 +3101,15 @@ function renderCars() { renderView(); }); + document.getElementById("carBrandFilter")?.addEventListener("input", (event) => { + const input = event.currentTarget; + if (!(input instanceof HTMLInputElement)) { + return; + } + carBrandFilter = input.value; + renderCars(); + }); + state.cars.forEach((c) => { document.getElementById(`car-edit-${c.id}`)?.addEventListener("click", () => { selectedCarEditId = c.id;