Merged in izzati-feat-uum (pull request #144)

update task uum
This commit is contained in:
izzati zulkifli
2026-01-14 01:29:36 +00:00
3 changed files with 590 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
<?php
namespace App\Exports;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithCustomStartCell;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DataPelangganDetailExport implements
FromCollection,
WithHeadings,
WithMapping,
WithStyles,
WithCustomStartCell
{
protected $pelanggan;
protected $year;
protected $no = 0;
public function __construct($pelanggan, $year)
{
$this->pelanggan = collect($pelanggan);
$this->year = $year;
}
/**
* Data source
*/
public function collection(): Collection
{
return $this->pelanggan;
}
/**
* Start data from row 5 (sebab ada tajuk)
*/
public function startCell(): string
{
return 'A5';
}
/**
* Header column
*/
public function headings(): array
{
return [
'No',
'Nama Pelanggan',
'Umur',
'Jantina',
'Pekerjaan',
'Lokasi',
'Tempoh Pajakan',
'Tarikh Mula',
'Tarikh Tamat',
'Hari Sejak Transaksi Terakhir',
'Bil Transaksi',
'Bil Tebus',
'Jumlah Pinjaman (RM)',
'Jumlah Ujrah (RM)',
'Tujuan Gadaian',
'Status Aktif',
'Cara Bayaran',
'Tarikh Lelong',
];
}
/**
* Map setiap row
*/
public function map($row): array
{
return [
++$this->no,
strtoupper($row['nama']),
$row['umur'],
$row['jantina'],
$row['pekerjaan'],
$row['daerah'],
$row['tempoh_pajakan'],
$row['tarikh_mula'],
$row['tarikh_tamat'],
$row['hari_sejak_transaksi'],
$row['bil_transaksi'],
$row['bil_tebus'],
number_format($row['jumlah_pinjaman'], 2),
number_format($row['jumlah_ujrah'], 2),
$row['tujuan_gadai'],
$row['status_aktif'],
$row['cara_bayaran'],
$row['tarikh_lelong'] ?? '-',
];
}
/**
* Styling Excel
*/
public function styles(Worksheet $sheet)
{
// Tajuk utama
$sheet->mergeCells('A1:R1');
$sheet->setCellValue('A1', 'LAPORAN DATA PELANGGAN');
$sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
$sheet->getStyle('A1')->getAlignment()->setHorizontal('center');
// Tahun
$sheet->mergeCells('A2:R2');
$sheet->setCellValue('A2', 'TAHUN : ' . $this->year);
$sheet->getStyle('A2')->getFont()->setBold(true);
$sheet->getStyle('A2')->getAlignment()->setHorizontal('center');
// Header table
$sheet->getStyle('A5:R5')->getFont()->setBold(true);
$sheet->getStyle('A5:R5')->getAlignment()->setHorizontal('center');
// Auto width
foreach (range('A', 'R') as $col) {
$sheet->getColumnDimension($col)->setAutoSize(true);
}
}
}
@@ -0,0 +1,223 @@
@extends('layouts.app')
@section('content')
<div class="bg-gray-900 opacity-50 hidden fixed inset-0 z-10" id="sidebarBackdrop"></div>
<div id="main-content" class="h-full w-full bg-gray-50 relative overflow-y-auto">
<main>
<div class="pt-6 px-4">
{{-- Breadcrumb --}}
<breadcrumb
:items="[
{ 'title': 'Laporan Keseluruhan', 'icon': 'fas fa-users', 'url': '{{ route('reports') }}' },
{ 'title': 'Data Pelanggan', 'icon': 'fas fa-users' }
]">
</breadcrumb>
<div class="container">
{{-- Error Alert --}}
@if ($errors->any())
<div id="error-alert" class="flex justify-center">
<div class="flex items-center p-4 text-red-800 border-t-4 border-red-300 bg-red-50 px-10"
role="alert">
<svg class="flex-shrink-0 w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3
1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1
1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
</svg>
<div class="ms-3 text-sm font-medium ml-2">
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
</div>
</div>
</div>
@endif
<div class="flex w-full">
<div class="xl:w-2/3 2xl:w-1/2 p-8 mx-auto">
{{-- Header --}}
<div class="bg-yellow-600 text-gray-200 text-lg px-6 py-4 rounded-t-lg">
<a href="{{ route('reports') }}">
<i class="fas fa-backspace text-white"></i>
</a>
<span class="text-white font-bold ml-2">
Laporan Data Pelanggan
</span>
</div>
{{-- FORM --}}
<form method="GET" id="formStokAudit"
action="{{ route('reports.dataPelanggan.show') }}">
<iframe name="downloadFrame" class="hidden"></iframe>
<div class="rounded bg-white shadow p-6">
{{-- Tahun --}}
<div class="pb-6">
<label class="font-semibold text-gray-700 block pb-1">
Tahun Laporan
</label>
<div class="flex items-center">
<span
class="text-gray-600 border border-2 rounded-l px-4 py-2"
style="background-color: rgb(249, 250, 251);">
Tahun
</span>
<select name="year" id="report_year"
class="border border-2 border-l-0 rounded-r px-4 py-2 w-40 mr-4"
required>
<option value="">-- Pilih Tahun --</option>
@for ($y = now()->year; $y >= 2019; $y--)
<option value="{{ $y }}">{{ $y }}</option>
@endfor
</select>
{{-- Hidden generate type --}}
<div class="hidden">
<input type="radio" name="generateType"
id="generateTypePdf" value="pdf">
<input type="radio" name="generateType"
id="generateTypeExcel" value="excel">
</div>
{{-- Excel --}}
<button id="generateExcel" type="button"
class="border border-yellow-600 text-yellow-600 py-2 px-6
rounded-xl hover:bg-yellow-600 hover:text-white transition-all">
<i class="fas fa-file-excel mr-2"></i> Excel
</button>
{{-- PDF --}}
<button id="generatePdf" type="button"
class="border border-yellow-600 text-yellow-600 py-2 px-6 ml-4
rounded-xl hover:bg-yellow-600 hover:text-white transition-all">
<i class="fas fa-eye mr-2"></i> View
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
@include('layouts.footer')
</div>
{{-- Loading Overlay --}}
<div id="loadingOverlay"
class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
<div class="bg-white p-6 rounded-xl flex flex-col items-center">
<svg class="animate-spin h-10 w-10 text-yellow-600 mb-4"
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
<span class="text-gray-700 font-semibold">
Sedang menjana laporan..
</span>
</div>
</div>
@endsection
{{-- =======================
| Scripts
|======================= --}}
@push('additional_scripts')
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById('formStokAudit');
const loading = document.getElementById('loadingOverlay');
let downloadFrame = null;
let forceHideTimeout = null;
let minLoadingTimeout = null;
function showLoading() {
loading.classList.remove('hidden');
}
function hideLoading() {
loading.classList.add('hidden');
clearTimeout(forceHideTimeout);
clearTimeout(minLoadingTimeout);
}
// =====================
// EXCEL DOWNLOAD
// =====================
document.getElementById('generateExcel').addEventListener("click", function () {
document.getElementById('generateTypeExcel').checked = true;
showLoading();
// Remove iframe lama
if (downloadFrame) downloadFrame.remove();
downloadFrame = document.createElement('iframe');
downloadFrame.name = 'downloadFrame';
downloadFrame.style.display = 'none';
document.body.appendChild(downloadFrame);
form.target = 'downloadFrame';
form.submit();
/**
* 🔐 Minimum loading time
* elak tutup terlalu awal
*/
minLoadingTimeout = setTimeout(() => {
// Lepas 5 saat, barulah loading boleh ditutup
}, 10000);
/**
* 🧯 Fallback safety
* Tutup paksa selepas 20 saat
*/
forceHideTimeout = setTimeout(() => {
hideLoading();
}, 20000);
});
// =====================
// PDF VIEW
// =====================
document.getElementById('generatePdf').addEventListener("click", function () {
document.getElementById('generateTypePdf').checked = true;
form.removeAttribute('target');
showLoading();
form.submit();
// PDF cepat → tutup awal
setTimeout(() => {
hideLoading();
}, 10000);
});
});
</script>
@endpush
@@ -0,0 +1,241 @@
<style>
#header {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#header td, #customers th {
padding:4px;
font-size:10px;
}
#header tr:nth-child(even){background-color: #ffffff;}
#header tr:hover {background-color: #ddd;}
#header th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#scan {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#scan td, #scan th {
padding: 8px;
font-size:11px;
}
#scan tr:nth-child(even){background-color: #ffffff;}
#scan tr:hover {background-color: #ddd;}
#scan th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#scan {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#scan td, #scan th {
border-collapse: collapse;
padding: 8px;
font-size:11px;
}
#scan tr:nth-child(even){background-color: #ffffff;}
#scan tr:hover {background-color: #ddd;}
#scan th {
border-collapse: collapse;
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#details {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#details td, #details th {
padding: 8px;
font-size:11px;
}
#details tr:nth-child(even){background-color: #ffffff;}
#details tr:hover {background-color: #ddd;}
#details th {
padding-top: 12px;
font-style:bold;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<style>
#sign {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#sign td, #sign th {
padding: 8px;
font-size:11px;
text-align: center;
}
#sign tr:nth-child(even){background-color: #ffffff;}
#sign tr:hover {background-color: #ddd;}
#sign th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
padding:4px;
color:black;
}
</style>
<table id="header">
<tr>
<th rowspan="4" style="width:50px"><br><img src="{{ asset('img/ARRAHN1.png') }}" style="width:70px"></img></th>
<th colspan="1">KOPERASI PERMODALAN KELANTAN BERHAD</th>
</tr>
<tr>
<td>Semua Cawangan</td>
<td></td>
</tr>
</table>
<br>
<table id="scan">
<tr>
<th colspan="6"><b>Laporan Data Pelanggan</b></th>
<th colspan="3" style="text-align:right">
<b>Tarikh Cetak:</b> {{ $tarikh_cetak }}
</th>
</tr>
<tr>
<td style="width:120px"><b>TAHUN LAPORAN</b></td>
<td colspan="8">{{ $tebus_date }}</td>
</tr>
</table>
<br>
<table id="details">
<thead>
<tr>
<th>No</th>
<th>Nama Pelanggan</th>
<th>Umur</th>
<th>Jantina</th>
<th>Pekerjaan</th>
<th>Lokasi</th>
<th>Tempoh Pajakan</th>
<th>Tarikh Mula</th>
<th>Tarikh Tamat</th>
<th>Hari Sejak Transaksi Terakhir</th>
<th>Bil Transaksi</th>
<th>Bil Tebus</th>
<th>Jumlah Pinjaman (RM)</th>
<th>Jumlah Ujrah (RM)</th>
<th>Tujuan Gadaian</th>
<th>Status Aktif</th>
<th>Cara Bayaran</th>
<th>Tarikh Lelong</th>
</tr>
</thead>
<tbody>
@foreach($pelanggan as $index => $row)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ strtoupper($row['nama']) }}</td>
<td>{{ $row['umur'] }}</td>
<td>{{ $row['jantina'] }}</td>
<td>{{ $row['pekerjaan'] }}</td>
<td>{{ $row['daerah'] }}</td>
<td>{{ $row['tempoh_pajakan'] }}</td>
<td>{{ $row['tarikh_mula'] }}</td>
<td>{{ $row['tarikh_tamat'] }}</td>
<td>{{ $row['hari_sejak_transaksi'] }}</td>
<td>{{ $row['bil_transaksi'] }}</td>
<td>{{ $row['bil_tebus'] }}</td>
<td>{{ number_format($row['jumlah_pinjaman'], 2) }}</td>
<td>{{ number_format($row['jumlah_ujrah'], 2) }}</td>
<td>{{ $row['tujuan_gadai'] }}</td>
<td>{{ $row['status_aktif'] }}</td>
<td>{{ $row['cara_bayaran'] }}</td>
<td>{{ $row['tarikh_lelong'] }}</td>
</tr>
@endforeach
</tbody>
</table>
<br>
<br>
<table id="sign">
<tr>
<td></td>
<td colspan="2"> .................................................................. </td>
<td> </td>
<td colspan="2">.................................................................. </td>
<td> </td>
</tr>
<tr>
<td></td>
<td colspan="2"><B> DI SEMAK</B> </td>
<td> </td>
<td colspan="2"><B>DI SAHKAN </B></td>
<td> </td>
</tr>
</table>