Add CSV imports and granular competition data cleanup

This commit is contained in:
larssand
2026-08-27 17:56:50 +02:00
parent a87c64a47c
commit e5af42ce2d
9 changed files with 504 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ import { createDefaultAmmcConfigHelper, getManagedWsUrlHelper, loadAmmcConfigFro
import { connectDecoderHelper, disconnectDecoderHelper, processDecoderMessageHelper } from "./decoder_runtime.js";
import { normalizeTrainingState as normalizeTrainingStateLogic, syncTrainingLedger as syncTrainingLedgerLogic, buildDriverTrainingStats as buildDriverTrainingStatsLogic, buildDriverTimingChannelStats as buildDriverTimingChannelStatsLogic, buildDriverActivePracticeStats as buildDriverActivePracticeStatsLogic, buildTrainingRecommendation as buildTrainingRecommendationLogic, getTrainingGoalProgress as getTrainingGoalProgressLogic, getNextTrainingMilestone as getNextTrainingMilestoneLogic } from "./training_logic.js";
import { renderTrainingView } from "./training_views.js";
import { importDriversCsv, importCarsCsv } from "./csv_import.js";
import { renderTimingView, renderJudgingView } from "./timing_views.js";
@@ -1021,9 +1022,29 @@ const TRANSLATIONS = {
"settings.import_directory": "Importera förare/klasser/bilar",
"settings.export_drivers_csv": "Exportera förare CSV",
"settings.export_cars_csv": "Exportera bilar CSV",
"settings.import_drivers_csv": "Importera förare CSV",
"settings.import_cars_csv": "Importera bilar CSV",
"settings.import_csv_success": "{type}: {added} tillagda, {updated} uppdaterade och {skipped} hoppade rader.",
"settings.csv_type_drivers": "Förare importerade",
"settings.csv_type_cars": "Bilar importerade",
"settings.storage_note_full": "Fullt datapaket innehåller klasser, förare, bilar, race/event, sessioner och resultat.",
"settings.storage_note_directory": "Registerpaket innehåller bara klasser, förare och bilar och mergeas in i befintlig data.",
"settings.storage_note_csv": "CSV-exporten är snabbast för registerlistor och enklare att öppna i Excel eller LibreOffice.",
"settings.storage_note_csv": "CSV kan exporteras eller importeras från Excel och LibreOffice. Komma, semikolon och tabb stöds. Importen slår ihop på ID, transponder eller namn i stället för att radera registret.",
"settings.clear_data_title": "Rensa data",
"settings.clear_data_note": "Exportera gärna en backup först. Rensningen kan inte ångras, men system-, decoder-, tema- och brandinginställningar behålls. Råa passeringar i SQLite behålls som revisionslogg.",
"settings.clear_drivers": "Rensa förare",
"settings.clear_cars": "Rensa bilar",
"settings.clear_classes": "Rensa klasser",
"settings.clear_races": "Rensa race och sessioner",
"settings.clear_training": "Rensa träningsstatistik",
"settings.clear_all": "Rensa all tävlingsdata",
"settings.clear_drivers_confirm": "Rensa alla förare, deras träningsdata och förarreferenser i race? Detta kan inte ångras.",
"settings.clear_cars_confirm": "Rensa alla bilar och bilreferenser i race? Detta kan inte ångras.",
"settings.clear_classes_confirm": "Rensa alla klasser och koppla loss dem från förare och race? Detta kan inte ångras.",
"settings.clear_races_confirm": "Rensa alla event, race, sessioner och resultat? Befintliga träningsvarv arkiveras först. Detta kan inte ångras.",
"settings.clear_training_confirm": "Rensa all träningsstatistik, mål, belöningar och publika profillänkar? Detta kan inte ångras.",
"settings.clear_all_confirm": "Rensa alla klasser, förare, bilar, race, sessioner, resultat och all träningsstatistik? Systeminställningar behålls. Detta kan inte ångras.",
"settings.clear_success": "Valda data har rensats.",
"settings.import_full_success": "Race-data importerad och ersatte nuvarande tävlingsdata.",
"settings.import_directory_success": "Klasser, förare och bilar importerades in i befintlig data.",
"settings.import_failed": "Import misslyckades: {msg}",
@@ -1288,7 +1309,8 @@ const TRANSLATIONS = {
"guide.sqlite_2": "API: /api/state och /api/passings",
"guide.sqlite_3": "I Inställningar -> Lagring kan du exportera hela racepaket för backup eller flytt till annan server/laptop.",
"guide.sqlite_4": "Du kan också exportera eller importera bara klasser, förare och bilar om du vill börja om med race men behålla registret.",
"guide.sqlite_5": "Exportera förare och bilar som CSV om du snabbt vill öppna registret i Excel eller skriva ut en enkel lista.",
"guide.sqlite_5": "CSV-filer för förare och bilar kan både exporteras och importeras. Egna exporter bevarar ID:n; externa komma-, semikolon- eller tabbseparerade listor slås ihop på transponder eller namn och klass.",
"guide.sqlite_6": "Rensa data kan tömma förare, bilar, klasser, race eller träningsstatistik separat. Rensa all tävlingsdata behåller decoder-, tema-, branding- och serverinställningar. Råa SQLite-passeringar behålls som revisionslogg. Exportera backup först.",
"guide.ammc_ref": "AMMC referens: https://www.ammconverter.eu/docs/intro/quick-start/",
"error.backend_offline": "Backend offline: {msg}",
"error.sync_failed": "Synk misslyckades: {msg}",
@@ -2075,9 +2097,29 @@ const TRANSLATIONS = {
"settings.import_directory": "Import drivers/classes/cars",
"settings.export_drivers_csv": "Export drivers CSV",
"settings.export_cars_csv": "Export cars CSV",
"settings.import_drivers_csv": "Import drivers CSV",
"settings.import_cars_csv": "Import cars CSV",
"settings.import_csv_success": "{type}: {added} added, {updated} updated, and {skipped} skipped rows.",
"settings.csv_type_drivers": "Drivers imported",
"settings.csv_type_cars": "Cars imported",
"settings.storage_note_full": "Full data package contains classes, drivers, cars, races/events, sessions and results.",
"settings.storage_note_directory": "Directory package only contains classes, drivers and cars and merges into existing data.",
"settings.storage_note_csv": "CSV export is the quickest way to open the directory in Excel or LibreOffice.",
"settings.storage_note_csv": "CSV can be exported or imported from Excel and LibreOffice. Comma, semicolon, and tab delimiters are supported. Import merges by ID, transponder, or name instead of replacing the directory.",
"settings.clear_data_title": "Clear data",
"settings.clear_data_note": "Export a backup first. Clearing cannot be undone, but system, decoder, theme, and branding settings are retained. Raw SQLite passings remain as an audit log.",
"settings.clear_drivers": "Clear drivers",
"settings.clear_cars": "Clear cars",
"settings.clear_classes": "Clear classes",
"settings.clear_races": "Clear races and sessions",
"settings.clear_training": "Clear training statistics",
"settings.clear_all": "Clear all competition data",
"settings.clear_drivers_confirm": "Clear all drivers, their training data, and driver references in races? This cannot be undone.",
"settings.clear_cars_confirm": "Clear all cars and car references in races? This cannot be undone.",
"settings.clear_classes_confirm": "Clear all classes and unlink them from drivers and races? This cannot be undone.",
"settings.clear_races_confirm": "Clear all events, races, sessions, and results? Existing training laps are archived first. This cannot be undone.",
"settings.clear_training_confirm": "Clear all training statistics, goals, rewards, and public profile links? This cannot be undone.",
"settings.clear_all_confirm": "Clear all classes, drivers, cars, races, sessions, results, and training statistics? System settings are retained. This cannot be undone.",
"settings.clear_success": "The selected data has been cleared.",
"settings.import_full_success": "Race data imported and replaced the current competition data.",
"settings.import_directory_success": "Classes, drivers and cars were imported into the existing data.",
"settings.import_failed": "Import failed: {msg}",
@@ -2342,7 +2384,8 @@ const TRANSLATIONS = {
"guide.sqlite_2": "API endpoints: /api/state and /api/passings",
"guide.sqlite_3": "In Settings -> Storage you can export a full race package for backup or moving to another server or laptop.",
"guide.sqlite_4": "You can also export or import only classes, drivers and cars if you want to reset race data but keep the directory.",
"guide.sqlite_5": "Export drivers and cars as CSV if you want to open the directory quickly in Excel or print a simple list.",
"guide.sqlite_5": "Driver and car CSV files can both be exported and imported. Native exports preserve IDs; external comma-, semicolon-, or tab-separated lists merge by transponder or by name and class.",
"guide.sqlite_6": "Clear data can remove drivers, cars, classes, races, or training statistics separately. Clear all competition data retains decoder, theme, branding, and server settings. Raw SQLite passings remain as an audit log. Export a backup first.",
"guide.ammc_ref": "AMMC reference: https://www.ammconverter.eu/docs/intro/quick-start/",
"error.backend_offline": "Backend offline: {msg}",
"error.sync_failed": "Sync failed: {msg}",
@@ -3063,6 +3106,111 @@ function buildCarsCsvRows() {
];
}
function importDirectoryCsv(text, type) {
try {
if (type === "drivers") {
const result = importDriversCsv(text, { classes: state.classes, drivers: state.drivers, uid });
state.classes = result.classes.map((item) => normalizeImportedClass(item)).filter((item) => item.name);
state.drivers = result.drivers.map((item) => normalizeDriver(item)).filter((item) => item.name);
settingsStorageNotice = t("settings.import_csv_success", {
type: t("settings.csv_type_drivers"),
added: result.added,
updated: result.updated,
skipped: result.skipped,
});
} else if (type === "cars") {
const result = importCarsCsv(text, { cars: state.cars, uid });
state.cars = result.cars.map((item) => normalizeCar(item)).filter((item) => item.name);
settingsStorageNotice = t("settings.import_csv_success", {
type: t("settings.csv_type_cars"),
added: result.added,
updated: result.updated,
skipped: result.skipped,
});
} else {
throw new Error("Unsupported CSV import type");
}
settingsStorageNoticeIsError = false;
saveState();
} catch (error) {
settingsStorageNotice = t("settings.import_failed", { msg: error instanceof Error ? error.message : String(error) });
settingsStorageNoticeIsError = true;
}
renderView();
}
function clearDataScope(scope) {
const clearDriverReferences = () => {
state.events = state.events.map((event) => ({
...event,
raceConfig: {
...(event.raceConfig || {}),
driverIds: [],
teams: (event.raceConfig?.teams || []).map((team) => ({ ...team, driverIds: [] })),
},
}));
state.sessions = state.sessions.map((session) => ({
...session,
driverIds: [],
manualGridIds: [],
assignments: (session.assignments || []).map((assignment) => ({ ...assignment, driverId: "" })),
}));
};
const clearCarReferences = () => {
state.events = state.events.map((event) => ({
...event,
raceConfig: {
...(event.raceConfig || {}),
teams: (event.raceConfig?.teams || []).map((team) => ({ ...team, carIds: [] })),
},
}));
state.sessions = state.sessions.map((session) => ({
...session,
assignments: (session.assignments || []).map((assignment) => ({ ...assignment, carId: "" })),
}));
};
if (scope === "drivers") {
state.drivers = [];
state.training = normalizeTrainingState({});
state.resultsBySession = {};
clearDriverReferences();
} else if (scope === "cars") {
state.cars = [];
state.resultsBySession = {};
clearCarReferences();
} else if (scope === "classes") {
state.classes = [];
state.drivers = state.drivers.map((driver) => ({ ...driver, classId: "" }));
state.events = state.events.map((event) => ({ ...event, classId: "" }));
} else if (scope === "races") {
syncTrainingLedger();
state.events = [];
state.sessions = [];
state.resultsBySession = {};
state.activeSessionId = null;
} else if (scope === "training") {
state.training = normalizeTrainingState({});
} else if (scope === "all") {
state.classes = [];
state.drivers = [];
state.cars = [];
state.events = [];
state.sessions = [];
state.resultsBySession = {};
state.training = normalizeTrainingState({});
state.activeSessionId = null;
} else {
return;
}
settingsStorageNotice = t("settings.clear_success");
settingsStorageNoticeIsError = false;
saveState();
renderNav();
renderView();
}
function collectEventPackageReferences(event, sessions) {
const driverIds = new Set(event.classId ? getDriversForClass(event.classId).map((driver) => driver.id) : []);
const carIds = new Set();
@@ -3342,6 +3490,8 @@ function renderView() {
downloadCsvFile,
buildDriversCsvRows,
buildCarsCsvRows,
importDirectoryCsv,
clearDataScope,
buildExportMeta,
normalizeStoredRacePreset,
saveAmmcConfigToBackend,