Compare commits

...

5 commits

7 changed files with 813 additions and 211 deletions

View file

@ -238,3 +238,9 @@ access_keys:
label: Update label: Update
- id: promo.delete - id: promo.delete
label: Delete label: Delete
- id: report
label: Reports Access
acls:
- id: report.view
label: View

81
config/routes/report.yaml Normal file
View file

@ -0,0 +1,81 @@
# reports
# dispatch
report_dispatch_time:
path: /reports/dispatch-time
controller: App\Controller\ReportController::dispatchTimeForm
methods: [GET]
report_dispatch_time_submit:
path: /reports/dispatch-time
controller: App\Controller\ReportController::dispatchTimeSubmit
methods: [POST]
report_dispatch_time_generate:
path: /reports/dispatch-time/generate/{start_date}/{end_date}
controller: App\Controller\ReportController::dispatchTimeGenerate
methods: [GET]
# out of stock
report_out_of_stock:
path: /reports/out-of-stock
controller: App\Controller\ReportController::outOfStockForm
methods: [GET]
report_out_of_stock_submit:
path: /reports/out-of-stock
controller: App\Controller\ReportController::outOfStockSubmit
methods: [POST]
report_out_of_stock_generate:
path: /reports/out-of-stock/generate/{start_date}/{end_date}
controller: App\Controller\ReportController::outOfStockGenerate
methods: [GET]
# rejection
report_rejection:
path: /reports/rejection
controller: App\Controller\ReportController::rejectionForm
methods: [GET]
report_rejection_submit:
path: /reports/rejection
controller: App\Controller\ReportController::rejectionSubmit
methods: [POST]
report_rejection_generate:
path: /reports/rejection/generate/{start_date}/{end_date}
controller: App\Controller\ReportController::rejectionGenerate
methods: [GET]
# sales
report_sales:
path: /reports/sales
controller: App\Controller\ReportController::salesForm
methods: [GET]
report_sales_submit:
path: /reports/sales
controller: App\Controller\ReportController::salesSubmit
methods: [POST]
report_sales_generate:
path: /reports/sales/generate/{start_date}/{end_date}
controller: App\Controller\ReportController::salesGenerate
methods: [GET]
# service
report_service:
path: /reports/service
controller: App\Controller\ReportController::serviceForm
methods: [GET]
report_service_submit:
path: /reports/service
controller: App\Controller\ReportController::serviceSubmit
methods: [POST]
report_service_generate:
path: /reports/service/generate/{start_date}/{end_date}
controller: App\Controller\ReportController::serviceGenerate
methods: [GET]

View file

