export function renderTeamStintLog(session, rows, { t, escapeHtml, buildTeamStintLog, formatTeamActiveMemberLabel, renderTable, formatRaceClock }) {
if (!rows.length) {
return `
${t("events.no_team_results")}
`;
}
return `
${rows
.map((row) => {
const stints = buildTeamStintLog(session, row);
return `
${escapeHtml(row.displayName || row.driverName)}
${t("overlay.active_member")}: ${escapeHtml(formatTeamActiveMemberLabel(row))}
${
stints.length
? renderTable(
[t("events.slot"), t("table.driver"), t("table.car"), t("table.time"), t("table.duration"), t("table.laps")],
stints.map(
(stint) => `
| ${stint.index} |
${escapeHtml(stint.driverName || "-")} |
${escapeHtml(stint.carName || "-")} |
${new Date(stint.startTs).toLocaleTimeString()} |
${formatRaceClock(stint.durationMs)} |
${stint.laps} |
`
)
)
: `${t("timing.no_passings")}
`
}
`;
})
.join("")}
`;
}
export function renderTeamRaceStandings(event, { t, escapeHtml, buildTeamRaceStandings, getSessionTypeLabel, renderTable, formatTeamActiveMemberLabel, formatLap, renderTeamStintLog }) {
const groups = buildTeamRaceStandings(event);
if (!groups.length) {
return `${t("events.no_team_results")}
`;
}
return groups
.map(
({ session, rows }) => `
${escapeHtml(session.name)} • ${escapeHtml(getSessionTypeLabel(session.type))}
${
rows.length
? renderTable(
[t("table.pos"), t("events.team_name"), t("table.laps"), t("table.result"), t("table.best_lap")],
rows.map(
(row, index) => `
| ${index + 1} |
${escapeHtml(row.displayName || row.driverName)}
${t("overlay.active_member")}: ${escapeHtml(formatTeamActiveMemberLabel(row))}
|
${row.laps} |
${escapeHtml(row.resultDisplay)} |
${formatLap(row.bestLapMs)} |
`
)
)
: `${t("events.no_team_results")}
`
}
${t("events.team_stint_log")}
${rows.length ? renderTeamStintLog(session, rows) : `
${t("events.no_team_results")}
`}
`
)
.join("");
}
export function getSessionSortWeight(session) {
const order = {
open_practice: 0,
free_practice: 1,
practice: 2,
qualification: 3,
heat: 4,
final: 5,
team_race: 6,
};
return order[String(session?.type || "").toLowerCase()] || 99;
}
export function getDriverDisplayById(driverId, { state, t }) {
const driver = state.drivers.find((item) => item.id === driverId);
if (!driver) {
return t("common.unknown_driver");
}
return driver.transponder ? `${driver.name} (${driver.transponder})` : driver.name;
}
export function renderPositionGrid(session, { t, escapeHtml, getSessionGridEntries }) {
const entries = getSessionGridEntries(session);
if (!entries.length) {
return "";
}
return `
${t("events.position_grid")}
${t("timing.position_grid_hint")}
${entries
.map(
(entry) => `
${entry.slot}
${escapeHtml(entry.name)}
${entry.meta ? `
${escapeHtml(entry.meta)}
` : ""}
`
)
.join("")}
`;
}
export function renderGridEditor(session, { t, escapeHtml, ensureSessionDriverOrder, state }) {
if (!session) {
return `${t("events.grid_empty")}
`;
}
const driverIds = ensureSessionDriverOrder(session);
return `
${driverIds
.map((driverId, index) => {
const driver = state.drivers.find((item) => item.id === driverId);
return `
${index + 1}
${escapeHtml(driver?.name || t("common.unknown_driver"))}
${escapeHtml(driver?.transponder || "-")}
`;
})
.join("")}
`;
}
export function getFinalMainLayouts(event, { getSessionsForEvent, getSessionGridOrder, getDriverDisplayById, t }) {
const finals = getSessionsForEvent(event.id)
.filter((session) => session.type === "final")
.sort((left, right) => {
const leftMain = String(left.name || "").match(/^([A-Z])/i)?.[1]?.toUpperCase() || "Z";
const rightMain = String(right.name || "").match(/^([A-Z])/i)?.[1]?.toUpperCase() || "Z";
if (leftMain !== rightMain) {
return leftMain.localeCompare(rightMain);
}
return String(left.name || "").localeCompare(String(right.name || ""));
});
const grouped = new Map();
finals.forEach((session) => {
const mainKey = String(session.name || "").match(/^([A-Z])/i)?.[1]?.toUpperCase() || "A";
if (!grouped.has(mainKey)) {
grouped.set(mainKey, []);
}
grouped.get(mainKey).push(session);
});
return [...grouped.entries()].map(([mainKey, sessions]) => {
const sortedSessions = [...sessions].sort((left, right) => String(left.name || "").localeCompare(String(right.name || "")));
const baseSession = sortedSessions[0];
const baseDriverIds = [...getSessionGridOrder(baseSession)];
const reservedSlots = Math.max(0, Number(baseSession?.reservedBumpSlots || 0) || 0);
const capacity = Math.max(
Number(baseSession?.maxCars || event.raceConfig?.carsPerFinal || 0) || 0,
baseDriverIds.length + reservedSlots
);
const slots = [];
for (let index = 0; index < capacity; index += 1) {
const driverId = baseDriverIds[index];
if (driverId) {
slots.push({
slot: index + 1,
label: getDriverDisplayById(driverId),
reserved: false,
});
} else if (index < baseDriverIds.length + reservedSlots) {
slots.push({
slot: index + 1,
label: t("events.reserved_slot"),
reserved: true,
});
} else {
slots.push({
slot: index + 1,
label: "-",
reserved: false,
});
}
}
return {
mainKey,
sessions: sortedSessions,
slots,
};
});
}
export function renderFinalMatrix(event, { t, escapeHtml, getFinalMainLayouts, getStatusLabel }) {
const mains = getFinalMainLayouts(event);
if (!mains.length) {
return `${t("events.no_final_matrix")}
`;
}
return `
${mains
.map(
(main) => `
${main.slots
.map(
(slot) => `
${t("events.slot")} ${slot.slot}
${escapeHtml(slot.label)}
`
)
.join("")}
${main.sessions
.map(
(session) => `
${escapeHtml(session.name)}
${escapeHtml(getStatusLabel(session.status))}
`
)
.join("")}
`
)
.join("")}
`;
}
export function buildPrintBrandBlock(branding, { escapeHtml }) {
return `
${branding.logoDataUrl ? `
})
` : ""}
${escapeHtml(branding.brandName)}
${escapeHtml(branding.brandTagline)}
`;
}
export function buildRaceStartListsHtml(event, { t, state, escapeHtml, resolveEventBranding, getSessionsForEvent, getSessionSortWeight, getClassName, buildPrintBrandBlock, getSessionGridEntries, getSessionTypeLabel, getStartModeLabel, renderTable }) {
const branding = resolveEventBranding(event);
const sessions = getSessionsForEvent(event.id)
.filter((session) => session.mode === "race")
.sort((left, right) => {
const weightDiff = getSessionSortWeight(left) - getSessionSortWeight(right);
if (weightDiff !== 0) {
return weightDiff;
}
return String(left.name || "").localeCompare(String(right.name || ""));
});
return `
${t("events.start_lists")}
${sessions
.map((session) => {
const entries = getSessionGridEntries(session);
return `
${escapeHtml(session.name)} • ${escapeHtml(getSessionTypeLabel(session.type))}
${t("table.start_mode")}: ${escapeHtml(getStartModeLabel(session.startMode))} • ${t("table.duration")}: ${session.durationMin} min
${
entries.length
? renderTable(
[t("events.slot"), t("table.driver"), t("table.brand"), t("table.transponder")],
entries.map((entry) => {
const driver = state.drivers.find((item) => item.id === entry.id);
return `
| ${entry.slot} |
${escapeHtml(entry.name)} |
${escapeHtml(driver?.brand || "-")} |
${escapeHtml(entry.meta || "-")} |
`;
})
)
: `${t("common.no_entries")}
`
}
`;
})
.join("")}
`;
}
export function buildRaceResultsHtml(event, { t, escapeHtml, resolveEventBranding, getClassName, buildPrintBrandBlock, renderRaceStandingsTableView, buildPracticeStandings, buildQualifyingStandings, buildFinalStandings, renderTeamRaceStandings }) {
const branding = resolveEventBranding(event);
return `
${t("events.practice_standings")}
${renderRaceStandingsTableView(buildPracticeStandings(event), t("events.no_practice_results"))}
${t("events.qualifying_standings")}
${renderRaceStandingsTableView(buildQualifyingStandings(event), t("events.no_qualifying_results"))}
${t("events.final_standings")}
${renderRaceStandingsTableView(buildFinalStandings(event), t("events.no_final_results"))}
${t("events.team_standings")}
${renderTeamRaceStandings(event)}
`;
}
export function buildTeamRaceResultsHtml(event, { t, escapeHtml, resolveEventBranding, getClassName, buildPrintBrandBlock, buildTeamRaceStandings, renderTable, formatLap, renderTeamStintLog }) {
const branding = resolveEventBranding(event);
const groups = buildTeamRaceStandings(event);
return `
${groups
.map(
({ session, rows }) => `
${t("events.team_report")} • ${escapeHtml(session.name)}
${
rows.length
? renderTable(
[t("table.pos"), t("events.team_name"), t("table.laps"), t("table.result"), t("table.best_lap")],
rows.map(
(row, index) => `
| ${index + 1} |
${escapeHtml(row.displayName || row.driverName)} |
${row.laps} |
${escapeHtml(row.resultDisplay)} |
${formatLap(row.bestLapMs)} |
`
)
)
: `${t("events.no_team_results")}
`
}
${t("events.team_stint_log")}
${renderTeamStintLog(session, rows)}
`
)
.join("")}
`;
}