Extract event workspace controller into module

This commit is contained in:
larssand
2026-03-26 20:39:08 +01:00
parent 0edf328b0b
commit ac0ccb1702
2 changed files with 369 additions and 285 deletions

View File

@@ -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) {

View 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();
});
}