Initial commit for consolidated dispatch time and service reports #99
This commit is contained in:
parent
1c868d15c8
commit
2029781d8c
4 changed files with 709 additions and 0 deletions
439
src/Controller/ReportController.php
Normal file
439
src/Controller/ReportController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
125
templates/report/dispatch-time.html.twig
Normal file
125
templates/report/dispatch-time.html.twig
Normal 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 %}
|
||||
20
templates/report/range-picker.html.twig
Normal file
20
templates/report/range-picker.html.twig
Normal 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>
|
||||
125
templates/report/service.html.twig
Normal file
125
templates/report/service.html.twig
Normal 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 %}
|
||||
Loading…
Reference in a new issue