546 lines
15 KiB
PHP
546 lines
15 KiB
PHP
@extends('admin.layout')
|
|
|
|
@section('title', 'Laporan')
|
|
|
|
@section('content')
|
|
<style>
|
|
.report-filter {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(180px, 1fr)) auto;
|
|
gap: 16px;
|
|
align-items: end;
|
|
}
|
|
|
|
.report-filter label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
color: #334155;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.report-filter input {
|
|
width: 100%;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 12px;
|
|
padding: 12px 14px;
|
|
color: #0f172a;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.report-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.report-card {
|
|
background: #ffffff;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 20px;
|
|
padding: 20px;
|
|
box-shadow: 0 18px 46px rgba(15, 23, 42, 0.05);
|
|
}
|
|
|
|
.report-card span {
|
|
display: block;
|
|
margin-bottom: 10px;
|
|
color: #64748b;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.report-card strong {
|
|
display: block;
|
|
color: #0f172a;
|
|
font-size: 24px;
|
|
line-height: 1.15;
|
|
}
|
|
|
|
.report-card.dark {
|
|
background: #0f172a;
|
|
border-color: #0f172a;
|
|
}
|
|
|
|
.report-card.dark span {
|
|
color: #cbd5e1;
|
|
}
|
|
|
|
.report-card.dark strong {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.report-status {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 12px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.status-mini {
|
|
padding: 14px;
|
|
border-radius: 16px;
|
|
background: #f8fafc;
|
|
border: 1px solid #eef2f7;
|
|
}
|
|
|
|
.status-mini span {
|
|
display: block;
|
|
color: #64748b;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.status-mini strong {
|
|
color: #0f172a;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.chart-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr);
|
|
gap: 20px;
|
|
align-items: start;
|
|
}
|
|
|
|
.bar-chart {
|
|
display: flex;
|
|
align-items: end;
|
|
gap: 12px;
|
|
min-height: 260px;
|
|
padding: 18px 8px 4px;
|
|
border-radius: 18px;
|
|
background: #f8fafc;
|
|
border: 1px solid #eef2f7;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.bar-item {
|
|
display: grid;
|
|
align-items: end;
|
|
justify-items: center;
|
|
gap: 8px;
|
|
min-width: 58px;
|
|
height: 220px;
|
|
}
|
|
|
|
.bar-value {
|
|
color: #334155;
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
white-space: nowrap;
|
|
writing-mode: vertical-rl;
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.bar-fill {
|
|
width: 34px;
|
|
min-height: 8px;
|
|
border-radius: 10px 10px 4px 4px;
|
|
background: #2563eb;
|
|
box-shadow: 0 12px 24px rgba(37, 99, 235, 0.18);
|
|
}
|
|
|
|
.bar-label {
|
|
color: #64748b;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.horizontal-chart {
|
|
display: grid;
|
|
gap: 14px;
|
|
}
|
|
|
|
.horizontal-row {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.horizontal-row header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
color: #334155;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.horizontal-track {
|
|
height: 13px;
|
|
border-radius: 999px;
|
|
background: #e2e8f0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.horizontal-fill {
|
|
height: 100%;
|
|
min-width: 6px;
|
|
border-radius: inherit;
|
|
background: #16a34a;
|
|
}
|
|
|
|
.horizontal-fill.pending {
|
|
background: #f59e0b;
|
|
}
|
|
|
|
.horizontal-fill.cancelled {
|
|
background: #dc2626;
|
|
}
|
|
|
|
.horizontal-fill.transfer {
|
|
background: #2563eb;
|
|
}
|
|
|
|
.horizontal-fill.cash {
|
|
background: #7c3aed;
|
|
}
|
|
|
|
.pie-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 20px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.pie-card {
|
|
display: grid;
|
|
justify-items: center;
|
|
gap: 16px;
|
|
padding: 18px;
|
|
border-radius: 18px;
|
|
background: #f8fafc;
|
|
border: 1px solid #eef2f7;
|
|
}
|
|
|
|
.pie-chart {
|
|
width: 180px;
|
|
height: 180px;
|
|
border-radius: 50%;
|
|
display: grid;
|
|
place-items: center;
|
|
background: var(--pie);
|
|
box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.08);
|
|
}
|
|
|
|
.pie-chart::after {
|
|
content: "";
|
|
width: 92px;
|
|
height: 92px;
|
|
border-radius: 50%;
|
|
background: #ffffff;
|
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
|
|
}
|
|
|
|
.pie-title {
|
|
margin: 0;
|
|
color: #0f172a;
|
|
font-weight: 800;
|
|
text-align: center;
|
|
}
|
|
|
|
.pie-legend {
|
|
display: grid;
|
|
gap: 8px;
|
|
width: 100%;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
color: #334155;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.legend-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.legend-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: var(--dot);
|
|
}
|
|
|
|
@media (max-width: 980px) {
|
|
.report-grid,
|
|
.report-status,
|
|
.chart-layout {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.report-filter {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 560px) {
|
|
.report-grid,
|
|
.report-status,
|
|
.chart-layout,
|
|
.pie-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="page-header">
|
|
<div>
|
|
<p style="margin:0; color:#6b7280; font-weight:600; letter-spacing:0.15em; text-transform:uppercase;">Laporan Penjualan</p>
|
|
<h2>Rekap transaksi showroom</h2>
|
|
<p>Lihat total penjualan, status pembayaran, dan detail transaksi berdasarkan periode tanggal.</p>
|
|
</div>
|
|
<div class="badge">Laporan</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<form class="report-filter" method="GET" action="{{ route('laporan') }}">
|
|
<div>
|
|
<label for="start_date">Tanggal Mulai</label>
|
|
<input id="start_date" type="date" name="start_date" value="{{ $startDate }}" />
|
|
</div>
|
|
<div>
|
|
<label for="end_date">Tanggal Akhir</label>
|
|
<input id="end_date" type="date" name="end_date" value="{{ $endDate }}" />
|
|
</div>
|
|
<button class="button-primary" type="submit" style="height:44px;">Filter</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="report-grid">
|
|
<div class="report-card dark">
|
|
<span>Total Penjualan Lunas</span>
|
|
<strong>Rp {{ number_format($summary['total_sales'], 0, ',', '.') }}</strong>
|
|
</div>
|
|
<div class="report-card">
|
|
<span>Total Transaksi</span>
|
|
<strong>{{ $summary['total_transactions'] }}</strong>
|
|
</div>
|
|
<div class="report-card">
|
|
<span>Transaksi Lunas</span>
|
|
<strong>{{ $summary['paid_transactions'] }}</strong>
|
|
</div>
|
|
<div class="report-card">
|
|
<span>Rata-rata Transaksi Lunas</span>
|
|
<strong>Rp {{ number_format($summary['average_sales'], 0, ',', '.') }}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<h3>Ringkasan Status</h3>
|
|
<p class="text-muted">Periode {{ \Carbon\Carbon::parse($startDate)->format('d/m/Y') }} sampai {{ \Carbon\Carbon::parse($endDate)->format('d/m/Y') }}</p>
|
|
</div>
|
|
<a href="{{ route('riwayat') }}" class="button-secondary" style="text-decoration:none;">Lihat Riwayat</a>
|
|
</div>
|
|
|
|
<div class="report-status">
|
|
<div class="status-mini">
|
|
<span>Pending</span>
|
|
<strong>{{ $summary['pending_transactions'] }}</strong>
|
|
</div>
|
|
<div class="status-mini">
|
|
<span>Dibatalkan</span>
|
|
<strong>{{ $summary['cancelled_transactions'] }}</strong>
|
|
</div>
|
|
<div class="status-mini">
|
|
<span>Mobil Tersedia</span>
|
|
<strong>{{ $summary['available_cars'] }}</strong>
|
|
</div>
|
|
<div class="status-mini">
|
|
<span>Mobil Terjual</span>
|
|
<strong>{{ $summary['sold_cars'] }}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-layout">
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<h3>Diagram Penjualan Harian</h3>
|
|
<p class="text-muted">Nilai penjualan lunas per hari sesuai periode filter.</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if($chartData['daily_sales']->isEmpty())
|
|
<div style="padding:24px; text-align:center; color:#64748b; background:#f8fafc; border-radius:18px;">Belum ada transaksi lunas untuk diagram periode ini.</div>
|
|
@else
|
|
<div class="bar-chart">
|
|
@foreach($chartData['daily_sales'] as $date => $amount)
|
|
<div class="bar-item">
|
|
<div class="bar-value">Rp {{ number_format($amount, 0, ',', '.') }}</div>
|
|
<div class="bar-fill" style="height: {{ max(($amount / $chartData['max_daily_sales']) * 150, 8) }}px;"></div>
|
|
<div class="bar-label">{{ \Carbon\Carbon::parse($date)->format('d/m') }}</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<h3>Bar Chart Status</h3>
|
|
<p class="text-muted">Perbandingan status dan metode pembayaran.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="horizontal-chart">
|
|
@php $maxStatus = max($chartData['payment_status']->max(), 1); @endphp
|
|
@foreach($chartData['payment_status'] as $label => $value)
|
|
<div class="horizontal-row">
|
|
<header>
|
|
<span>{{ $label }}</span>
|
|
<span>{{ $value }}</span>
|
|
</header>
|
|
<div class="horizontal-track">
|
|
<div class="horizontal-fill {{ $label === 'Pending' ? 'pending' : ($label === 'Dibatalkan' ? 'cancelled' : '') }}" style="width: {{ ($value / $maxStatus) * 100 }}%;"></div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<div class="horizontal-chart" style="margin-top:24px;">
|
|
@php $maxMethod = max($chartData['payment_method']->max(), 1); @endphp
|
|
@foreach($chartData['payment_method'] as $label => $value)
|
|
<div class="horizontal-row">
|
|
<header>
|
|
<span>{{ $label }}</span>
|
|
<span>{{ $value }}</span>
|
|
</header>
|
|
<div class="horizontal-track">
|
|
<div class="horizontal-fill {{ $label === 'Tunai' ? 'cash' : 'transfer' }}" style="width: {{ ($value / $maxMethod) * 100 }}%;"></div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@php
|
|
$paidCount = $chartData['payment_status']['Lunas'];
|
|
$pendingCount = $chartData['payment_status']['Pending'];
|
|
$cancelledCount = $chartData['payment_status']['Dibatalkan'];
|
|
$statusTotal = max($paidCount + $pendingCount + $cancelledCount, 1);
|
|
$paidEnd = ($paidCount / $statusTotal) * 100;
|
|
$pendingEnd = $paidEnd + (($pendingCount / $statusTotal) * 100);
|
|
|
|
$cashCount = $chartData['payment_method']['Tunai'];
|
|
$transferCount = $chartData['payment_method']['Transfer Bank'];
|
|
$methodTotal = max($cashCount + $transferCount, 1);
|
|
$cashEnd = ($cashCount / $methodTotal) * 100;
|
|
@endphp
|
|
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<h3>Pie Chart Laporan</h3>
|
|
<p class="text-muted">Komposisi transaksi berdasarkan status pembayaran dan metode pembayaran.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pie-grid">
|
|
<div class="pie-card">
|
|
<p class="pie-title">Status Pembayaran</p>
|
|
<div class="pie-chart" style="--pie: conic-gradient(#16a34a 0 {{ $paidEnd }}%, #f59e0b {{ $paidEnd }}% {{ $pendingEnd }}%, #dc2626 {{ $pendingEnd }}% 100%);"></div>
|
|
<div class="pie-legend">
|
|
<div class="legend-item">
|
|
<span class="legend-label"><span class="legend-dot" style="--dot:#16a34a;"></span>Lunas</span>
|
|
<span>{{ $paidCount }}</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-label"><span class="legend-dot" style="--dot:#f59e0b;"></span>Pending</span>
|
|
<span>{{ $pendingCount }}</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-label"><span class="legend-dot" style="--dot:#dc2626;"></span>Dibatalkan</span>
|
|
<span>{{ $cancelledCount }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pie-card">
|
|
<p class="pie-title">Metode Pembayaran</p>
|
|
<div class="pie-chart" style="--pie: conic-gradient(#7c3aed 0 {{ $cashEnd }}%, #2563eb {{ $cashEnd }}% 100%);"></div>
|
|
<div class="pie-legend">
|
|
<div class="legend-item">
|
|
<span class="legend-label"><span class="legend-dot" style="--dot:#7c3aed;"></span>Tunai</span>
|
|
<span>{{ $cashCount }}</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-label"><span class="legend-dot" style="--dot:#2563eb;"></span>Transfer Bank</span>
|
|
<span>{{ $transferCount }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<h3>Detail Transaksi</h3>
|
|
<p class="text-muted">Daftar transaksi sesuai filter tanggal.</p>
|
|
</div>
|
|
<a href="{{ route('input') }}" class="button-primary" style="text-decoration:none;">Input Transaksi</a>
|
|
</div>
|
|
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Kode</th>
|
|
<th>Customer</th>
|
|
<th>Mobil</th>
|
|
<th>Metode</th>
|
|
<th>Status</th>
|
|
<th>Total</th>
|
|
<th>Tanggal</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($transactions as $transaction)
|
|
<tr>
|
|
<td>{{ $transaction->transaction_code }}</td>
|
|
<td>
|
|
<strong>{{ $transaction->customer_name }}</strong>
|
|
@if($transaction->customer_phone)
|
|
<br><span class="text-muted">{{ $transaction->customer_phone }}</span>
|
|
@endif
|
|
</td>
|
|
<td>{{ $transaction->category->name ?? 'Produk dihapus' }}</td>
|
|
<td>{{ $transaction->payment_method_label }}</td>
|
|
<td>
|
|
@if($transaction->payment_status === 'paid')
|
|
<span class="tag tag-active">{{ $transaction->payment_status_label }}</span>
|
|
@elseif($transaction->payment_status === 'cancelled')
|
|
<span class="tag tag-inactive">{{ $transaction->payment_status_label }}</span>
|
|
@else
|
|
<span class="badge" style="background:#fef3c7; color:#92400e;">{{ $transaction->payment_status_label }}</span>
|
|
@endif
|
|
</td>
|
|
<td>Rp {{ number_format($transaction->total_amount, 0, ',', '.') }}</td>
|
|
<td>{{ $transaction->created_at ? $transaction->created_at->format('d/m/Y H:i') : '-' }}</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7" style="text-align:center; color:#64748b; padding:24px 0;">Tidak ada transaksi pada periode ini.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
@endsection
|