217 lines
12 KiB
JavaScript
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>
|
|
`
|
|
)
|
|
);
|
|
}
|