@ -0,0 +1,439 @@
<?php
namespace App\Controller;
use App\Ramcar\BaseController;
use App\Entity\Hub;
use App\Entity\JobOrder;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
use DateTime;
use DateInterval;
use DatePeriod;
class ReportController extends BaseController
{
public function dispatchTimeForm(Request $req)
{
$this->denyAccessUnlessGranted('report.view', null, 'No access.');
$params = $this->initParameters('report_dispatch_time');
return $this->render('report/dispatch-time.html.twig', $params);
}
public function dispatchTimeSubmit(Request $req)
{
// initialize error list
$error_array = [];
$start_date = $req->request->get('start_date');
$end_date = $req->request->get('end_date');
// validate dates
$dates = $this->validateDateRange($start_date, $end_date, $error_array);
// check if any errors were found
if (!empty($error_array))
{
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
else
{
// return response
return $this->json([
'success' => true,
'url' => $this->generateUrl('report_dispatch_time_generate', [
'start_date' => $dates['start'],
'end_date' => $dates['end']
])
]);
}
}
public function dispatchTimeGenerate(Request $req, $start_date, $end_date)
{
// get dates
$date_format = "Y-m-d";
$sd = DateTime::createFromFormat($date_format, $start_date);
$ed = DateTime::createFromFormat($date_format, $end_date);
// format filename
$filename = "consolidated-dispatch-time-" . $sd->format('Ymd') . '-' . $ed->format('Ymd') . ".csv";
// build response
$response = new StreamedResponse();
$response->setCallback(function() use ($sd, $ed) {
$handle = fopen('php://output', 'w+');
// make table headers
$headers = ['Agent'];
// add date interval headers
$period = $this->generateDateHeaders($sd, $ed, $headers);
$headers[] = 'Grand Total';
// set table headers
fputcsv($handle, $headers);
$em = $this->getDoctrine()->getManager();
$conn = $em->getConnection();
// get all users
$sql = "SELECT * FROM user ORDER BY first_name ASC";
$stmt = $conn->prepare($sql);
$stmt->execute();
$users = $stmt->fetchAll();
// initiate
$tallies = [];
$jo_counts = [];
$averages = [];
$col_jo_counts = [];
$col_totals = [];
// get all job orders
$sql = "SELECT * FROM job_order WHERE date_schedule IS NOT NULL AND date_assign IS NOT NULL AND date_assign BETWEEN ? AND ?";
$stmt = $conn->prepare($sql);
$stmt->bindValue(1, $sd->format("Y-m-d"));
$stmt->bindValue(2, $ed->format("Y-m-d"));
$stmt->execute();
$job_orders = $stmt->fetchAll();
foreach ($job_orders as $job_order)
{
$date_schedule = DateTime::createFromFormat("Y-m-d H:i:s", $job_order['date_schedule']);
$date_assign = DateTime::createFromFormat("Y-m-d H:i:s", $job_order['date_assign']);
$date_assign_formatted = $date_assign->format("Y-m-d");
// get difference between times
$date_schedule_ts = $date_schedule->getTimestamp();
$date_assign_ts = $date_assign->getTimestamp();
$diff_mins = ($date_assign_ts - $date_schedule_ts) / 60;
// add to minutes tally
if (!isset($tallies[$job_order['assign_user_id']][$date_assign_formatted]))
$tallies[$job_order['assign_user_id']][$date_assign_formatted] = $diff_mins;
else
$tallies[$job_order['assign_user_id']][$date_assign_formatted] += $diff_mins;
// append to jo counter
if (!isset($jo_counts[$job_order['assign_user_id']][$date_assign_formatted]))
$jo_counts[$job_order['assign_user_id']][$date_assign_formatted] = 1;
else
$jo_counts[$job_order['assign_user_id']][$date_assign_formatted]++;
}
// append to csv
foreach ($users as $user)
{
// check if this user is an agent
$sql = "SELECT * FROM user_role WHERE user_id = ? AND (role_id = ? OR role_id = ?)";
$stmt = $conn->prepare($sql);
$stmt->bindValue(1, $user['id']);
$stmt->bindValue(2, "ROLE_AGENT_TIER_1");
$stmt->bindValue(3, "ROLE_AGENT_TIER_2");
$stmt->execute();
$roles = $stmt->fetchAll();
// only include if user is agent
if (count($roles) > 0)
{
$total_mins = 0;
$total_jos = 0;
$full_name = implode(" ", [
$user['first_name'],
$user['last_name']
]);
$rowdata = [$full_name];
foreach ($period as $date)
{
$date_formatted = $date->format("Y-m-d");
// get average per date for this user
if (isset($tallies[$user['id']][$date_formatted]) && isset($jo_counts[$user['id']][$date_formatted]))
{
$date_tally = $tallies[$user['id']][$date_formatted];
$jo_tally = $jo_counts[$user['id']][$date_formatted];
$rowdata[] = $date_tally / $jo_tally;
}
else
{
$date_tally = 0;
$jo_tally = 0;
$rowdata[] = 0;
}
// add to totals
$total_mins += $date_tally;
$total_jos += $jo_tally;
// add to column totals
if (!isset($col_totals[$date_formatted]))
$col_totals[$date_formatted] = $date_tally;
else
$col_totals[$date_formatted] += $date_tally;
// append to column jo counter
if (!isset($col_jo_counts[$date_formatted]))
$col_jo_counts[$date_formatted] = $jo_tally;
else
$col_jo_counts[$date_formatted] += $jo_tally;
}
// add total to rowdata
$rowdata[] = $total_jos > 0 ? $total_mins / $total_jos : 0;
fputcsv($handle, $rowdata);
}
}
// include column totals row
$totals_row = ['GRAND TOTAL'];
foreach ($period as $date)
{
$date_formatted = $date->format("Y-m-d");
$totals_row[] = $col_jo_counts[$date_formatted] > 0 ? $col_totals[$date_formatted] / $col_jo_counts[$date_formatted] : 0;
}
// add overall total
$overall_jo_total = count($job_orders);
$totals_row[] = $overall_jo_total > 0 ? array_sum($col_totals) / $overall_jo_total : 0;
fputcsv($handle, $totals_row);
fclose($handle);
});
// add response headers
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename);
// send
$response->send();
}
public function serviceForm(Request $req)
{
$this->denyAccessUnlessGranted('report.view', null, 'No access.');
$params = $this->initParameters('report_service');
return $this->render('report/service.html.twig', $params);
}
public function serviceSubmit(Request $req)
{
// initialize error list
$error_array = [];
$start_date = $req->request->get('start_date');
$end_date = $req->request->get('end_date');
// validate dates
$dates = $this->validateDateRange($start_date, $end_date, $error_array);
// check if any errors were found
if (!empty($error_array))
{
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
else
{
// return response
return $this->json([
'success' => true,
'url' => $this->generateUrl('report_service_generate', [
'start_date' => $dates['start'],
'end_date' => $dates['end']
])
]);
}
}
public function serviceGenerate(Request $req, $start_date, $end_date)
{
// get dates
$date_format = "Y-m-d";
$sd = DateTime::createFromFormat($date_format, $start_date);
$ed = DateTime::createFromFormat($date_format, $end_date);
// format filename
$filename = "service-" . $sd->format('Ymd') . '-' . $ed->format('Ymd') . ".csv";
// build response
$response = new StreamedResponse();
$response->setCallback(function() use ($sd, $ed) {
$handle = fopen('php://output', 'w+');
// make table headers
$headers = [
'Hub',
'Branch'
];
// add date interval headers
$period = $this->generateDateHeaders($sd, $ed, $headers);
$headers[] = 'GRAND TOTAL';
// set table headers
fputcsv($handle, $headers);
$em = $this->getDoctrine()->getManager();
$conn = $em->getConnection();
// get all hubs
$sql = "SELECT * FROM hub ORDER BY name ASC";
$stmt = $conn->prepare($sql);
$stmt->execute();
$hubs = $stmt->fetchAll();
$tallies = [];
$col_totals = [];
// get all job orders
$sql = "SELECT * FROM job_order WHERE date_schedule BETWEEN ? AND ?";
$stmt = $conn->prepare($sql);
$stmt->bindValue(1, $sd->format("Y-m-d"));
$stmt->bindValue(2, $ed->format("Y-m-d"));
$stmt->execute();
$job_orders = $stmt->fetchAll();
foreach ($job_orders as $job_order)
{
$date_sched = DateTime::createFromFormat("Y-m-d H:i:s", $job_order['date_schedule']);
$date_sched_formatted = $date_sched->format("Y-m-d");
if (!isset($tallies[$job_order['hub_id']][$date_sched_formatted]))
$tallies[$job_order['hub_id']][$date_sched_formatted] = 1;
else
$tallies[$job_order['hub_id']][$date_sched_formatted]++;
}
// append to csv
foreach ($hubs as $hub)
{
$total = 0;
$rowdata = [
$hub['name'],
$hub['branch']
];
foreach ($period as $date)
{
$date_formatted = $date->format("Y-m-d");
$date_tally = $tallies[$hub['id']][$date_formatted] ?? 0;
$rowdata[] = $date_tally;
$total += $date_tally;
// add to column totals
if (!isset($col_totals[$date_formatted]))
$col_totals[$date_formatted] = $date_tally;
else
$col_totals[$date_formatted] += $date_tally;
}
// add total to rowdata
$rowdata[] = $total;
fputcsv($handle, $rowdata);
}
// include column totals row
$totals_row = ['GRAND TOTAL', ''];
foreach ($period as $date)
$totals_row[] = $col_totals[$date->format("Y-m-d")];
// add overall total
$totals_row[] = array_sum($col_totals);
fputcsv($handle, $totals_row);
fclose($handle);
});
// add response headers
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename);
// send
$response->send();
}
protected function validateDateRange($start_date, $end_date, &$error_array)
{
$date_format = "d M Y";
$error = false;
if (empty($start_date))
{
$error = true;
$error_array['start_date'] = 'No start date specified.';
}
else
{
$sd = DateTime::createFromFormat($date_format, $start_date);
if (!$sd || $sd->format($date_format) != $start_date)
{
$error = true;
$error_array['start_date'] = 'Invalid start date specified.';
}
}
if (empty($end_date))
{
$error = true;
$error_array['end_date'] = 'No end date specified.';
}
else
{
$ed = DateTime::createFromFormat($date_format, $end_date);
if (!$ed || $ed->format($date_format) != $end_date)
{
$error = true;
$error_array['end_date'] = 'Invalid end date specified.';
}
}
return $error ? false : [
'start' => $sd->format("Y-m-d"),
'end' => $ed->format("Y-m-d")
];
}
protected function generateDateHeaders($start_date, $end_date, &$headers)
{
$interval = DateInterval::createFromDateString('1 day');
$period = new DatePeriod($start_date, $interval, $end_date);
foreach ($period as $date)
$headers[] = $date->format('M j, Y');
return $period;
}
}

