Files
Live_RC/src/event_manager_controller.js
larssand 346b1e7657 aadd
2026-03-27 21:20:32 +01:00

874 lines
31 KiB
JavaScript

export function renderEventManagerView(context) {
const {
eventId,
state,
t,
escapeHtml,
normalizeEvent,
ensureRaceParticipantsConfigured,
getSessionsForEvent,
getSelectedTeamEditId,
setSelectedTeamEditId,
getSelectedSessionEditId,
setSelectedSessionEditId,
getSelectedGridSessionId,
setSelectedGridSessionId,
getRaceFormatAdvanced,
setRaceFormatAdvanced,
getTeamDriverPool,
getEventTeams,
normalizeBrandingConfig,
getSessionTypeChoices,
getRaceFormatPresets,
getRaceSummaryItems,
getRaceSummaryWarnings,
getRaceManageStatuses,
normalizeStartMode,
renderEventManagerMarkup,
getSessionEntrants,
getSessionTypeLabel,
getStartModeLabel,
getStatusLabel,
renderManageStatusBadgeView,
getDriverDisplayById,
renderRaceFormatContextCardView,
renderRaceFormatFieldView,
renderGridEditor,
renderRaceStandingsTableView,
buildPracticeStandings,
buildQualifyingStandings,
buildFinalStandings,
renderTeamRaceStandings,
renderFinalMatrix,
getModeLabel,
renderTable,
bindModalShell,
setFormError,
saveState,
renderView,
rerenderEventManager,
updateHeaderState,
uid,
normalizeSession,
buildSessionHeatSheetHtml,
openPrintWindow,
exportSessionHeatSheet,
exportSessionHeatSheetPdf,
getSelectedAssignmentSessionId,
autoAssignTrackSession,
renderAssignmentList,
normalizeRaceTeam,
buildRaceFormatConfigFromForm,
applyRaceFormatPreset,
normalizeStoredRacePreset,
generateQualifyingForRace,
clearGeneratedQualifying,
reseedUpcomingQualifying,
generateFinalsForRace,
clearGeneratedFinals,
applyBumpsForRace,
buildRacePackagePayload,
downloadJsonFile,
sanitizeFilenameSegment,
importRacePackagePayload,
buildRaceStartListsHtml,
buildRaceResultsHtml,
buildTeamRaceResultsHtml,
exportRaceStartListsPdf,
exportRaceResultsPdf,
exportTeamRaceResultsPdf,
ensureSessionDriverOrder,
reorderList,
getEventName,
} = context;
const selectedTeamEditId = getSelectedTeamEditId();
const selectedSessionEditId = getSelectedSessionEditId();
const selectedGridSessionId = getSelectedGridSessionId();
const raceFormatAdvanced = getRaceFormatAdvanced();
const event = state.events.find((e) => e.id === eventId);
if (!event) {
return;
}
const normalizedEvent = normalizeEvent(event);
if (normalizedEvent !== event) {
Object.assign(event, normalizedEvent);
}
ensureRaceParticipantsConfigured(event);
const sessions = getSessionsForEvent(eventId);
const eventManageArea = document.getElementById("eventManageArea");
if (!eventManageArea) {
return;
}
const driverOptions = state.drivers
.map((d) => `<option value="${d.id}">${escapeHtml(d.name)}</option>`)
.join("");
const teamDriverPool = event.mode === "race" ? getTeamDriverPool(event) : { drivers: [], fallback: false };
const raceDrivers = event.mode === "race" ? teamDriverPool.drivers : [];
const raceTeams = event.mode === "race" ? getEventTeams(event) : [];
if (selectedTeamEditId && !raceTeams.some((team) => team.id === selectedTeamEditId)) {
setSelectedTeamEditId(null);
}
const editingTeam = event.mode === "race" ? raceTeams.find((team) => team.id === selectedTeamEditId) || null : null;
const carOptions = state.cars
.map((c) => `<option value="${c.id}">${escapeHtml(c.name)} (${escapeHtml(c.transponder)})</option>`)
.join("");
const branding = normalizeBrandingConfig(event.branding);
const editingSession = sessions.find((session) => session.id === selectedSessionEditId) || null;
const sessionTypeChoices = getSessionTypeChoices(event.mode);
const sessionTypeHintKey = event.mode === "track" ? "events.session_type_hint_track" : "events.session_type_hint_race";
const racePresets = getRaceFormatPresets();
const selectedPreset = racePresets.find((preset) => preset.id === event.raceConfig.presetId) || racePresets[0];
const isEndurancePreset = event.mode === "race" && selectedPreset?.id === "endurance";
const showTeamsSection =
event.mode === "race" &&
(sessions.some((session) => session.type === "team_race") || raceTeams.length > 0 || Boolean(selectedTeamEditId));
const showBasicQualifyingFields = raceFormatAdvanced || !isEndurancePreset;
const showBasicFinalFields = raceFormatAdvanced || !isEndurancePreset;
const selectedParticipantCount = event.mode === "race" ? (event.raceConfig.participantsConfigured ? (event.raceConfig.driverIds || []).length : raceDrivers.length) : 0;
const raceSummaryItems = event.mode === "race" ? getRaceSummaryItems(event, sessions, raceDrivers, selectedPreset, { t, getStartModeLabel }) : [];
const raceSummaryWarnings = event.mode === "race" ? getRaceSummaryWarnings(event, sessions, raceDrivers, raceTeams, selectedPreset, { t }) : [];
const manageStatuses = event.mode === "race" ? getRaceManageStatuses(event, sessions, raceDrivers, raceTeams, selectedPreset) : null;
const gridSessions = event.mode === "race" ? sessions.filter((session) => normalizeStartMode(session.startMode) === "position") : [];
if (selectedGridSessionId && !gridSessions.some((session) => session.id === selectedGridSessionId)) {
setSelectedGridSessionId("");
}
const selectedGridSession =
gridSessions.find((session) => session.id === selectedGridSessionId) || gridSessions[0] || null;
if (!selectedGridSessionId && selectedGridSession) {
setSelectedGridSessionId(selectedGridSession.id);
}
eventManageArea.innerHTML = renderEventManagerMarkup({
event,
t,
escapeHtml,
sessions,
sessionTypeChoices,
sessionTypeHintKey,
raceTeams,
getSessionEntrants,
getSessionTypeLabel,
getStartModeLabel,
getStatusLabel,
normalizeStartMode,
branding,
driverOptions,
carOptions,
manageStatuses,
renderManageStatusBadgeView,
selectedParticipantCount,
raceDrivers,
teamDriverPool,
showTeamsSection,
state,
getDriverDisplayById,
raceFormatAdvanced,
racePresets,
selectedPreset,
isEndurancePreset,
showBasicQualifyingFields,
showBasicFinalFields,
renderRaceFormatContextCardView,
renderRaceFormatFieldView,
raceSummaryWarnings,
raceSummaryItems,
selectedGridSession,
renderGridEditor,
renderRaceStandingsTableView,
buildPracticeStandings,
buildQualifyingStandings,
buildFinalStandings,
renderTeamRaceStandings,
renderFinalMatrix,
editingTeam,
editingSession,
getModeLabel,
renderTable,
});
const bindManageJump = (node) => {
const triggerJump = () => {
const targetId = node.getAttribute("data-target") || "";
const target = targetId ? document.getElementById(targetId) : null;
if (target) {
target.scrollIntoView({ behavior: "smooth", block: "start" });
}
};
node.addEventListener("click", triggerJump);
node.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
triggerJump();
}
});
};
eventManageArea.querySelectorAll(".summary-warning-link, .manage-step-card-link").forEach((node) => {
bindManageJump(node);
});
const refreshManager = () => {
renderView();
rerenderEventManager(eventId);
};
document.getElementById("eventBrandingForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
event.branding = normalizeBrandingConfig({
...event.branding,
brandName: String(form.get("brandName") || "").trim(),
brandTagline: String(form.get("brandTagline") || "").trim(),
pdfFooter: String(form.get("pdfFooter") || "").trim(),
pdfTheme: String(form.get("pdfTheme") || "").trim(),
});
saveState();
rerenderEventManager(eventId);
});
document.getElementById("eventLogoUpload")?.addEventListener("change", (eventInput) => {
const input = eventInput.currentTarget;
const file = input instanceof HTMLInputElement ? input.files?.[0] : null;
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = () => {
event.branding = normalizeBrandingConfig({
...event.branding,
logoDataUrl: typeof reader.result === "string" ? reader.result : "",
});
saveState();
rerenderEventManager(eventId);
};
reader.readAsDataURL(file);
});
document.getElementById("eventLogoClear")?.addEventListener("click", () => {
event.branding = normalizeBrandingConfig({
...event.branding,
logoDataUrl: "",
});
saveState();
rerenderEventManager(eventId);
});
document.getElementById("sessionForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const formNode = e.currentTarget;
if (!(formNode instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formNode);
const typeField = formNode.querySelector('[name="type"]');
const selectedType = typeField instanceof HTMLSelectElement ? typeField.value : String(form.get("type") || "");
state.sessions.push(normalizeSession({
id: uid("session"),
eventId,
name: String(form.get("name")).trim(),
type: selectedType,
durationMin: Number(form.get("durationMin")),
followUpSec: Math.max(0, Number(form.get("followUpSec") || 0) || 0),
startMode: String(form.get("startMode") || "mass"),
seedBestLapCount: Math.max(0, Number(form.get("seedBestLapCount") || 0) || 0),
seedMethod: String(form.get("seedMethod") || "best_sum"),
staggerGapSec: Math.max(0, Number(form.get("staggerGapSec") || 0) || 0),
maxCars: Number(form.get("maxCars") || 0) || null,
mode: event.mode,
status: "ready",
startedAt: null,
endedAt: null,
finishedByTimer: false,
assignments: [],
}));
saveState();
refreshManager();
updateHeaderState();
});
sessions.forEach((s) => {
document.getElementById(`session-edit-${s.id}`)?.addEventListener("click", () => {
setSelectedSessionEditId(s.id);
rerenderEventManager(eventId);
});
document.getElementById(`session-active-${s.id}`)?.addEventListener("click", () => {
state.activeSessionId = s.id;
saveState();
updateHeaderState();
renderView();
});
document.getElementById(`session-delete-${s.id}`)?.addEventListener("click", () => {
state.sessions = state.sessions.filter((x) => x.id !== s.id);
delete state.resultsBySession[s.id];
if (state.activeSessionId === s.id) {
state.activeSessionId = null;
}
saveState();
refreshManager();
updateHeaderState();
});
document.getElementById(`session-grid-${s.id}`)?.addEventListener("click", () => {
ensureSessionDriverOrder(s);
setSelectedGridSessionId(s.id);
saveState();
refreshManager();
});
document.getElementById(`session-sheet-print-${s.id}`)?.addEventListener("click", () => {
openPrintWindow(`${getEventName(eventId)} - ${s.name}`, buildSessionHeatSheetHtml(s));
});
document.getElementById(`session-sheet-export-${s.id}`)?.addEventListener("click", () => {
exportSessionHeatSheet(s);
});
document.getElementById(`session-sheet-pdf-${s.id}`)?.addEventListener("click", async () => {
await exportSessionHeatSheetPdf(s);
});
});
document.getElementById("sessionEditCancel")?.addEventListener("click", () => {
setSelectedSessionEditId(null);
refreshManager();
});
document.getElementById("sessionEditCancelFooter")?.addEventListener("click", () => {
setSelectedSessionEditId(null);
refreshManager();
});
document.getElementById("sessionEditModalOverlay")?.addEventListener("click", (event) => {
if (event.target?.id === "sessionEditModalOverlay") {
setSelectedSessionEditId(null);
refreshManager();
}
});
bindModalShell("sessionEditModalOverlay", () => {
setSelectedSessionEditId(null);
refreshManager();
});
document.getElementById("sessionEditForm")?.addEventListener("submit", (event) => {
event.preventDefault();
if (!editingSession) {
return;
}
const formNode = event.currentTarget;
if (!(formNode instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formNode);
const typeField = formNode.querySelector('[name="type"]');
const selectedType = typeField instanceof HTMLSelectElement ? typeField.value : String(form.get("type") || editingSession.type);
const cleanedName = String(form.get("name") || "").trim();
const cleanedDuration = Number(form.get("durationMin") || editingSession.durationMin || 5) || 0;
if (!cleanedName) {
setFormError("sessionEditError", t("validation.required_name"));
return;
}
if (cleanedDuration < 1) {
setFormError("sessionEditError", t("validation.required_duration"));
return;
}
setFormError("sessionEditError", "");
editingSession.name = cleanedName;
editingSession.type = selectedType;
editingSession.durationMin = Math.max(1, cleanedDuration);
editingSession.followUpSec = Math.max(0, Number(form.get("followUpSec") || 0) || 0);
editingSession.startMode = normalizeStartMode(String(form.get("startMode") || editingSession.startMode || "mass"));
editingSession.seedBestLapCount = Math.max(0, Number(form.get("seedBestLapCount") || 0) || 0);
editingSession.seedMethod = ["best_sum", "average", "consecutive"].includes(String(form.get("seedMethod") || "").toLowerCase())
? String(form.get("seedMethod")).toLowerCase()
: "best_sum";
editingSession.staggerGapSec = Math.max(0, Number(form.get("staggerGapSec") || 0) || 0);
editingSession.maxCars = Number(form.get("maxCars") || 0) || null;
setSelectedSessionEditId(null);
saveState();
refreshManager();
});
if (event.mode === "track") {
document.getElementById("sponsorRoundsForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
const qualificationRounds = Number(form.get("qualificationRounds") || 0);
const heatRounds = Number(form.get("heatRounds") || 0);
const finalRounds = Number(form.get("finalRounds") || 0);
const durationMin = Number(form.get("roundDuration") || 5);
createSponsorRounds(eventId, {
qualificationRounds,
heatRounds,
finalRounds,
durationMin,
});
saveState();
rerenderEventManager(eventId);
});
document.getElementById("assignForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
const sessionId = String(form.get("sessionId"));
const session = state.sessions.find((x) => x.id === sessionId);
if (!session) {
return;
}
const driverId = String(form.get("driverId"));
const carId = String(form.get("carId"));
const car = state.cars.find((x) => x.id === carId);
if (!car) {
return;
}
const duplicateCar = (session.assignments || []).find((a) => a.carId === carId);
if (duplicateCar) {
alert(t("events.duplicate_car"));
return;
}
const duplicateDriver = (session.assignments || []).find((a) => a.driverId === driverId);
if (duplicateDriver) {
alert(t("events.duplicate_driver"));
return;
}
const duplicateTp = (session.assignments || []).find((a) => {
const existingCar = state.cars.find((x) => x.id === a.carId);
return existingCar?.transponder && existingCar.transponder === car.transponder;
});
if (duplicateTp) {
alert(t("events.duplicate_tp"));
return;
}
session.assignments = session.assignments || [];
session.assignments.push({ id: uid("as"), driverId, carId });
saveState();
rerenderEventManager(eventId);
});
document.getElementById("autoAssignSession")?.addEventListener("click", () => {
const sessionId = getSelectedAssignmentSessionId();
if (!sessionId) {
return;
}
autoAssignTrackSession(event, sessionId);
saveState();
rerenderEventManager(eventId);
});
document.getElementById("clearAssignSession")?.addEventListener("click", () => {
const sessionId = getSelectedAssignmentSessionId();
if (!sessionId) {
return;
}
const session = state.sessions.find((x) => x.id === sessionId);
if (!session) {
return;
}
session.assignments = [];
saveState();
rerenderEventManager(eventId);
});
renderAssignmentList(eventId);
}
if (event.mode === "race") {
const persistRaceParticipants = () => {
const selectedIds = Array.from(document.querySelectorAll(".race-participant:checked")).map((node) => node.value);
event.raceConfig.driverIds = selectedIds;
event.raceConfig.participantsConfigured = true;
saveState();
};
document.querySelectorAll(".race-participant").forEach((node) => {
node.addEventListener("change", persistRaceParticipants);
});
document.getElementById("selectAllParticipants")?.addEventListener("click", () => {
document.querySelectorAll(".race-participant").forEach((node) => {
node.checked = true;
});
persistRaceParticipants();
});
document.getElementById("clearParticipants")?.addEventListener("click", () => {
document.querySelectorAll(".race-participant").forEach((node) => {
node.checked = false;
});
persistRaceParticipants();
});
if (showTeamsSection) {
document.getElementById("teamForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const formNode = e.currentTarget;
if (!(formNode instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formNode);
const name = String(form.get("teamName") || "").trim();
const driverIds = Array.from(document.querySelectorAll('[form="teamForm"][name="teamDriverIds"]:checked'))
.map((node) => String(node.value))
.filter(Boolean);
const carIds = Array.from(document.querySelectorAll('[form="teamForm"][name="teamCarIds"]:checked'))
.map((node) => String(node.value))
.filter(Boolean);
if (!name) {
setFormError("teamCreateError", t("validation.required_name"));
return;
}
if (!driverIds.length && !carIds.length) {
setFormError("teamCreateError", t("validation.invalid_selection"));
return;
}
setFormError("teamCreateError", "");
const createdTeam = normalizeRaceTeam({ id: uid("team"), name, driverIds, carIds });
event.raceConfig.teams = [...getEventTeams(event), createdTeam];
setSelectedTeamEditId(null);
saveState();
refreshManager();
});
raceTeams.forEach((team) => {
document.getElementById(`team-edit-${team.id}`)?.addEventListener("click", () => {
setSelectedTeamEditId(team.id);
refreshManager();
});
document.getElementById(`team-delete-${team.id}`)?.addEventListener("click", () => {
event.raceConfig.teams = getEventTeams(event).filter((item) => item.id !== team.id);
if (getSelectedTeamEditId() === team.id) {
setSelectedTeamEditId(null);
}
saveState();
refreshManager();
});
});
document.getElementById("teamEditCancel")?.addEventListener("click", () => {
setSelectedTeamEditId(null);
refreshManager();
});
document.getElementById("teamEditCancelFooter")?.addEventListener("click", () => {
setSelectedTeamEditId(null);
refreshManager();
});
document.getElementById("teamEditModalOverlay")?.addEventListener("click", (modalEvent) => {
if (modalEvent.target?.id === "teamEditModalOverlay") {
setSelectedTeamEditId(null);
refreshManager();
}
});
bindModalShell("teamEditModalOverlay", () => {
setSelectedTeamEditId(null);
refreshManager();
});
document.getElementById("teamEditForm")?.addEventListener("submit", (submitEvent) => {
submitEvent.preventDefault();
if (!editingTeam) {
return;
}
const formNode = submitEvent.currentTarget;
if (!(formNode instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formNode);
const name = String(form.get("teamName") || "").trim();
const driverIds = Array.from(formNode.querySelectorAll('[name="teamDriverIds"]:checked'))
.map((node) => String(node.value))
.filter(Boolean);
const carIds = Array.from(formNode.querySelectorAll('[name="teamCarIds"]:checked'))
.map((node) => String(node.value))
.filter(Boolean);
if (!name) {
setFormError("teamEditError", t("validation.required_name"));
return;
}
if (!driverIds.length && !carIds.length) {
setFormError("teamEditError", t("validation.invalid_selection"));
return;
}
setFormError("teamEditError", "");
event.raceConfig.teams = getEventTeams(event).map((team) =>
team.id === editingTeam.id ? normalizeRaceTeam({ ...team, name, driverIds, carIds }) : team
);
setSelectedTeamEditId(null);
saveState();
refreshManager();
});
}
document.getElementById("raceFormatBasicToggle")?.addEventListener("click", () => {
setRaceFormatAdvanced(false);
rerenderEventManager(eventId);
});
document.getElementById("raceFormatAdvancedToggle")?.addEventListener("click", () => {
setRaceFormatAdvanced(true);
rerenderEventManager(eventId);
});
document.getElementById("raceFormatForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
event.raceConfig = buildRaceFormatConfigFromForm(form, event);
saveState();
rerenderEventManager(eventId);
});
document.getElementById("applyRacePreset")?.addEventListener("click", () => {
const formElement = document.getElementById("raceFormatForm");
if (!(formElement instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formElement);
applyRaceFormatPreset(event, String(form.get("presetId") || "custom"));
saveState();
rerenderEventManager(eventId);
});
document.getElementById("saveRacePreset")?.addEventListener("click", () => {
const formElement = document.getElementById("raceFormatForm");
if (!(formElement instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formElement);
const presetName = String(form.get("presetName") || "").trim();
if (!presetName) {
return;
}
const config = buildRaceFormatConfigFromForm(form, event);
const selectedPresetId = String(form.get("presetId") || "custom");
const existingCustomPreset = (state.settings.racePresets || []).find((preset) => preset.id === selectedPresetId);
const presetId = existingCustomPreset ? existingCustomPreset.id : uid("preset");
const storedPreset = normalizeStoredRacePreset({
id: presetId,
name: presetName,
values: {
qualifyingScoring: config.qualifyingScoring,
qualifyingRounds: config.qualifyingRounds,
carsPerHeat: config.carsPerHeat,
qualDurationMin: config.qualDurationMin,
qualStartMode: config.qualStartMode,
qualSeedLapCount: config.qualSeedLapCount,
qualSeedMethod: config.qualSeedMethod,
countedQualRounds: config.countedQualRounds,
qualifyingPointsTable: config.qualifyingPointsTable,
qualifyingTieBreak: config.qualifyingTieBreak,
carsPerFinal: config.carsPerFinal,
finalLegs: config.finalLegs,
countedFinalLegs: config.countedFinalLegs,
finalDurationMin: config.finalDurationMin,
finalStartMode: config.finalStartMode,
followUpSec: config.followUpSec,
minLapMs: config.minLapMs,
maxLapMs: config.maxLapMs,
bumpCount: config.bumpCount,
reserveBumpSlots: config.reserveBumpSlots,
finalsSource: config.finalsSource,
},
});
const otherPresets = (state.settings.racePresets || []).filter((preset) => preset.id !== presetId);
state.settings.racePresets = [...otherPresets, storedPreset];
event.raceConfig = { ...config, presetId };
saveState();
rerenderEventManager(eventId);
});
document.getElementById("deleteRacePreset")?.addEventListener("click", () => {
const formElement = document.getElementById("raceFormatForm");
if (!(formElement instanceof HTMLFormElement)) {
return;
}
const form = new FormData(formElement);
const presetId = String(form.get("presetId") || "custom");
if (!(state.settings.racePresets || []).some((preset) => preset.id === presetId)) {
return;
}
state.settings.racePresets = (state.settings.racePresets || []).filter((preset) => preset.id !== presetId);
event.raceConfig.presetId = "custom";
saveState();
rerenderEventManager(eventId);
});
document.getElementById("generateQualifying")?.addEventListener("click", () => {
const created = generateQualifyingForRace(event);
saveState();
rerenderEventManager(eventId);
if (created > 0) {
alert(t("events.generated_qualifying"));
}
});
document.getElementById("clearGeneratedQualifying")?.addEventListener("click", () => {
clearGeneratedQualifying(event.id);
saveState();
rerenderEventManager(eventId);
});
document.getElementById("reseedQualifying")?.addEventListener("click", () => {
const result = reseedUpcomingQualifying(event);
saveState();
rerenderEventManager(eventId);
const messages = [];
if (result.updated > 0) {
messages.push(t("events.reseed_done"));
} else {
messages.push(t("events.no_reseed_done"));
}
if (result.locked > 0) {
messages.push(t("events.reseed_locked", { count: result.locked }));
}
alert(messages.join("\n"));
});
document.getElementById("generateFinals")?.addEventListener("click", () => {
const created = generateFinalsForRace(event);
saveState();
rerenderEventManager(eventId);
if (created > 0) {
alert(t("events.finals_generated"));
}
});
document.getElementById("clearGeneratedFinals")?.addEventListener("click", () => {
clearGeneratedFinals(event.id);
saveState();
rerenderEventManager(eventId);
});
document.getElementById("applyBumps")?.addEventListener("click", () => {
const applied = applyBumpsForRace(event);
saveState();
rerenderEventManager(eventId);
alert(t(applied > 0 ? "events.bumps_applied" : "events.no_bumps_applied"));
});
document.getElementById("exportRacePackage")?.addEventListener("click", () => {
const payload = buildRacePackagePayload(eventId);
downloadJsonFile(`${sanitizeFilenameSegment(event.name)}_race_package.json`, payload);
});
document.getElementById("importRacePackage")?.addEventListener("change", (importEvent) => {
const input = importEvent.currentTarget;
const file = input instanceof HTMLInputElement ? input.files?.[0] : null;
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = () => {
try {
const parsed = JSON.parse(String(reader.result || "{}"));
importRacePackagePayload(parsed);
} catch (error) {
alert(t("settings.import_failed", { msg: error instanceof Error ? error.message : String(error) }));
}
};
reader.readAsText(file);
});
document.getElementById("printStartlists")?.addEventListener("click", () => {
openPrintWindow(`${event.name} - ${t("events.start_lists")}`, buildRaceStartListsHtml(event));
});
document.getElementById("printResults")?.addEventListener("click", () => {
openPrintWindow(`${event.name} - ${t("events.results_overview")}`, buildRaceResultsHtml(event));
});
document.getElementById("printTeamResults")?.addEventListener("click", () => {
openPrintWindow(`${event.name} - ${t("events.team_report")}`, buildTeamRaceResultsHtml(event));
});
document.getElementById("pdfStartlists")?.addEventListener("click", async () => {
await exportRaceStartListsPdf(event);
});
document.getElementById("pdfResults")?.addEventListener("click", async () => {
await exportRaceResultsPdf(event);
});
document.getElementById("pdfTeamResults")?.addEventListener("click", async () => {
await exportTeamRaceResultsPdf(event);
});
document.getElementById("gridResetOrder")?.addEventListener("click", () => {
if (!selectedGridSession) {
return;
}
selectedGridSession.driverIds = getSessionEntrants(selectedGridSession)
.map((driver) => driver.id)
.filter(Boolean);
selectedGridSession.manualGridIds = [...selectedGridSession.driverIds];
selectedGridSession.gridCustomized = false;
saveState();
rerenderEventManager(eventId);
});
document.getElementById("gridToggleLock")?.addEventListener("click", () => {
if (!selectedGridSession) {
return;
}
if (!selectedGridSession.gridCustomized) {
selectedGridSession.manualGridIds = [...ensureSessionDriverOrder(selectedGridSession)];
selectedGridSession.gridCustomized = true;
} else {
selectedGridSession.manualGridIds = [...selectedGridSession.driverIds];
selectedGridSession.gridCustomized = false;
}
saveState();
rerenderEventManager(eventId);
});
let dragIndex = null;
document.querySelectorAll("#gridDragList .drag-item").forEach((node) => {
node.addEventListener("dragstart", () => {
dragIndex = Number(node.dataset.index);
node.classList.add("drag-item-active");
});
node.addEventListener("dragend", () => {
dragIndex = null;
node.classList.remove("drag-item-active");
});
node.addEventListener("dragover", (dragEvent) => {
dragEvent.preventDefault();
node.classList.add("drag-item-over");
});
node.addEventListener("dragleave", () => {
node.classList.remove("drag-item-over");
});
node.addEventListener("drop", (dropEvent) => {
dropEvent.preventDefault();
node.classList.remove("drag-item-over");
if (!selectedGridSession || dragIndex === null) {
return;
}
const dropIndex = Number(node.dataset.index);
if (Number.isNaN(dropIndex) || dropIndex === dragIndex) {
return;
}
selectedGridSession.manualGridIds = reorderList(ensureSessionDriverOrder(selectedGridSession), dragIndex, dropIndex);
selectedGridSession.gridCustomized = true;
saveState();
rerenderEventManager(eventId);
});
});
}
}