[Perl] DAQ system for the FAPG.
feat navbar page title and mobile timeframe dropdown
Show a centered page title in the main navbar on all screen sizes. Collapse timeframe buttons into a dropdown on mobile; keep full button group on md+. Remove redundant inline h1 elements from page content.
Changed files
- roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Pages.pm
- roles/dashboard/public/js/dashboard/graph-controls.js
- roles/dashboard/t/01-graph-page.t
- roles/dashboard/t/05-status-page.t
- roles/dashboard/t/06-downloads.t
- roles/dashboard/t/08-insights.t
- roles/dashboard/templates/dashboard/downloads.html.ep
- roles/dashboard/templates/dashboard/graph-control.html.ep
- roles/dashboard/templates/dashboard/graph.html.ep
- roles/dashboard/templates/dashboard/insights.html.ep
- roles/dashboard/templates/dashboard/probes.html.ep
- roles/dashboard/templates/dashboard/status.html.ep
- roles/dashboard/templates/dashboard/weather.html.ep
- roles/dashboard/templates/layouts/default.html.ep
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Pages.pm
@@ -12,17 +12,19 @@
12
12
13
13
sub status ($self) {
14
14
$self->render(
15
Removed:
template => 'dashboard/status',
16
Removed:
items => $self->status_items,
17
Removed:
nav_page => 'probes',
15
Added:
template => 'dashboard/status',
16
Added:
items => $self->status_items,
17
Added:
nav_page => 'probes',
18
Added:
page_title => 'Status',
18
19
);
19
20
}
20
21
21
22
sub probe_list ($self) {
22
23
$self->render(
23
Removed:
template => 'dashboard/probes',
24
Removed:
probes => $self->probes,
25
Removed:
nav_page => 'probes',
24
Added:
template => 'dashboard/probes',
25
Added:
probes => $self->probes,
26
Added:
nav_page => 'probes',
27
Added:
page_title => 'Probe Readings',
26
28
);
27
29
}
28
30
@@ -44,21 +46,24 @@
44
46
oldest => $oldest,
45
47
probes => $self->probes,
46
48
nav_page => 'downloads',
49
Added:
page_title => 'Downloads',
47
50
);
48
51
}
49
52
50
53
sub insights ($self) {
51
54
$self->render(
52
Removed:
template => 'dashboard/insights',
53
Removed:
probes => $self->probes,
54
Removed:
nav_page => 'insights',
55
Added:
template => 'dashboard/insights',
56
Added:
probes => $self->probes,
57
Added:
nav_page => 'insights',
58
Added:
page_title => 'Insights',
55
59
);
56
60
}
57
61
58
62
sub weather ($self) {
59
63
$self->render(
60
Removed:
template => 'dashboard/weather',
61
Removed:
nav_page => 'weather',
64
Added:
template => 'dashboard/weather',
65
Added:
nav_page => 'weather',
66
Added:
page_title => 'Weather',
62
67
);
63
68
}
64
69
@@ -73,6 +78,7 @@
73
78
probe => $probe,
74
79
nav_page => 'probes',
75
80
single_probe => 1,
81
Added:
page_title => $probe->{label},
76
82
);
77
83
}
78
84
roles/dashboard/public/js/dashboard/graph-controls.js
@@ -21,6 +21,12 @@
21
21
button.setAttribute("aria-pressed", active ? "true" : "false");
22
22
});
23
23
24
Added:
document.querySelectorAll("[data-timeframe-dropdown-label]").forEach(el => {
25
Added:
el.textContent = document.querySelector(
26
Added:
`.timeframe-button[data-timeframe="${timeframe.selected()}"]`
27
Added:
)?.dataset.timeframeLabel || "Daily";
28
Added:
});
29
Added:
24
30
document.querySelectorAll("[data-range-label]").forEach(label => {
25
31
label.textContent = timeframeRangeLabel(timeframe.selected(), range);
26
32
});
roles/dashboard/t/01-graph-page.t
@@ -11,7 +11,8 @@
11
11
12
12
$t->get_ok('/probes/ph')
13
13
->status_is(200)
14
Removed:
->text_is( 'h1', 'pH' )
14
Added:
->element_exists('.navbar-text')
15
Added:
->text_like('.navbar-text', qr/pH/)
15
16
->element_exists('.sticky-controls .graph-control')
16
17
->text_is( '.card-header h1 a[href="/probes/ph"]', 'pH' )
17
18
->element_exists('.navbar-nav a.nav-link.active.dropdown-toggle')
@@ -40,7 +41,7 @@
40
41
41
42
$t->get_ok('/probes')
42
43
->status_is(200)
43
Removed:
->text_is( 'h1', 'Probes' )
44
Added:
->text_like('.navbar-text', qr/Probe Readings/)
44
45
->element_exists('.sticky-controls .graph-control')
45
46
->element_exists('main.container')
46
47
->element_exists('div.probes-grid')
roles/dashboard/t/05-status-page.t
@@ -11,7 +11,7 @@
11
11
12
12
$t->get_ok('/probes/status')
13
13
->status_is(200)
14
Removed:
->text_is( 'h1', 'Status' )
14
Added:
->text_like('.navbar-text', qr/Status/)
15
15
->element_exists('main.container')
16
16
->element_exists('.list-group');
17
17
roles/dashboard/t/06-downloads.t
@@ -11,7 +11,7 @@
11
11
12
12
$t->get_ok('/downloads')
13
13
->status_is(200)
14
Removed:
->text_is( 'h1', 'Downloads' )
14
Added:
->text_like('.navbar-text', qr/Downloads/)
15
15
->element_exists('.navbar-nav a.nav-link.active[href="/downloads"][aria-current="page"]')
16
16
->element_exists('form.downloads-form[action="/downloads/readings.csv"]')
17
17
->element_exists('[data-downloads-error]')
roles/dashboard/t/08-insights.t
@@ -13,7 +13,7 @@
13
13
$t->get_ok('/insights')
14
14
->status_is(200)
15
15
->text_is('title', 'FAPG DAQ Insights')
16
Removed:
->text_is('h1', 'Insights')
16
Added:
->text_like('.navbar-text', qr/Insights/)
17
17
->element_exists('.navbar-nav a.nav-link.active[href="/insights"]')
18
18
->element_exists('[data-default-timeframe="day"]');
19
19
};
roles/dashboard/templates/dashboard/downloads.html.ep
@@ -2,10 +2,6 @@
2
2
% title 'FAPG DAQ Downloads';
3
3
4
4
<section class="mb-4" aria-label="Data downloads">
5
Removed:
<div class="mb-3">
6
Removed:
<h1 class="h4 fw-bold mb-1">Downloads</h1>
7
Removed:
<p class="text-body-secondary small mb-0">Download recorded DAQ readings as a CSV or Excel file.</p>
8
Removed:
</div>
9
5
10
6
<div class="card">
11
7
roles/dashboard/templates/dashboard/graph-control.html.ep
@@ -1,29 +1,49 @@
1
1
% my $dt_attr = stash('default_timeframe') ? 'data-default-timeframe="' . stash('default_timeframe') . '"' : '';
2
Removed:
<div class="graph-control-scroll overflow-x-auto" tabindex="0" role="region" aria-label="<%= $aria_label %>" <%== $dt_attr %>> <div class="graph-control d-inline-flex gap-2 flex-nowrap">
3
Removed:
<div class="input-group input-group-sm range-control" aria-label="Adjust range">
4
Removed:
<button
5
Removed:
class="btn btn-outline-secondary"
6
Removed:
type="button"
7
Removed:
data-range-adjustment="decrease"
8
Removed:
aria-label="Decrease range">−</button>
9
Removed:
<span
10
Removed:
class="input-group-text range-label"
11
Removed:
data-range-label
12
Removed:
aria-live="polite">1</span>
13
Removed:
<button
14
Removed:
class="btn btn-outline-secondary"
15
Removed:
type="button"
16
Removed:
data-range-adjustment="increase"
17
Removed:
aria-label="Increase range">+</button>
18
Removed:
</div>
19
Removed:
<div class="btn-group timeframe-control" role="group" aria-label="Select timeframe">
2
Added:
<div class="graph-control d-inline-flex gap-2 flex-nowrap align-items-center" <%== $dt_attr %>>
3
Added:
<div class="input-group input-group-sm range-control" aria-label="Adjust range">
4
Added:
<button
5
Added:
class="btn btn-outline-secondary"
6
Added:
type="button"
7
Added:
data-range-adjustment="decrease"
8
Added:
aria-label="Decrease range">−</button>
9
Added:
<span
10
Added:
class="input-group-text range-label"
11
Added:
data-range-label
12
Added:
aria-live="polite">1</span>
13
Added:
<button
14
Added:
class="btn btn-outline-secondary"
15
Added:
type="button"
16
Added:
data-range-adjustment="increase"
17
Added:
aria-label="Increase range">+</button>
18
Added:
</div>
19
Added:
<%# Full button group for md+ screens %>
20
Added:
<div class="btn-group timeframe-control d-none d-md-inline-flex" role="group" aria-label="Select timeframe">
21
Added:
% for my $timeframe ( [ hour => 'Hourly' ], [ day => 'Daily' ], [ week => 'Weekly' ], [ month => 'Monthly' ], [ year => 'Yearly' ] ) {
22
Added:
<button
23
Added:
class="btn btn-sm btn-outline-secondary timeframe-button"
24
Added:
type="button"
25
Added:
data-timeframe="<%= $timeframe->[0] %>"
26
Added:
data-timeframe-label="<%= $timeframe->[1] %>"><%= $timeframe->[1] %></button>
27
Added:
% }
28
Added:
</div>
29
Added:
<%# Dropdown for small screens %>
30
Added:
<div class="dropdown d-md-none timeframe-control">
31
Added:
<button
32
Added:
class="btn btn-sm btn-outline-secondary dropdown-toggle timeframe-dropdown-toggle"
33
Added:
type="button"
34
Added:
data-bs-toggle="dropdown"
35
Added:
aria-expanded="false"
36
Added:
data-timeframe-dropdown-label>Daily</button>
37
Added:
<ul class="dropdown-menu dropdown-menu-end">
20
38
% for my $timeframe ( [ hour => 'Hourly' ], [ day => 'Daily' ], [ week => 'Weekly' ], [ month => 'Monthly' ], [ year => 'Yearly' ] ) {
21
Removed:
<button
22
Removed:
class="btn btn-sm btn-outline-secondary timeframe-button"
23
Removed:
type="button"
24
Removed:
data-timeframe="<%= $timeframe->[0] %>"
25
Removed:
data-timeframe-label="<%= $timeframe->[1] %>"><%= $timeframe->[1] %></button>
39
Added:
<li>
40
Added:
<button
41
Added:
class="dropdown-item timeframe-button"
42
Added:
type="button"
43
Added:
data-timeframe="<%= $timeframe->[0] %>"
44
Added:
data-timeframe-label="<%= $timeframe->[1] %>"><%= $timeframe->[1] %></button>
45
Added:
</li>
26
46
% }
27
Removed:
</div>
47
Added:
</ul>
28
48
</div>
29
49
</div>
roles/dashboard/templates/dashboard/graph.html.ep
@@ -3,8 +3,7 @@
3
3
% stash use_charts => 1;
4
4
5
5
<section class="mb-4" aria-label="<%= $probe->{label} %> probe detail">
6
Removed:
<div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-3 sticky-controls">
7
Removed:
<h1 class="h4 fw-bold mb-0"><%= $probe->{label} %></h1>
6
Added:
<div class="d-flex align-items-center justify-content-end gap-2 mb-3 sticky-controls">
8
7
%= include 'dashboard/graph-control', aria_label => "$probe->{label} chart timeframe", default_timeframe => 'day'
9
8
</div>
10
9
roles/dashboard/templates/dashboard/insights.html.ep
@@ -3,11 +3,7 @@
3
3
% stash use_charts => 1;
4
4
5
5
<section class="mb-4" aria-label="Insights visualisations">
6
Removed:
<div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-3 sticky-controls">
7
Removed:
<div>
8
Removed:
<h1 class="h4 fw-bold mb-1">Insights</h1>
9
Removed:
<p class="text-body-secondary small mb-0">Derived visualisations for system monitoring</p>
10
Removed:
</div>
6
Added:
<div class="d-flex align-items-center justify-content-end gap-2 mb-3 sticky-controls">
11
7
%= include 'dashboard/graph-control', aria_label => 'Insights timeframe', default_timeframe => 'day'
12
8
</div>
13
9
roles/dashboard/templates/dashboard/probes.html.ep
@@ -3,8 +3,7 @@
3
3
% stash use_charts => 1;
4
4
5
5
<section class="mb-4" aria-label="Probe readings">
6
Removed:
<div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-3 sticky-controls">
7
Removed:
<h1 class="h4 fw-bold mb-0">Probes</h1>
6
Added:
<div class="d-flex align-items-center justify-content-end gap-2 mb-3 sticky-controls">
8
7
%= include 'dashboard/graph-control', aria_label => 'All probe chart timeframes', default_timeframe => 'day'
9
8
</div>
10
9
roles/dashboard/templates/dashboard/status.html.ep
@@ -2,10 +2,6 @@
2
2
% title 'FAPG DAQ Status';
3
3
4
4
<section class="mb-4" aria-label="DAQ status">
5
Removed:
<div class="mb-3">
6
Removed:
<h1 class="h4 fw-bold mb-1">Status</h1>
7
Removed:
<p class="text-body-secondary small mb-0">Current connection status for the hub and sensor probes.</p>
8
Removed:
</div>
9
5
10
6
<div class="card">
11
7
roles/dashboard/templates/dashboard/weather.html.ep
@@ -3,11 +3,7 @@
3
3
% stash use_charts => 1;
4
4
5
5
<section class="mb-4" aria-label="Weather data">
6
Removed:
<div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-3 sticky-controls">
7
Removed:
<div>
8
Removed:
<h1 class="h4 fw-bold mb-1">Weather</h1>
9
Removed:
<p class="text-body-secondary small mb-0">Outdoor conditions at Versonnex — Météo France (AROME)</p>
10
Removed:
</div>
6
Added:
<div class="d-flex align-items-center justify-content-end gap-2 mb-3 sticky-controls">
11
7
%= include 'dashboard/graph-control', aria_label => 'Weather timeframe', default_timeframe => 'day'
12
8
</div>
13
9
roles/dashboard/templates/layouts/default.html.ep
@@ -23,6 +23,12 @@
23
23
24
24
<a class="navbar-brand fw-bold text-success" href="/">FAPG</a>
25
25
26
Added:
% my $page_title = stash('page_title') // '';
27
Added:
% if ($page_title) {
28
Added:
<span class="navbar-text fw-semibold text-body position-absolute start-50 translate-middle-x d-none d-sm-inline" aria-hidden="true"><%= $page_title %></span>
29
Added:
<span class="navbar-text fw-semibold text-body mx-auto d-sm-none" aria-hidden="true"><%= $page_title %></span>
30
Added:
% }
31
Added:
26
32
<div class="d-flex align-items-center gap-2 ms-auto order-md-last">
27
33
<button
28
34
class="navbar-toggler"