View file

@ -96,247 +96,53 @@
<i class="m-menu__hor-arrow la la-angle-down"></i> <i class="m-menu__hor-arrow la la-angle-down"></i>
<i class="m-menu__ver-arrow la la-angle-right"></i> <i class="m-menu__ver-arrow la la-angle-right"></i>
</a> </a>
<div class="m-menu__submenu m-menu__submenu--fixed m-menu__submenu--left" style="width:1000px"> <div class="m-menu__submenu m-menu__submenu--fixed m-menu__submenu--left" style="width:600px">
<span class="m-menu__arrow m-menu__arrow--adjust"></span> <span class="m-menu__arrow m-menu__arrow--adjust"></span>
<div class="m-menu__subnav"> <div class="m-menu__subnav">
<ul class="m-menu__content"> <ul class="m-menu__content">
<li class="m-menu__item"> <li class="m-menu__item">
<h3 class="m-menu__heading m-menu__toggle"> <ul class="m-menu__inner" style="padding-top:20px">
<span class="m-menu__link-text">
Finance Reports
</span>
<i class="m-menu__ver-arrow la la-angle-right"></i>
</h3>
<ul class="m-menu__inner">
<li class="m-menu__item " data-redirect="true" aria-haspopup="true"> <li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link"> <a href="{{ url('report_dispatch_time') }}" class="m-menu__link">
<i class="m-menu__link-icon flaticon-map"></i> <i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span>
</i>
<span class="m-menu__link-text"> <span class="m-menu__link-text">
Annual Reports Consolidated Dispatch Time
</span> </span>
</a> </a>
</li> </li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true"> <li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link"> <a href="{{ url('report_rejection') }}" class="m-menu__link">
<i class="m-menu__link-icon flaticon-user"></i> <i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span>
</i>
<span class="m-menu__link-text"> <span class="m-menu__link-text">
HR Reports Consolidated Rejection
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-icon flaticon-clipboard"></i>
<span class="m-menu__link-text">
IPO Reports
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-icon flaticon-graphic-1"></i>
<span class="m-menu__link-text">
Finance Margins
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-icon flaticon-graphic-2"></i>
<span class="m-menu__link-text">
Revenue Reports
</span> </span>
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
<li class="m-menu__item"> <li class="m-menu__item">
<h3 class="m-menu__heading m-menu__toggle"> <ul class="m-menu__inner" style="padding-top:20px">
<span class="m-menu__link-text">
Project Reports
</span>
<i class="m-menu__ver-arrow la la-angle-right"></i>
</h3>
<ul class="m-menu__inner">
<li class="m-menu__item " data-redirect="true" aria-haspopup="true"> <li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link"> <a href="{{ url('report_sales') }}" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--line">
<span></span>
</i>
<span class="m-menu__link-text">
Coca Cola CRM
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--line">
<span></span>
</i>
<span class="m-menu__link-text">
Delta Airlines Booking Site
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--line">
<span></span>
</i>
<span class="m-menu__link-text">
Malibu Accounting
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--line">
<span></span>
</i>
<span class="m-menu__link-text">
Vineseed Website Rewamp
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--line">
<span></span>
</i>
<span class="m-menu__link-text">
Zircon Mobile App
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--line">
<span></span>
</i>
<span class="m-menu__link-text">
Mercury CMS
</span>
</a>
</li>
</ul>
</li>
<li class="m-menu__item">
<h3 class="m-menu__heading m-menu__toggle">
<span class="m-menu__link-text">
HR Reports
</span>
<i class="m-menu__ver-arrow la la-angle-right"></i>
</h3>
<ul class="m-menu__inner">
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--dot"> <i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span> <span></span>
</i> </i>
<span class="m-menu__link-text"> <span class="m-menu__link-text">
Staff Directory Consolidated Sales
</span> </span>
</a> </a>
</li> </li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true"> <li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link"> <a href="{{ url('report_service') }}" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--dot"> <i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span> <span></span>
</i> </i>
<span class="m-menu__link-text"> <span class="m-menu__link-text">
Client Directory Service
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span>
</i>
<span class="m-menu__link-text">
Salary Reports
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span>
</i>
<span class="m-menu__link-text">
Staff Payslips
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span>
</i>
<span class="m-menu__link-text">
Corporate Expenses
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<i class="m-menu__link-bullet m-menu__link-bullet--dot">
<span></span>
</i>
<span class="m-menu__link-text">
Project Expenses
</span>
</a>
</li>
</ul>
</li>
<li class="m-menu__item">
<h3 class="m-menu__heading m-menu__toggle">
<span class="m-menu__link-text">
Reporting Apps
</span>
<i class="m-menu__ver-arrow la la-angle-right"></i>
</h3>
<ul class="m-menu__inner">
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<span class="m-menu__link-text">
Report Adjusments
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<span class="m-menu__link-text">
Sources &amp; Mediums
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<span class="m-menu__link-text">
Reporting Settings
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<span class="m-menu__link-text">
Conversions
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<span class="m-menu__link-text">
Report Flows
</span>
</a>
</li>
<li class="m-menu__item " data-redirect="true" aria-haspopup="true">
<a href="header/actions.html" class="m-menu__link">
<span class="m-menu__link-text">
Audit &amp; Logs
</span> </span>
</a> </a>
</li> </li>

