Initial commit - expense
Deploy to VPS (PAT over HTTPS) / deploy (push) Has been cancelled

This commit is contained in:
root
2026-06-01 15:01:03 +07:00
parent 9e9fab4a3f
commit 1c1418f3e0
123 changed files with 18622 additions and 10376 deletions
@@ -1,125 +1,357 @@
@extends('layouts.app')
@section('title')
Dashboard
@section('title', 'Dashboard Operational Analytics')
@section('styles')
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap4.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.bootstrap4.min.css">
{{-- Tambahan CDN Select2 untuk menjamin inisialisasi select2 dropdown berjalan lancar --}}
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ttskch/select2-bootstrap4-theme@x.x.x/dist/select2-bootstrap4.min.css">
<style>
.chart-container { position: relative; height: 300px; width: 100%; }
.select2-container--bootstrap4 .select2-selection--single { height: calc(1.8125rem + 2px) !important; }
#table-monitoring thead th a { color: inherit; text-decoration: none; display: block; width: 100%; }
#table-monitoring tbody tr:hover { background-color: rgba(0,123,255,0.05); transition: 0.2s; }
.section-title { border-left: 5px solid #007bff; padding-left: 15px; margin-bottom: 20px; font-weight: 700; color: #333; text-transform: uppercase; }
.row-region-group { background-color: #f4f6f9 !important; border-top: 2px solid #dee2e6; }
</style>
@endsection
@section('admin-content')
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Home</h1>
<div class="container-fluid pt-5">
{{-- 1. HEADER & LIVE CLOCK --}}
<div class="alert bg-primary text-white shadow-sm d-flex justify-content-between align-items-center mb-4" style="border-radius: 15px; border: none;">
<div>
<span class="font-weight-bold"><i class="fas fa-clock mr-2"></i> Waktu Sistem: </span>
<span id="live-clock" class="badge badge-light text-primary px-3 py-2 shadow-sm" style="font-size: 0.95rem; border-radius: 10px;">
Memuat...
</span>
</div>
<div class="d-flex align-items-center">
<div class="bg-white text-primary px-4 py-2 shadow-sm d-none d-md-block ml-3" style="border-radius: 50px;">
<i class="far fa-calendar-alt mr-2"></i>
<span class="font-weight-bold">Periode Input: {{ $periodeLabel ?? '-' }}</span>
</div>
</div>
</div>
{{-- 2. KARTU STATISTIK --}}
<div class="row">
<div class="col-lg-3 col-6 mb-3">
<div class="small-box bg-info shadow-sm h-100" style="border-radius: 15px;">
<div class="inner p-4"><h3>{{ $totalRegions ?? 0 }}</h3><p>Total Region</p></div>
<div class="icon"><i class="fas fa-map-marked-alt"></i></div>
</div>
</div>
<div class="col-lg-3 col-6 mb-3">
<div class="small-box bg-success shadow-sm h-100" style="border-radius: 15px;">
<div class="inner p-4"><h3>{{ $totalCabangs ?? 0 }}</h3><p>Cabang Aktif</p></div>
<div class="icon"><i class="fas fa-building"></i></div>
</div>
</div>
<div class="col-lg-3 col-6 mb-3">
<div class="small-box bg-warning shadow-sm h-100" style="border-radius: 15px;">
<div class="inner p-4 text-white"><h3>{{ $pendingCount ?? 0 }}</h3><p>Total Dokumen Pending</p></div>
<div class="icon"><i class="fas fa-file-signature"></i></div>
</div>
</div>
<div class="col-lg-3 col-6 mb-3">
<div class="small-box bg-danger shadow-sm h-100" style="border-radius: 15px;">
<div class="inner p-4">
<h3>{{ $totalKategori ?? 0 }}</h3>
<p>Master Kategori</p>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('dashboard.index') }}">Dashboard</a></li>
</ol>
<div class="icon"><i class="fas fa-tags"></i></div>
</div>
</div>
</div>
{{-- FILTER KHUSUS GRAFIK PERFORMA --}}
<div class="card shadow-sm mb-4 mt-2" style="border-radius: 15px; background-color: #f8f9fa; border-left: 5px solid #17a2b8;">
<div class="card-body py-2 px-3">
<form action="{{ route('admin.dashboard') }}" method="GET" id="chart-filter-form" class="m-0">
<input type="hidden" name="region_id" value="{{ request('region_id') }}">
<input type="hidden" name="cabang_id" value="{{ request('cabang_id') }}">
<input type="hidden" name="category" value="{{ request('category') }}">
<input type="hidden" name="search" value="{{ request('search') }}">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<h6 class="m-0 font-weight-bold text-info"><i class="fas fa-filter mr-2"></i>Filter Performa Closing Expense</h6>
<div class="d-flex align-items-center mt-2 mt-md-0">
<label class="small font-weight-bold text-muted mr-2 mb-0">Bulan:</label>
<select name="chart_month" class="form-control form-control-sm mr-3" style="width: 130px;" onchange="this.form.submit()">
<option value="">Semua Bulan</option>
@foreach(range(1, 12) as $m)
<option value="{{ sprintf('%02d', $m) }}" {{ (request('chart_month') ?? date('m')) == sprintf('%02d', $m) ? 'selected' : '' }}>
{{ Carbon\Carbon::create()->month($m)->translatedFormat('F') }}
</option>
@endforeach
</select>
<label class="small font-weight-bold text-muted mr-2 mb-0">Tahun:</label>
<select name="chart_year" class="form-control form-control-sm" style="width: 100px;" onchange="this.form.submit()">
@php $currentYear = date('Y'); @endphp
@for($y = $currentYear; $y >= 2023; $y--)
<option value="{{ $y }}" {{ (request('chart_year') ?? date('Y')) == $y ? 'selected' : '' }}>{{ $y }}</option>
@endfor
</select>
</div>
</div>
</form>
</div>
</div>
{{-- 3. GRAFIK PERFORMA CLOSING EXPENSE --}}
<h5 class="section-title mt-4">Respon Speed Expense</h5>
<div class="row mt-2">
<div class="col-md-6 mb-4">
<div class="card shadow-sm h-100" style="border-radius: 15px;">
<div class="card-header bg-white border-0 pt-3">
<h3 class="card-title font-weight-bold text-success text-uppercase small"><i class="fas fa-bolt mr-2"></i> 5 Cabang Tercepat</h3>
</div>
<div class="card-body">
<div class="chart-container"><canvas id="chartFastest"></canvas></div>
</div>
</div>
</div>
</section>
<section class="content">
<div class="container-fluid">
<!-- Statistic Cards Grid -->
<div class="row row-cols-2 row-cols-md-2 row-cols-lg-3 g-4">
<!-- Statistic Card 1 -->
<div class="col">
<div class="small-box bg-info">
<div class="inner">
<h3>Rp {{ number_format($forms['vehicle'] + $forms['entertainment'] + $forms['meeting'] + $forms['upcountry'] + $forms['others'], 0, ',', '.') }}</h3>
<p>All Approved Expenses</p>
</div>
<div class="icon">
<i class="fas fa-check-circle"></i>
</div>
<a href="#" class="small-box-footer">
More info <i class="fas fa-arrow-circle-right"></i>
</a>
</div>
<div class="col-md-6 mb-4">
<div class="card shadow-sm h-100" style="border-radius: 15px;">
<div class="card-header bg-white border-0 pt-3">
<h3 class="card-title font-weight-bold text-danger text-uppercase small"><i class="fas fa-snail mr-2"></i> 5 Cabang Terlama</h3>
</div>
<!-- Statistic Card 2 -->
<div class="col">
<div class="small-box bg-success">
<div class="inner">
<h3>Rp {{ number_format($forms['upcountry'], 0, ',', '.') }}</h3>
<p>Approved Up Country Expenses</p>
</div>
<div class="icon">
<i class="fa fa-flag" aria-hidden="true"></i>
</div>
<a href="{{ route('forms.up-country') }}" class="small-box-footer">
More info <i class="fas fa-arrow-circle-right"></i>
</a>
</div>
</div>
<!-- Statistic Card 3 -->
<div class="col">
<div class="small-box bg-warning">
<div class="inner text-white">
<h3>Rp {{ number_format($forms['vehicle'], 0, ',', '.') }}</h3>
<p>Approved Vehicle Running Cost Expenses</p>
</div>
<div class="icon">
<i class="fa fa-car" aria-hidden="true"></i>
</div>
<a href="{{ route('forms.vehicle') }}" class="small-box-footer text-white">
<span class="text-white">More info</span> <i class="fas fa-arrow-circle-right text-white"></i>
</a>
</div>
</div>
<!-- Statistic Card 4 -->
<div class="col">
<div class="small-box bg-purple">
<div class="inner">
<h3>Rp {{ number_format($forms['entertainment'], 0, ',', '.') }}</h3>
<p>Approved Entertainment Presentation Expenses</p>
</div>
<div class="icon">
<i class="fa fa-desktop" aria-hidden="true"></i>
</div>
<a href="{{ route('forms.entertainment') }}" class="small-box-footer">
More info <i class="fas fa-arrow-circle-right"></i>
</a>
</div>
</div>
<!-- Statistic Card 5 -->
<div class="col">
<div class="small-box bg-danger">
<div class="inner">
<h3>Rp {{ number_format($forms['meeting'], 0, ',', '.') }}</h3>
<p>Approved Meeting Seminar Expenses</p>
</div>
<div class="icon">
<i class="fa fa-users" aria-hidden="true"></i>
</div>
<a href="{{ route('forms.meeting') }}" class="small-box-footer">
More info <i class="fas fa-arrow-circle-right"></i>
</a>
</div>
</div>
<!-- Statistic Card 6 -->
<div class="col">
<div class="small-box bg-pink">
<div class="inner">
<h3>Rp {{ number_format($forms['others'], 0, ',', '.') }}</h3>
<p>Approved Other Expenses</p>
</div>
<div class="icon">
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
<a href="{{ route('forms.other') }}" class="small-box-footer">
More info <i class="fas fa-arrow-circle-right"></i>
</a>
</div>
</div>
</div>
<!-- Welcome Card -->
<div class="card card-primary card-outline mt-4">
<div class="card-body text-center">
<p class="lead">Welcome to {{ config('app.name') }} (development version).</p>
<div class="card-body">
<div class="chart-container"><canvas id="chartSlowest"></canvas></div>
</div>
</div>
</div>
</section>
</div>
{{-- 4. FILTER PANEL TABEL PENDING --}}
<div class="card shadow-sm mb-4" style="border-radius: 15px;">
<div class="card-body py-3">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="font-weight-bold text-muted m-0 small text-uppercase">Tabel Monitoring Dokumen Pending</h6>
{{-- Container Tombol Excel --}}
<div id="export-button-container"></div>
</div>
<form action="{{ route('admin.dashboard') }}" method="GET" id="filter-form">
<input type="hidden" name="chart_month" value="{{ request('chart_month') }}">
<input type="hidden" name="chart_year" value="{{ request('chart_year') }}">
<div class="row align-items-end">
<div class="col-md-3 mb-2 mb-md-0">
<label class="small font-weight-bold text-muted text-uppercase">Region</label>
<select name="region_id" class="form-control form-control-sm select2" onchange="this.form.submit()">
<option value="">Semua Region</option>
@foreach($regions as $reg)
<option value="{{ $reg->id }}" {{ request('region_id') == $reg->id ? 'selected' : '' }}>{{ $reg->name }}</option>
@endforeach
</select>
</div>
<div class="col-md-3 mb-2 mb-md-0">
<label class="small font-weight-bold text-muted text-uppercase">Cabang</label>
<select name="cabang_id" class="form-control form-control-sm select2" onchange="this.form.submit()">
<option value="">Semua Cabang</option>
@foreach($cabangOptions as $cab)
<option value="{{ $cab->id }}" {{ request('cabang_id') == $cab->id ? 'selected' : '' }}>{{ $cab->name }}</option>
@endforeach
</select>
</div>
<div class="col-md-3 mb-2 mb-md-0">
<label class="small font-weight-bold text-muted text-uppercase">Highlight Kategori</label>
<select name="category" class="form-control form-control-sm" onchange="this.form.submit()">
<option value="">Pilih Kategori</option>
@php
$categories = [
'up_country' => 'Up Country',
'vehicle' => 'Vehicle Running Cost',
'entertainment' => 'Entertainment',
'meeting' => 'Meeting Seminar',
'others' => 'Others'
];
@endphp
@foreach($categories as $key => $val)
<option value="{{ $key }}" {{ request('category') == $key ? 'selected' : '' }}>{{ $val }}</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<div class="d-flex">
<input type="text" name="search" class="form-control form-control-sm mr-2" placeholder="Cari..." value="{{ request('search') }}">
<button type="submit" class="btn btn-sm btn-primary px-3 shadow-sm">Filter</button>
<a href="{{ route('admin.dashboard') }}" class="btn btn-sm btn-secondary ml-1 shadow-sm" title="Reset Filter"><i class="fas fa-sync-alt"></i></a>
</div>
</div>
</div>
</form>
</div>
</div>
{{-- 5. TABEL MONITORING --}}
<div class="card shadow-sm" style="border-radius: 15px; overflow: hidden;">
<div class="card-body p-0 table-responsive">
<table id="table-monitoring" class="table table-bordered m-0">
<thead class="bg-light text-center small text-uppercase font-weight-bold">
<tr>
<th rowspan="2" class="align-middle">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'region', 'direction' => request('direction') == 'asc' ? 'desc' : 'asc']) }}">Region <i class="fas fa-sort ml-1 opacity-50"></i></a>
</th>
<th rowspan="2" class="align-middle text-left">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'name', 'direction' => request('direction') == 'asc' ? 'desc' : 'asc']) }}">Cabang <i class="fas fa-sort ml-1 opacity-50"></i></a>
</th>
<th colspan="5" class="bg-white text-center align-middle">Rincian Dokumen Pending</th>
<th rowspan="2" class="align-middle text-center">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'total_pending', 'direction' => request('direction') == 'asc' ? 'desc' : 'asc']) }}">Total <i class="fas fa-sort ml-1 opacity-50"></i></a>
</th>
<th rowspan="2" class="align-middle no-export text-center">Aksi</th>
</tr>
<tr class="bg-white">
<th class="{{ request('category') == 'up_country' ? 'bg-warning' : '' }}">UC</th>
<th class="{{ request('category') == 'vehicle' ? 'bg-warning' : '' }}">VC</th>
<th class="{{ request('category') == 'entertainment' ? 'bg-warning' : '' }}">EN</th>
<th class="{{ request('category') == 'meeting' ? 'bg-warning' : '' }}">MT</th>
<th class="{{ request('category') == 'others' ? 'bg-warning' : '' }}">OT</th>
</tr>
</thead>
<tbody class="text-center">
@php $currentRegion = null; @endphp
@forelse($cabangDetails as $detail)
@if($currentRegion != ($detail->region_id ?? null))
<tr class="row-region-group">
<td colspan="9" class="text-left font-weight-bold text-primary text-uppercase" style="letter-spacing: 1px;">
<i class="fas fa-map-marked-alt mr-2 text-warning"></i> REGION: {{ $detail->region->name ?? 'TANPA REGION' }}
</td>
</tr>
@php $currentRegion = $detail->region_id ?? null; @endphp
@endif
<tr>
<td class="text-left small text-muted"><i class="fas fa-level-up-alt fa-rotate-90 text-muted mr-1 ml-2"></i> {{ $detail->region->name ?? '-' }}</td>
<td class="text-left font-weight-bold text-dark">{{ $detail->name }}</td>
<td>{{ $detail->pending_details['up_country'] ?? 0 }}</td>
<td>{{ $detail->pending_details['vehicle'] ?? 0 }}</td>
<td>{{ $detail->pending_details['entertainment'] ?? 0 }}</td>
<td>{{ $detail->pending_details['meeting'] ?? 0 }}</td>
<td>{{ $detail->pending_details['others'] ?? 0 }}</td>
<td class="bg-light font-weight-bold text-center align-middle">{{ $detail->total_pending }}</td>
<td class="no-export text-center align-middle">
<a href="{{ route('admin.dashboard.show', $detail->id) }}" class="btn btn-xs btn-info shadow-sm" title="Lihat Detail"><i class="fas fa-eye"></i></a>
</td>
</tr>
@empty
<tr>
<td colspan="9" class="py-5 text-center text-muted">
<i class="fas fa-info-circle mr-2"></i> Data tidak ditemukan untuk filter ini.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="card-footer bg-white border-0 py-3">
<div class="d-flex justify-content-between align-items-center">
@if(isset($cabangDetails) && method_exists($cabangDetails, 'firstItem'))
<div class="small text-muted">Menampilkan <b>{{ $cabangDetails->firstItem() ?? 0 }}</b> - <b>{{ $cabangDetails->lastItem() ?? 0 }}</b> dari <b>{{ $cabangDetails->total() ?? 0 }}</b> Cabang.</div>
<div>{{ $cabangDetails->appends(request()->query())->links('pagination::bootstrap-4') }}</div>
@endif
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
{{-- Library Wajib DataTables --}}
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap4.min.js"></script>
{{-- Library Wajib untuk Tombol Export --}}
<script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.bootstrap4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
{{-- Library Grafik --}}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
{{-- Library Select2 untuk keindahan select input --}}
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
// 1. LIVE CLOCK
function updateClock() {
const now = new Date();
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' };
const clockEl = document.getElementById('live-clock');
if(clockEl) clockEl.innerText = now.toLocaleDateString('id-ID', options) + ' WIB';
}
setInterval(updateClock, 1000); updateClock();
$(document).ready(function() {
// Inisialisasi Select2
$('.select2').select2({
theme: 'bootstrap4',
width: '100%'
});
// Hindari pesan error alert bawaan browser
$.fn.dataTable.ext.errMode = 'none';
// Inisialisasi DataTable
var table = $('#table-monitoring').DataTable({
paging: false,
searching: false,
info: false,
ordering: false,
autoWidth: false,
buttons: [
{
extend: 'excelHtml5',
text: '<i class="fas fa-file-excel mr-1"></i> Export Excel',
className: 'btn btn-sm btn-success font-weight-bold shadow-sm',
title: 'Laporan Pending - {{ $periodeLabel ?? "Mei 2026" }}',
exportOptions: {
columns: ':not(.no-export)'
}
}
]
});
// Append tombol ke container
if (table.buttons().container().length) {
table.buttons().container().appendTo('#export-button-container');
}
});
// 3. CHARTS PERFORMA
Chart.register(ChartDataLabels);
const configChart = (ctxId, labels, data, color, rawDurations) => {
const ctx = document.getElementById(ctxId);
if(!ctx || !labels || labels.length === 0) return;
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{ data: data, backgroundColor: color, borderRadius: 8, barThickness: 20 }]
},
options: {
indexAxis: 'y', responsive: true, maintainAspectRatio: false,
layout: { padding: { right: 85 } },
plugins: {
legend: { display: false },
datalabels: {
anchor: 'end', align: 'right', color: '#444', font: { weight: 'bold', size: 10 },
formatter: (val, ctx) => rawDurations[ctx.dataIndex] || ''
}
},
scales: { x: { display: false }, y: { ticks: { font: { size: 10, weight: 'bold' } } } }
}
});
};
configChart('chartFastest', {!! json_encode($chartFastest['labels'] ?? []) !!}, {!! json_encode($chartFastest['data'] ?? []) !!}, '#28a745', {!! json_encode($chartFastest['raw'] ?? []) !!});
configChart('chartSlowest', {!! json_encode($chartSlowest['labels'] ?? []) !!}, {!! json_encode($chartSlowest['data'] ?? []) !!}, '#dc3545', {!! json_encode($chartSlowest['raw'] ?? []) !!});
</script>
@endsection
@@ -0,0 +1,42 @@
@extends('layouts.app')
@section('admin-content')
<div class="container-fluid">
<div class="card shadow-sm mt-3">
<div class="card-header bg-info text-white">
<h3 class="card-title">Detail Pending: {{ $cabang->name }} ({{ $cabang->region->name ?? '' }})</h3>
<div class="card-tools">
<a href="{{ route('admin.dashboard') }}" class="btn btn-tool text-white"><i class="fas fa-times"></i></a>
</div>
</div>
<div class="card-body">
{{-- Loop menggunakan variabel $data yang dikirim dari controller --}}
@foreach($data as $label => $items)
@if($items->count() > 0)
<h5 class="mt-4 text-uppercase font-weight-bold text-secondary">{{ $label }}</h5>
<table class="table table-sm table-striped">
<thead>
<tr>
<th>No. Expense</th>
<th>Tanggal</th>
<th>Total</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach($items as $item)
<tr>
<td>{{ $item->expense_number }}</td>
<td>{{ \Carbon\Carbon::parse($item->tanggal)->format('d/m/Y') }}</td>
<td>Rp {{ number_format($item->total, 0, ',', '.') }}</td>
<td><span class="badge badge-warning">{{ $item->status }}</span></td>
</tr>
@endforeach
</tbody>
</table>
@endif
@endforeach
</div>
</div>
</div>
@endsection