874 lines
31 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
}
|
|
|
|
}
|