Files
Live_RC/src/race_setup_ui.js
2026-03-25 19:40:02 +01:00

217 lines
12 KiB
JavaScript

export function renderRaceFormatField(labelKey, hintKey, controlHtml, options = {}, { t }) {
const extraClass = options.checkbox ? " field-card-checkbox" : "";
return `
<label class="field-card${extraClass}">
<span class="field-label">${t(labelKey)}</span>
<span class="field-hint">${t(hintKey)}</span>
${controlHtml}
</label>
`;
}
export function renderRaceFormatContextCard(titleKey, hintKey, { t }) {
return `
<article class="field-card race-format-context-card">
<span class="field-label">${t(titleKey)}</span>
<span class="field-hint">${t(hintKey)}</span>
</article>
`;
}
export function getRaceSummaryItems(event, sessions, raceDrivers, selectedPreset, { t, getStartModeLabel }) {
const cfg = event.raceConfig || {};
const participantCount = cfg.participantsConfigured ? (cfg.driverIds || []).length : raceDrivers.length;
const practiceCount = sessions.filter((session) => ["practice", "free_practice", "open_practice"].includes(session.type)).length;
const qualCount = sessions.filter((session) => session.type === "qualification").length;
const finalCount = sessions.filter((session) => session.type === "final").length;
const teamCount = sessions.filter((session) => session.type === "team_race").length;
const scoringLabel = cfg.qualifyingScoring === "best" ? t("events.qualifying_scoring_best") : t("events.qualifying_scoring_points");
const lapWindow = `${((cfg.minLapMs || 0) / 1000).toFixed(1)}s / ${((cfg.maxLapMs || 0) / 1000).toFixed(1)}s`;
const isEndurancePreset = selectedPreset?.id === "endurance";
if (isEndurancePreset) {
return [
{ label: t("events.race_summary_preset"), value: selectedPreset?.label || t("events.preset_custom") },
{ label: t("events.race_summary_focus"), value: `${t("events.team_race")} · ${getStartModeLabel("mass")}` },
{ label: t("events.race_summary_participants"), value: String(participantCount || 0) },
{ label: t("events.race_summary_created_sessions"), value: `P ${practiceCount} · T ${teamCount}` },
{ label: t("events.race_summary_generation"), value: `${teamCount ? t("events.team_race") : t("events.wizard_use_team_race")} · ${cfg.finalDurationMin || 0} min` },
{ label: t("events.race_summary_validation"), value: lapWindow },
{ label: t("events.race_summary_follow_up"), value: `${cfg.followUpSec || 0}s` },
];
}
return [
{ label: t("events.race_summary_preset"), value: selectedPreset?.label || t("events.preset_custom") },
{ label: t("events.race_summary_participants"), value: String(participantCount || 0) },
{ label: t("events.race_summary_created_sessions"), value: `P ${practiceCount} · Q ${qualCount} · F ${finalCount} · T ${teamCount}` },
{ label: t("events.race_summary_qualifying"), value: `${cfg.qualifyingRounds || 0} x ${cfg.qualDurationMin || 0} min · ${cfg.carsPerHeat || 0}/heat · ${scoringLabel}` },
{ label: t("events.race_summary_finals"), value: `${cfg.carsPerFinal || 0}/main · ${cfg.finalLegs || 0} leg · ${getStartModeLabel(cfg.finalStartMode || "position")}` },
{ label: t("events.race_summary_generation"), value: `${cfg.finalsSource === "practice" ? t("events.finals_from_practice") : t("events.finals_from_qualifying")} · bump ${cfg.bumpCount || 0}` },
{ label: t("events.race_summary_validation"), value: lapWindow },
{ label: t("events.race_summary_follow_up"), value: `${cfg.followUpSec || 0}s` },
];
}
export function getRaceSummaryWarnings(event, sessions, raceDrivers, raceTeams, selectedPreset, { t }) {
const cfg = event.raceConfig || {};
const participantCount = cfg.participantsConfigured ? (cfg.driverIds || []).length : raceDrivers.length;
const warnings = [];
const isEndurancePreset = selectedPreset?.id === "endurance";
const hasQualifying = sessions.some((session) => session.type === "qualification");
const hasFinals = sessions.some((session) => session.type === "final");
const hasAnySession = sessions.length > 0;
if (participantCount <= 0) warnings.push({ message: t("events.summary_warning_no_participants"), target: "manage-setup-participants" });
if (!hasAnySession) warnings.push({ message: t("events.summary_warning_no_sessions"), target: "manage-session-plan" });
if (!isEndurancePreset && !hasQualifying) warnings.push({ message: t("events.summary_warning_no_qualifying"), target: "manage-generation" });
if (!isEndurancePreset && !hasFinals) warnings.push({ message: t("events.summary_warning_no_finals"), target: "manage-generation" });
if (isEndurancePreset && raceTeams.length <= 0) warnings.push({ message: t("events.summary_warning_no_teams"), target: "manage-setup-teams" });
const minLapMs = Math.max(0, Number(cfg.minLapMs || 0) || 0);
const maxLapMs = Math.max(0, Number(cfg.maxLapMs || 0) || 0);
if (maxLapMs > 0 && maxLapMs <= minLapMs) warnings.push({ message: t("events.summary_warning_invalid_lap_window"), target: "manage-format" });
return warnings;
}
export function renderManageStatusBadge(status, { t }) {
return `<span class="status-chip status-chip-${status}">${t(`events.status_${status}`)}</span>`;
}
export function renderRaceWizardSteps({ t, raceWizardStep, escapeHtml }) {
const steps = [t("events.wizard_step_1"), t("events.wizard_step_2"), t("events.wizard_step_3"), t("events.wizard_step_4")];
return steps
.map(
(label, index) => `
<article class="wizard-step ${index + 1 === raceWizardStep ? "wizard-step-active" : ""} ${index + 1 < raceWizardStep ? "wizard-step-complete" : ""}">
<span>${index + 1}</span>
<strong>${escapeHtml(label)}</strong>
</article>
`
)
.join("");
}
export function renderRaceWizardContent(draft, classOptions, wizardDrivers, preset, { t, escapeHtml, raceWizardStep, getRaceFormatPresets, getClassName, getRaceWizardSessionPlan }) {
if (raceWizardStep === 1) {
return `
<form id="raceWizardStepForm" class="form-grid cols-4">
<input required name="name" value="${escapeHtml(draft.name || "")}" placeholder="${t("events.name_placeholder")}" />
<input required type="date" name="date" value="${escapeHtml(draft.date || "")}" />
<select name="classId">${classOptions}</select>
<select name="presetId">${getRaceFormatPresets()
.filter((item) => item.id !== "custom")
.map((item) => `<option value="${item.id}" ${item.id === draft.presetId ? "selected" : ""}>${escapeHtml(item.label)}</option>`)
.join("")}</select>
</form>
`;
}
if (raceWizardStep === 2) {
return `
<div class="actions">
<button id="wizardSelectAllParticipants" class="btn" type="button">${t("events.select_all_participants")}</button>
<button id="wizardClearParticipants" class="btn btn-danger" type="button">${t("events.clear_participants")}</button>
</div>
${wizardDrivers.length ? `
<div class="check-grid mt-16">
${wizardDrivers
.map(
(driver) => `
<label class="check-card">
<input type="checkbox" class="wizard-participant" value="${driver.id}" ${draft.driverIds.includes(driver.id) ? "checked" : ""} />
<span>${escapeHtml(driver.name)}${driver.transponder ? ` (${escapeHtml(driver.transponder)})` : ""}</span>
</label>
`
)
.join("")}
</div>
` : `<p class="hint">${t("events.wizard_no_class_drivers")}</p>`}
`;
}
if (raceWizardStep === 3) {
const isEndurance = preset.id === "endurance";
return `
<form id="raceWizardPlanForm" class="form-grid cols-2">
<label class="field-card">
<span class="field-label">${t("events.wizard_use_practice")}</span>
<span class="field-hint">${t("events.free_practice_note")}</span>
<label class="toggle"><input type="checkbox" name="createPractice" ${draft.createPractice ? "checked" : ""} /><span>${t("events.wizard_use_practice")}</span></label>
</label>
<label class="field-card">
<span class="field-label">${t("events.wizard_practice_sessions")}</span>
<span class="field-hint">${t("events.practice_standings")}</span>
<input type="number" min="0" step="1" name="practiceSessions" value="${draft.practiceSessions}" ${draft.createPractice ? "" : "disabled"} />
</label>
${!isEndurance ? `
<label class="field-card">
<span class="field-label">${t("events.wizard_use_qualifying")}</span>
<span class="field-hint">${t("events.qualifying_rounds_hint")}</span>
<label class="toggle"><input type="checkbox" name="createQualifying" ${draft.createQualifying ? "checked" : ""} /><span>${t("events.wizard_use_qualifying")}</span></label>
</label>
<label class="field-card">
<span class="field-label">${t("events.wizard_qualifying_rounds")}</span>
<span class="field-hint">${t("events.qual_duration_hint")}</span>
<input type="number" min="0" step="1" name="qualifyingRounds" value="${draft.qualifyingRounds}" ${draft.createQualifying ? "" : "disabled"} />
</label>
` : `
<label class="field-card">
<span class="field-label">${t("events.wizard_use_team_race")}</span>
<span class="field-hint">${t("events.team_race_intro")}</span>
<label class="toggle"><input type="checkbox" name="createTeamRace" ${draft.createTeamRace ? "checked" : ""} /><span>${t("events.wizard_use_team_race")}</span></label>
</label>
<label class="field-card">
<span class="field-label">${t("events.wizard_team_duration")}</span>
<span class="field-hint">${t("events.final_duration_hint")}</span>
<input type="number" min="1" step="1" name="teamRaceDurationMin" value="${draft.teamRaceDurationMin}" ${draft.createTeamRace ? "" : "disabled"} />
</label>
`}
</form>
<p class="hint mt-16">${t("events.wizard_finals_note")}</p>
`;
}
const selectedClassName = getClassName(draft.classId);
const selectedDrivers = draft.driverIds.length ? draft.driverIds.length : wizardDrivers.length;
return `
<div class="wizard-summary-grid">
<article class="race-summary-item">
<span>${t("events.wizard_summary_title")}</span>
<strong>${escapeHtml(draft.name || "-")}</strong>
</article>
<article class="race-summary-item">
<span>${t("table.class")}</span>
<strong>${escapeHtml(selectedClassName || "-")}</strong>
</article>
<article class="race-summary-item">
<span>${t("events.race_summary_preset")}</span>
<strong>${escapeHtml(preset?.label || t("events.preset_custom"))}</strong>
</article>
<article class="race-summary-item">
<span>${t("events.race_summary_participants")}</span>
<strong>${selectedDrivers}</strong>
</article>
<article class="race-summary-item wizard-summary-item-wide">
<span>${t("events.wizard_summary_sessions")}</span>
<strong>${escapeHtml(getRaceWizardSessionPlan(draft).join(" • ") || "-")}</strong>
</article>
</div>
`;
}
export function renderRaceStandingsTable(rows, emptyLabel, { t, renderTable, escapeHtml }) {
if (!rows.length) {
return `<p>${emptyLabel}</p>`;
}
return renderTable(
[t("table.pos"), t("table.driver"), t("table.score")],
rows.map(
(row) => `
<tr>
<td>${row.rank}</td>
<td>${escapeHtml(row.driverName || t("common.unknown_driver"))}</td>
<td>
<div class="table-primary">${escapeHtml(row.score || "-")}</div>
${row.scoreNote ? `<div class="table-subnote">${escapeHtml(row.scoreNote)}</div>` : ""}
</td>
</tr>
`
)
);
}