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} // [] +% } | 
