testing a more organized history navigation
This commit is contained in:
parent
e3fba3dcd4
commit
ccfdd8007e
18 changed files with 187 additions and 83 deletions
|
@ -7,10 +7,16 @@ use Travel::Status::DE::IRIS::Stations;
|
|||
use strict;
|
||||
use warnings;
|
||||
use 5.020;
|
||||
use utf8;
|
||||
|
||||
use DateTime;
|
||||
use JSON;
|
||||
|
||||
my @month_name
|
||||
= (
|
||||
qw(Januar Februar März April Mai Juni Juli August September Oktober November Dezember)
|
||||
);
|
||||
|
||||
sub epoch_to_dt {
|
||||
my ($epoch) = @_;
|
||||
|
||||
|
@ -708,11 +714,58 @@ sub get_years {
|
|||
return @ret;
|
||||
}
|
||||
|
||||
sub get_months {
|
||||
sub get_months_for_year {
|
||||
my ( $self, %opt ) = @_;
|
||||
|
||||
my $uid = $opt{uid};
|
||||
my $db = $opt{db} // $self->{pg}->db;
|
||||
my $uid = $opt{uid};
|
||||
my $db = $opt{db} // $self->{pg}->db;
|
||||
my $year = $opt{year};
|
||||
|
||||
my $res = $db->select(
|
||||
'journeys',
|
||||
'distinct extract(year from real_departure) as year, extract(month from real_departure) as month',
|
||||
{ user_id => $uid },
|
||||
{ order_by => { -asc => 'year' } }
|
||||
);
|
||||
|
||||
my @ret;
|
||||
|
||||
for my $month ( 1 .. 12 ) {
|
||||
push( @ret,
|
||||
[ sprintf( '%d/%02d', $year, $month ), $month_name[ $month - 1 ] ]
|
||||
);
|
||||
}
|
||||
|
||||
for my $row ( $res->hashes->each ) {
|
||||
if ( $row->{year} == $year ) {
|
||||
|
||||
# TODO delegate query to the (not yet present) JourneyStats model
|
||||
my $stats = $db->select(
|
||||
'journey_stats',
|
||||
['data'],
|
||||
{
|
||||
user_id => $uid,
|
||||
year => $year,
|
||||
month => $row->{month}
|
||||
}
|
||||
)->expand->hash;
|
||||
if ($stats) {
|
||||
$ret[ $row->{month} - 1 ][2] = $stats->{data};
|
||||
}
|
||||
}
|
||||
}
|
||||
return @ret;
|
||||
}
|
||||
|
||||
sub get_nav_months {
|
||||
my ( $self, %opt ) = @_;
|
||||
|
||||
my $uid = $opt{uid};
|
||||
my $db = $opt{db} // $self->{pg}->db;
|
||||
my $filter_year = $opt{year};
|
||||
my $filter_month = $opt{month};
|
||||
|
||||
my $selected_index = undef;
|
||||
|
||||
my $res = $db->select(
|
||||
'journeys',
|
||||
|
@ -721,11 +774,39 @@ sub get_months {
|
|||
{ order_by => { -asc => 'yearmonth' } }
|
||||
);
|
||||
|
||||
my @ret;
|
||||
my @months;
|
||||
for my $row ( $res->hashes->each ) {
|
||||
my ( $year, $month ) = split( qr{[.]}, $row->{yearmonth} );
|
||||
push( @ret, [ "${year}/${month}", "${month}.${year}" ] );
|
||||
push( @months, [ $year, $month ] );
|
||||
if ( $year eq $filter_year and $month eq $filter_month ) {
|
||||
$selected_index = $#months;
|
||||
}
|
||||
}
|
||||
|
||||
# returns (previous entry, current month, next entry). if there is no
|
||||
# previous or next entry, the corresponding field is undef. Previous/next
|
||||
# entry is usually previous/next month, but may also have a distance of
|
||||
# more than one month if there are months without travels
|
||||
my @ret = ( undef, undef, undef );
|
||||
|
||||
$ret[1] = [
|
||||
"${filter_year}/${filter_month}",
|
||||
$month_name[ $filter_month - 1 ] // $filter_month
|
||||
];
|
||||
|
||||
if ( not defined $selected_index ) {
|
||||
return @ret;
|
||||
}
|
||||
|
||||
if ( $selected_index > 0 and $months[ $selected_index - 1 ] ) {
|
||||
my ( $year, $month ) = @{ $months[ $selected_index - 1 ] };
|
||||
$ret[0] = [ "${year}/${month}", "${month}.${year}" ];
|
||||
}
|
||||
if ( $selected_index < $#months ) {
|
||||
my ( $year, $month ) = @{ $months[ $selected_index + 1 ] };
|
||||
$ret[2] = [ "${year}/${month}", "${month}.${year}" ];
|
||||
}
|
||||
|
||||
return @ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
const CACHE_NAME = 'static-cache-v34';
|
||||
const CACHE_NAME = 'static-cache-v35';
|
||||
const FILES_TO_CACHE = [
|
||||
'/favicon.ico',
|
||||
'/offline.html',
|
||||
'/static/v34/css/light.min.css',
|
||||
'/static/v34/css/dark.min.css',
|
||||
'/static/v34/css/material-icons.css',
|
||||
'/static/v34/css/local.css',
|
||||
'/static/v34/fonts/MaterialIcons-Regular.woff2',
|
||||
'/static/v34/fonts/MaterialIcons-Regular.woff',
|
||||
'/static/v34/fonts/MaterialIcons-Regular.ttf',
|
||||
'/static/v34/js/jquery-3.4.1.min.js',
|
||||
'/static/v34/js/materialize.min.js',
|
||||
'/static/v34/js/travelynx-actions.min.js',
|
||||
'/static/v34/js/autocomplete.min.js',
|
||||
'/static/v34/js/geolocation.min.js',
|
||||
'/static/v35/css/light.min.css',
|
||||
'/static/v35/css/dark.min.css',
|
||||
'/static/v35/css/material-icons.css',
|
||||
'/static/v35/css/local.css',
|
||||
'/static/v35/fonts/MaterialIcons-Regular.woff2',
|
||||
'/static/v35/fonts/MaterialIcons-Regular.woff',
|
||||
'/static/v35/fonts/MaterialIcons-Regular.ttf',
|
||||
'/static/v35/js/jquery-3.4.1.min.js',
|
||||
'/static/v35/js/materialize.min.js',
|
||||
'/static/v35/js/travelynx-actions.min.js',
|
||||
'/static/v35/js/autocomplete.min.js',
|
||||
'/static/v35/js/geolocation.min.js',
|
||||
];
|
||||
|
||||
self.addEventListener('install', (evt) => {
|
||||
|
|
4
public/static/css/dark.min.css
vendored
4
public/static/css/dark.min.css
vendored
File diff suppressed because one or more lines are too long
4
public/static/css/light.min.css
vendored
4
public/static/css/light.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -2,12 +2,12 @@
|
|||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(/static/v34/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: url(/static/v35/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(/static/v34/fonts/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(/static/v34/fonts/MaterialIcons-Regular.woff) format('woff'),
|
||||
url(/static/v34/fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||
url(/static/v35/fonts/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(/static/v35/fonts/MaterialIcons-Regular.woff) format('woff'),
|
||||
url(/static/v35/fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
|
|
|
@ -3,27 +3,27 @@
|
|||
"short_name": "Travelynx",
|
||||
"scope": "/",
|
||||
"icons": [{
|
||||
"src": "/static/v34/icons/icon-128x128.png",
|
||||
"src": "/static/v35/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/static/v34/icons/icon-144x144.png",
|
||||
"src": "/static/v35/icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/static/v34/icons/icon-152x152.png",
|
||||
"src": "/static/v35/icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/static/v34/icons/icon-192x192.png",
|
||||
"src": "/static/v35/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/static/v34/icons/icon-256x256.png",
|
||||
"src": "/static/v35/icons/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/static/v34/icons/icon-512x512.png",
|
||||
"src": "/static/v35/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
|
|
|
@ -29,3 +29,13 @@ a.unmarked {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collection-item {
|
||||
color: $off-black;
|
||||
.secondary-content {
|
||||
color: $off-black;
|
||||
}
|
||||
&.disabled {
|
||||
color: $inactive-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
$bg-color: #101010 !default;
|
||||
$collection-bg-color: $bg-color;
|
||||
$info-color: color('blue-grey', 'darken-4');
|
||||
|
||||
$inactive-color: color('grey', 'darken-1');
|
||||
|
||||
$off-black: color('shades', 'white');
|
||||
|
||||
$primary-color: color('materialize-red', 'darken-2');
|
||||
$primary-color: color('materialize-red', 'darken-4');
|
||||
$secondary-color: color('cyan', 'darken-2');
|
||||
$link-color: color('light-blue', 'darken-1');
|
||||
$collection-link-color: color('cyan', 'darken-4');
|
||||
$success-color: color('green', 'darken-2');
|
||||
$error-color: color('red', 'darken-2');
|
||||
$input-border-color: $off-black;
|
||||
$collection-border-color: color('grey', 'darken-3');
|
||||
$collection-link-color: color('shades', 'white');
|
||||
$collection-hover-bg-color: color('grey', 'darken-4');
|
||||
$radio-empty-color: $off-black !default;
|
||||
|
||||
$table-striped-color: color('grey', 'darken-4');
|
||||
|
|
|
@ -2,4 +2,6 @@ $bg-color: #fff;
|
|||
$info-color: color('yellow', 'lighten-4');
|
||||
$link-color: color('light-blue', 'darken-1');
|
||||
$card-link-color: $link-color;
|
||||
$collection-link-color: color('shades', 'black');
|
||||
$card-bg-color: color('blue-grey', 'lighten-5');
|
||||
$inactive-color: color('grey', 'darken-2');
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
<div class="row">
|
||||
<div class="col s12">
|
||||
<ul class="pagination">
|
||||
% for my $month (journeys->get_months(uid => current_user->{id})) {
|
||||
% my $link_to = $month->[0];
|
||||
% my $text = $month->[1];
|
||||
% my $class = $link_to eq $current ? 'active' : 'waves-effect';
|
||||
<li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li>
|
||||
% my ($prev, $current, $next) = journeys->get_nav_months(uid => current_user->{id}, year => $year, month => $month);
|
||||
% if ($prev) {
|
||||
<li class="waves-effect waves-light"><a href="/history/<%= $prev->[0] %>"><i class="material-icons">chevron_left</i></a></li>
|
||||
% }
|
||||
% else {
|
||||
<li class="disabled"><a href="#!"><i class="material-icons">chevron_left</i></a></li>
|
||||
% }
|
||||
% if ($current) {
|
||||
<li class="" style="min-width: 8em;"><a href="/history/<%= $current->[0] %>"><%= $current->[1] %></a></li>
|
||||
% }
|
||||
% if ($next) {
|
||||
<li class="waves-effect waves-light"><a href="/history/<%= $next->[0] %>"><i class="material-icons">chevron_right</i></a></li>
|
||||
% }
|
||||
% else {
|
||||
<li class="disabled"><a href="#!"><i class="material-icons">chevron_right</i></a></li>
|
||||
% }
|
||||
<li class=""><a href="/history/<%= $year %>"><%= $year %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
16
templates/_history_months_for_year.html.ep
Normal file
16
templates/_history_months_for_year.html.ep
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="collection">
|
||||
% for my $month (journeys->get_months_for_year(uid => current_user->{id}, year => $year)) {
|
||||
% if (defined $month->[2]) {
|
||||
<a class="collection-item" href="/history/<%= $month->[0] %>"><%= $month->[1] %>
|
||||
<span class="secondary-content"><%= sprintf('%.f', $month->[2]{km_route}) %> km</span>
|
||||
</a>
|
||||
% }
|
||||
% else {
|
||||
<div class="collection-item disabled"><%= $month->[1] %></div>
|
||||
% }
|
||||
% }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,12 +1,18 @@
|
|||
<div class="row">
|
||||
<div class="col s12">
|
||||
<ul class="pagination">
|
||||
% for my $year (journeys->get_years(uid => current_user->{id})) {
|
||||
% my $link_to = $year->[0];
|
||||
% my $text = $year->[1];
|
||||
% my $class = $link_to eq $current ? 'active' : 'waves-effect';
|
||||
<li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li>
|
||||
% }
|
||||
</ul>
|
||||
% my @years = journeys->get_years(uid => current_user->{id});
|
||||
% if (@years) {
|
||||
<ul class="pagination">
|
||||
% for my $year (journeys->get_years(uid => current_user->{id})) {
|
||||
% my $link_to = $year->[0];
|
||||
% my $text = $year->[1];
|
||||
% my $class = $link_to eq $current ? 'active' : 'waves-effect';
|
||||
<li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li>
|
||||
% }
|
||||
</ul>
|
||||
% }
|
||||
% else {
|
||||
Keine Fahrten gefunden.
|
||||
% }
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
</div>
|
||||
% }
|
||||
|
||||
<h1>Account</h1>
|
||||
% my $acc = current_user();
|
||||
% my $hook = get_webhook();
|
||||
% my $traewelling = traewelling->get($acc->{id});
|
||||
% my $use_history = users->use_history(uid => $acc->{id});
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h2>Account</h2>
|
||||
<table class="striped">
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
|
@ -159,10 +159,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<h2>API</h2>
|
||||
% my $token = get_api_token();
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h2>API</h2>
|
||||
<p>
|
||||
Die folgenden API-Token erlauben den passwortlosen automatisierten Zugriff auf
|
||||
API-Endpunkte. Bitte umsichtig behandeln – sobald ein Token gesetzt
|
||||
|
@ -273,10 +273,10 @@
|
|||
</div>
|
||||
|
||||
|
||||
<h2>Export</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h2>Export</h2>
|
||||
<ul>
|
||||
<li><a href="/export.json">Rohdaten</a> (Kein API-Ersatz, das Format kann sich jederzeit ändern)</li>
|
||||
</ul>
|
||||
|
@ -284,9 +284,9 @@
|
|||
</div>
|
||||
|
||||
% if (not $acc->{deletion_requested}) {
|
||||
<h2>Löschen</h2>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h2>Löschen</h2>
|
||||
<p>
|
||||
Der Löschauftrag wird vorgemerkt und erst nach drei Tagen
|
||||
umgesetzt, bis dahin kann er jederzeit zurückgenommen werden. Nach
|
||||
|
|
|
@ -1,31 +1,6 @@
|
|||
<h1>Fahrten</h1>
|
||||
<h2>Fahrten</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
Hier finden sich alle bisherigen Zugfahrten und Statistiken für jedes
|
||||
Jahr und jeden Monat.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Nach Jahr</h2>
|
||||
%= include '_history_years', current => '';
|
||||
% if(0) {
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
Noch keine Fahrten.
|
||||
</div>
|
||||
</div>
|
||||
% }
|
||||
|
||||
<h2>Nach Monat</h2>
|
||||
%= include '_history_months', current => '';
|
||||
% if(0) {
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
Noch keine Fahrten.
|
||||
</div>
|
||||
</div>
|
||||
% }
|
||||
|
||||
<h2>Auswertungen</h2>
|
||||
<div class="row">
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
%= include '_history_months', current => "${year}/${month}";
|
||||
|
||||
<h1><%= stash('month_name') %> <%= stash('year') %></h1>
|
||||
%= include '_history_months';
|
||||
|
||||
% if (stash('statistics')) {
|
||||
%= include '_history_stats', stats => stash('statistics');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
%= include '_history_years', current => $year;
|
||||
|
||||
<h1>Jahresrückblick <%= $year %></h1>
|
||||
|
||||
% if (stash('statistics')) {
|
||||
%= include '_history_stats', stats => stash('statistics');
|
||||
% }
|
||||
%
|
||||
%= include '_history_months_for_year';
|
||||
|
||||
% if (stash('journeys')) {
|
||||
%= include '_history_trains', date_format => '%d.%m.', journeys => stash('journeys');
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
% while (my ($key, $value) = each %{stash('opengraph') // {}}) {
|
||||
<meta property="og:<%= $key %>" content="<%= $value %>">
|
||||
% }
|
||||
% my $av = 'v34'; # asset version
|
||||
% my $av = 'v35'; # asset version
|
||||
<link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-96x96.png" sizes="96x96">
|
||||
|
|
Loading…
Reference in a new issue