View file

@ -0,0 +1,125 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Reports</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-6">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<span class="m-portlet__head-icon">
<i class="la la-user"></i>
</span>
<h3 class="m-portlet__head-text">
Consolidated Dispatch Time
</h3>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right" method="post" action="{{ url('report_dispatch_time_submit') }}">
{% include 'report/range-picker.html.twig' %}
<div class="m-portlet__foot m-portlet__foot--fit">
<div class="m-form__actions m-form__actions--solid m-form__actions--right">
<div class="row">
<div class="col-lg-12">
<button type="submit" class="btn btn-success">Download Report</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
// date range picker
$('#daterange_picker').datepicker({
todayHighlight: true,
autoclose: true,
bootcssVer: 3,
format: "dd M yyyy",
orientation: "bottom left",
templates: {
leftArrow: '<i class="la la-angle-left"></i>',
rightArrow: '<i class="la la-angle-right"></i>'
}
});
$("#row-form").submit(function(e) {
var form = $(this);
e.preventDefault();
$.ajax({
method: "POST",
url: form.prop('action'),
data: form.serialize()
}).done(function(response) {
// remove all error classes
removeErrors();
window.location.href = response.url;
}).fail(function(response) {
if (response.status == 422) {
var errors = response.responseJSON.errors;
var firstfield = false;
// remove all error classes first
removeErrors();
// display errors contextually
$.each(errors, function(field, msg) {
var formfield = $("[name='" + field + "'], [data-name='" + field + "']");
var label = $("label[data-field='" + field + "']");
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
// add error classes to bad fields
formfield.addClass('form-control-danger');
label.addClass('has-danger');
msgbox.html(msg).addClass('has-danger').removeClass('hide');
// check if this field comes first in DOM
var domfield = formfield.get(0);
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
firstfield = domfield;
}
});
// focus on first bad field
//firstfield.focus();
// scroll to above that field to make it visible
$('html, body').animate({
scrollTop: $(firstfield).offset().top - 200
}, 100);
}
});
});
// remove all error classes
function removeErrors() {
$(".form-control-danger").removeClass('form-control-danger');
$("[data-field]").removeClass('has-danger');
$(".form-control-feedback[data-field]").addClass('hide');
}
});
</script>
{% endblock %}

