Loading lib/Travelynx.pm +35 −0 Original line number Diff line number Diff line Loading @@ -2718,6 +2718,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 ) = @_; Loading Loading @@ -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'); Loading lib/Travelynx/Controller/Traveling.pm +96 −2 Original line number Diff line number Diff line Loading @@ -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) = @_; Loading templates/commute.html.ep 0 → 100644 +89 −0 Original line number Diff line number Diff line <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} // [] % } Loading
lib/Travelynx.pm +35 −0 Original line number Diff line number Diff line Loading @@ -2718,6 +2718,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 ) = @_; Loading Loading @@ -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'); Loading
lib/Travelynx/Controller/Traveling.pm +96 −2 Original line number Diff line number Diff line Loading @@ -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) = @_; Loading
templates/commute.html.ep 0 → 100644 +89 −0 Original line number Diff line number Diff line <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} // [] % }