feat(series): choose open series or club members only for series points
Who scores is now an explicit per-series choice: every driver (default) or club members only, with the club field shown only for the latter. Series saved with a club keep members-only scoring. README updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
22
src/app.js
22
src/app.js
@@ -542,7 +542,7 @@ const seriesCell = (table, row, event) => {
|
||||
const text = result.absent ? "-" : String(result.points);
|
||||
return result.counted ? text : `(${text})`;
|
||||
};
|
||||
const seriesSubtitle = (series) => [series.club, t("series.count_rule", { rule: series.countMode === "drop" ? t("series.count_drop_value", { count: series.countValue }) : t("series.count_best_value", { count: series.countValue }) })].filter(Boolean).join(" • ");
|
||||
const seriesSubtitle = (series) => [series.scoring === "club" && series.club ? t("series.subtitle_members", { club: series.club }) : "", t("series.count_rule", { rule: series.countMode === "drop" ? t("series.count_drop_value", { count: series.countValue }) : t("series.count_best_value", { count: series.countValue }) })].filter(Boolean).join(" • ");
|
||||
const printSeries = (series, tables) => openPrintWindow(series.name, `
|
||||
<div class="print-header"><div><p class="print-kicker">${escapeHtml(t("series.standings"))}</p><h1>${escapeHtml(series.name)}</h1><p>${escapeHtml(seriesSubtitle(series))}</p></div>
|
||||
<div class="print-meta"><p>${escapeHtml(new Date().toLocaleDateString())}</p></div></div>
|
||||
@@ -710,9 +710,13 @@ const TRANSLATIONS = {
|
||||
"series.settings": "Inställningar",
|
||||
"series.name": "Namn",
|
||||
"series.name_placeholder": "t.ex. Winter Championship 2026/2027",
|
||||
"series.club": "Klubb (bara dess förare får poäng)",
|
||||
"series.club_all": "tomt = alla får poäng",
|
||||
"series.club_hint": "Förare från andra klubbar kör och syns i dagens resultat men får inga seriepoäng; klubbens förare rangordnas om utan dem. Klubben hämtas från förarregistret.",
|
||||
"series.scoring": "Vem får seriepoäng",
|
||||
"series.scoring_all": "Alla förare (öppen serie)",
|
||||
"series.scoring_club": "Endast klubbmedlemmar",
|
||||
"series.club": "Klubb",
|
||||
"series.club_placeholder": "t.ex. Östersunds RC",
|
||||
"series.club_hint": "Förare från andra klubbar kör och syns i dagens resultat men får inga seriepoäng; medlemmarna rangordnas om utan dem. Klubben hämtas från förarregistret.",
|
||||
"series.subtitle_members": "endast medlemmar i {club}",
|
||||
"series.classes": "Klasser (en tabell per klass)",
|
||||
"series.no_classes": "Skapa klasser först under Klasser.",
|
||||
"series.points_preset": "Poängtabell",
|
||||
@@ -2432,9 +2436,13 @@ const TRANSLATIONS = {
|
||||
"series.settings": "Settings",
|
||||
"series.name": "Name",
|
||||
"series.name_placeholder": "e.g. Winter Championship 2026/2027",
|
||||
"series.club": "Club (only its drivers score)",
|
||||
"series.club_all": "empty = everyone scores",
|
||||
"series.club_hint": "Drivers from other clubs race and appear in the day result but get no series points; the club's drivers are re-ranked without them. The club comes from the driver register.",
|
||||
"series.scoring": "Who scores series points",
|
||||
"series.scoring_all": "Every driver (open series)",
|
||||
"series.scoring_club": "Club members only",
|
||||
"series.club": "Club",
|
||||
"series.club_placeholder": "e.g. Östersunds RC",
|
||||
"series.club_hint": "Drivers from other clubs race and appear in the day result but get no series points; members are re-ranked without them. The club comes from the driver register.",
|
||||
"series.subtitle_members": "members of {club} only",
|
||||
"series.classes": "Classes (one table per class)",
|
||||
"series.no_classes": "Create classes first under Classes.",
|
||||
"series.points_preset": "Points table",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
//
|
||||
// Rules supported (see ROADMAP "Series / cup"):
|
||||
// - points per place from a table, then a fixed step down to a minimum for later places;
|
||||
// - only drivers from the series' club score (others still race and appear in the day
|
||||
// result); club drivers are re-ranked without them;
|
||||
// - who scores: every driver (open series), or only members of the series' club - others
|
||||
// still race and appear in the day result, and members are re-ranked without them;
|
||||
// - a driver with no registered lap in the event gets 0 points and no place;
|
||||
// - count the best N results, or all completed events minus X (so a cancelled event
|
||||
// reduces the count automatically);
|
||||
@@ -37,6 +37,9 @@ export function normalizeSeries(series = {}, { uid } = {}) {
|
||||
id: String(series.id || (uid ? uid("series") : "")),
|
||||
name: String(series.name || "").trim(),
|
||||
club: String(series.club || "").trim(),
|
||||
// "all" = every driver scores; "club" = only drivers whose club matches. Series saved before
|
||||
// this choice existed keep their behaviour: a club filled in meant members only.
|
||||
scoring: series.scoring === "club" || (series.scoring === undefined && String(series.club || "").trim()) ? "club" : "all",
|
||||
classIds: Array.isArray(series.classIds) ? [...new Set(series.classIds.map(String).filter(Boolean))] : [],
|
||||
eventIds: Array.isArray(series.eventIds) ? [...new Set(series.eventIds.map(String).filter(Boolean))] : [],
|
||||
pointsTable: table.length ? table : preset.table.slice(),
|
||||
@@ -67,7 +70,7 @@ export function scoreSeriesEvent(series, dayResult, { getDriverClub = () => "" }
|
||||
let place = 0;
|
||||
(dayResult || []).forEach((row) => {
|
||||
if (!row?.driverId || scored.has(row.driverId)) return;
|
||||
if (series.club && !sameClub(getDriverClub(row.driverId), series.club)) return;
|
||||
if (series.scoring === "club" && series.club && !sameClub(getDriverClub(row.driverId), series.club)) return;
|
||||
if (!row.hasLap) {
|
||||
scored.set(row.driverId, { place: null, points: 0, driverName: row.driverName });
|
||||
return;
|
||||
|
||||
@@ -16,7 +16,11 @@ function renderSeriesForm(series, { t, escapeHtml, state }) {
|
||||
return `
|
||||
<form id="seriesForm" class="form-grid cols-3 settings-grid">
|
||||
<label><span>${t("series.name")}</span><input name="name" required value="${escapeHtml(series.name)}" placeholder="${escapeHtml(t("series.name_placeholder"))}" /></label>
|
||||
<label><span>${t("series.club")}</span><input name="club" list="seriesClubOptions" value="${escapeHtml(series.club)}" placeholder="${escapeHtml(t("series.club_all"))}" />
|
||||
<label><span>${t("series.scoring")}</span><select name="scoring" data-series-scoring>
|
||||
<option value="all" ${series.scoring === "all" ? "selected" : ""}>${t("series.scoring_all")}</option>
|
||||
<option value="club" ${series.scoring === "club" ? "selected" : ""}>${t("series.scoring_club")}</option>
|
||||
</select></label>
|
||||
<label data-series-club ${series.scoring === "club" ? "" : "hidden"}><span>${t("series.club")}</span><input name="club" list="seriesClubOptions" value="${escapeHtml(series.club)}" placeholder="${escapeHtml(t("series.club_placeholder"))}" />
|
||||
<datalist id="seriesClubOptions">${clubs.map((club) => `<option value="${escapeHtml(club)}"></option>`).join("")}</datalist>
|
||||
<small>${t("series.club_hint")}</small></label>
|
||||
<fieldset class="series-classes"><legend>${t("series.classes")}</legend>
|
||||
@@ -120,6 +124,9 @@ export function renderSeriesView(deps) {
|
||||
if (!series) return;
|
||||
|
||||
const form = document.getElementById("seriesForm");
|
||||
form?.querySelector("[data-series-scoring]")?.addEventListener("change", (event) => {
|
||||
form.querySelector("[data-series-club]").hidden = event.target.value !== "club";
|
||||
});
|
||||
form?.querySelector("[data-series-preset]")?.addEventListener("change", (event) => {
|
||||
const preset = SERIES_POINT_PRESETS[event.target.value];
|
||||
if (!preset) return;
|
||||
@@ -133,6 +140,7 @@ export function renderSeriesView(deps) {
|
||||
Object.assign(series, normalizeSeries({
|
||||
...series,
|
||||
name: data.get("name"),
|
||||
scoring: data.get("scoring"),
|
||||
club: data.get("club"),
|
||||
classIds: data.getAll("classIds"),
|
||||
pointsTable: parsePointsTable(data.get("pointsTable")),
|
||||
|
||||
Reference in New Issue
Block a user