diff --git a/public/javascripts/chart_utilities.js b/public/javascripts/chart_utilities.js index f20798f..aa4ce2b 100644 --- a/public/javascripts/chart_utilities.js +++ b/public/javascripts/chart_utilities.js @@ -180,17 +180,41 @@ export async function addSensorID2chart(chart, sensor, width) { } export const showError = (err) => { - let e = document.querySelector('#dialogError .modal-dialog .modal-content .modal-body') - e.innerHTML = err - let myModal = new bootstrap.Modal(document.getElementById('dialogError')) - myModal.show() + const dialog = document.getElementById('dialogError') + const body = dialog.querySelector('.dialog-body') + body.innerHTML = err + dialog.showModal() } export const showReset = () => { - let myModal = new bootstrap.Modal(document.getElementById('dialogReset')) - myModal.show() + const dialog = document.getElementById('dialogReset') + dialog.showModal() } +// Setup dialog close buttons +document.addEventListener('DOMContentLoaded', () => { + const errorClose = document.querySelector('#dialogError .dialog-close') + if (errorClose) { + errorClose.addEventListener('click', () => { + document.getElementById('dialogError').close() + }) + } + + const resetClose = document.querySelector('#dialogReset .dialog-close') + if (resetClose) { + resetClose.addEventListener('click', () => { + document.getElementById('dialogReset').close() + }) + } + + const resetOk = document.querySelector('#btnResetOk') + if (resetOk) { + resetOk.addEventListener('click', () => { + document.getElementById('dialogReset').close() + }) + } +}) + // remove the taps (if shownig the map) export const removeTabs = () => { document.querySelector('#navi').style.display = 'none' diff --git a/public/javascripts/global.js b/public/javascripts/global.js index 7a2b04d..4b8d122 100644 --- a/public/javascripts/global.js +++ b/public/javascripts/global.js @@ -135,37 +135,32 @@ import * as spin from './spinner.js' // } }) - $.datepicker.regional['de'] = { - monthNames: ['Januar','Februar','März','April','Mai','Juni', - 'Juli','August','September','Oktober','November','Dezember'], - monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun', - 'Jul','Aug','Sep','Okt','Nov','Dez'], - dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], - dayNamesShort: ['Son','Mon','Die','Mit','Don','Fre','Sam'], - dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'], - firstDay: 1}; - // Btn 'Settings' pressed document.querySelector('#btnSet').addEventListener('click', () => { const curtab = getCurrentTab() - let myModal = new bootstrap.Modal(document.getElementById('dialogSettings'), {backdrop: 'static'}) - const lang = localStorage.getItem('curlang') - $('#startday').datepicker($.extend( - {}, - $.datepicker.regional[lang], - { - dateFormat: 'yy-mm-dd', - maxDate: new Date(), - minDate: minDate, - }) - ) - document.getElementById('startday').value = params.seldate + const dialog = document.getElementById('dialogSettings') + + // Set max and min dates for date input + const today = new Date().toISOString().split('T')[0] + const minDateStr = minDate.toISOString().split('T')[0] + const startdayInput = document.getElementById('startday') + startdayInput.max = today + startdayInput.min = minDateStr + + // Convert seldate format if needed + let seldate = params.seldate + if (seldate === 'today' || seldate === 'heute') { + seldate = today + } + + startdayInput.value = seldate document.getElementById('nbrofdays').value = params.span document.getElementById('peaklim').value = params.peak document.getElementById('olderthan').value = params.weeks const centercity = JSON.parse(localStorage.getItem('centercity')) document.getElementById('centercity').value = centercity.name - // select different infos depending on the selcted tab + + // select different infos depending on the selected tab // first clear all document.querySelector('#ccity').style.display = 'none' document.querySelector('#stday').style.display = 'none' @@ -175,13 +170,22 @@ import * as spin from './spinner.js' for(let i = 0; i < setting.length; i++) { if(setting[i].typ === curtab) { for(let j = 0; j < setting[i].show.length; j++) { - let x = `#${setting[i].show[j]}` document.querySelector(`#${setting[i].show[j]}`).style.display = 'inline' } break } } - myModal.show() + dialog.showModal() + }) + + // Close button for settings dialog + document.querySelector('#dialogSettings .dialog-close').addEventListener('click', () => { + document.getElementById('dialogSettings').close() + }) + + // Close button (secondary button) + document.querySelector('#btnClose').addEventListener('click', () => { + document.getElementById('dialogSettings').close() }) const getSensorType = async (sid) => { @@ -231,13 +235,20 @@ import * as spin from './spinner.js' setInterval(() => dt.showDate(false, params), 1000) // initialise tabs - const triggerTabList = [].slice.call(document.querySelectorAll('.nav-link')) + const triggerTabList = [].slice.call(document.querySelectorAll('.tab-button')) triggerTabList.forEach(function (triggerEl) { - let tabTrigger = new bootstrap.Tab(triggerEl) - triggerEl.addEventListener('click', function (event) { event.preventDefault() - tabTrigger.show() + + // Remove active from all tabs + triggerTabList.forEach(tab => tab.classList.remove('active')) + document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')) + + // Add active to clicked tab + triggerEl.classList.add('active') + const targetId = triggerEl.getAttribute('data-target') + document.getElementById(targetId).classList.add('active') + let newtab = event.currentTarget.id setCurrentTab(newtab) let tab = tabtable.find(tab => tab.id === newtab) @@ -263,8 +274,13 @@ import * as spin from './spinner.js' params.center.coords = [props.location[0].loc.coordinates[1], props.location[0].loc.coordinates[0]] params.sid = csid let t = triggerTabList[1] - let tt = new bootstrap.Tab(t) - tt.show() + + // Switch to live tab + triggerTabList.forEach(tab => tab.classList.remove('active')) + document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')) + t.classList.add('active') + document.getElementById('t_live').classList.add('active') + setCurrentTab('livetab') let err = await showChart(params, ttIndex.live) spin.spinner.stop() diff --git a/public/javascripts/map_noise.js b/public/javascripts/map_noise.js index 07658c0..8490655 100644 --- a/public/javascripts/map_noise.js +++ b/public/javascripts/map_noise.js @@ -151,8 +151,13 @@ async function onMarkerClick(e, click, txts, params, mpp) { let ok = await showChart(params, utils.ttIndex.live) spin.spinner.stop() if (ok) { + // Switch to live tab let triggerEl = document.querySelector(`#livetab`) - bootstrap.Tab.getInstance(triggerEl).show() // Select tab by name + document.querySelectorAll('.tab-button').forEach(tab => tab.classList.remove('active')) + document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')) + triggerEl.classList.add('active') + document.getElementById('t_live').classList.add('active') + params.center.coords = [e.latlng.lat, e.latlng.lng] ev.preventDefault() await loadAll(params) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index bf2fdb5..47e7e1f 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -261,15 +261,6 @@ footer #author #versn { display: none; } } -.nav-tabs .nav-item .nav-link { - background-color: #2D6AF8; - color: #FFF; -} - -.nav-tabs .nav-item .nav-link.active { - color: #2D6AF8; - background-color: #FFFFFF; -} #stday, #nbday, #pklim, #odth { display: none; @@ -298,4 +289,199 @@ footer #author #versn { .errdiv { display: none; -}/*# sourceMappingURL=style.css.map */ \ No newline at end of file +} + +/* Button Styles */ +button { + padding: 8px 16px; + border: 1px solid #ccc; + border-radius: 4px; + background-color: #f8f9fa; + color: #333; + cursor: pointer; + font-size: 14px; + font-family: inherit; +} + +button:hover { + background-color: #e9ecef; +} + +button:active { + background-color: #dee2e6; +} + +#btnSet, #btnHelp { + background-color: #2D6AF8; + color: white; + border-color: #2D6AF8; +} + +#btnSet:hover, #btnHelp:hover { + background-color: #1e5ae8; +} + +#btnMyLocation { + background-color: white; +} + +/* Tabs */ +.tabs { + display: flex; + gap: 2px; + background-color: #f1f1f1; + border-bottom: 1px solid #ccc; + padding: 0; + margin: 0 0.5vw; +} + +.tab-button { + background-color: #2D6AF8; + color: white; + border: none; + border-radius: 4px 4px 0 0; + padding: 10px 20px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s; +} + +.tab-button:hover { + background-color: #1e5ae8; +} + +.tab-button.active { + background-color: white; + color: #2D6AF8; + border-bottom: 2px solid white; +} + +.tab-content { + display: block; +} + +.tab-pane { + display: none; +} + +.tab-pane.active { + display: block; +} + +/* Dialog Styles */ +dialog { + border: 1px solid #ccc; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 0; + max-width: 600px; + width: 90%; +} + +dialog::backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.dialog-content { + display: flex; + flex-direction: column; +} + +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid #dee2e6; + background-color: #f8f9fa; +} + +.dialog-title { + margin: 0; + font-size: 1.25rem; + font-weight: 500; +} + +.dialog-close { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + color: #000; + opacity: 0.5; +} + +.dialog-close:hover { + opacity: 1; + background-color: rgba(0, 0, 0, 0.05); + border-radius: 4px; +} + +.dialog-body { + padding: 20px; + overflow-y: auto; + max-height: 60vh; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 20px; + border-top: 1px solid #dee2e6; + background-color: #f8f9fa; +} + +.dialog-button { + padding: 8px 16px; + border: 1px solid #ccc; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.dialog-button.primary { + background-color: #2D6AF8; + color: white; + border-color: #2D6AF8; +} + +.dialog-button.primary:hover { + background-color: #1e5ae8; +} + +.dialog-button.secondary { + background-color: #6c757d; + color: white; + border-color: #6c757d; +} + +.dialog-button.secondary:hover { + background-color: #5a6268; +} + +/* Radio buttons */ +.radio-group { + display: flex; + gap: 15px; +} + +.radio-label { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; +} + +.radio-label input[type="radio"] { + margin: 0; + cursor: pointer; +} + +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/views/index_noise.pug b/views/index_noise.pug index 4a62496..ffb8b16 100644 --- a/views/index_noise.pug +++ b/views/index_noise.pug @@ -5,125 +5,110 @@ block content #hline1 #h1name #{t("NoiseMeasurement")} #buttonsRight - button.btn.btn-primary#btnSet(value='set') #{t("Settings")} - // button.btn.btn-primary#btnHelp(value='help') #{t("Info")} + button#btnSet(value='set') #{t("Settings")} + // button#btnHelp(value='help') #{t("Info")} #h1datum #fenster #spinner #navi - ul.nav.nav-tabs#tablist - li.nav-item(role='presentation') - button.nav-link.active#maptab(data-bs-toggle="tab" data-bs-target="#t_map" type="button" role="tab" aria-controls="map" aria-selected="true") #{t("Map")} - li.nav-item(role='presentation') - button.nav-link#livetab(data-bs-toggle="tab" data-bs-target="#t_live" type="button" role="tab" aria-controls="live" aria-selected="true") #{t("Live")} - li.nav-item(role='presentation') - button.nav-link#houravgtab(data-bs-toggle="tab" data-bs-target="#t_houravg" type="button" role="tab" aria-controls="houravg" aria-selected="true") #{t("Hour_AVG")} - li.nav-item(role='presentation') - button.nav-link#dayavgtab(data-bs-toggle="tab" data-bs-target="#t_dayavg" type="button" role="tab" aria-controls="dayavg" aria-selected="true") #{t("Day_AVG")} - li.nav-item(role='presentation') - button.nav-link#daynighttab(data-bs-toggle="tab" data-bs-target="#t_daynight" type="button" role="tab" aria-controls="daynight" aria-selected="true") #{t("Day_Night")} - li.nav-item(role='presentation') - button.nav-link#ldentab(data-bs-toggle="tab" data-bs-target="#t_lden" type="button" role="tab" aria-controls="lden" aria-selected="true") LDEN-Index + .tabs#tablist + button.tab-button.active#maptab(data-target="t_map" type="button" role="tab" aria-controls="map" aria-selected="true") #{t("Map")} + button.tab-button#livetab(data-target="t_live" type="button" role="tab" aria-controls="live") #{t("Live")} + button.tab-button#houravgtab(data-target="t_houravg" type="button" role="tab" aria-controls="houravg") #{t("Hour_AVG")} + button.tab-button#dayavgtab(data-target="t_dayavg" type="button" role="tab" aria-controls="dayavg") #{t("Day_AVG")} + button.tab-button#daynighttab(data-target="t_daynight" type="button" role="tab" aria-controls="daynight") #{t("Day_Night")} + button.tab-button#ldentab(data-target="t_lden" type="button" role="tab" aria-controls="lden") LDEN-Index .tab-content#thetabs - .tab-pane.fade.show.active#t_map(role="tabpanel" aria-labelledby="map-tab") + .tab-pane.active#t_map(role="tabpanel") #map - button.btn.btn-light#btnMyLocation(type="button" title=t("MyLocation")) + button#btnMyLocation(type="button" title=t("MyLocation")) span 📍 #maptaberr.errdiv #mapdateactsens #actsensors #mapdate - .tab-pane.fade#t_live(role="tabpanel" aria-labelledby="live-tab") + .tab-pane#t_live(role="tabpanel") #dlive #livetaberr.errdiv - .tab-pane.fade#t_houravg(role="tabpanel" aria-labelledby="map-tab") + .tab-pane#t_houravg(role="tabpanel") #dhour #houravgtaberr.errdiv - .tab-pane.fade#t_dayavg(role="tabpanel" aria-labelledby="map-tab") + .tab-pane#t_dayavg(role="tabpanel") #dday #dayavgtaberr.errdiv - .tab-pane.fade#t_daynight(role="tabpanel" aria-labelledby="map-tab") + .tab-pane#t_daynight(role="tabpanel") #ddaynight #daynighttaberr.errdiv - .tab-pane.fade#t_lden(role="tabpanel" aria-labelledby="map-tab") + .tab-pane#t_lden(role="tabpanel") #dlden #ldentaberr.errdiv // Error-Dialog - .modal.fade#dialogError(tabindex="-1" role="dialog" aria-labelledby="Error" aria-hidden="true") - .modal-dialog.modal-dialog-centered(role="document") - .modal-content - .modal-header - h5.modal-title.dialogErrorTitle #{t("Error")} - button(type="button" class="close" data-bs-dismiss="modal" aria-label="Close") - span(aria-hidden="true") × - .modal-body - .modal-footer - //button(type="button" class="btn btn-secondary" data-bs-dismiss="modal") Close - // button(type="button" class="btn btn-primary") Save changes + dialog#dialogError + .dialog-content + .dialog-header + h3.dialog-title #{t("Error")} + button.dialog-close(type="button" aria-label="Close") × + .dialog-body + .dialog-footer // Settings-Dialog - .modal.fade#dialogSettings(tabindex="-1" role="dialog" aria-labelledby="Settings" aria-hidden="true") - .modal-dialog.modal-dialog-centered(role="document") - .modal-content - .modal-header - h5.modal-title.dialogErrorTitle #{t("Settings")} - button(type="button" class="close" data-bs-dismiss="modal" aria-label="Close") - span(aria-hidden="true") × - .modal-body.settings - .rows - .comlumn#ccity - label.thelabels(for='city') #{t("CenterMap")}: - input.theInputs#centercity(name="centercity") - .column#stday - label.thelabels(for='startday') #{t("StartDate")}: - input.theInputs#startday(name='startday' ) - .column - .column#nbday - label.thelabels(for='nbrofdays') - | #{t("NumberOfDays")}:
- // span.klein (#{t("Only_for_live_chart")}) - input.theInputs#nbrofdays(type = 'number' name = 'nbrofdays', min="1", max="10") - span.klein   (max. 10) - .column#pklim - label.thelabels(for='peaklim') - | #{t("Count_peaks_over")} - input.theInputs#peaklim(type = 'number' name = 'peaklim', min="10", max="130") - |   dbA - span.klein   (max. 130) - .column#odth - label.thelabels(for='olderthan') - | #{t("RemoveFromMap")} - input.theInputs#olderthan(type = 'number' name = 'olderthan', min="0", max="52") - |   #{t("weeks")} - span.klein   (max. 52) - .column - .column#sellan - label.thelabels - | #{t("Language")}: - .form-check.form-check-inline - input#de.form-check-input(type="radio" name="lanbut") - label.form-check-label(for='de') Deutsch - .form-check.form-check-inline - input#en.form-check-input(type="radio" name="lanbut", checked) - label.form-check-label(for="en") English - .modal-footer - button.btn.btn-secondary(type="button" data-bs-dismiss="modal") #{t("Close")} - button#btnSave.btn.btn-primary(type="button" data-bs-dismiss="modal") #{t("Save_changes")} + dialog#dialogSettings + .dialog-content + .dialog-header + h3.dialog-title #{t("Settings")} + button.dialog-close(type="button" aria-label="Close") × + .dialog-body.settings + .rows + .column#ccity + label.thelabels(for='city') #{t("CenterMap")}: + input.theInputs#centercity(name="centercity") + .column#stday + label.thelabels(for='startday') #{t("StartDate")}: + input.theInputs#startday(type='date' name='startday') + .column + .column#nbday + label.thelabels(for='nbrofdays') + | #{t("NumberOfDays")}:
+ input.theInputs#nbrofdays(type='number' name='nbrofdays' min="1" max="10") + span.klein   (max. 10) + .column#pklim + label.thelabels(for='peaklim') + | #{t("Count_peaks_over")} + input.theInputs#peaklim(type='number' name='peaklim' min="10" max="130") + |   dbA + span.klein   (max. 130) + .column#odth + label.thelabels(for='olderthan') + | #{t("RemoveFromMap")} + input.theInputs#olderthan(type='number' name='olderthan' min="0" max="52") + |   #{t("weeks")} + span.klein   (max. 52) + .column + .column#sellan + label.thelabels + | #{t("Language")}: + .radio-group + label.radio-label + input#de(type="radio" name="lanbut") + span Deutsch + label.radio-label + input#en(type="radio" name="lanbut" checked) + span English + .dialog-footer + button.dialog-button.secondary#btnClose(type="button") #{t("Close")} + button.dialog-button.primary#btnSave(type="button") #{t("Save_changes")} // Reset-Dialog - .modal.fade#dialogReset(tabindex="-1" role="dialog" aria-labelledby="Error" aria-hidden="true") - .modal-dialog.modal-dialog-centered(role="document") - .modal-content - .modal-header - h5.modal-title.dialogErrorTitle Info - button(type="button" class="close" data-bs-dismiss="modal" aria-label="Close") - span(aria-hidden="true") × - .modal-body - .modal-footer - //button(type="button" class="btn btn-secondary" data-bs-dismiss="modal") Close - button(type="button" class="btn btn-primary" data-bs-dismiss="modal") OK + dialog#dialogReset + .dialog-content + .dialog-header + h3.dialog-title Info + button.dialog-close(type="button" aria-label="Close") × + .dialog-body + .dialog-footer + button.dialog-button.primary#btnResetOk(type="button") OK diff --git a/views/layout.pug b/views/layout.pug index 984b5b9..2332bff 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -3,14 +3,10 @@ html head title= title meta(name="viewport" content="width=device-width, initial-scale=1" charset="utf-8") - link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" - rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" - crossorigin="anonymous") link(rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="") link(rel='stylesheet', href='/stylesheets/MarkerCluster.css') - link(rel="stylesheet" href="https://code.jquery.com/ui/1.14.0/themes/base/jquery-ui.css") link(rel='stylesheet', href='/spin.js/spin.css') @@ -33,14 +29,9 @@ html a(href="mailto:rexfue@gmail.com") mailto:rexfue@gmail.com #versn #{t('Version')}: #{version} #{t('from')} #{date} - script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" - integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" - crossorigin="anonymous") script(src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin="") script(src="/javascripts/leaflet.markercluster.js") script(src="https://code.highcharts.com/highcharts.js") - script(src="https://code.jquery.com/jquery-3.7.1.min.js") - script(src="https://code.jquery.com/ui/1.14.0/jquery-ui.min.js") script(type="module" src="/javascripts/global.js") \ No newline at end of file