Merge pull request #65 from Pear-Trading/TBSliver/Mobile-Admin-View
Admin area improvements
This commit is contained in:
commit
ede5fe1cee
21 changed files with 694 additions and 185 deletions
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
# Next Release
|
# Next Release
|
||||||
|
|
||||||
|
* **Admin Feature:** Report of transaction data graphs
|
||||||
|
* **Fix:** Mobile view meta tag for admin
|
||||||
|
* Upgrade all CSS to Bootstrap 4 beta
|
||||||
|
* **Admin Feature:** Added version number to admin console
|
||||||
|
|
||||||
# v0.9.3
|
# v0.9.3
|
||||||
|
|
||||||
* **Feature:** lat/long locations on customers and organisations
|
* **Feature:** lat/long locations on customers and organisations
|
||||||
|
|
|
@ -21,12 +21,15 @@ has schema => sub {
|
||||||
sub startup {
|
sub startup {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
|
my $version = `git describe --tags`;
|
||||||
|
|
||||||
$self->plugin('Config', {
|
$self->plugin('Config', {
|
||||||
default => {
|
default => {
|
||||||
storage_path => tempdir,
|
storage_path => tempdir,
|
||||||
sessionTimeSeconds => 60 * 60 * 24 * 7,
|
sessionTimeSeconds => 60 * 60 * 24 * 7,
|
||||||
sessionTokenJsonName => 'session_key',
|
sessionTokenJsonName => 'session_key',
|
||||||
sessionExpiresJsonName => 'sessionExpires',
|
sessionExpiresJsonName => 'sessionExpires',
|
||||||
|
version => $version,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
my $config = $self->config;
|
my $config = $self->config;
|
||||||
|
@ -36,6 +39,7 @@ sub startup {
|
||||||
$self->plugin('Pear::LocalLoop::Plugin::BootstrapPagination', { bootstrap4 => 1 } );
|
$self->plugin('Pear::LocalLoop::Plugin::BootstrapPagination', { bootstrap4 => 1 } );
|
||||||
$self->plugin('Pear::LocalLoop::Plugin::Validators');
|
$self->plugin('Pear::LocalLoop::Plugin::Validators');
|
||||||
$self->plugin('Pear::LocalLoop::Plugin::Datetime');
|
$self->plugin('Pear::LocalLoop::Plugin::Datetime');
|
||||||
|
$self->plugin('Pear::LocalLoop::Plugin::TemplateHelpers');
|
||||||
|
|
||||||
$self->plugin('Authentication' => {
|
$self->plugin('Authentication' => {
|
||||||
'load_user' => sub {
|
'load_user' => sub {
|
||||||
|
@ -192,6 +196,8 @@ sub startup {
|
||||||
$admin_routes->get('/transactions/:id/image')->to('admin-transactions#image');
|
$admin_routes->get('/transactions/:id/image')->to('admin-transactions#image');
|
||||||
$admin_routes->post('/transactions/:id/delete')->to('admin-transactions#delete');
|
$admin_routes->post('/transactions/:id/delete')->to('admin-transactions#delete');
|
||||||
|
|
||||||
|
$admin_routes->get('/reports/transactions')->to('admin-reports#transaction_data');
|
||||||
|
|
||||||
# my $user_routes = $r->under('/')->to('root#under');
|
# my $user_routes = $r->under('/')->to('root#under');
|
||||||
|
|
||||||
# $user_routes->get('/home')->to('root#home');
|
# $user_routes->get('/home')->to('root#home');
|
||||||
|
|
|
@ -7,7 +7,7 @@ sub under {
|
||||||
if ( $c->is_user_authenticated ) {
|
if ( $c->is_user_authenticated ) {
|
||||||
return 1 if $c->current_user->is_admin;
|
return 1 if $c->current_user->is_admin;
|
||||||
}
|
}
|
||||||
$c->redirect_to('/');
|
$c->redirect_to('/admin');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,15 @@ has result_set => sub {
|
||||||
sub index {
|
sub index {
|
||||||
my $c = shift;
|
my $c = shift;
|
||||||
|
|
||||||
my $feedback_rs = $c->result_set;
|
my $feedback_rs = $c->result_set->search(
|
||||||
$c->stash( feedbacks => [ $feedback_rs->all ] );
|
undef,
|
||||||
|
{
|
||||||
|
page => $c->param('page') || 1,
|
||||||
|
rows => 12,
|
||||||
|
order_by => { -desc => 'submitted_at' },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
$c->stash( feedback_rs => $feedback_rs );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub read {
|
sub read {
|
||||||
|
|
80
lib/Pear/LocalLoop/Controller/Admin/Reports.pm
Normal file
80
lib/Pear/LocalLoop/Controller/Admin/Reports.pm
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package Pear::LocalLoop::Controller::Admin::Reports;
|
||||||
|
use Mojo::Base 'Mojolicious::Controller';
|
||||||
|
|
||||||
|
use Mojo::JSON qw/ encode_json /;
|
||||||
|
|
||||||
|
sub transaction_data {
|
||||||
|
my $c = shift;
|
||||||
|
|
||||||
|
my $quantised_column = 'quantised_hours';
|
||||||
|
if ( defined $c->param('scale') && $c->param('scale') eq 'days' ) {
|
||||||
|
$quantised_column = 'quantised_days';
|
||||||
|
}
|
||||||
|
|
||||||
|
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||||
|
my $transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
columns => [
|
||||||
|
{
|
||||||
|
quantised => $quantised_column,
|
||||||
|
count => \"COUNT(*)",
|
||||||
|
sum_distance => $c->pg_or_sqlite(
|
||||||
|
'SUM("me"."distance")',
|
||||||
|
'SUM("me"."distance")',
|
||||||
|
),
|
||||||
|
average_distance => $c->pg_or_sqlite(
|
||||||
|
'AVG("me"."distance")',
|
||||||
|
'AVG("me"."distance")',
|
||||||
|
),
|
||||||
|
sum_value => $c->pg_or_sqlite(
|
||||||
|
'SUM("me"."value")',
|
||||||
|
'SUM("me"."value")',
|
||||||
|
),
|
||||||
|
average_value => $c->pg_or_sqlite(
|
||||||
|
'AVG("me"."value")',
|
||||||
|
'AVG("me"."value")',
|
||||||
|
),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
group_by => $quantised_column,
|
||||||
|
order_by => { '-asc' => $quantised_column },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
my $transaction_data = [
|
||||||
|
map{
|
||||||
|
my $quantised = $c->db_datetime_parser->parse_datetime($_->get_column('quantised'));
|
||||||
|
{
|
||||||
|
sum_value => ($_->get_column('sum_value') || 0) * 1,
|
||||||
|
sum_distance => ($_->get_column('sum_distance') || 0) * 1,
|
||||||
|
average_value => ($_->get_column('average_value') || 0) * 1,
|
||||||
|
average_distance => ($_->get_column('average_distance') || 0) * 1,
|
||||||
|
count => $_->get_column('count'),
|
||||||
|
quantised => $c->format_iso_datetime($quantised),
|
||||||
|
}
|
||||||
|
} $transaction_rs->all
|
||||||
|
];
|
||||||
|
|
||||||
|
$c->respond_to(
|
||||||
|
json => { json => { data => $transaction_data } },
|
||||||
|
html => { transaction_rs => encode_json( $transaction_data ) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub pg_or_sqlite {
|
||||||
|
my ( $c, $pg_sql, $sqlite_sql ) = @_;
|
||||||
|
|
||||||
|
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||||
|
|
||||||
|
if ( $driver eq 'Pg' ) {
|
||||||
|
return \$pg_sql;
|
||||||
|
} elsif ( $driver eq 'SQLite' ) {
|
||||||
|
return \$sqlite_sql;
|
||||||
|
} else {
|
||||||
|
$c->app->log->warn('Unknown Driver Used');
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
|
@ -6,6 +6,17 @@ use DateTime::Format::Strptime;
|
||||||
sub register {
|
sub register {
|
||||||
my ( $plugin, $app, $conf ) = @_;
|
my ( $plugin, $app, $conf ) = @_;
|
||||||
|
|
||||||
|
$app->helper( human_datetime_parser => sub {
|
||||||
|
return DateTime::Format::Strptime->new( pattern => '%x %X' );
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->helper( format_human_datetime => sub {
|
||||||
|
my ( $c, $datetime_obj ) = @_;
|
||||||
|
return $c->human_datetime_parser->format_datetime(
|
||||||
|
$datetime_obj,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$app->helper( iso_datetime_parser => sub {
|
$app->helper( iso_datetime_parser => sub {
|
||||||
return DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S.%3N%z' );
|
return DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S.%3N%z' );
|
||||||
});
|
});
|
||||||
|
|
18
lib/Pear/LocalLoop/Plugin/TemplateHelpers.pm
Normal file
18
lib/Pear/LocalLoop/Plugin/TemplateHelpers.pm
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package Pear::LocalLoop::Plugin::TemplateHelpers;
|
||||||
|
use Mojo::Base 'Mojolicious::Plugin';
|
||||||
|
|
||||||
|
sub register {
|
||||||
|
my ( $plugin, $app, $conf ) = @_;
|
||||||
|
|
||||||
|
$app->helper( truncate_text => sub {
|
||||||
|
my ( $c, $string, $length ) = @_;
|
||||||
|
if ( length $string < $length ) {
|
||||||
|
return $string;
|
||||||
|
} else {
|
||||||
|
return substr( $string, 0, $length - 3 ) . '...';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
|
@ -0,0 +1,21 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::ViewQuantisedTransactionPg;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||||
|
__PACKAGE__->table('view_quantised_transactions');
|
||||||
|
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||||
|
|
||||||
|
__PACKAGE__->result_source_instance->view_definition( qq/
|
||||||
|
SELECT "value",
|
||||||
|
"distance",
|
||||||
|
"purchase_time",
|
||||||
|
DATE_TRUNC('hour', "purchase_time") AS "quantised_hours",
|
||||||
|
DATE_TRUNC('day', "purchase_time") AS "quantised_days"
|
||||||
|
FROM "transactions"
|
||||||
|
/);
|
||||||
|
|
||||||
|
1;
|
|
@ -0,0 +1,21 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::ViewQuantisedTransactionSQLite;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||||
|
__PACKAGE__->table('view_quantised_transactions');
|
||||||
|
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||||
|
|
||||||
|
__PACKAGE__->result_source_instance->view_definition( qq/
|
||||||
|
SELECT "value",
|
||||||
|
"distance",
|
||||||
|
"purchase_time",
|
||||||
|
DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"purchase_time")) AS "quantised_hours",
|
||||||
|
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time")) AS "quantised_days"
|
||||||
|
FROM "transactions"
|
||||||
|
/);
|
||||||
|
|
||||||
|
1;
|
|
@ -1,6 +1,7 @@
|
||||||
body {
|
body {
|
||||||
background: whitesmoke;
|
background: whitesmoke;
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
|
padding-bottom: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
|
|
141
t/admin/reports/transactions.t
Normal file
141
t/admin/reports/transactions.t
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
use Mojo::Base -strict;
|
||||||
|
|
||||||
|
use FindBin qw/ $Bin /;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
use Test::Pear::LocalLoop;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
my $framework = Test::Pear::LocalLoop->new(
|
||||||
|
etc_dir => "$Bin/../../etc",
|
||||||
|
);
|
||||||
|
$framework->install_fixtures('full');
|
||||||
|
my $t = $framework->framework;
|
||||||
|
my $schema = $t->app->schema;
|
||||||
|
|
||||||
|
my $dt_today = DateTime->today;
|
||||||
|
my $dt_start = $dt_today->clone->subtract( 'minutes' => 30 );
|
||||||
|
|
||||||
|
use Devel::Dwarn;
|
||||||
|
|
||||||
|
my $session_key = $framework->login({
|
||||||
|
email => 'test1@example.com',
|
||||||
|
password => 'abc123',
|
||||||
|
});
|
||||||
|
|
||||||
|
sub create_transaction {
|
||||||
|
my ( $value, $time ) = @_;
|
||||||
|
$t->ua->post('/api/upload' => json => {
|
||||||
|
transaction_value => $value,
|
||||||
|
transaction_type => 1,
|
||||||
|
purchase_time => $time,
|
||||||
|
organisation_id => 1,
|
||||||
|
session_key => $session_key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
my $expected_days = {};
|
||||||
|
my $expected_hours = {};
|
||||||
|
|
||||||
|
sub increment_day {
|
||||||
|
my ( $value, $day, $distance ) = @_;
|
||||||
|
$value *= 100000;
|
||||||
|
$distance //= 0;
|
||||||
|
$expected_days->{$day} = {
|
||||||
|
quantised => $day,
|
||||||
|
sum_value => ($expected_days->{$day}->{sum_value} || 0) + $value,
|
||||||
|
sum_distance => ($expected_days->{$day}->{sum_distance} || 0) + $distance,
|
||||||
|
count => ++$expected_days->{$day}->{count},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub increment_hour {
|
||||||
|
my ( $value, $day, $distance ) = @_;
|
||||||
|
$value *= 100000;
|
||||||
|
$distance //= 0;
|
||||||
|
$expected_hours->{$day} = {
|
||||||
|
quantised => $day,
|
||||||
|
sum_value => ($expected_hours->{$day}->{sum_value} || 0) + $value,
|
||||||
|
sum_distance => ($expected_hours->{$day}->{sum_distance} || 0) + $distance,
|
||||||
|
count => ++$expected_hours->{$day}->{count},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $i ( 0 .. 48 ) {
|
||||||
|
my $dt = $dt_start->clone->subtract( 'minutes' => 60 * $i );
|
||||||
|
my $purchase_time = $t->app->format_iso_datetime($dt);
|
||||||
|
my $quantised_day = $t->app->format_iso_datetime($dt->clone->truncate(to => 'day'));
|
||||||
|
my $quantised_hour = $t->app->format_iso_datetime($dt->clone->truncate(to => 'hour'));
|
||||||
|
create_transaction(10, $purchase_time);
|
||||||
|
increment_day(10, $quantised_day);
|
||||||
|
increment_hour(10, $quantised_hour);
|
||||||
|
if ( $i % 2 == 0 ) {
|
||||||
|
create_transaction(20, $purchase_time);
|
||||||
|
increment_day(20, $quantised_day);
|
||||||
|
increment_hour(20, $quantised_hour);
|
||||||
|
}
|
||||||
|
if ( $i % 3 == 0 ) {
|
||||||
|
create_transaction(30, $purchase_time);
|
||||||
|
increment_day(30, $quantised_day);
|
||||||
|
increment_hour(30, $quantised_hour);
|
||||||
|
}
|
||||||
|
if ( $i % 5 == 0 ) {
|
||||||
|
create_transaction(50, $purchase_time);
|
||||||
|
increment_day(50, $quantised_day);
|
||||||
|
increment_hour(50, $quantised_hour);
|
||||||
|
}
|
||||||
|
if ( $i % 7 == 0 ) {
|
||||||
|
create_transaction(70, $purchase_time);
|
||||||
|
increment_day(70, $quantised_day);
|
||||||
|
increment_hour(70, $quantised_hour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $expected_days_array = [ map {
|
||||||
|
my $data = $expected_days->{$_};
|
||||||
|
{
|
||||||
|
quantised => $data->{quantised},
|
||||||
|
count => $data->{count},
|
||||||
|
sum_value => $data->{sum_value},
|
||||||
|
sum_distance => $data->{sum_distance},
|
||||||
|
average_value => $data->{sum_value} / $data->{count},
|
||||||
|
average_distance => $data->{sum_distance} / $data->{count},
|
||||||
|
}
|
||||||
|
} sort keys %$expected_days ];
|
||||||
|
|
||||||
|
my $expected_hours_array = [ map {
|
||||||
|
my $data = $expected_hours->{$_};
|
||||||
|
{
|
||||||
|
quantised => $data->{quantised},
|
||||||
|
count => $data->{count},
|
||||||
|
sum_value => $data->{sum_value},
|
||||||
|
sum_distance => $data->{sum_distance},
|
||||||
|
average_value => $data->{sum_value} / $data->{count},
|
||||||
|
average_distance => $data->{sum_distance} / $data->{count},
|
||||||
|
}
|
||||||
|
} sort keys %$expected_hours ];
|
||||||
|
|
||||||
|
is $t->app->schema->resultset('Transaction')->count, 108, 'Transactions created';
|
||||||
|
|
||||||
|
#login to admin
|
||||||
|
$t->post_ok('/admin', form => {
|
||||||
|
email => 'admin@example.com',
|
||||||
|
password => 'abc123',
|
||||||
|
})->status_is(302);
|
||||||
|
|
||||||
|
$t->get_ok(
|
||||||
|
'/admin/reports/transactions',
|
||||||
|
{ Accept => 'application/json' }
|
||||||
|
)
|
||||||
|
->status_is(200)
|
||||||
|
->json_is('/data', $expected_hours_array)->or($framework->dump_error);
|
||||||
|
|
||||||
|
$t->get_ok(
|
||||||
|
'/admin/reports/transactions',
|
||||||
|
{ Accept => 'application/json' },
|
||||||
|
form => { scale => 'days' }
|
||||||
|
)
|
||||||
|
->status_is(200)
|
||||||
|
->json_is('/data', $expected_days_array)->or($framework->dump_error);
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -11,12 +11,30 @@
|
||||||
<strong>Success!</strong> <%= $success %>
|
<strong>Success!</strong> <%= $success %>
|
||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
<div class="list-group">
|
<div class="row">
|
||||||
% for my $feedback (@$feedbacks) {
|
% for my $feedback ( $feedback_rs->all ) {
|
||||||
<a href="<%= url_for . '/' . $feedback->id %>" class="list-group-item list-group-item-action">
|
<div class="col col-md-4 mb-3">
|
||||||
<div>
|
<div class="card">
|
||||||
<%= $feedback->user->email %> <%= $feedback->submitted_at %>
|
<div class="card-header">
|
||||||
|
%= $feedback->user->email;
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title">
|
||||||
|
%= format_human_datetime $feedback->submitted_at;
|
||||||
|
</h6>
|
||||||
|
<pre class="card-text"><%= truncate_text $feedback->feedbacktext => 50; %></pre>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-right">
|
||||||
|
<a href="<%= url_for . '/' . $feedback->id %>" class="card-link">
|
||||||
|
More info
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
% }
|
% }
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
%= bootstrap_pagination( $c->param('page') || 1, $feedback_rs->pager->last_page, { class => 'justify-content-center' } );
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -11,33 +11,59 @@
|
||||||
<strong>Success!</strong> <%= $success %>
|
<strong>Success!</strong> <%= $success %>
|
||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
<form action="<%= url_for %>" method="post">
|
<div class="row">
|
||||||
<div class="form-group">
|
<div class="col col-md-6 mb-3">
|
||||||
<label for="email">Email Address</label>
|
<div class="card">
|
||||||
<input id="email" type="text" class="form-control" placeholder="Email" name="email" value="<%= $feedback->user->email %>" disabled>
|
<h4 class="card-header">
|
||||||
|
User Info
|
||||||
|
</h4>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item justify-content-between">
|
||||||
|
<span>Email Address:</span>
|
||||||
|
<span><%= $feedback->user->email %></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item justify-content-between">
|
||||||
|
<span>Submitted At:</span>
|
||||||
|
<span><%= format_human_datetime $feedback->submitted_at %></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="joindate">Submitted At</label>
|
|
||||||
<input id="submitted_at" type="datetime" class="form-control" placeholder="Date" name="submitted_at" value="<%= $feedback->submitted_at %>" disabled>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="col col-md-6 mb-3">
|
||||||
<label for="feedback">Feedback</label>
|
<div class="card">
|
||||||
<input id="feedback" type="text" class="form-control" placeholder="Feedback" name="feedback" value="<%= $feedback->feedbacktext %>" disabled>
|
<h4 class="card-header">
|
||||||
|
Feedback Message
|
||||||
|
</h4>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-text">
|
||||||
|
<pre><%= $feedback->feedbacktext %></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="app_name">App Name</label>
|
|
||||||
<input id="app_name" type="text" class="form-control" placeholder="App Name" name="app_name" value="<%= $feedback->app_name %>" disabled>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="package_name">Package Name</label>
|
|
||||||
<input id="package_name" type="text" class="form-control" placeholder="Package Name" name="package_name" value="<%= $feedback->package_name %>" disabled>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="version_code">Version Code</label>
|
|
||||||
<input id="version_code" type="text" class="form-control" placeholder="Version Code" name="version_code" value="<%= $feedback->version_code %>" disabled>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="col col-md-6 mb-3">
|
||||||
<label for="version_number">Version Number</label>
|
<div class="card">
|
||||||
<input id="version_number" type="text" class="form-control" placeholder="Version Number" name="feedback" value="<%= $feedback->version_number %>" disabled>
|
<h4 class="card-header">
|
||||||
|
Debug Info
|
||||||
|
</h4>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item justify-content-between">
|
||||||
|
<span>App Name:</span>
|
||||||
|
<span><%= $feedback->app_name %></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item justify-content-between">
|
||||||
|
<span>Package Name:</span>
|
||||||
|
<span><%= $feedback->package_name %></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item justify-content-between">
|
||||||
|
<span>Version Code:</span>
|
||||||
|
<span><%= $feedback->version_code %></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item justify-content-between">
|
||||||
|
<span>Version Number:</span>
|
||||||
|
<span><%= $feedback->version_number %></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -3,41 +3,41 @@
|
||||||
% content_for javascript => begin
|
% content_for javascript => begin
|
||||||
% end
|
% end
|
||||||
<div class="card-deck">
|
<div class="card-deck">
|
||||||
<div class="card text-center">
|
<div class="card text-center text-white bg-primary">
|
||||||
<div class="card-header card-inverse card-primary">
|
<div class="card-header card-inverse">
|
||||||
User Count
|
User Count
|
||||||
</div>
|
</div>
|
||||||
<div class="card-block">
|
<div class="card-body">
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">
|
||||||
%= $user_count
|
%= $user_count
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card text-center">
|
<div class="card text-center text-white bg-success">
|
||||||
<div class="card-header card-inverse card-success">
|
<div class="card-header card-inverse">
|
||||||
Unused Tokens
|
Unused Tokens
|
||||||
</div>
|
</div>
|
||||||
<div class="card-block">
|
<div class="card-body">
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">
|
||||||
<%= $tokens->{unused} %> / <%= $tokens->{total} %>
|
<%= $tokens->{unused} %> / <%= $tokens->{total} %>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card text-center">
|
<div class="card text-center text-white bg-danger">
|
||||||
<div class="card-header card-inverse card-danger">
|
<div class="card-header card-inverse">
|
||||||
Pending Organisations
|
Pending Organisations
|
||||||
</div>
|
</div>
|
||||||
<div class="card-block">
|
<div class="card-body">
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">
|
||||||
%= $pending_orgs
|
%= $pending_orgs
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card text-center">
|
<div class="card text-center text-white bg-danger">
|
||||||
<div class="card-header card-inverse card-danger">
|
<div class="card-header card-inverse">
|
||||||
Pending Transactions
|
Pending Transactions
|
||||||
</div>
|
</div>
|
||||||
<div class="card-block">
|
<div class="card-body">
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">
|
||||||
%= $pending_trans
|
%= $pending_trans
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
@ -36,7 +36,7 @@ function initMap() {
|
||||||
<h3 class="card-header">
|
<h3 class="card-header">
|
||||||
%= $valid_org->name
|
%= $valid_org->name
|
||||||
</h3>
|
</h3>
|
||||||
<div class="card-block">
|
<div class="card-body">
|
||||||
<form action="<%= url_for %>" method="post">
|
<form action="<%= url_for %>" method="post">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="name" class="col-md-4 col-form-label">Organisation Name</label>
|
<label for="name" class="col-md-4 col-form-label">Organisation Name</label>
|
||||||
|
@ -93,23 +93,17 @@ function initMap() {
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div id="mapBody" role="tabpanel">
|
<div id="mapBody" role="tabpanel">
|
||||||
<div class="card-block">
|
|
||||||
<!-- Yes this is nasty. no i dont care. --!>
|
|
||||||
<style> #map { width: 100%; height: 400px; background-color: grey; } </style>
|
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h3 class="card-header">
|
<h3 class="card-header">
|
||||||
Transactions
|
Transactions
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
% for my $transaction ( $transactions->all ) {
|
% for my $transaction ( $transactions->all ) {
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="container">
|
|
||||||
<a href="<%= url_for '/admin/transactions/' . $transaction->id %>" class="list-group-item list-group-item-action">
|
<a href="<%= url_for '/admin/transactions/' . $transaction->id %>" class="list-group-item list-group-item-action">
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<div class="col">From: <%= $transaction->buyer->name %></div>
|
<div class="col">From: <%= $transaction->buyer->name %></div>
|
||||||
|
@ -119,15 +113,11 @@ function initMap() {
|
||||||
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
|
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
% }
|
% }
|
||||||
<li class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="container">
|
|
||||||
%= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } );
|
%= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } );
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
140
templates/admin/reports/transaction_data.html.ep
Normal file
140
templates/admin/reports/transaction_data.html.ep
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
% layout 'admin';
|
||||||
|
% title 'Transaction Report';
|
||||||
|
% content_for javascript => begin
|
||||||
|
<script src="//www.gstatic.com/charts/loader.js"></script>
|
||||||
|
<script>
|
||||||
|
var raw_data = <%== $transaction_rs %>;
|
||||||
|
var mapped_data = $.map(raw_data, function( val, i ) {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
new Date(val.quantised),
|
||||||
|
val.count,
|
||||||
|
val.average_value / 100000,
|
||||||
|
val.sum_value / 100000,
|
||||||
|
val.average_distance / 1000,
|
||||||
|
val.sum_distance / 1000,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
google.charts.load('current', {packages: ['corechart']});
|
||||||
|
google.charts.setOnLoadCallback(loadData);
|
||||||
|
|
||||||
|
function loadData() {
|
||||||
|
var data = new google.visualization.DataTable();
|
||||||
|
data.addColumn('datetime', 'Hours');
|
||||||
|
data.addColumn('number', 'Count');
|
||||||
|
data.addColumn('number', 'Average Value');
|
||||||
|
data.addColumn('number', 'Sum Value');
|
||||||
|
data.addColumn('number', 'Average Distance');
|
||||||
|
data.addColumn('number', 'Sum Distance');
|
||||||
|
|
||||||
|
data.addRows(mapped_data);
|
||||||
|
|
||||||
|
drawCountChart(data);
|
||||||
|
drawDistanceChart(data);
|
||||||
|
drawValueChart(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCountChart(data) {
|
||||||
|
var options = {
|
||||||
|
title: 'Transaction Count',
|
||||||
|
height: 500,
|
||||||
|
series: {
|
||||||
|
0: {targetAxisIndex: 0},
|
||||||
|
},
|
||||||
|
vAxes: {
|
||||||
|
0: { title: 'Count' },
|
||||||
|
},
|
||||||
|
explorer: { axis: 'horizontal' }
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart_data = new google.visualization.DataView(data);
|
||||||
|
chart_data.setColumns([0, 1]);
|
||||||
|
|
||||||
|
var chart = new google.visualization.LineChart(document.getElementById('count_chart_div'));
|
||||||
|
chart.draw(chart_data, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDistanceChart(data) {
|
||||||
|
var options = {
|
||||||
|
title: 'Transaction Distance',
|
||||||
|
height: 500,
|
||||||
|
series: {
|
||||||
|
0: {targetAxisIndex: 0},
|
||||||
|
1: {targetAxisIndex: 0},
|
||||||
|
},
|
||||||
|
vAxes: {
|
||||||
|
0: { title: 'Distance (km)' },
|
||||||
|
},
|
||||||
|
explorer: { axis: 'horizontal' }
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart_data = new google.visualization.DataView(data);
|
||||||
|
chart_data.setColumns([0, 4, 5]);
|
||||||
|
|
||||||
|
var chart = new google.visualization.LineChart(document.getElementById('distance_chart_div'));
|
||||||
|
chart.draw(chart_data, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawValueChart(data) {
|
||||||
|
var options = {
|
||||||
|
title: 'Transaction Value',
|
||||||
|
height: 500,
|
||||||
|
series: {
|
||||||
|
0: {targetAxisIndex: 0},
|
||||||
|
1: {targetAxisIndex: 0},
|
||||||
|
},
|
||||||
|
vAxes: {
|
||||||
|
0: { title: 'Value (GBP)' },
|
||||||
|
},
|
||||||
|
explorer: { axis: 'horizontal' }
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart_data = new google.visualization.DataView(data);
|
||||||
|
chart_data.setColumns([0, 2, 3]);
|
||||||
|
|
||||||
|
var chart = new google.visualization.LineChart(document.getElementById('value_chart_div'));
|
||||||
|
chart.draw(chart_data, options);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
% end
|
||||||
|
% if ( my $error = flash 'error' ) {
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<strong>Error!</strong> <%= $error %>
|
||||||
|
</div>
|
||||||
|
% } elsif ( my $success = flash 'success' ) {
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<strong>Success!</strong> <%= $success %>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
<div class="card mb-3 text-center">
|
||||||
|
<div class="card-header">
|
||||||
|
Transaction Count
|
||||||
|
</div>
|
||||||
|
<div id="count_chart_div" class="card-body">
|
||||||
|
<div class="card-text">
|
||||||
|
<em>Loading...</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-3 text-center">
|
||||||
|
<div class="card-header">
|
||||||
|
Transaction Distance
|
||||||
|
</div>
|
||||||
|
<div id="distance_chart_div" class="card-body">
|
||||||
|
<div class="card-text">
|
||||||
|
<em>Loading...</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-3 text-center">
|
||||||
|
<div class="card-header">
|
||||||
|
Transaction Value
|
||||||
|
</div>
|
||||||
|
<div id="value_chart_div" class="card-body">
|
||||||
|
<div class="card-text">
|
||||||
|
<em>Loading...</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -23,13 +23,9 @@
|
||||||
</form>
|
</form>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
% for my $token (@$tokens) {
|
% for my $token (@$tokens) {
|
||||||
<a href="<%= url_for . '/' . $token->{id} %>" class="list-group-item list-group-item-action">
|
<a href="<%= url_for . '/' . $token->{id} %>" class="list-group-item list-group-item-action d-flex justify-content-between <%= $token->{used} ? 'disabled' : 'list-group-item-success' %>">
|
||||||
<div>
|
<span><%= $token->{name} %></span>
|
||||||
%= $token->{name}
|
<span><%= $token->{used} == 1 ? 'Used' : 'Available' %></span>
|
||||||
</div>
|
|
||||||
<div class="ml-auto">
|
|
||||||
<%= $token->{used} == 1 ? 'Used' : 'Available' %>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
% }
|
% }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,10 +11,8 @@
|
||||||
<strong>Success!</strong> <%= $success %>
|
<strong>Success!</strong> <%= $success %>
|
||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group">
|
||||||
% for my $transaction ( $transactions->all ) {
|
% for my $transaction ( $transactions->all ) {
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="container">
|
|
||||||
<a href="<%= url_for . '/' . $transaction->id %>" class="list-group-item list-group-item-action">
|
<a href="<%= url_for . '/' . $transaction->id %>" class="list-group-item list-group-item-action">
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<div class="col">From: <%= $transaction->buyer->name %></div>
|
<div class="col">From: <%= $transaction->buyer->name %></div>
|
||||||
|
@ -24,12 +22,8 @@
|
||||||
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
|
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
% }
|
% }
|
||||||
<li class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="container">
|
|
||||||
%= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } );
|
%= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } );
|
||||||
</div>
|
</div>
|
||||||
</li>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,15 +12,15 @@
|
||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h3 class="card-header">
|
<h3 class="card-header d-flex justify-content-between">
|
||||||
Transaction Details
|
Transaction Details
|
||||||
<form action="<%= url_for . '/delete' %>" method="post">
|
<form class="form-inline" action="<%= url_for . '/delete' %>" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button class="btn btn-danger" type="submit" style="float: left">Delete Transaction</button>
|
<button class="btn btn-danger" type="submit">Delete Transaction</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="card-block">
|
<div class="card-body">
|
||||||
<form>
|
<form>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Buyer</label>
|
<label for="email">Buyer</label>
|
||||||
|
@ -42,8 +42,8 @@
|
||||||
<label for="email">Purchase Time</label>
|
<label for="email">Purchase Time</label>
|
||||||
<input id="purchase_time" type="text" class="form-control" placeholder="Purchase Time" name="purchase_time" value="<%= $transaction->purchase_time %>" disabled>
|
<input id="purchase_time" type="text" class="form-control" placeholder="Purchase Time" name="purchase_time" value="<%= $transaction->purchase_time %>" disabled>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group d-flex justify-content-center">
|
||||||
<img src="<%= url_for . '/image' %>"/>
|
<img class="mw-100" src="<%= url_for . '/image' %>"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,9 +12,11 @@
|
||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
<form action="<%= url_for %>" method="post" autocomplete="off">
|
<form action="<%= url_for %>" method="post" autocomplete="off">
|
||||||
|
<div class="card mb-3">
|
||||||
<h3 class="card-header">
|
<h3 class="card-header">
|
||||||
User Details
|
User Details
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email Address</label>
|
<label for="email">Email Address</label>
|
||||||
<input id="email" type="text" autocomplete="off" class="form-control" placeholder="Email" name="email" value="<%= $user->email %>">
|
<input id="email" type="text" autocomplete="off" class="form-control" placeholder="Email" name="email" value="<%= $user->email %>">
|
||||||
|
@ -32,10 +34,14 @@
|
||||||
<input id="new_password" type="password" autocomplete="off" class="form-control" placeholder="New Password" name="new_password">
|
<input id="new_password" type="password" autocomplete="off" class="form-control" placeholder="New Password" name="new_password">
|
||||||
<p class="help-block">Leave blank unless you want to change their password</p>
|
<p class="help-block">Leave blank unless you want to change their password</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-3">
|
||||||
% if ( my $customer_rs = $user->entity->customer ) {
|
% if ( my $customer_rs = $user->entity->customer ) {
|
||||||
<h3 class="card-header">
|
<h3 class="card-header">
|
||||||
Customer Details
|
Customer Details
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="postcode">Customer Postcode</label>
|
<label for="postcode">Customer Postcode</label>
|
||||||
<input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $customer_rs->postcode %>">
|
<input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $customer_rs->postcode %>">
|
||||||
|
@ -52,10 +58,12 @@
|
||||||
<label for="year_of_birth">Year of Birth</label>
|
<label for="year_of_birth">Year of Birth</label>
|
||||||
<input id="year_of_birth" type="number" class="form-control" placeholder="Year of Birth" name="year_of_birth" value="<%= $customer_rs->year_of_birth %>" disabled>
|
<input id="year_of_birth" type="number" class="form-control" placeholder="Year of Birth" name="year_of_birth" value="<%= $customer_rs->year_of_birth %>" disabled>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
% } elsif ( my $org_rs = $user->entity->organisation ) {
|
% } elsif ( my $org_rs = $user->entity->organisation ) {
|
||||||
<h3 class="card-header">
|
<h3 class="card-header">
|
||||||
Organisation Details
|
Organisation Details
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="postcode">Organisation Postcode</label>
|
<label for="postcode">Organisation Postcode</label>
|
||||||
<input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $org_rs->postcode %>">
|
<input id="postcode" type="text" class="form-control" placeholder="Postcode" name="postcode" value="<%= $org_rs->postcode %>">
|
||||||
|
@ -76,12 +84,16 @@
|
||||||
<label for="town">Town</label>
|
<label for="town">Town</label>
|
||||||
<input id="town" type="sector" class="form-control" placeholder="Sector Area Code" name="sector" value="<%= $org_rs->sector %>">
|
<input id="town" type="sector" class="form-control" placeholder="Sector Area Code" name="sector" value="<%= $org_rs->sector %>">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
% } else {
|
% } else {
|
||||||
<h3 class="card-header">
|
<h3 class="card-header text-white bg-danger">
|
||||||
User is not a customer or an organisation
|
Warning!
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="card-body">
|
||||||
|
User is not a customer or an organisation
|
||||||
|
</div>
|
||||||
% }
|
% }
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button class="btn btn-primary form-control" type="submit">Edit Account</button>
|
<button class="btn btn-primary form-control" type="submit">Edit Account</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,10 +102,8 @@
|
||||||
<h3 class="card-header">
|
<h3 class="card-header">
|
||||||
Transactions
|
Transactions
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
% for my $transaction ( $transactions->all ) {
|
% for my $transaction ( $transactions->all ) {
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="container">
|
|
||||||
<a href="<%= url_for '/admin/transactions/' . $transaction->id %>" class="list-group-item list-group-item-action">
|
<a href="<%= url_for '/admin/transactions/' . $transaction->id %>" class="list-group-item list-group-item-action">
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<div class="col">From: <%= $transaction->buyer->name %></div>
|
<div class="col">From: <%= $transaction->buyer->name %></div>
|
||||||
|
@ -103,13 +113,9 @@
|
||||||
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
|
<div class="col">Purchase Time: <%= $transaction->purchase_time %></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
% }
|
% }
|
||||||
<li class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="container">
|
|
||||||
%= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } );
|
%= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } );
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,19 +2,18 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>LocalLoop Admin - <%= title %></title>
|
<title>LocalLoop Admin - <%= title %></title>
|
||||||
|
|
||||||
<!-- Bootstrap and jQuery js -->
|
<!-- Bootstrap and jQuery js -->
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
%= stylesheet '/static/admin/css/main.css';
|
%= stylesheet '/static/admin/css/main.css';
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-toggleable-md fixed-top navbar-inverse bg-danger">
|
<nav class="navbar navbar-expand-md fixed-top navbar-dark bg-danger">
|
||||||
|
<a class="navbar-brand" href="<%= url_for '/admin/home' %>">LocalLoop Admin</a>
|
||||||
<button class="navbar-toggler navbar-toggler-right"
|
<button class="navbar-toggler navbar-toggler-right"
|
||||||
type="button"
|
type="button"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
|
@ -25,21 +24,50 @@
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a class="navbar-brand" href="<%= url_for '/admin/home' %>">LocalLoop Admin</a>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<div class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<a class="nav-item nav-link<%= title eq 'Feedback' ? ' active' : '' %>" href="<%= url_for '/admin/feedback' %>">Feedback</a>
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-item nav-link<%= title eq 'Tokens' ? ' active' : '' %>" href="<%= url_for '/admin/tokens' %>">Tokens</a>
|
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
<a class="nav-item nav-link<%= title eq 'Transactions' ? ' active' : '' %>" href="<%= url_for '/admin/transactions' %>">Transactions</a>
|
Reports
|
||||||
<a class="nav-item nav-link<%= title eq 'Users' ? ' active' : '' %>" href="<%= url_for '/admin/users' %>">Users</a>
|
</a>
|
||||||
<a class="nav-item nav-link<%= title eq 'Organisations' ? ' active' : '' %>" href="<%= url_for '/admin/organisations' %>">Organisations</a>
|
<div class="dropdown-menu">
|
||||||
<a class="nav-item nav-link" href="<%= url_for '/admin/logout' %>">Logout</a>
|
<a class="dropdown-item" href="<%= url_for '/admin/reports/transactions' %>">Transactions (Hourly)</a>
|
||||||
|
<a class="dropdown-item" href="<%= url_for('/admin/reports/transactions')->query(scale =>'days') %>">Transactions (Daily)</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link<%= title eq 'Feedback' ? ' active' : '' %>" href="<%= url_for '/admin/feedback' %>">Feedback</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link<%= title eq 'Tokens' ? ' active' : '' %>" href="<%= url_for '/admin/tokens' %>">Tokens</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link<%= title eq 'Transactions' ? ' active' : '' %>" href="<%= url_for '/admin/transactions' %>">Transactions</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link<%= title eq 'Users' ? ' active' : '' %>" href="<%= url_for '/admin/users' %>">Users</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link<%= title eq 'Organisations' ? ' active' : '' %>" href="<%= url_for '/admin/organisations' %>">Organisations</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="<%= url_for '/admin/logout' %>">Logout</a>
|
||||||
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<%= content %>
|
<%= content %>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="navbar bg-dark fixed-bottom">
|
||||||
|
<span class="navbar-text ml-auto text-muted">
|
||||||
|
Version: <%= $c->config->{version} %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.5/umd/popper.min.js" integrity="sha256-jpW4gXAhFvqGDD5B7366rIPD7PDbAmqq4CO0ZnHbdM4=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
%= content_for 'javascript';
|
%= content_for 'javascript';
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Reference in a new issue