View file

@ -0,0 +1,20 @@
<div class="m-portlet__body">
<div class="m-form__section m-form__section--first">
<div class="m-form__heading">
<h3 class="m-form__heading-title">
Date Range
</h3>
</div>
<div class="form-group m-form__group">
<div class="input-daterange input-group" id="daterange_picker">
<input type="text" class="form-control m-input" name="start_date" placeholder="Start date">
<span class="input-group-addon">
to
</span>
<input type="text" class="form-control" name="end_date" placeholder="End date">
</div>
<div class="form-control-feedback hide" data-field="start_date"></div>
<div class="form-control-feedback hide" data-field="end_date"></div>
</div>
</div>
</div>

View file

@ -0,0 +1,125 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Reports</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-6">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<span class="m-portlet__head-icon">
<i class="la la-user"></i>
</span>
<h3 class="m-portlet__head-text">
Service
</h3>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right" method="post" action="{{ url('report_service_submit') }}">
{% include 'report/range-picker.html.twig' %}
<div class="m-portlet__foot m-portlet__foot--fit">
<div class="m-form__actions m-form__actions--solid m-form__actions--right">
<div class="row">
<div class="col-lg-12">
<button type="submit" class="btn btn-success">Download Report</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
// date range picker
$('#daterange_picker').datepicker({
todayHighlight: true,
autoclose: true,
bootcssVer: 3,
format: "dd M yyyy",
orientation: "bottom left",
templates: {
leftArrow: '<i class="la la-angle-left"></i>',
rightArrow: '<i class="la la-angle-right"></i>'
}
});
$("#row-form").submit(function(e) {
var form = $(this);
e.preventDefault();
$.ajax({
method: "POST",
url: form.prop('action'),
data: form.serialize()
}).done(function(response) {
// remove all error classes
removeErrors();
window.location.href = response.url;
}).fail(function(response) {
if (response.status == 422) {
var errors = response.responseJSON.errors;
var firstfield = false;
// remove all error classes first
removeErrors();
// display errors contextually
$.each(errors, function(field, msg) {
var formfield = $("[name='" + field + "'], [data-name='" + field + "']");
var label = $("label[data-field='" + field + "']");
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
// add error classes to bad fields
formfield.addClass('form-control-danger');
label.addClass('has-danger');
msgbox.html(msg).addClass('has-danger').removeClass('hide');
// check if this field comes first in DOM
var domfield = formfield.get(0);
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
firstfield = domfield;
}
});
// focus on first bad field
//firstfield.focus();
// scroll to above that field to make it visible
$('html, body').animate({
scrollTop: $(firstfield).offset().top - 200
}, 100);
}
});
});
// remove all error classes
function removeErrors() {
$(".form-control-danger").removeClass('form-control-danger');
$("[data-field]").removeClass('has-danger');
$(".form-control-feedback[data-field]").addClass('hide');
}
});
</script>
{% endblock %}