testing a more organized history navigation

This commit is contained in:
Daniel Friesel 2020-11-21 15:33:43 +01:00
parent e3fba3dcd4
commit ccfdd8007e
18 changed files with 187 additions and 83 deletions

View file

@ -7,10 +7,16 @@ use Travel::Status::DE::IRIS::Stations;
use strict; use strict;
use warnings; use warnings;
use 5.020; use 5.020;
use utf8;
use DateTime; use DateTime;
use JSON; use JSON;
my @month_name
= (
qw(Januar Februar März April Mai Juni Juli August September Oktober November Dezember)
);
sub epoch_to_dt { sub epoch_to_dt {
my ($epoch) = @_; my ($epoch) = @_;
@ -708,11 +714,58 @@ sub get_years {
return @ret; return @ret;
} }
sub get_months { sub get_months_for_year {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid}; my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db; 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( my $res = $db->select(
'journeys', 'journeys',
@ -721,11 +774,39 @@ sub get_months {
{ order_by => { -asc => 'yearmonth' } } { order_by => { -asc => 'yearmonth' } }
); );
my @ret; my @months;
for my $row ( $res->hashes->each ) { for my $row ( $res->hashes->each ) {
my ( $year, $month ) = split( qr{[.]}, $row->{yearmonth} ); 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; return @ret;
} }

View file

@ -1,19 +1,19 @@
const CACHE_NAME = 'static-cache-v34'; const CACHE_NAME = 'static-cache-v35';
const FILES_TO_CACHE = [ const FILES_TO_CACHE = [
'/favicon.ico', '/favicon.ico',
'/offline.html', '/offline.html',
'/static/v34/css/light.min.css', '/static/v35/css/light.min.css',
'/static/v34/css/dark.min.css', '/static/v35/css/dark.min.css',
'/static/v34/css/material-icons.css', '/static/v35/css/material-icons.css',
'/static/v34/css/local.css', '/static/v35/css/local.css',
'/static/v34/fonts/MaterialIcons-Regular.woff2', '/static/v35/fonts/MaterialIcons-Regular.woff2',
'/static/v34/fonts/MaterialIcons-Regular.woff', '/static/v35/fonts/MaterialIcons-Regular.woff',
'/static/v34/fonts/MaterialIcons-Regular.ttf', '/static/v35/fonts/MaterialIcons-Regular.ttf',
'/static/v34/js/jquery-3.4.1.min.js', '/static/v35/js/jquery-3.4.1.min.js',
'/static/v34/js/materialize.min.js', '/static/v35/js/materialize.min.js',
'/static/v34/js/travelynx-actions.min.js', '/static/v35/js/travelynx-actions.min.js',
'/static/v34/js/autocomplete.min.js', '/static/v35/js/autocomplete.min.js',
'/static/v34/js/geolocation.min.js', '/static/v35/js/geolocation.min.js',
]; ];
self.addEventListener('install', (evt) => { self.addEventListener('install', (evt) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,12 +2,12 @@
font-family: 'Material Icons'; font-family: 'Material Icons';
font-style: normal; font-style: normal;
font-weight: 400; 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'), src: local('Material Icons'),
local('MaterialIcons-Regular'), local('MaterialIcons-Regular'),
url(/static/v34/fonts/MaterialIcons-Regular.woff2) format('woff2'), url(/static/v35/fonts/MaterialIcons-Regular.woff2) format('woff2'),
url(/static/v34/fonts/MaterialIcons-Regular.woff) format('woff'), url(/static/v35/fonts/MaterialIcons-Regular.woff) format('woff'),
url(/static/v34/fonts/MaterialIcons-Regular.ttf) format('truetype'); url(/static/v35/fonts/MaterialIcons-Regular.ttf) format('truetype');
} }
.material-icons { .material-icons {

View file

@ -3,27 +3,27 @@
"short_name": "Travelynx", "short_name": "Travelynx",
"scope": "/", "scope": "/",
"icons": [{ "icons": [{
"src": "/static/v34/icons/icon-128x128.png", "src": "/static/v35/icons/icon-128x128.png",
"sizes": "128x128", "sizes": "128x128",
"type": "image/png" "type": "image/png"
}, { }, {
"src": "/static/v34/icons/icon-144x144.png", "src": "/static/v35/icons/icon-144x144.png",
"sizes": "144x144", "sizes": "144x144",
"type": "image/png" "type": "image/png"
}, { }, {
"src": "/static/v34/icons/icon-152x152.png", "src": "/static/v35/icons/icon-152x152.png",
"sizes": "152x152", "sizes": "152x152",
"type": "image/png" "type": "image/png"
}, { }, {
"src": "/static/v34/icons/icon-192x192.png", "src": "/static/v35/icons/icon-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, { }, {
"src": "/static/v34/icons/icon-256x256.png", "src": "/static/v35/icons/icon-256x256.png",
"sizes": "256x256", "sizes": "256x256",
"type": "image/png" "type": "image/png"
}, { }, {
"src": "/static/v34/icons/icon-512x512.png", "src": "/static/v35/icons/icon-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
}], }],

View file

@ -29,3 +29,13 @@ a.unmarked {
} }
} }
} }
.collection-item {
color: $off-black;
.secondary-content {
color: $off-black;
}
&.disabled {
color: $inactive-color;
}
}

View file

@ -1,15 +1,20 @@
$bg-color: #101010 !default; $bg-color: #101010 !default;
$collection-bg-color: $bg-color;
$info-color: color('blue-grey', 'darken-4'); $info-color: color('blue-grey', 'darken-4');
$inactive-color: color('grey', 'darken-1');
$off-black: color('shades', 'white'); $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'); $secondary-color: color('cyan', 'darken-2');
$link-color: color('light-blue', 'darken-1'); $link-color: color('light-blue', 'darken-1');
$collection-link-color: color('cyan', 'darken-4');
$success-color: color('green', 'darken-2'); $success-color: color('green', 'darken-2');
$error-color: color('red', 'darken-2'); $error-color: color('red', 'darken-2');
$input-border-color: $off-black; $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; $radio-empty-color: $off-black !default;
$table-striped-color: color('grey', 'darken-4'); $table-striped-color: color('grey', 'darken-4');

View file

@ -2,4 +2,6 @@ $bg-color: #fff;
$info-color: color('yellow', 'lighten-4'); $info-color: color('yellow', 'lighten-4');
$link-color: color('light-blue', 'darken-1'); $link-color: color('light-blue', 'darken-1');
$card-link-color: $link-color; $card-link-color: $link-color;
$collection-link-color: color('shades', 'black');
$card-bg-color: color('blue-grey', 'lighten-5'); $card-bg-color: color('blue-grey', 'lighten-5');
$inactive-color: color('grey', 'darken-2');

View file

@ -1,12 +1,23 @@
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<ul class="pagination"> <ul class="pagination">
% for my $month (journeys->get_months(uid => current_user->{id})) { % my ($prev, $current, $next) = journeys->get_nav_months(uid => current_user->{id}, year => $year, month => $month);
% my $link_to = $month->[0]; % if ($prev) {
% my $text = $month->[1]; <li class="waves-effect waves-light"><a href="/history/<%= $prev->[0] %>"><i class="material-icons">chevron_left</i></a></li>
% my $class = $link_to eq $current ? 'active' : 'waves-effect';
<li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></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> </ul>
</div> </div>
</div> </div>

View 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>

View file

@ -1,5 +1,7 @@
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
% my @years = journeys->get_years(uid => current_user->{id});
% if (@years) {
<ul class="pagination"> <ul class="pagination">
% for my $year (journeys->get_years(uid => current_user->{id})) { % for my $year (journeys->get_years(uid => current_user->{id})) {
% my $link_to = $year->[0]; % my $link_to = $year->[0];
@ -8,5 +10,9 @@
<li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li> <li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li>
% } % }
</ul> </ul>
% }
% else {
Keine Fahrten gefunden.
% }
</div> </div>
</div> </div>

View file

@ -31,13 +31,13 @@
</div> </div>
% } % }
<h1>Account</h1>
% my $acc = current_user(); % my $acc = current_user();
% my $hook = get_webhook(); % my $hook = get_webhook();
% my $traewelling = traewelling->get($acc->{id}); % my $traewelling = traewelling->get($acc->{id});
% my $use_history = users->use_history(uid => $acc->{id}); % my $use_history = users->use_history(uid => $acc->{id});
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h2>Account</h2>
<table class="striped"> <table class="striped">
<tr> <tr>
<th scope="row">Name</th> <th scope="row">Name</th>
@ -159,10 +159,10 @@
</div> </div>
</div> </div>
<h2>API</h2>
% my $token = get_api_token(); % my $token = get_api_token();
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h2>API</h2>
<p> <p>
Die folgenden API-Token erlauben den passwortlosen automatisierten Zugriff auf Die folgenden API-Token erlauben den passwortlosen automatisierten Zugriff auf
API-Endpunkte. Bitte umsichtig behandeln sobald ein Token gesetzt API-Endpunkte. Bitte umsichtig behandeln sobald ein Token gesetzt
@ -273,10 +273,10 @@
</div> </div>
<h2>Export</h2>
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h2>Export</h2>
<ul> <ul>
<li><a href="/export.json">Rohdaten</a> (Kein API-Ersatz, das Format kann sich jederzeit ändern)</li> <li><a href="/export.json">Rohdaten</a> (Kein API-Ersatz, das Format kann sich jederzeit ändern)</li>
</ul> </ul>
@ -284,9 +284,9 @@
</div> </div>
% if (not $acc->{deletion_requested}) { % if (not $acc->{deletion_requested}) {
<h2>Löschen</h2>
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h2>Löschen</h2>
<p> <p>
Der Löschauftrag wird vorgemerkt und erst nach drei Tagen Der Löschauftrag wird vorgemerkt und erst nach drei Tagen
umgesetzt, bis dahin kann er jederzeit zurückgenommen werden. Nach umgesetzt, bis dahin kann er jederzeit zurückgenommen werden. Nach

View file

@ -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 => ''; %= 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> <h2>Auswertungen</h2>
<div class="row"> <div class="row">

View file

@ -1,6 +1,4 @@
%= include '_history_months', current => "${year}/${month}"; %= include '_history_months';
<h1><%= stash('month_name') %> <%= stash('year') %></h1>
% if (stash('statistics')) { % if (stash('statistics')) {
%= include '_history_stats', stats => stash('statistics'); %= include '_history_stats', stats => stash('statistics');

View file

@ -1,10 +1,10 @@
%= include '_history_years', current => $year; %= include '_history_years', current => $year;
<h1>Jahresrückblick <%= $year %></h1>
% if (stash('statistics')) { % if (stash('statistics')) {
%= include '_history_stats', stats => stash('statistics'); %= include '_history_stats', stats => stash('statistics');
% } % }
%
%= include '_history_months_for_year';
% if (stash('journeys')) { % if (stash('journeys')) {
%= include '_history_trains', date_format => '%d.%m.', journeys => stash('journeys'); %= include '_history_trains', date_format => '%d.%m.', journeys => stash('journeys');

View file

@ -13,7 +13,7 @@
% while (my ($key, $value) = each %{stash('opengraph') // {}}) { % while (my ($key, $value) = each %{stash('opengraph') // {}}) {
<meta property="og:<%= $key %>" content="<%= $value %>"> <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-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-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-96x96.png" sizes="96x96"> <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-96x96.png" sizes="96x96">