TA/web_JMW/resources/views/admin/laporan.blade.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