MIF_E31230892/sim-pkpps/resources/views/admin/uang-saku/riwayat.blade.php

504 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('layouts.app')
@section('content')
<div class="page-header">
<h2><i class="fas fa-history"></i> Riwayat Uang Saku &mdash; {{ $santri->nama_lengkap }}</h2>
</div>
{{-- Filter Periode --}}
<div class="content-box" style="margin-bottom:14px;">
<form method="GET" action="{{ route('admin.uang-saku.riwayat', $santri->id_santri) }}" id="filterPeriode">
<div style="display:flex;align-items:flex-end;gap:11px;flex-wrap:wrap;">
<div class="form-group" style="margin-bottom:0;flex:1;min-width:200px;">
<label for="tanggal_dari" style="display:block;margin-bottom:5px;font-weight:600;">
<i class="fas fa-calendar-alt"></i> Dari Tanggal
</label>
<input type="date" name="tanggal_dari" id="tanggal_dari"
class="form-control" value="{{ $tanggalDari }}" max="{{ date('Y-m-d') }}">
</div>
<div class="form-group" style="margin-bottom:0;flex:1;min-width:200px;">
<label for="tanggal_sampai" style="display:block;margin-bottom:5px;font-weight:600;">
<i class="fas fa-calendar-check"></i> Sampai Tanggal
</label>
<input type="date" name="tanggal_sampai" id="tanggal_sampai"
class="form-control" value="{{ $tanggalSampai }}" max="{{ date('Y-m-d') }}">
</div>
<div style="display:flex;gap:10px;">
<button type="submit" class="btn btn-primary">
<i class="fas fa-filter"></i> Terapkan
</button>
<button type="button" class="btn btn-success" onclick="setBulanIni()">
<i class="fas fa-calendar-day"></i> Bulan Ini
</button>
<a href="{{ route('admin.uang-saku.riwayat', $santri->id_santri) }}" class="btn btn-secondary">
<i class="fas fa-redo"></i> Reset
</a>
</div>
</div>
</form>
</div>
{{-- ═══════════════════════════════════════════════════
PERSAMAAN SALDO PERIODE
════════════════════════════════════════════════════ --}}
<div style="margin-bottom:10px;">
<div style="font-size:.78rem;font-weight:600;color:var(--text-light);
text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;">
<i class="fas fa-calculator" style="color:var(--primary-color);"></i>
Ringkasan Periode:
<strong style="color:var(--text-color);">
{{ $periodeDari->format('d M Y') }} {{ $periodeSampai->format('d M Y') }}
</strong>
</div>
{{-- Persamaan saldo --}}
<div style="display:grid;
grid-template-columns: 1fr 32px 1fr 32px 1fr 32px 1fr;
gap:0;align-items:stretch;
border-radius:12px;overflow:hidden;
border:1.5px solid #e2e8f0;background:#fff;
margin-bottom:10px;">
{{-- Saldo Awal --}}
<div style="padding:14px 16px;border-right:1px solid #e2e8f0;">
<div style="font-size:.66rem;color:#aaa;text-transform:uppercase;letter-spacing:.4px;margin-bottom:5px;">
Saldo Awal Periode
</div>
<div style="font-size:1rem;font-weight:800;color:#4a5568;">
Rp {{ number_format($saldoAwalPeriode, 0, ',', '.') }}
</div>
<div style="font-size:.66rem;color:#bbb;margin-top:3px;">
per {{ $periodeDari->format('d M Y') }}
@if($saldoAwalPeriode == 0)
<span style="color:#f5a623;">(belum ada saldo sebelumnya)</span>
@endif
</div>
</div>
{{-- + --}}
<div style="display:flex;align-items:center;justify-content:center;
font-size:1.3rem;font-weight:300;color:#6FBA9D;
border-right:1px solid #e2e8f0;">+</div>
{{-- Pemasukan --}}
<div style="padding:14px 16px;border-right:1px solid #e2e8f0;">
<div style="font-size:.66rem;color:#aaa;text-transform:uppercase;letter-spacing:.4px;margin-bottom:5px;">
Pemasukan Periode
</div>
<div style="font-size:1rem;font-weight:800;color:#6FBA9D;">
Rp {{ number_format($totalPemasukan, 0, ',', '.') }}
</div>
<div style="font-size:.66rem;color:#bbb;margin-top:3px;">uang masuk di periode ini</div>
</div>
{{-- --}}
<div style="display:flex;align-items:center;justify-content:center;
font-size:1.3rem;font-weight:300;color:#FF8B94;
border-right:1px solid #e2e8f0;">&minus;</div>
{{-- Pengeluaran --}}
<div style="padding:14px 16px;border-right:1px solid #e2e8f0;">
<div style="font-size:.66rem;color:#aaa;text-transform:uppercase;letter-spacing:.4px;margin-bottom:5px;">
Pengeluaran Periode
</div>
<div style="font-size:1rem;font-weight:800;color:#FF8B94;">
Rp {{ number_format($totalPengeluaran, 0, ',', '.') }}
</div>
<div style="font-size:.66rem;color:#bbb;margin-top:3px;">uang keluar di periode ini</div>
</div>
{{-- = --}}
<div style="display:flex;align-items:center;justify-content:center;
font-size:1.3rem;font-weight:300;color:#718096;
border-right:1px solid #e2e8f0;">=</div>
{{-- Saldo Akhir --}}
<div style="padding:14px 16px;background:linear-gradient(135deg,#f0fdf7 0%,#e6f9f2 100%);">
<div style="font-size:.66rem;color:#aaa;text-transform:uppercase;letter-spacing:.4px;margin-bottom:5px;">
Saldo Akhir Periode
</div>
<div style="font-size:1rem;font-weight:800;
color:{{ $saldoAkhirPeriode >= 0 ? '#38a169' : '#e53e3e' }};">
Rp {{ number_format($saldoAkhirPeriode, 0, ',', '.') }}
</div>
<div style="font-size:.66rem;color:#bbb;margin-top:3px;">
per {{ $periodeSampai->format('d M Y') }}
</div>
</div>
</div>
{{-- Notif jika saldo akhir periode saldo saat ini --}}
@if(round($saldoAkhirPeriode) !== round($saldoTerakhir))
<div style="background:#fffbeb;border:1px solid #fbd38d;border-radius:9px;
padding:10px 14px;font-size:.8rem;color:#744210;margin-bottom:10px;
display:flex;align-items:flex-start;gap:8px;">
<i class="fas fa-exclamation-triangle" style="color:#f5a623;margin-top:2px;flex-shrink:0;"></i>
<div>
<strong>Catatan:</strong>
Saldo akhir periode (Rp {{ number_format($saldoAkhirPeriode, 0, ',', '.') }})
berbeda dengan <strong>Saldo Saat Ini (Rp {{ number_format($saldoTerakhir, 0, ',', '.') }})</strong>
karena ada transaksi di luar rentang
{{ $periodeDari->format('d M') }}{{ $periodeSampai->format('d M Y') }}.
</div>
</div>
@endif
{{-- Saldo saat ini (real-time) --}}
<div style="background:linear-gradient(135deg,#ebf8ff 0%,#bee3f8 40%,#ebf8ff 100%);
border:1.5px solid #90cdf4;border-radius:12px;
padding:14px 18px;display:flex;justify-content:space-between;
align-items:center;flex-wrap:wrap;gap:10px;">
<div>
<div style="font-size:.66rem;color:#2b6cb0;text-transform:uppercase;
letter-spacing:.5px;margin-bottom:4px;font-weight:600;">
<i class="fas fa-wallet"></i> Saldo Saat Ini (Akumulasi Semua Waktu)
</div>
<div style="font-size:1.4rem;font-weight:900;
color:{{ $saldoTerakhir >= 0 ? '#2b6cb0' : '#e53e3e' }};">
Rp {{ number_format($saldoTerakhir, 0, ',', '.') }}
</div>
</div>
<div style="font-size:.78rem;color:#2c5282;text-align:right;max-width:300px;line-height:1.5;">
<i class="fas fa-info-circle"></i>
Total uang santri saat ini, dihitung dari <strong>seluruh transaksi sejak awal</strong>,
bukan hanya periode yang ditampilkan.
</div>
</div>
</div>
{{-- ═══════════════════════════════════════════════════
GRAFIK PERJALANAN SALDO
════════════════════════════════════════════════════ --}}
<div class="content-box" style="margin-bottom:22px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;
flex-wrap:wrap;gap:8px;margin-bottom:16px;">
<div>
<h3 style="margin:0 0 4px;color:var(--primary-color);">
<i class="fas fa-chart-area"></i> Perjalanan Saldo
</h3>
<div style="font-size:.78rem;color:var(--text-light);">
{{ $periodeDari->format('d M Y') }} {{ $periodeSampai->format('d M Y') }}
&bull; Grafik menunjukkan nilai saldo aktual setiap hari
</div>
</div>
{{-- Mini legend --}}
<div style="display:flex;gap:14px;align-items:center;font-size:.78rem;color:var(--text-light);">
<span>
<span style="display:inline-block;width:24px;height:3px;background:#6FBA9D;
vertical-align:middle;border-radius:2px;margin-right:5px;"></span>
Saldo naik (pemasukan)
</span>
<span>
<span style="display:inline-block;width:24px;height:3px;background:#FF8B94;
vertical-align:middle;border-radius:2px;margin-right:5px;"></span>
Saldo turun (pengeluaran)
</span>
</div>
</div>
<canvas id="chartSaldo" style="max-height:380px;"></canvas>
</div>
{{-- Action Buttons --}}
<div class="content-box" style="margin-bottom:14px;">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:11px;">
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<a href="{{ route('admin.uang-saku.index') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Kembali
</a>
<a href="{{ route('admin.santri.show', $santri->id) }}" class="btn btn-primary">
<i class="fas fa-user"></i> Profil Santri
</a>
</div>
{{-- Tambah Transaksi: hanya pamong --}}
@if($canCrud)
<a href="{{ route('admin.uang-saku.create') }}?id_santri={{ $santri->id_santri }}"
class="btn btn-success">
<i class="fas fa-plus"></i> Tambah Transaksi
</a>
@endif
</div>
</div>
{{-- Tabel Riwayat --}}
<div class="content-box">
<h3 style="margin-bottom:12px;color:var(--text-color);">
<i class="fas fa-list"></i> Daftar Transaksi
@if($transaksi->total() > 0)
<span style="color:var(--text-light);font-weight:400;">
({{ $transaksi->total() }} transaksi)
</span>
@endif
</h3>
@if($transaksi->count() > 0)
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;
padding:9px 14px;margin-bottom:12px;font-size:.78rem;color:var(--text-light);">
<i class="fas fa-info-circle" style="color:var(--primary-color);"></i>
<strong>Saldo Sebelum</strong> &amp; <strong>Saldo Sesudah</strong>
menunjukkan saldo kumulatif santri sebelum dan setelah tiap transaksi.
</div>
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th style="width:4%;">No</th>
<th style="width:11%;">ID</th>
<th style="width:10%;">Tanggal</th>
<th style="width:10%;">Jenis</th>
<th style="width:13%;">Nominal</th>
<th style="width:13%;">Saldo Sebelum</th>
<th style="width:13%;">Saldo Sesudah</th>
<th style="width:20%;">Keterangan</th>
<th style="width:6%;" class="text-center">Aksi</th>
</tr>
</thead>
<tbody>
@foreach($transaksi as $index => $item)
<tr>
<td>{{ $transaksi->firstItem() + $index }}</td>
<td><strong>{{ $item->id_uang_saku }}</strong></td>
<td>{{ $item->tanggal_transaksi->format('d/m/Y') }}</td>
<td>
@if($item->jenis_transaksi === 'pemasukan')
<span class="badge badge-success">
<i class="fas fa-arrow-down"></i> Masuk
</span>
@else
<span class="badge badge-danger">
<i class="fas fa-arrow-up"></i> Keluar
</span>
@endif
</td>
<td class="nominal-highlight">{{ $item->nominal_format }}</td>
<td style="color:#718096;">
Rp {{ number_format($item->saldo_sebelum, 0, ',', '.') }}
</td>
<td>
<strong style="color:{{ $item->saldo_sesudah >= 0 ? '#38a169' : '#e53e3e' }};">
{{ $item->saldo_sesudah_format }}
</strong>
</td>
<td><div class="content-preview">{{ $item->keterangan ?? '-' }}</div></td>
<td class="text-center">
<div style="display:flex;gap:4px;justify-content:center;">
<a href="{{ route('admin.uang-saku.show', $item->id) }}"
class="btn btn-primary btn-sm" title="Detail">
<i class="fas fa-eye"></i>
</a>
{{-- Edit: hanya pamong --}}
@if($canCrud)
<a href="{{ route('admin.uang-saku.edit', $item->id) }}"
class="btn btn-warning btn-sm" title="Edit">
<i class="fas fa-edit"></i>
</a>
@endif
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div style="margin-top:14px;">{{ $transaksi->links() }}</div>
@else
<div class="empty-state">
<i class="fas fa-calendar-times"></i>
<h3>Tidak Ada Transaksi</h3>
<p>
Tidak ada transaksi pada periode
{{ $periodeDari->format('d F Y') }} &ndash; {{ $periodeSampai->format('d F Y') }}.
</p>
@if(round($saldoTerakhir) > 0)
<p style="font-size:.85rem;color:var(--text-light);">
Santri memiliki saldo
<strong>Rp {{ number_format($saldoTerakhir, 0, ',', '.') }}</strong>
dari transaksi di periode lain.
</p>
@endif
@if($canCrud)
<a href="{{ route('admin.uang-saku.create') }}?id_santri={{ $santri->id_santri }}"
class="btn btn-success">
<i class="fas fa-plus"></i> Tambah Transaksi
</a>
@endif
</div>
@endif
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script>
// ═══════════════════════════════════════════════════════════════
// DATA dari Laravel
// ═══════════════════════════════════════════════════════════════
const rawData = @json($dataGrafikSaldo);
// ═══════════════════════════════════════════════════════════════
// Bangun dataset perjalanan saldo
// ═══════════════════════════════════════════════════════════════
const labels = [];
const saldoArr = [];
rawData.forEach((d, i) => {
const dt = new Date(d.tanggal + 'T00:00:00');
const opt = { day: 'numeric', month: 'short' };
labels.push(
d.is_awal
? dt.toLocaleDateString('id-ID', opt) + ' (Awal)'
: dt.toLocaleDateString('id-ID', opt)
);
saldoArr.push(d.saldo);
});
function segmentColor(ctx, naik, turun, flat) {
const i = ctx.p1DataIndex;
const cur = saldoArr[i];
const prv = saldoArr[i - 1] ?? cur;
if (cur > prv) return naik;
if (cur < prv) return turun;
return flat;
}
function buildGradient(ctx) {
const chart = ctx.chart;
const { chartArea } = chart;
if (!chartArea) return 'rgba(111,186,157,0.15)';
const gradient = chart.ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
gradient.addColorStop(0, 'rgba(111,186,157,0.25)');
gradient.addColorStop(0.6, 'rgba(111,186,157,0.06)');
gradient.addColorStop(1, 'rgba(111,186,157,0.0)');
return gradient;
}
const ctx = document.getElementById('chartSaldo').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
label: 'Saldo',
data: saldoArr,
borderWidth: 3,
tension: 0.35,
fill: true,
backgroundColor: buildGradient,
pointRadius: function(ctx) {
const i = ctx.dataIndex;
const cur = saldoArr[i];
const prv = saldoArr[i - 1] ?? cur;
return cur !== prv || i === 0 || i === saldoArr.length - 1 ? 6 : 3;
},
pointHoverRadius: 8,
pointBackgroundColor: function(ctx) {
const i = ctx.dataIndex;
const cur = saldoArr[i];
const prv = saldoArr[i - 1] ?? cur;
if (i === 0) return '#718096';
if (cur > prv) return '#6FBA9D';
if (cur < prv) return '#FF8B94';
return '#d1d9e0';
},
pointBorderColor: '#fff',
pointBorderWidth: 2,
segment: {
borderColor: ctx => segmentColor(ctx, '#6FBA9D', '#FF8B94', '#d1d9e0'),
},
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
interaction: { intersect: false, mode: 'index' },
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(30,41,59,0.92)',
padding: 13,
cornerRadius: 10,
titleFont: { size: 13, weight: 'bold' },
bodyFont: { size: 13 },
callbacks: {
title: items => items[0].label,
label: item => {
const i = item.dataIndex;
const cur = saldoArr[i];
const prv = saldoArr[i - 1] ?? cur;
const diff = cur - prv;
const fmt = v => 'Rp\u00a0' + new Intl.NumberFormat('id-ID').format(v);
let lines = [' Saldo: ' + fmt(cur)];
if (i > 0 && diff !== 0) {
const sign = diff > 0 ? '▲ +' : '▼ ';
lines.push(' ' + sign + fmt(Math.abs(diff))
+ (diff > 0 ? ' (pemasukan)' : ' (pengeluaran)'));
} else if (i > 0) {
lines.push(' — tidak ada transaksi');
}
return lines;
},
labelColor: item => {
const i = item.dataIndex;
const cur = saldoArr[i];
const prv = saldoArr[i - 1] ?? cur;
const c = i === 0 ? '#718096'
: cur > prv ? '#6FBA9D'
: cur < prv ? '#FF8B94' : '#d1d9e0';
return { borderColor: c, backgroundColor: c, borderRadius: 3 };
}
}
}
},
scales: {
y: {
min: function() {
const minVal = Math.min(...saldoArr);
return Math.max(0, Math.floor(minVal * 0.8 / 10000) * 10000);
}(),
ticks: {
callback: v => 'Rp\u00a0' + new Intl.NumberFormat('id-ID', {
notation: 'compact', compactDisplay: 'short'
}).format(v),
font: { size: 12 },
maxTicksLimit: 8,
},
grid: { color: 'rgba(0,0,0,.05)', drawBorder: false }
},
x: {
grid: { display: false, drawBorder: false },
ticks: {
font: { size: 11 },
maxRotation: 45,
minRotation: 45,
maxTicksLimit: rawData.length > 20 ? 12 : rawData.length,
autoSkip: true,
}
}
},
animation: { duration: 1200, easing: 'easeInOutQuart' }
}
});
// ═══════════════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════════════
function setBulanIni() {
const today = new Date();
const first = new Date(today.getFullYear(), today.getMonth(), 1);
const last = new Date(today.getFullYear(), today.getMonth() + 1, 0);
document.getElementById('tanggal_dari').value = first.toISOString().split('T')[0];
document.getElementById('tanggal_sampai').value = last.toISOString().split('T')[0];
document.getElementById('filterPeriode').submit();
}
document.getElementById('tanggal_sampai').addEventListener('change', function () {
const dari = document.getElementById('tanggal_dari').value;
if (dari && this.value && this.value < dari) {
alert('Tanggal sampai tidak boleh lebih kecil dari tanggal dari!');
this.value = dari;
}
});
</script>
@endsection