Add a month filter to the custom dashboard and stop the OTP form from being submitted twice.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ravi Gulhane 2026-10-07 15:24:51 +05:30
parent 957beb7ae0
commit a462bb3100
5 changed files with 214 additions and 32 deletions

View File

@ -123,7 +123,7 @@ class ZenovaReportExport implements FromArray, WithEvents
if ($column['type'] === 'text') {
$sheet->getStyle($dataRange)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
} elseif ($column['type'] === 'percent') {
$sheet->getStyle($dataRange)->getNumberFormat()->setFormatCode('0.00"%"');
$sheet->getStyle($dataRange)->getNumberFormat()->setFormatCode('0.0"%"');
$sheet->getStyle($dataRange)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
} else {
$sheet->getStyle($dataRange)->getNumberFormat()->setFormatCode('#,##0');
@ -134,9 +134,9 @@ class ZenovaReportExport implements FromArray, WithEvents
$col += $span;
}
$sheet->getRowDimension(1)->setRowHeight(22);
$sheet->getRowDimension(1)->setRowHeight(36);
$sheet->getRowDimension(2)->setRowHeight(36);
$sheet->freezePane('A3');
$sheet->freezePane('C3');
if (!empty($this->totals)) {
$sheet->getStyle('A' . $rowCount . ':' . $lastCol . $rowCount)->applyFromArray([

View File

@ -23,7 +23,7 @@ class ZenovaReportController extends Controller
$this->_service = 'reports';
$this->_module = 'zenova_reports';
$this->_viewname = 'zenovareport';
$this->_title = 'Glipiq & Sitazit Dashboard';
$this->_title = 'Custom Dashboard';
$this->_base_url = '/zenovareport';
}
@ -35,11 +35,14 @@ class ZenovaReportController extends Controller
public function dashboard(Request $request)
{
$level = $this->level($request);
$built = $this->build($level);
$month = $this->month($request);
$built = $this->build($level, $month);
return view('zenovareport.dashboard', [
'title' => $this->_title,
'level' => $level,
'month' => $month,
'months' => $this->months(),
'groups' => $built['groups'],
'body' => $built['body'],
'totals' => $built['totals'],
@ -50,10 +53,11 @@ class ZenovaReportController extends Controller
public function export(Request $request)
{
$level = $this->level($request);
$built = $this->build($level);
$month = $this->month($request);
$built = $this->build($level, $month);
if ($built['error']) {
return redirect('/zenovareport/dashboard?level=' . $level)->with('error', $built['error']);
return redirect('/zenovareport/dashboard?level=' . $level . '&month=' . urlencode($month))->with('error', $built['error']);
}
return Excel::download(
@ -69,24 +73,51 @@ class ZenovaReportController extends Controller
return ($level >= 1 && $level <= 5) ? $level : 3;
}
private function build(int $level): array
private function months(): array
{
$now = now()->startOfMonth();
$startYear = $now->month >= 4 ? $now->year : $now->year - 1;
$cursor = now()->setDate($startYear, 4, 1)->startOfMonth();
$months = [];
while ($cursor->lte($now)) {
$months[$cursor->format('Y-m')] = $cursor->format('F Y');
$cursor->addMonth();
}
return $months;
}
private function month(Request $request): string
{
$months = $this->months();
$selected = (string) $request->input('month', '');
if (isset($months[$selected])) {
return $selected;
}
return (string) array_key_last($months);
}
private function build(int $level, string $month): array
{
$response = LambdaRequest::signedrequest(
'get',
'reports',
'zenova_reports/glipiqsitazit_report',
['level' => (string) $level],
['level' => (string) $level, 'month' => $month],
120
);
$rows = [];
$error = null;
if (!$response || ($response->status ?? '') !== 'success') {
$error = $response->message ?? 'Unable to load the Glipiq & Sitazit report.';
$error = $response->message ?? 'Unable to load the Custom Dashboard.';
} else {
$rows = is_array($response->data ?? null) ? $response->data : [];
}
$rows = $this->sortRows($rows);
$groups = $this->groups($rows);
$columns = [];
foreach ($groups as $group) {
@ -153,6 +184,70 @@ class ZenovaReportController extends Controller
];
}
private function sortRows(array $rows): array
{
usort($rows, function ($left, $right) {
$zoneLeft = $this->zoneName($left);
$zoneRight = $this->zoneName($right);
if ($zoneLeft === '' && $zoneRight !== '') {
return 1;
}
if ($zoneRight === '' && $zoneLeft !== '') {
return -1;
}
$zone = strcasecmp($zoneLeft, $zoneRight);
if ($zone !== 0) {
return $zone;
}
$users = $this->fsoCount($right) <=> $this->fsoCount($left);
if ($users !== 0) {
return $users;
}
return strcasecmp($this->rowName($left), $this->rowName($right));
});
return $rows;
}
private function zoneName($row): string
{
$parents = (array) ($row->parent_positions ?? []);
foreach ($parents as $key => $value) {
if (strcasecmp((string) $key, 'Zone') === 0 && $value !== null && $value !== '') {
return (string) $value;
}
}
$demo = (array) ($row->{'Demography & Details'} ?? []);
foreach ($demo as $key => $value) {
if (strcasecmp((string) $key, 'Name of Zone') === 0 && $value !== null && $value !== '') {
return (string) $value;
}
}
return '';
}
private function fsoCount($row): float
{
$value = $row->{'Demography & Details'}->{'No. of FSOs'} ?? 0;
return is_numeric($value) ? (float) $value : 0;
}
private function rowName($row): string
{
$demo = (array) ($row->{'Demography & Details'} ?? []);
foreach ($demo as $key => $value) {
if (strpos((string) $key, 'Name of ') === 0) {
return (string) $value;
}
}
return (string) ($row->position_name ?? '');
}
private function groups(array $rows): array
{
$nameKey = 'Name of Region';
@ -197,7 +292,7 @@ class ZenovaReportController extends Controller
];
$groups = [
['label' => 'Demography & Details', 'tone' => 'demo', 'columns' => $demoColumns],
['label' => 'Demography & Details (Data as per Yesterday)', 'tone' => 'demo', 'columns' => $demoColumns],
];
$first = isset($rows[0]) ? $rows[0] : null;
@ -247,10 +342,13 @@ class ZenovaReportController extends Controller
if (strcasecmp($currentType, $type) === 0) {
$column = $this->column($type, 'Demography & Details', $nameKey, 'text');
$column['is_total'] = true;
$columns[] = $column;
continue;
} else {
$column = $this->column($type, 'parent_positions', $type, 'text');
}
$columns[] = $this->column($type, 'parent_positions', $type, 'text');
if (in_array($type, ['Zone', 'Region'], true)) {
$column['sticky'] = strtolower($type);
}
$columns[] = $column;
}
return $columns;
@ -337,7 +435,7 @@ class ZenovaReportController extends Controller
}
$number = (float) $value;
if ($type === 'percent') {
return number_format($number, 2) . '%';
return number_format($number, 1) . '%';
}
if (floor($number) == $number) {
return number_format($number, 0);

View File

@ -101,14 +101,17 @@
@endif
</div>
<div class="mt-0">
<form method="POST" action="">
@if (session('message'))
<div class="alert alert-danger" role="alert">{{ session('message') }}</div>
@endif
<form method="POST" action="" id="otp-form">
@csrf
<div class="mb-2">
<label for="otp" class="form-label">OTP</label>
<input class="form-control" type="text" id="otp" name="otp" required="" placeholder="Enter OTP">
</div>
<div class="mb-3">
<button class="btn btn-primary btn-block waves-effect waves-light" type="submit"> Verify OTP </button>
<button class="btn btn-primary btn-block waves-effect waves-light" type="submit" id="verify-otp"> Verify OTP </button>
</div>
</form>
@ -136,7 +139,26 @@
</footer>
@include('layouts.shared/footer-script')
<script>
(function () {
var form = document.getElementById('otp-form');
var button = document.getElementById('verify-otp');
if (!form || !button) {
return;
}
var submitting = false;
form.addEventListener('submit', function (event) {
if (submitting) {
event.preventDefault();
return;
}
submitting = true;
setTimeout(function () {
button.disabled = true;
}, 0);
});
})();
</script>
</body>

View File

@ -194,7 +194,7 @@
<a href="/zenovareport/dashboard">
<i data-feather="file-text"></i>
<span>
Glipiq &amp; Sitazit </span>
Custom Dashboard </span>
</a>
</li>
@endif

View File

@ -27,9 +27,47 @@
}
.zenova-table thead tr:first-child th {
top: 0;
height: 56px;
}
.zenova-table thead tr:nth-child(2) th {
top: 34px;
top: 56px;
}
.zenova-table thead tr:nth-child(2) th.sticky-zone,
.zenova-table tbody td.sticky-zone {
position: sticky;
left: 0;
min-width: 160px;
width: 160px;
background: #fff;
}
.zenova-table thead tr:nth-child(2) th.sticky-region,
.zenova-table tbody td.sticky-region {
position: sticky;
left: 160px;
min-width: 160px;
width: 160px;
background: #fff;
box-shadow: 2px 0 0 #e5e7eb;
}
.zenova-table tbody td.sticky-zone,
.zenova-table tbody td.sticky-region {
z-index: 1;
}
.zenova-table tr.totals td.sticky-zone,
.zenova-table tr.totals td.sticky-region {
background: #f8f9fa;
}
.zenova-table thead tr:first-child th.sticky-group {
position: sticky;
left: 0;
z-index: 6;
min-width: 320px;
white-space: normal;
box-shadow: 2px 0 0 #e5e7eb;
}
.zenova-table thead th.sticky-zone,
.zenova-table thead th.sticky-region {
z-index: 5;
}
.zenova-table td {
text-align: right;
@ -54,11 +92,15 @@
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-md-6 p-2">
<h4 class="page-title">{{ $title }}</h4>
</div>
<div class="col-md-6 p-2 d-flex justify-content-end align-items-center">
<form method="get" action="/zenovareport/dashboard" class="d-flex align-items-center gap-2">
<div class="col-12 p-2 d-flex flex-wrap justify-content-between align-items-center gap-2">
<h4 class="page-title mb-0">{{ $title }}</h4>
<form method="get" action="/zenovareport/dashboard" class="d-flex flex-wrap align-items-center gap-2">
<label for="month" class="mb-0">Month</label>
<select class="form-select" name="month" id="month" style="width: 160px;" onchange="this.form.submit()">
@foreach ($months as $value => $label)
<option value="{{ $value }}" {{ $month === $value ? 'selected' : '' }}>{{ $label }}</option>
@endforeach
</select>
<label for="level" class="mb-0">Level</label>
<select class="form-select" name="level" id="level" style="width: 110px;" onchange="this.form.submit()">
@for ($option = 1; $option <= 5; $option++)
@ -67,9 +109,13 @@
</option>
@endfor
</select>
<a href="/zenovareport/export?level={{ $level }}" class="btn btn-primary">Export</a>
<a href="/zenovareport/dashboard?level={{ $level }}&month={{ $month }}" class="btn btn-outline-secondary">Refresh</a>
<a href="/zenovareport/export?level={{ $level }}&month={{ $month }}" class="btn btn-primary">Export</a>
</form>
</div>
<div class="col-12 px-2">
<p class="text-muted mb-2">Please refresh before downloading the latest report.</p>
</div>
</div>
@if (session()->has('error'))
@ -86,15 +132,31 @@
<thead>
<tr>
@foreach ($groups as $group)
<th class="tone-{{ $group['tone'] }}" colspan="{{ count($group['columns']) }}">
{{ $group['label'] }}
</th>
@php
$stickyCount = 0;
foreach ($group['columns'] as $column) {
if (!empty($column['sticky'])) {
$stickyCount++;
}
}
$scrollCount = count($group['columns']) - $stickyCount;
@endphp
@if ($stickyCount > 0)
<th class="tone-{{ $group['tone'] }} sticky-group" colspan="{{ $stickyCount }}">
{{ $group['label'] }}
</th>
@endif
@if ($scrollCount > 0)
<th class="tone-{{ $group['tone'] }}" colspan="{{ $scrollCount }}">
{{ $stickyCount > 0 ? '' : $group['label'] }}
</th>
@endif
@endforeach
</tr>
<tr>
@foreach ($groups as $group)
@foreach ($group['columns'] as $column)
<th class="tone-{{ $group['tone'] }} {{ $column['type'] === 'text' ? 'text-cell' : '' }}">
<th class="tone-{{ $group['tone'] }} {{ $column['type'] === 'text' ? 'text-cell' : '' }} {{ !empty($column['sticky']) ? 'sticky-'.$column['sticky'] : '' }}">
{{ $column['label'] }}
</th>
@endforeach
@ -107,7 +169,7 @@
@php $cellIndex = 0; @endphp
@foreach ($groups as $group)
@foreach ($group['columns'] as $column)
<td class="{{ $column['type'] === 'text' ? 'text-cell' : '' }}">
<td class="{{ $column['type'] === 'text' ? 'text-cell' : '' }} {{ !empty($column['sticky']) ? 'sticky-'.$column['sticky'] : '' }}">
{{ $line[$cellIndex] ?? '' }}
</td>
@php $cellIndex++; @endphp
@ -126,7 +188,7 @@
@php $cellIndex = 0; @endphp
@foreach ($groups as $group)
@foreach ($group['columns'] as $column)
<td class="{{ $column['type'] === 'text' ? 'text-cell' : '' }}">
<td class="{{ $column['type'] === 'text' ? 'text-cell' : '' }} {{ !empty($column['sticky']) ? 'sticky-'.$column['sticky'] : '' }}">
{{ $totals[$cellIndex] ?? '' }}
</td>
@php $cellIndex++; @endphp