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 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 $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;
}

View file

@ -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) => {

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

View file

@ -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"
}],

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;
$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');

View file

@ -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');

View file

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

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="col s12">
% 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];
@ -8,5 +10,9 @@
<li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li>
% }
</ul>
% }
% else {
Keine Fahrten gefunden.
% }
</div>
</div>

View file

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

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 => '';
% 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">

View file

@ -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');

View file

@ -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');

View file

@ -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">