diff options
-rwxr-xr-x | lib/Travelynx.pm | 35 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 98 | ||||
-rw-r--r-- | templates/commute.html.ep | 89 |
3 files changed, 220 insertions, 2 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index d5dca3f..36a3998 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -2719,6 +2719,40 @@ sub startup { ); $self->helper( + 'get_top_destinations' => sub { + my ( $self, %opt ) = @_; + my $uid = $opt{uid} //= $self->current_user->{id}; + my $db = $opt{db} //= $self->pg->db; + + my @stations; + + my $res = $db->query( + qq{ + select arr_eva, count(arr_eva) as count + from journeys_str + where user_id = ? + and real_dep_ts between ? and ? + group by arr_eva + order by count + limit 5 + }, $uid, $opt{after}->epoch, $opt{before}->epoch + ); + + for my $dest ( $res->hashes->each ) { + $self->app->log->debug( $dest->{arr_eva} ); + $self->app->log->debug( $dest->{count} ); + if ( my $station + = $self->app->station_by_eva->{ $dest->{arr_eva} } ) + { + push( @stations, $station ); + } + } + + return @stations; + } + ); + + $self->helper( 'get_connection_targets' => sub { my ( $self, %opt ) = @_; @@ -4043,6 +4077,7 @@ sub startup { $authed_r->get('/export.json')->to('account#json_export'); $authed_r->get('/history.json')->to('traveling#json_history'); $authed_r->get('/history')->to('traveling#history'); + $authed_r->get('/history/commute')->to('traveling#commute'); $authed_r->get('/history/map')->to('traveling#map_history'); $authed_r->get('/history/:year')->to('traveling#yearly_history'); $authed_r->get('/history/:year/:month')->to('traveling#monthly_history'); diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index fb84f6b..bcc0090 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -104,7 +104,7 @@ sub user_status { $tw_data{description} = sprintf( '%s %s von %s nach %s', $status->{train_type}, $status->{train_line} // $status->{train_no}, - $status->{dep_name}, $status->{arr_name} // 'irgendwo' + $status->{dep_name}, $status->{arr_name} // 'irgendwo' ); if ( $status->{real_arrival}->epoch ) { $tw_data{description} .= $status->{real_arrival} @@ -439,7 +439,7 @@ sub station { @results = map { $_->[0] } sort { $b->[1] <=> $a->[1] } - map { [ $_, $_->departure->epoch // $_->sched_departure->epoch ] } + map { [ $_, $_->departure->epoch // $_->sched_departure->epoch ] } @results; if ($train) { @@ -488,6 +488,100 @@ sub history { $self->render( template => 'history' ); } +sub commute { + my ($self) = @_; + + my $year = $self->param('year'); + my $filter_type = $self->param('filter_type') || 'exact'; + my $station = $self->param('station'); + + # DateTime is very slow when looking far into the future due to DST changes + # -> Limit time range to avoid accidental DoS. + if ( + not( $year + and $year =~ m{ ^ [0-9]{4} $ }x + and $year > 1990 + and $year < 2100 ) + ) + { + $year = DateTime->now( time_zone => 'Europe/Berlin' )->year - 1; + } + my $interval_start = DateTime->new( + time_zone => 'Europe/Berlin', + year => $year, + month => 1, + day => 1, + hour => 0, + minute => 0, + second => 0, + ); + my $interval_end = $interval_start->clone->add( years => 1 ); + + if ( not $station ) { + my @top_station_ids = $self->get_top_destinations( + after => $interval_start, + before => $interval_end, + ); + if (@top_station_ids) { + $station = $top_station_ids[0][1]; + } + } + + my @journeys = $self->get_user_travels( + after => $interval_start, + before => $interval_end, + with_datetime => 1, + ); + + my %journeys_by_month; + my $total = 0; + + for my $journey ( reverse @journeys ) { + my $month = $journey->{rt_departure}->month; + if ( + $filter_type eq 'exact' + and ( $journey->{to_name} eq $station + or $journey->{from_name} eq $station ) + ) + { + push( @{ $journeys_by_month{$month} }, $journey ); + $total++; + } + elsif ( + $filter_type eq 'substring' + and ( $journey->{to_name} =~ m{\Q$station\E} + or $journey->{from_name} =~ m{\Q$station\E} ) + ) + { + push( @{ $journeys_by_month{$month} }, $journey ); + $total++; + } + elsif ( + $filter_type eq 'regex' + and ( $journey->{to_name} =~ m{$station} + or $journey->{from_name} =~ m{$station} ) + ) + { + push( @{ $journeys_by_month{$month} }, $journey ); + $total++; + } + } + + $self->param( year => $year ); + $self->param( filter_type => $filter_type ); + $self->param( station => $station ); + + $self->render( + template => 'commute', + with_autocomplete => 1, + journeys_by_month => \%journeys_by_month, + total_journeys => $total, + months => [ + qw(Januar Februar März April Mai Juni Juli August September Oktober November Dezember) + ], + ); +} + sub map_history { my ($self) = @_; diff --git a/templates/commute.html.ep b/templates/commute.html.ep new file mode 100644 index 0000000..ef1c7ab --- /dev/null +++ b/templates/commute.html.ep @@ -0,0 +1,89 @@ +<h1>Pendel-Statistiken</h1> + +<div class="row"> + <div class="col s12"> + <p>Diese Daten können zum Beispiel für die Angaben zur Pendlerpauschale + bei der Steuererklärung genutzt werden.</p> + </div> +</div> + +%= form_for '/history/commute' => begin + <div class="row"> + <div class="input-field col s12 m12 l12"> + %= text_field 'year', id => 'year', class => 'validate', pattern => '[0-9][0-9][0-9][0-9]' + <label for="year">Jahr</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12 m12 l6"> + <div> + <label> + %= radio_button filter_type => 'exact' + <span>Name der Station ist:</span> + </label> + </div> + <div> + <label> + %= radio_button filter_type => 'substring' + <span>Name der Station enthält:</span> + </label> + </div> + <div> + <label> + %= radio_button filter_type => 'regex' + <span>Name der Station erfüllt den regulären Ausdruck:</span> + </label> + </div> + </div> + <div class="input-field col s12 m12 l6"> + %= text_field 'station', id => 'station', required => undef, class => 'autocomplete contrast-color-text' + <label for="station">Fahrtziel</label> + </div> + </div> + <div class="row"> + <div class="col s12 m12 l12 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="show"> + <i class="material-icons left" aria-hidden="true">send</i> + Anzeigen + </button> + </div> + </div> +%= end + +<h2><%= param('year') %></h2> +<div class="row"> + <div class="col s12 m12 l12 center-align"> + <p> + An <b><%= $total_journeys %></b> Tagen im Jahr wurde mindestens + eine Zugfahrt von oder zu + % if (param('filter_type') eq 'exact') { + der ausgewählten Station + % } + % else { + den ausgewählten Stationen + % } + eingetragen. + </p> + <table class="striped"> + <thead> + <tr> + <th>Monat</th> + <th>Tage mit Fahrten</th> + </tr> + </thead> + <tbody> + % for my $i (0 .. $#{$months}) { + <tr> + <td><%= $months->[$i] %></td> + <td><%= scalar @{$journeys_by_month->{$i+1} // []} %></td> + </tr> + % } + </tbody> + </table> + </div> +</div> + +% for my $i (0 .. $#{$months}) { + <h2><%= $months->[$i] %></h2> + %= include '_history_trains', date_format => '%d.%m.', journeys => $journeys_by_month->{$i+1} // [] +% } |