Extract event workspace controller into module
This commit is contained in:
310
src/app.js
310
src/app.js
@@ -12,7 +12,7 @@ import { renderDashboardView, renderClassesView, renderDriversView, renderCarsVi
|
|||||||
|
|
||||||
import { renderGuideView, renderOverlayPageView } from "./misc_views.js";
|
import { renderGuideView, renderOverlayPageView } from "./misc_views.js";
|
||||||
import { getSessionsForEventHelper, getModeLabelHelper, normalizeStartModeHelper, getStartModeLabelHelper, getClassNameHelper, getEventNameHelper, renderAssignmentListView, renderSessionsTableView } from "./event_common.js";
|
import { getSessionsForEventHelper, getModeLabelHelper, normalizeStartModeHelper, getStartModeLabelHelper, getClassNameHelper, getEventNameHelper, renderAssignmentListView, renderSessionsTableView } from "./event_common.js";
|
||||||
import { renderEventWorkspaceMarkup } from "./event_views.js";
|
import { renderEventWorkspaceView } from "./event_workspace_controller.js";
|
||||||
import { renderEventManagerMarkup } from "./event_manager_view.js";
|
import { renderEventManagerMarkup } from "./event_manager_view.js";
|
||||||
import { createDefaultAmmcConfigHelper, getManagedWsUrlHelper, loadAmmcConfigFromBackendHelper, saveAmmcConfigToBackendHelper, refreshAmmcStatusHelper, startManagedAmmcHelper, stopManagedAmmcHelper, applyPersistedStateHelper, hydrateFromBackendHelper, scheduleBackendSyncHelper, syncStateToBackendHelper, pingBackendHelper, checkAppVersionHelper, startAppVersionPollingHelper, startOverlaySyncHelper, startOverlayRotationHelper, startOverlayLiveRefreshHelper, ensureAudioContextHelper, playPassingBeepHelper, playFinishSirenHelper, playLeaderCueHelper, playStartCueHelper, playBestLapCueHelper, pushOverlayEventHelper, speakTextHelper, announcePassingHelper, announceRaceFinishedHelper, handleSessionTimerTickHelper, tickClockHelper } from "./runtime_services.js";
|
import { createDefaultAmmcConfigHelper, getManagedWsUrlHelper, loadAmmcConfigFromBackendHelper, saveAmmcConfigToBackendHelper, refreshAmmcStatusHelper, startManagedAmmcHelper, stopManagedAmmcHelper, applyPersistedStateHelper, hydrateFromBackendHelper, scheduleBackendSyncHelper, syncStateToBackendHelper, pingBackendHelper, checkAppVersionHelper, startAppVersionPollingHelper, startOverlaySyncHelper, startOverlayRotationHelper, startOverlayLiveRefreshHelper, ensureAudioContextHelper, playPassingBeepHelper, playFinishSirenHelper, playLeaderCueHelper, playStartCueHelper, playBestLapCueHelper, pushOverlayEventHelper, speakTextHelper, announcePassingHelper, announceRaceFinishedHelper, handleSessionTimerTickHelper, tickClockHelper } from "./runtime_services.js";
|
||||||
import { connectDecoderHelper, disconnectDecoderHelper, processDecoderMessageHelper } from "./decoder_runtime.js";
|
import { connectDecoderHelper, disconnectDecoderHelper, processDecoderMessageHelper } from "./decoder_runtime.js";
|
||||||
@@ -2812,302 +2812,42 @@ function renderRaceSetup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderEventWorkspace(mode) {
|
function renderEventWorkspace(mode) {
|
||||||
const isRaceMode = mode === "race";
|
renderEventWorkspaceView({
|
||||||
if (isRaceMode) {
|
mode,
|
||||||
ensureRaceWizardDraft();
|
|
||||||
}
|
|
||||||
const filteredEvents = state.events.filter((event) => event.mode === mode);
|
|
||||||
const editingEvent = filteredEvents.find((event) => event.id === selectedEventEditId) || null;
|
|
||||||
|
|
||||||
dom.view.innerHTML = renderEventWorkspaceMarkup(mode, {
|
|
||||||
state,
|
state,
|
||||||
|
dom,
|
||||||
t,
|
t,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
renderTable,
|
renderTable,
|
||||||
renderRaceWizardStepsView,
|
renderRaceWizardStepsView,
|
||||||
renderRaceWizardContentView,
|
renderRaceWizardContentView,
|
||||||
raceWizardDraft,
|
getRaceWizardDraft: () => raceWizardDraft,
|
||||||
raceWizardStep,
|
setRaceWizardDraft: (value) => { raceWizardDraft = value; },
|
||||||
|
getRaceWizardStep: () => raceWizardStep,
|
||||||
|
setRaceWizardStep: (value) => { raceWizardStep = value; },
|
||||||
|
ensureRaceWizardDraft,
|
||||||
getDriversForClass,
|
getDriversForClass,
|
||||||
getRaceWizardPreset,
|
getRaceWizardPreset,
|
||||||
getSessionsForEvent,
|
getSessionsForEvent,
|
||||||
getClassName,
|
getClassName,
|
||||||
getModeLabel,
|
getModeLabel,
|
||||||
editingEvent,
|
getSelectedEventEditId: () => selectedEventEditId,
|
||||||
|
setSelectedEventEditId: (value) => { selectedEventEditId = value; },
|
||||||
|
renderView,
|
||||||
|
renderEventManager,
|
||||||
|
uid,
|
||||||
|
normalizeEvent,
|
||||||
|
applyRaceFormatPreset,
|
||||||
|
buildRaceSessionsFromWizard,
|
||||||
|
buildDefaultRaceWizardDraft,
|
||||||
|
applyRaceWizardPresetDefaults,
|
||||||
|
setSelectedTeamEditId: (value) => { selectedTeamEditId = value; },
|
||||||
|
setSelectedSessionEditId: (value) => { selectedSessionEditId = value; },
|
||||||
|
setFormError,
|
||||||
|
bindModalShell,
|
||||||
|
isValidIsoDate,
|
||||||
|
saveState,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isRaceMode) {
|
|
||||||
const persistWizardStepOne = () => {
|
|
||||||
const form = document.getElementById("raceWizardStepForm");
|
|
||||||
if (!(form instanceof HTMLFormElement)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const data = new FormData(form);
|
|
||||||
const nextName = String(data.get("name") || "").trim();
|
|
||||||
const nextDate = String(data.get("date") || "").trim();
|
|
||||||
const nextClassId = String(data.get("classId") || "").trim();
|
|
||||||
const nextPresetId = String(data.get("presetId") || "club_qualifying").trim() || "club_qualifying";
|
|
||||||
if (!nextName || !nextDate || !nextClassId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const classChanged = raceWizardDraft.classId !== nextClassId;
|
|
||||||
raceWizardDraft.name = nextName;
|
|
||||||
raceWizardDraft.date = nextDate;
|
|
||||||
raceWizardDraft.classId = nextClassId;
|
|
||||||
if (classChanged) {
|
|
||||||
raceWizardDraft.driverIds = getDriversForClass(nextClassId).map((driver) => driver.id);
|
|
||||||
}
|
|
||||||
if (raceWizardDraft.presetId !== nextPresetId) {
|
|
||||||
applyRaceWizardPresetDefaults(raceWizardDraft, nextPresetId);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const persistWizardParticipants = () => {
|
|
||||||
raceWizardDraft.driverIds = Array.from(document.querySelectorAll(".wizard-participant:checked")).map((node) => node.value);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const persistWizardPlan = () => {
|
|
||||||
const form = document.getElementById("raceWizardPlanForm");
|
|
||||||
if (!(form instanceof HTMLFormElement)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const data = new FormData(form);
|
|
||||||
raceWizardDraft.createPractice = data.get("createPractice") === "on";
|
|
||||||
raceWizardDraft.practiceSessions = Math.max(0, Number(data.get("practiceSessions") || 0) || 0);
|
|
||||||
raceWizardDraft.createQualifying = data.get("createQualifying") === "on";
|
|
||||||
raceWizardDraft.qualifyingRounds = Math.max(0, Number(data.get("qualifyingRounds") || 0) || 0);
|
|
||||||
raceWizardDraft.createTeamRace = data.get("createTeamRace") === "on";
|
|
||||||
raceWizardDraft.teamRaceDurationMin = Math.max(1, Number(data.get("teamRaceDurationMin") || 1) || 1);
|
|
||||||
return raceWizardDraft.createPractice || raceWizardDraft.createQualifying || raceWizardDraft.createTeamRace;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById("raceWizardReset")?.addEventListener("click", () => {
|
|
||||||
raceWizardDraft = applyRaceWizardPresetDefaults(buildDefaultRaceWizardDraft(), "club_qualifying");
|
|
||||||
raceWizardStep = 1;
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("raceWizardPrev")?.addEventListener("click", () => {
|
|
||||||
if (raceWizardStep === 2) {
|
|
||||||
persistWizardParticipants();
|
|
||||||
}
|
|
||||||
if (raceWizardStep === 3) {
|
|
||||||
persistWizardPlan();
|
|
||||||
}
|
|
||||||
raceWizardStep = Math.max(1, raceWizardStep - 1);
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("raceWizardNext")?.addEventListener("click", () => {
|
|
||||||
let valid = true;
|
|
||||||
if (raceWizardStep === 1) {
|
|
||||||
valid = persistWizardStepOne();
|
|
||||||
} else if (raceWizardStep === 2) {
|
|
||||||
valid = persistWizardParticipants();
|
|
||||||
} else if (raceWizardStep === 3) {
|
|
||||||
valid = persistWizardPlan();
|
|
||||||
}
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
raceWizardStep = Math.min(4, raceWizardStep + 1);
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
const wizardBasicsForm = document.getElementById("raceWizardStepForm");
|
|
||||||
const syncWizardBasicsDraft = () => {
|
|
||||||
if (!(wizardBasicsForm instanceof HTMLFormElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = new FormData(wizardBasicsForm);
|
|
||||||
raceWizardDraft.name = String(data.get("name") || "").trim();
|
|
||||||
raceWizardDraft.date = String(data.get("date") || raceWizardDraft.date || "").trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
wizardBasicsForm?.querySelector('[name="classId"]')?.addEventListener("change", (event) => {
|
|
||||||
syncWizardBasicsDraft();
|
|
||||||
const nextClassId = String(event.currentTarget?.value || "").trim();
|
|
||||||
if (!nextClassId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const classChanged = raceWizardDraft.classId !== nextClassId;
|
|
||||||
raceWizardDraft.classId = nextClassId;
|
|
||||||
if (classChanged) {
|
|
||||||
raceWizardDraft.driverIds = getDriversForClass(nextClassId).map((driver) => driver.id);
|
|
||||||
}
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
wizardBasicsForm?.querySelector('[name="presetId"]')?.addEventListener("change", (event) => {
|
|
||||||
syncWizardBasicsDraft();
|
|
||||||
const nextPresetId = String(event.currentTarget?.value || "club_qualifying").trim() || "club_qualifying";
|
|
||||||
applyRaceWizardPresetDefaults(raceWizardDraft, nextPresetId);
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
const wizardPlanForm = document.getElementById("raceWizardPlanForm");
|
|
||||||
const toggleWizardPlanField = (toggleName, fieldName) => {
|
|
||||||
const toggle = wizardPlanForm?.querySelector(`[name="${toggleName}"]`);
|
|
||||||
const field = wizardPlanForm?.querySelector(`[name="${fieldName}"]`);
|
|
||||||
if (!(toggle instanceof HTMLInputElement) || !(field instanceof HTMLInputElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const applyDisabledState = () => {
|
|
||||||
field.disabled = !toggle.checked;
|
|
||||||
};
|
|
||||||
applyDisabledState();
|
|
||||||
toggle.addEventListener("change", applyDisabledState);
|
|
||||||
};
|
|
||||||
toggleWizardPlanField("createPractice", "practiceSessions");
|
|
||||||
toggleWizardPlanField("createQualifying", "qualifyingRounds");
|
|
||||||
toggleWizardPlanField("createTeamRace", "teamRaceDurationMin");
|
|
||||||
|
|
||||||
document.getElementById("wizardSelectAllParticipants")?.addEventListener("click", () => {
|
|
||||||
raceWizardDraft.driverIds = getDriversForClass(raceWizardDraft.classId).map((driver) => driver.id);
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("wizardClearParticipants")?.addEventListener("click", () => {
|
|
||||||
raceWizardDraft.driverIds = [];
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("raceWizardCreate")?.addEventListener("click", () => {
|
|
||||||
const selectedDrivers = raceWizardDraft.driverIds.length ? raceWizardDraft.driverIds : getDriversForClass(raceWizardDraft.classId).map((driver) => driver.id);
|
|
||||||
const event = normalizeEvent({
|
|
||||||
id: uid("event"),
|
|
||||||
name: raceWizardDraft.name.trim(),
|
|
||||||
date: raceWizardDraft.date,
|
|
||||||
classId: raceWizardDraft.classId,
|
|
||||||
mode,
|
|
||||||
});
|
|
||||||
applyRaceFormatPreset(event, raceWizardDraft.presetId);
|
|
||||||
event.raceConfig.driverIds = selectedDrivers;
|
|
||||||
event.raceConfig.participantsConfigured = true;
|
|
||||||
state.events.push(event);
|
|
||||||
buildRaceSessionsFromWizard(event, raceWizardDraft).forEach((session) => state.sessions.push(session));
|
|
||||||
selectedTeamEditId = null;
|
|
||||||
selectedSessionEditId = null;
|
|
||||||
raceWizardDraft = applyRaceWizardPresetDefaults(buildDefaultRaceWizardDraft(), "club_qualifying");
|
|
||||||
raceWizardStep = 1;
|
|
||||||
saveState();
|
|
||||||
renderView();
|
|
||||||
renderEventManager(event.id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
document.getElementById("eventForm")?.addEventListener("submit", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const form = new FormData(e.currentTarget);
|
|
||||||
const event = {
|
|
||||||
id: uid("event"),
|
|
||||||
name: String(form.get("name")).trim(),
|
|
||||||
date: String(form.get("date")),
|
|
||||||
classId: String(form.get("classId")),
|
|
||||||
mode,
|
|
||||||
};
|
|
||||||
state.events.push(normalizeEvent(event));
|
|
||||||
saveState();
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredEvents.forEach((e) => {
|
|
||||||
document.getElementById(`event-edit-${e.id}`)?.addEventListener("click", () => {
|
|
||||||
selectedEventEditId = e.id;
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById(`event-delete-${e.id}`)?.addEventListener("click", () => {
|
|
||||||
const sessionIds = getSessionsForEvent(e.id).map((s) => s.id);
|
|
||||||
state.events = state.events.filter((x) => x.id !== e.id);
|
|
||||||
state.sessions = state.sessions.filter((x) => x.eventId !== e.id);
|
|
||||||
sessionIds.forEach((id) => delete state.resultsBySession[id]);
|
|
||||||
if (state.activeSessionId && sessionIds.includes(state.activeSessionId)) {
|
|
||||||
state.activeSessionId = null;
|
|
||||||
}
|
|
||||||
saveState();
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById(`event-manage-${e.id}`)?.addEventListener("click", () => {
|
|
||||||
renderEventManager(e.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("eventEditCancel")?.addEventListener("click", () => {
|
|
||||||
selectedEventEditId = null;
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("eventEditCancelFooter")?.addEventListener("click", () => {
|
|
||||||
selectedEventEditId = null;
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("eventEditModalOverlay")?.addEventListener("click", (event) => {
|
|
||||||
if (event.target?.id === "eventEditModalOverlay") {
|
|
||||||
selectedEventEditId = null;
|
|
||||||
renderView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bindModalShell("eventEditModalOverlay", () => {
|
|
||||||
selectedEventEditId = null;
|
|
||||||
renderView();
|
|
||||||
});
|
|
||||||
|
|
||||||
const commitEventEdit = () => {
|
|
||||||
if (!editingEvent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const formNode = document.getElementById("eventEditForm");
|
|
||||||
if (!(formNode instanceof HTMLFormElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const form = new FormData(formNode);
|
|
||||||
const cleanedName = String(form.get("name") || "").trim();
|
|
||||||
const cleanedDate = String(form.get("date") || "").trim();
|
|
||||||
const cleanedClassId = String(form.get("classId") || "").trim();
|
|
||||||
if (!cleanedName) {
|
|
||||||
setFormError("eventEditError", t("validation.required_name"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!cleanedDate) {
|
|
||||||
setFormError("eventEditError", t("validation.required_date"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isValidIsoDate(cleanedDate)) {
|
|
||||||
setFormError("eventEditError", t("validation.invalid_date"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (cleanedClassId && !state.classes.some((item) => item.id === cleanedClassId)) {
|
|
||||||
setFormError("eventEditError", t("validation.invalid_selection"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setFormError("eventEditError", "");
|
|
||||||
state.events = state.events.map((item) =>
|
|
||||||
item.id === editingEvent.id
|
|
||||||
? normalizeEvent({
|
|
||||||
...item,
|
|
||||||
name: cleanedName,
|
|
||||||
date: cleanedDate,
|
|
||||||
classId: cleanedClassId || item.classId,
|
|
||||||
})
|
|
||||||
: item
|
|
||||||
);
|
|
||||||
selectedEventEditId = null;
|
|
||||||
saveState();
|
|
||||||
renderView();
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById("eventEditForm")?.addEventListener("submit", (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
commitEventEdit();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("eventEditSave")?.addEventListener("click", commitEventEdit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEventManager(eventId) {
|
function renderEventManager(eventId) {
|
||||||
|
|||||||
344
src/event_workspace_controller.js
Normal file
344
src/event_workspace_controller.js
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import { renderEventWorkspaceMarkup } from "./event_views.js";
|
||||||
|
|
||||||
|
export function renderEventWorkspaceView(context) {
|
||||||
|
const {
|
||||||
|
mode,
|
||||||
|
state,
|
||||||
|
dom,
|
||||||
|
t,
|
||||||
|
escapeHtml,
|
||||||
|
renderTable,
|
||||||
|
renderRaceWizardStepsView,
|
||||||
|
renderRaceWizardContentView,
|
||||||
|
getRaceWizardDraft,
|
||||||
|
setRaceWizardDraft,
|
||||||
|
getRaceWizardStep,
|
||||||
|
setRaceWizardStep,
|
||||||
|
ensureRaceWizardDraft,
|
||||||
|
getDriversForClass,
|
||||||
|
getRaceWizardPreset,
|
||||||
|
getSessionsForEvent,
|
||||||
|
getClassName,
|
||||||
|
getModeLabel,
|
||||||
|
getSelectedEventEditId,
|
||||||
|
setSelectedEventEditId,
|
||||||
|
renderView,
|
||||||
|
renderEventManager,
|
||||||
|
uid,
|
||||||
|
normalizeEvent,
|
||||||
|
applyRaceFormatPreset,
|
||||||
|
buildRaceSessionsFromWizard,
|
||||||
|
buildDefaultRaceWizardDraft,
|
||||||
|
applyRaceWizardPresetDefaults,
|
||||||
|
setSelectedTeamEditId,
|
||||||
|
setSelectedSessionEditId,
|
||||||
|
setFormError,
|
||||||
|
bindModalShell,
|
||||||
|
isValidIsoDate,
|
||||||
|
saveState,
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
const isRaceMode = mode === "race";
|
||||||
|
if (isRaceMode) {
|
||||||
|
ensureRaceWizardDraft();
|
||||||
|
}
|
||||||
|
const filteredEvents = state.events.filter((event) => event.mode === mode);
|
||||||
|
const editingEvent = filteredEvents.find((event) => event.id === getSelectedEventEditId()) || null;
|
||||||
|
const raceWizardDraft = getRaceWizardDraft();
|
||||||
|
const raceWizardStep = getRaceWizardStep();
|
||||||
|
|
||||||
|
dom.view.innerHTML = renderEventWorkspaceMarkup(mode, {
|
||||||
|
state,
|
||||||
|
t,
|
||||||
|
escapeHtml,
|
||||||
|
renderTable,
|
||||||
|
renderRaceWizardStepsView,
|
||||||
|
renderRaceWizardContentView,
|
||||||
|
raceWizardDraft,
|
||||||
|
raceWizardStep,
|
||||||
|
getDriversForClass,
|
||||||
|
getRaceWizardPreset,
|
||||||
|
getSessionsForEvent,
|
||||||
|
getClassName,
|
||||||
|
getModeLabel,
|
||||||
|
editingEvent,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isRaceMode) {
|
||||||
|
const persistWizardStepOne = () => {
|
||||||
|
const form = document.getElementById("raceWizardStepForm");
|
||||||
|
if (!(form instanceof HTMLFormElement)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const data = new FormData(form);
|
||||||
|
const nextName = String(data.get("name") || "").trim();
|
||||||
|
const nextDate = String(data.get("date") || "").trim();
|
||||||
|
const nextClassId = String(data.get("classId") || "").trim();
|
||||||
|
const nextPresetId = String(data.get("presetId") || "club_qualifying").trim() || "club_qualifying";
|
||||||
|
if (!nextName || !nextDate || !nextClassId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
const classChanged = draft.classId !== nextClassId;
|
||||||
|
draft.name = nextName;
|
||||||
|
draft.date = nextDate;
|
||||||
|
draft.classId = nextClassId;
|
||||||
|
if (classChanged) {
|
||||||
|
draft.driverIds = getDriversForClass(nextClassId).map((driver) => driver.id);
|
||||||
|
}
|
||||||
|
if (draft.presetId !== nextPresetId) {
|
||||||
|
applyRaceWizardPresetDefaults(draft, nextPresetId);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistWizardParticipants = () => {
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
draft.driverIds = Array.from(document.querySelectorAll(".wizard-participant:checked")).map((node) => node.value);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistWizardPlan = () => {
|
||||||
|
const form = document.getElementById("raceWizardPlanForm");
|
||||||
|
if (!(form instanceof HTMLFormElement)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const data = new FormData(form);
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
draft.createPractice = data.get("createPractice") === "on";
|
||||||
|
draft.practiceSessions = Math.max(0, Number(data.get("practiceSessions") || 0) || 0);
|
||||||
|
draft.createQualifying = data.get("createQualifying") === "on";
|
||||||
|
draft.qualifyingRounds = Math.max(0, Number(data.get("qualifyingRounds") || 0) || 0);
|
||||||
|
draft.createTeamRace = data.get("createTeamRace") === "on";
|
||||||
|
draft.teamRaceDurationMin = Math.max(1, Number(data.get("teamRaceDurationMin") || 1) || 1);
|
||||||
|
return draft.createPractice || draft.createQualifying || draft.createTeamRace;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("raceWizardReset")?.addEventListener("click", () => {
|
||||||
|
setRaceWizardDraft(applyRaceWizardPresetDefaults(buildDefaultRaceWizardDraft(), "club_qualifying"));
|
||||||
|
setRaceWizardStep(1);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("raceWizardPrev")?.addEventListener("click", () => {
|
||||||
|
if (getRaceWizardStep() === 2) {
|
||||||
|
persistWizardParticipants();
|
||||||
|
}
|
||||||
|
if (getRaceWizardStep() === 3) {
|
||||||
|
persistWizardPlan();
|
||||||
|
}
|
||||||
|
setRaceWizardStep(Math.max(1, getRaceWizardStep() - 1));
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("raceWizardNext")?.addEventListener("click", () => {
|
||||||
|
let valid = true;
|
||||||
|
if (getRaceWizardStep() === 1) {
|
||||||
|
valid = persistWizardStepOne();
|
||||||
|
} else if (getRaceWizardStep() === 2) {
|
||||||
|
valid = persistWizardParticipants();
|
||||||
|
} else if (getRaceWizardStep() === 3) {
|
||||||
|
valid = persistWizardPlan();
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setRaceWizardStep(Math.min(4, getRaceWizardStep() + 1));
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
const wizardBasicsForm = document.getElementById("raceWizardStepForm");
|
||||||
|
const syncWizardBasicsDraft = () => {
|
||||||
|
if (!(wizardBasicsForm instanceof HTMLFormElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = new FormData(wizardBasicsForm);
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
draft.name = String(data.get("name") || "").trim();
|
||||||
|
draft.date = String(data.get("date") || draft.date || "").trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
wizardBasicsForm?.querySelector('[name="classId"]')?.addEventListener("change", (event) => {
|
||||||
|
syncWizardBasicsDraft();
|
||||||
|
const nextClassId = String(event.currentTarget?.value || "").trim();
|
||||||
|
if (!nextClassId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
const classChanged = draft.classId !== nextClassId;
|
||||||
|
draft.classId = nextClassId;
|
||||||
|
if (classChanged) {
|
||||||
|
draft.driverIds = getDriversForClass(nextClassId).map((driver) => driver.id);
|
||||||
|
}
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
wizardBasicsForm?.querySelector('[name="presetId"]')?.addEventListener("change", (event) => {
|
||||||
|
syncWizardBasicsDraft();
|
||||||
|
const nextPresetId = String(event.currentTarget?.value || "club_qualifying").trim() || "club_qualifying";
|
||||||
|
applyRaceWizardPresetDefaults(getRaceWizardDraft(), nextPresetId);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
const wizardPlanForm = document.getElementById("raceWizardPlanForm");
|
||||||
|
const toggleWizardPlanField = (toggleName, fieldName) => {
|
||||||
|
const toggle = wizardPlanForm?.querySelector(`[name="${toggleName}"]`);
|
||||||
|
const field = wizardPlanForm?.querySelector(`[name="${fieldName}"]`);
|
||||||
|
if (!(toggle instanceof HTMLInputElement) || !(field instanceof HTMLInputElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const applyDisabledState = () => {
|
||||||
|
field.disabled = !toggle.checked;
|
||||||
|
};
|
||||||
|
applyDisabledState();
|
||||||
|
toggle.addEventListener("change", applyDisabledState);
|
||||||
|
};
|
||||||
|
toggleWizardPlanField("createPractice", "practiceSessions");
|
||||||
|
toggleWizardPlanField("createQualifying", "qualifyingRounds");
|
||||||
|
toggleWizardPlanField("createTeamRace", "teamRaceDurationMin");
|
||||||
|
|
||||||
|
document.getElementById("wizardSelectAllParticipants")?.addEventListener("click", () => {
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
draft.driverIds = getDriversForClass(draft.classId).map((driver) => driver.id);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("wizardClearParticipants")?.addEventListener("click", () => {
|
||||||
|
getRaceWizardDraft().driverIds = [];
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("raceWizardCreate")?.addEventListener("click", () => {
|
||||||
|
const draft = getRaceWizardDraft();
|
||||||
|
const selectedDrivers = draft.driverIds.length ? draft.driverIds : getDriversForClass(draft.classId).map((driver) => driver.id);
|
||||||
|
const event = normalizeEvent({
|
||||||
|
id: uid("event"),
|
||||||
|
name: draft.name.trim(),
|
||||||
|
date: draft.date,
|
||||||
|
classId: draft.classId,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
applyRaceFormatPreset(event, draft.presetId);
|
||||||
|
event.raceConfig.driverIds = selectedDrivers;
|
||||||
|
event.raceConfig.participantsConfigured = true;
|
||||||
|
state.events.push(event);
|
||||||
|
buildRaceSessionsFromWizard(event, draft).forEach((session) => state.sessions.push(session));
|
||||||
|
setSelectedTeamEditId(null);
|
||||||
|
setSelectedSessionEditId(null);
|
||||||
|
setRaceWizardDraft(applyRaceWizardPresetDefaults(buildDefaultRaceWizardDraft(), "club_qualifying"));
|
||||||
|
setRaceWizardStep(1);
|
||||||
|
saveState();
|
||||||
|
renderView();
|
||||||
|
renderEventManager(event.id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.getElementById("eventForm")?.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = new FormData(e.currentTarget);
|
||||||
|
const event = {
|
||||||
|
id: uid("event"),
|
||||||
|
name: String(form.get("name")).trim(),
|
||||||
|
date: String(form.get("date")),
|
||||||
|
classId: String(form.get("classId")),
|
||||||
|
mode,
|
||||||
|
};
|
||||||
|
state.events.push(normalizeEvent(event));
|
||||||
|
saveState();
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredEvents.forEach((e) => {
|
||||||
|
document.getElementById(`event-edit-${e.id}`)?.addEventListener("click", () => {
|
||||||
|
setSelectedEventEditId(e.id);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById(`event-delete-${e.id}`)?.addEventListener("click", () => {
|
||||||
|
const sessionIds = getSessionsForEvent(e.id).map((s) => s.id);
|
||||||
|
state.events = state.events.filter((x) => x.id !== e.id);
|
||||||
|
state.sessions = state.sessions.filter((x) => x.eventId !== e.id);
|
||||||
|
sessionIds.forEach((id) => delete state.resultsBySession[id]);
|
||||||
|
if (state.activeSessionId && sessionIds.includes(state.activeSessionId)) {
|
||||||
|
state.activeSessionId = null;
|
||||||
|
}
|
||||||
|
saveState();
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById(`event-manage-${e.id}`)?.addEventListener("click", () => {
|
||||||
|
renderEventManager(e.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("eventEditCancel")?.addEventListener("click", () => {
|
||||||
|
setSelectedEventEditId(null);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("eventEditCancelFooter")?.addEventListener("click", () => {
|
||||||
|
setSelectedEventEditId(null);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("eventEditModalOverlay")?.addEventListener("click", (event) => {
|
||||||
|
if (event.target?.id === "eventEditModalOverlay") {
|
||||||
|
setSelectedEventEditId(null);
|
||||||
|
renderView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bindModalShell("eventEditModalOverlay", () => {
|
||||||
|
setSelectedEventEditId(null);
|
||||||
|
renderView();
|
||||||
|
});
|
||||||
|
|
||||||
|
const commitEventEdit = () => {
|
||||||
|
if (!editingEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formNode = document.getElementById("eventEditForm");
|
||||||
|
if (!(formNode instanceof HTMLFormElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const form = new FormData(formNode);
|
||||||
|
const cleanedName = String(form.get("name") || "").trim();
|
||||||
|
const cleanedDate = String(form.get("date") || "").trim();
|
||||||
|
const cleanedClassId = String(form.get("classId") || "").trim();
|
||||||
|
if (!cleanedName) {
|
||||||
|
setFormError("eventEditError", t("validation.required_name"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!cleanedDate) {
|
||||||
|
setFormError("eventEditError", t("validation.required_date"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isValidIsoDate(cleanedDate)) {
|
||||||
|
setFormError("eventEditError", t("validation.invalid_date"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cleanedClassId && !state.classes.some((item) => item.id === cleanedClassId)) {
|
||||||
|
setFormError("eventEditError", t("validation.invalid_selection"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFormError("eventEditError", "");
|
||||||
|
state.events = state.events.map((item) =>
|
||||||
|
item.id === editingEvent.id
|
||||||
|
? normalizeEvent({
|
||||||
|
...item,
|
||||||
|
name: cleanedName,
|
||||||
|
date: cleanedDate,
|
||||||
|
classId: cleanedClassId || item.classId,
|
||||||
|
})
|
||||||
|
: item
|
||||||
|
);
|
||||||
|
setSelectedEventEditId(null);
|
||||||
|
saveState();
|
||||||
|
renderView();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("eventEditForm")?.addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
commitEventEdit();
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user