2 Commits

Author SHA1 Message Date
PaulVua
a3b2bef5c1 Improve database page layout and highlight most recent data
Layout improvements:
- Reorganized cards to fit 3 across on large screens (col-lg-4)
- View database, download data, and danger zone now on same row
- Added h-100 class to cards for equal height
- Made delete button larger and full-width for better visibility

Button spacing:
- Added mb-2 (margin-bottom) to all measurement trigger buttons
- Improved vertical spacing for better readability

Data visualization:
- Added light green background (#d4edda) to first table row
- First row now highlights the most recent data entry
- Makes it easy to see the latest sensor reading at a glance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-07 14:48:34 +01:00
PaulVua
f679732591 Fix i18n translations not applying properly on database page
Fixed race condition where translations weren't being applied to the database page when French language was selected. The issue was caused by the i18n system loading asynchronously while page content was being rendered.

Changes:
- Added applyTranslationsWhenReady() function that waits for translations to load
- Re-apply translations after dynamic content (sidebar/topbar) is loaded
- Added languageChanged event listener to re-apply translations on language switch
- Ensures all text appears in the selected language (French/English)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-07 14:42:33 +01:00

View File

@@ -26,6 +26,10 @@
.offcanvas-backdrop {
z-index: 1040;
}
/* Highlight most recent data row with light green background */
.most-recent-row {
background-color: #d4edda !important;
}
</style>
</head>
<body>
@@ -54,8 +58,8 @@
<div class="row mb-3">
<div class="col-sm-5">
<div class="card text-dark bg-light">
<div class="col-lg-4 col-md-6 mb-3">
<div class="card text-dark bg-light h-100">
<div class="card-body">
<h5 class="card-title" data-i18n="database.viewDatabase">Consulter la base de donnée</h5>
<!-- Dropdown to select number of records -->
@@ -67,23 +71,20 @@
<option value="30" data-i18n="database.last30">30 dernières</option>
</select>
</div>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)" data-i18n="database.pmMeasures">Mesures PM</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)" data-i18n="database.pm5Channels">Mesures PM (5 canaux)</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NOISE',getSelectedLimit(),false)" data-i18n="database.noiseProbe">Sonde bruit</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)" data-i18n="database.windProbe">Sonde Vent</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_MPPT',getSelectedLimit(),false)" data-i18n="database.battery">Batterie</button>
<button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)" data-i18n="database.timestampTable">Timestamp Table</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)" data-i18n="database.pmMeasures">Mesures PM</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)" data-i18n="database.pm5Channels">Mesures PM (5 canaux)</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',getSelectedLimit(),false)" data-i18n="database.noiseProbe">Sonde bruit</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)" data-i18n="database.windProbe">Sonde Vent</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MPPT',getSelectedLimit(),false)" data-i18n="database.battery">Batterie</button>
<button class="btn btn-warning mb-2" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)" data-i18n="database.timestampTable">Timestamp Table</button>
</div>
</div>
</div>
<div class="col-sm-5">
<div class="card text-dark bg-light">
<div class="col-lg-4 col-md-6 mb-3">
<div class="card text-dark bg-light h-100">
<div class="card-body">
<h5 class="card-title" data-i18n="database.downloadData">Télécharger les données</h5>
<!-- Date selection for download -->
@@ -94,32 +95,28 @@
<input type="date" id="end_date" class="form-control w-auto">
</div>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',10,true, getStartDate(), getEndDate())" data-i18n="database.pmMeasures">Mesures PM</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,true, getStartDate(), getEndDate())" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',10,true, getStartDate(), getEndDate())" data-i18n="database.pm5Channels">Mesures PM (5 canaux)</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NOISE',10,true, getStartDate(), getEndDate())" data-i18n="database.noiseProbe">Sonde Bruit</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_mppt',10,true, getStartDate(), getEndDate())" data-i18n="database.battery">Batterie</button>
</table>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NPM',10,true, getStartDate(), getEndDate())" data-i18n="database.pmMeasures">Mesures PM</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_BME280',10,true, getStartDate(), getEndDate())" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NPM_5channels',10,true, getStartDate(), getEndDate())" data-i18n="database.pm5Channels">Mesures PM (5 canaux)</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',10,true, getStartDate(), getEndDate())" data-i18n="database.noiseProbe">Sonde Bruit</button>
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_mppt',10,true, getStartDate(), getEndDate())" data-i18n="database.battery">Batterie</button>
</div>
</div>
</div>
<div class="col-sm-5">
<div class="card text-white bg-danger">
<div class="col-lg-4 col-md-12 mb-3">
<div class="card text-white bg-danger h-100">
<div class="card-body">
<h5 class="card-title" data-i18n="database.dangerZone">Zone dangereuse</h5>
<p class="card-text" data-i18n="database.dangerWarning">Attention: Cette action est irréversible!</p>
<button class="btn btn-dark" onclick="emptySensorTables()" data-i18n="database.emptyAllTables">Vider toutes les tables de capteurs</button>
<button class="btn btn-dark btn-lg w-100 mb-2" onclick="emptySensorTables()" data-i18n="database.emptyAllTables">Vider toutes les tables de capteurs</button>
<small class="d-block mt-2" data-i18n="database.emptyTablesNote">Note: Les tables de configuration et horodatage seront préservées.</small>
</div>
</div>
</div>
<div>
</div>
<div class="row mt-2">
<div id="table_data"></div>
@@ -148,6 +145,19 @@
{ id: 'sidebar_mobile', file: 'sidebar.html' }
];
let loadedCount = 0;
const totalElements = elementsToLoad.length;
function applyTranslationsWhenReady() {
if (typeof i18n !== 'undefined' && i18n.translations && Object.keys(i18n.translations).length > 0) {
console.log("Applying translations to dynamically loaded content");
i18n.applyTranslations();
} else {
// Retry after a short delay if translations aren't loaded yet
setTimeout(applyTranslationsWhenReady, 100);
}
}
elementsToLoad.forEach(({ id, file }) => {
fetch(file)
.then(response => response.text())
@@ -156,10 +166,21 @@
if (element) {
element.innerHTML = data;
}
loadedCount++;
// Re-apply translations after all dynamic content is loaded
if (loadedCount === totalElements) {
applyTranslationsWhenReady();
}
})
.catch(error => console.error(`Error loading ${file}:`, error));
});
// Also listen for language change events to re-apply translations
document.addEventListener('languageChanged', function() {
console.log("Language changed, re-applying translations");
i18n.applyTranslations();
});
});
@@ -322,9 +343,11 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
tableHTML += `</tr></thead><tbody>`;
// Loop through rows and create table rows
rows.forEach(row => {
rows.forEach((row, index) => {
let columns = row.replace(/[()]/g, "").split(", "); // Remove parentheses and split
tableHTML += "<tr>";
// Add special class to first row (most recent data)
const rowClass = index === 0 ? ' class="most-recent-row"' : '';
tableHTML += `<tr${rowClass}>`;
if (table === "data_NPM") {
tableHTML += `