Add multilingual support, competition close/reopen, and backup directory
This commit is contained in:
+68
-7
@@ -56,8 +56,15 @@
|
||||
const r = state.competition.role;
|
||||
return r === "system_admin" || r === "chief_scorer";
|
||||
}
|
||||
function isSysAdmin() {
|
||||
return state.competition.role === "system_admin";
|
||||
}
|
||||
function isClosed() {
|
||||
return !!state.competition.closed;
|
||||
}
|
||||
|
||||
function canEditPenalty(p) {
|
||||
if (isClosed()) return false;
|
||||
if (isChief()) return true;
|
||||
if (p.created_by === user.id) return true;
|
||||
return !!state.competition.allow_any_scorer_edit;
|
||||
@@ -139,11 +146,11 @@
|
||||
);
|
||||
|
||||
const toolbar = el("div", { class: "toolbar" },
|
||||
el("button", { class: "primary", onclick: () => openPenaltyModal() }, t("add_penalty")),
|
||||
el("button", { class: "primary", disabled: isClosed(), onclick: () => { if (!isClosed()) openPenaltyModal(); } }, t("add_penalty")),
|
||||
search,
|
||||
applyFilter,
|
||||
el("div", { class: "spacer" }),
|
||||
isChief() && el("button", { onclick: openApplyByTaskModal }, t("apply_by_task")),
|
||||
isChief() && !isClosed() && el("button", { onclick: openApplyByTaskModal }, t("apply_by_task")),
|
||||
isChief() && el("button", { onclick: async () => {
|
||||
try {
|
||||
const blob = await API.exportPenaltiesCSV(competitionId);
|
||||
@@ -1197,18 +1204,19 @@
|
||||
// ---- Settings tab ------------------------------------------------------
|
||||
|
||||
function renderSettingsTab() {
|
||||
const name = el("input", { type: "text", value: state.competition.name });
|
||||
const allow = el("input", { type: "checkbox", checked: !!state.competition.allow_any_scorer_edit });
|
||||
const readonly = isClosed() && !isSysAdmin();
|
||||
const name = el("input", { type: "text", value: state.competition.name, disabled: readonly });
|
||||
const allow = el("input", { type: "checkbox", checked: !!state.competition.allow_any_scorer_edit, disabled: readonly });
|
||||
const currentRulesLang = state.competition.rules_language || "en";
|
||||
const langOptions = (state.ruleLanguages && state.ruleLanguages.length)
|
||||
? [...state.ruleLanguages].sort()
|
||||
: I18N_AVAILABLE;
|
||||
const rulesLang = el("select", null,
|
||||
const rulesLang = el("select", { disabled: readonly },
|
||||
...langOptions.map((l) => el("option", { value: l, selected: l === currentRulesLang },
|
||||
I18N_NAMES[l] || l))
|
||||
);
|
||||
const msg = el("div", { class: "muted", style: { color: "var(--accent)", display: "none" } });
|
||||
return el("div", { class: "card" },
|
||||
const card = el("div", { class: "card" },
|
||||
el("h2", null, t("settings_tab")),
|
||||
el("div", { class: "field" }, el("label", null, t("competition_name")), name),
|
||||
el("div", { class: "field" }, el("label", null, t("rules_language")), rulesLang,
|
||||
@@ -1216,7 +1224,7 @@
|
||||
el("label", { class: "row", style: { marginTop: "0.5rem" } }, allow, " " + t("allow_any_scorer_edit")),
|
||||
msg,
|
||||
el("div", { class: "row", style: { justifyContent: "flex-end", marginTop: "1rem" } },
|
||||
el("button", { class: "primary", onclick: async () => {
|
||||
el("button", { class: "primary", disabled: readonly, onclick: async () => {
|
||||
try {
|
||||
await API.updateCompetition(competitionId, {
|
||||
name: name.value,
|
||||
@@ -1232,6 +1240,57 @@
|
||||
} }, t("save_settings")),
|
||||
),
|
||||
);
|
||||
|
||||
if (isSysAdmin()) {
|
||||
const adminCard = el("div", { class: "card", style: { marginTop: "1rem" } });
|
||||
adminCard.appendChild(el("h2", null, t("admin_zone")));
|
||||
if (isClosed()) {
|
||||
adminCard.appendChild(el("div", { class: "muted", style: { marginBottom: "0.5rem" } },
|
||||
t("competition_closed_explain"),
|
||||
state.competition.closed_at ? " (" + state.competition.closed_at + ")" : "",
|
||||
));
|
||||
adminCard.appendChild(el("div", { class: "row", style: { gap: "0.5rem", flexWrap: "wrap" } },
|
||||
el("button", { class: "primary", onclick: async () => {
|
||||
if (!confirm(t("confirm_reopen_competition"))) return;
|
||||
try {
|
||||
await API.reopenCompetition(competitionId);
|
||||
state.competition = await API.getCompetition(competitionId);
|
||||
render();
|
||||
} catch (e) { alert((e.data && e.data.error) || e.message); }
|
||||
} }, t("reopen_competition")),
|
||||
el("button", { class: "danger", onclick: async () => {
|
||||
if (!confirm(t("confirm_delete_competition"))) return;
|
||||
try {
|
||||
await API.deleteCompetition(competitionId);
|
||||
navigate("competitions");
|
||||
} catch (e) { alert((e.data && e.data.error) || e.message); }
|
||||
} }, t("delete_competition")),
|
||||
));
|
||||
} else {
|
||||
adminCard.appendChild(el("div", { class: "muted", style: { marginBottom: "0.5rem" } },
|
||||
t("close_competition_explain")));
|
||||
adminCard.appendChild(el("div", { class: "row", style: { gap: "0.5rem", flexWrap: "wrap" } },
|
||||
el("button", { class: "primary", onclick: async () => {
|
||||
if (!confirm(t("confirm_close_competition"))) return;
|
||||
try {
|
||||
const res = await API.closeCompetition(competitionId);
|
||||
state.competition = await API.getCompetition(competitionId);
|
||||
if (res && res.backup) alert(t("backup_written", { file: res.backup }));
|
||||
render();
|
||||
} catch (e) { alert((e.data && e.data.error) || e.message); }
|
||||
} }, t("close_competition")),
|
||||
el("button", { class: "danger", onclick: async () => {
|
||||
if (!confirm(t("confirm_delete_competition"))) return;
|
||||
try {
|
||||
await API.deleteCompetition(competitionId);
|
||||
navigate("competitions");
|
||||
} catch (e) { alert((e.data && e.data.error) || e.message); }
|
||||
} }, t("delete_competition")),
|
||||
));
|
||||
}
|
||||
return el("div", null, card, adminCard);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
// ---- Main render -------------------------------------------------------
|
||||
@@ -1249,6 +1308,8 @@
|
||||
state.competition.name,
|
||||
" ",
|
||||
el("span", { class: "badge accent" }, t(state.competition.role)),
|
||||
isClosed() ? " " : null,
|
||||
isClosed() ? el("span", { class: "badge warn" }, t("closed")) : null,
|
||||
),
|
||||
el("div", { class: "row" },
|
||||
el("span", { class: "connection-status " + (state.wsOnline ? "online" : "offline") }),
|
||||
|
||||
Reference in New Issue
Block a user