Warn when a driver's transponder collides with a different driver
Registering or editing a driver with a transponder already assigned to a different driver (e.g. a wrong-class typo) previously saved silently - the only symptom was that driver's laps never registering during their heat, with no indication why. Now a confirm dialog names the conflicting driver and their class before saving; the same person's own multiple class entries sharing one physical transponder are correctly never flagged. Also confirms two things the user asked to verify: the race clock already counts down for all timed session types (only open practice counts up unbounded), and a driver not entered in a heat already has their laps fully discarded rather than recorded - both were already correct, no changes needed there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -866,6 +866,7 @@ const TRANSLATIONS = {
|
||||
"drivers.brand_placeholder": "Team / märke (valfritt)",
|
||||
"drivers.brand_filter_placeholder": "Sök namn / transponder / märke / klubb / land",
|
||||
"drivers.transponder_placeholder": "Personlig transponder (valfritt)",
|
||||
"drivers.transponder_conflict_confirm": "Transpondern {tp} används redan av {name} ({class}). Spara ändå?",
|
||||
"drivers.speech_name": "Uttalsnamn",
|
||||
"drivers.speech_name_placeholder": "Uttal för uppläsning (valfritt)",
|
||||
"drivers.speech_name_hint": "Skriv namnet som webbläsarrösten ska uttala det. Lämna tomt för vanligt förarnamn.",
|
||||
@@ -2381,6 +2382,7 @@ const TRANSLATIONS = {
|
||||
"drivers.brand_placeholder": "Team / brand (optional)",
|
||||
"drivers.brand_filter_placeholder": "Search name / transponder / brand / club / country",
|
||||
"drivers.transponder_placeholder": "Personal transponder (optional)",
|
||||
"drivers.transponder_conflict_confirm": "Transponder {tp} is already used by {name} ({class}). Save anyway?",
|
||||
"drivers.speech_name": "Spoken name",
|
||||
"drivers.speech_name_placeholder": "Pronunciation for announcements (optional)",
|
||||
"drivers.speech_name_hint": "Enter the name as the browser voice should pronounce it. Leave blank to use the regular driver name.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getDriverProfileGroups, getDriverProfileId } from "./driver_profile_logic.js";
|
||||
import { getDriverProfileGroups, getDriverProfileId, findTransponderConflict } from "./driver_profile_logic.js";
|
||||
|
||||
function getScheduleDriftSummary(state) {
|
||||
const scheduledSessions = state.sessions
|
||||
@@ -515,13 +515,18 @@ export function renderDriversView(deps) {
|
||||
? uid("profile")
|
||||
: sameNameProfile?.id || uid("profile");
|
||||
const linkedProfile = profileGroups.find((profile) => profile.id === profileId);
|
||||
const cleanedTransponder = String(form.get("transponder") || "").trim();
|
||||
const transponderConflict = findTransponderConflict(state.drivers, { transponder: cleanedTransponder, profileId });
|
||||
if (transponderConflict && !window.confirm(t("drivers.transponder_conflict_confirm", { tp: cleanedTransponder, name: transponderConflict.name, class: getClassName(transponderConflict.classId) }))) {
|
||||
return;
|
||||
}
|
||||
state.drivers.push(
|
||||
normalizeDriver({
|
||||
id: driverId,
|
||||
name: driverName,
|
||||
classId: String(form.get("classId")),
|
||||
brand: String(form.get("brand") || "").trim(),
|
||||
transponder: String(form.get("transponder") || "").trim(),
|
||||
transponder: cleanedTransponder,
|
||||
speechName: String(form.get("speechName") || "").trim(),
|
||||
passSound: String(form.get("passSound") || "").trim(),
|
||||
profileId,
|
||||
@@ -634,6 +639,10 @@ export function renderDriversView(deps) {
|
||||
setFormError("driverEditError", t("validation.invalid_selection"));
|
||||
return;
|
||||
}
|
||||
const transponderConflict = findTransponderConflict(state.drivers, { transponder: cleanedTp, profileId: cleanedProfileId, excludeDriverId: editingDriver.id });
|
||||
if (transponderConflict && !window.confirm(t("drivers.transponder_conflict_confirm", { tp: cleanedTp, name: transponderConflict.name, class: getClassName(transponderConflict.classId) }))) {
|
||||
return;
|
||||
}
|
||||
setFormError("driverEditError", "");
|
||||
state.drivers = state.drivers.map((item) => {
|
||||
if (item.id === editingDriver.id) {
|
||||
|
||||
@@ -44,3 +44,19 @@ export function getDriverProfileReferences(state, reference) {
|
||||
profile.driverIds.forEach((driverId) => references.add(driverId));
|
||||
return references;
|
||||
}
|
||||
|
||||
// A transponder is one physical chip, so the same value legitimately repeats across a
|
||||
// single person's own multi-class driver entries (same profile) - that is not a
|
||||
// conflict. It only means something went wrong when it also shows up on a driver
|
||||
// belonging to a DIFFERENT profile, since two different people can't share one chip:
|
||||
// most often a typo at registration that quietly grabbed someone else's transponder.
|
||||
export function findTransponderConflict(drivers, { transponder, profileId, excludeDriverId } = {}) {
|
||||
const value = String(transponder || "").trim();
|
||||
if (!value) return null;
|
||||
const list = Array.isArray(drivers) ? drivers : [];
|
||||
return list.find((driver) => (
|
||||
String(driver.id || "") !== String(excludeDriverId || "")
|
||||
&& String(driver.transponder || "").trim() === value
|
||||
&& getDriverProfileId(driver) !== String(profileId || "")
|
||||
)) || null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user