SIPDAM/samooflutter/lib/keuangan/Kasbon.dart

261 lines
9.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../api/KasbonApi.dart';
const _bg = Color(0xFFF9FAFB);
const _bg1 = Color(0xFFFFFFFF);
const _bg2 = Color(0xFFF3F4F6);
const _green = Color(0xFF10B981);
const _greenDim = Color(0x1A10B981);
const _cyan = Color(0xFF06B6D4);
const _cyanDim = Color(0x1A06B6D4);
const _amber = Color(0xFFF59E0B);
const _amberDim = Color(0x1AF59E0B);
const _rose = Color(0xFFEF4444);
const _roseDim = Color(0x1AEF4444);
const _t1 = Color(0xFF111827);
const _t2 = Color(0xFF6B7280);
const _t3 = Color(0xFF9CA3AF);
const _line2 = Color(0xFFE5E7EB);
class KasbonRiwayatScreen extends StatefulWidget {
const KasbonRiwayatScreen({super.key});
@override
State<KasbonRiwayatScreen> createState() => _KasbonRiwayatScreenState();
}
class _KasbonRiwayatScreenState extends State<KasbonRiwayatScreen> {
final KasbonApi _apiService = KasbonApi();
List<dynamic> _riwayat = [];
Map<String, dynamic>? _statistik;
bool _isLoading = false;
@override
void initState() {
super.initState();
_fetchData();
}
Future<void> _fetchData() async {
setState(() => _isLoading = true);
try {
final results = await Future.wait([
_apiService.getRiwayat(),
_fetchStatistik(),
]);
if (mounted) {
setState(() {
final resRiwayat = results[0] as Map<String, dynamic>?;
final resStat = results[1] as Map<String, dynamic>?;
if (resRiwayat != null && resRiwayat['success'] == true) {
// Laravel paginate structure
final rawData = resRiwayat['data'];
if (rawData is Map && rawData.containsKey('data')) {
_riwayat = rawData['data'] ?? [];
} else {
_riwayat = rawData ?? [];
}
}
if (resStat != null) {
_statistik = resStat;
}
_isLoading = false;
});
}
} catch (e) {
if (mounted) setState(() => _isLoading = false);
}
}
Future<Map<String, dynamic>?> _fetchStatistik() async {
// API Route: /kasbon/statistik
try {
final response = await _apiService.getStatistik(); // Use the new method
return response['data'];
} catch (e) { return null; }
}
String _formatCurrency(dynamic amount) {
final formatter = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0);
return formatter.format(amount ?? 0);
}
@override
Widget build(BuildContext context) {
return _isLoading
? const Center(child: CircularProgressIndicator(color: _rose))
: RefreshIndicator(
onRefresh: _fetchData,
color: _rose,
backgroundColor: _bg1,
child: CustomScrollView(
slivers: [
// STATS SECTION
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('RINGKASAN KASBON',
style: TextStyle(color: _t3, fontSize: 10, fontWeight: FontWeight.w900, letterSpacing: 1.5)),
const SizedBox(height: 12),
_buildTotalKasbonCard(),
],
),
),
),
// LIST SECTION
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('RIWAYAT PINJAMAN',
style: TextStyle(color: _t3, fontSize: 10, fontWeight: FontWeight.w900, letterSpacing: 1.5)),
Text('${_riwayat.length} Transaksi',
style: const TextStyle(color: _t2, fontSize: 10)),
],
),
),
),
const SliverPadding(padding: EdgeInsets.only(top: 12)),
_riwayat.isEmpty
? SliverToBoxAdapter(child: _buildEmptyState())
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: _buildKasbonCard(_riwayat[i]),
),
childCount: _riwayat.length,
),
),
const SliverPadding(padding: EdgeInsets.only(bottom: 32)),
],
),
);
}
Widget _buildTotalKasbonCard() {
final outstanding = _statistik?['total_hutang'] ?? _statistik?['outstanding_kasbon'] ?? 0;
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [_rose, _rose.withOpacity(0.7)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(color: _rose.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 8)),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Sisa Hutang Kasbon',
style: TextStyle(color: Colors.white70, fontSize: 12, fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
Text(_formatCurrency(outstanding),
style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: -0.5)),
],
),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(Icons.money_off_rounded, color: Colors.white, size: 28),
),
],
),
);
}
Widget _buildEmptyState() {
return Padding(
padding: const EdgeInsets.only(top: 40),
child: Center(
child: Column(
children: [
Icon(Icons.history_rounded, size: 48, color: _t3),
const SizedBox(height: 12),
const Text('Belum ada riwayat kasbon', style: TextStyle(color: _t2, fontSize: 13)),
],
),
),
);
}
Widget _buildKasbonCard(dynamic item) {
final bool isLunas = item['status'] == 'Lunas';
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _line2),
),
child: Row(
children: [
Container(
width: 44, height: 44,
decoration: BoxDecoration(
color: isLunas ? _greenDim : _roseDim,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: (isLunas ? _green : _rose).withOpacity(0.3)),
),
child: Icon(isLunas ? Icons.check_circle_rounded : Icons.pending_rounded,
color: isLunas ? _green : _rose, size: 20),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_formatCurrency(item['nominal']),
style: const TextStyle(color: _t1, fontWeight: FontWeight.w800, fontSize: 15)),
const SizedBox(height: 4),
Text(item['keperluan'] ?? 'Tanpa keterangan',
style: const TextStyle(color: _t2, fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis),
const SizedBox(height: 4),
Text(item['tanggal'] ?? '-',
style: const TextStyle(color: _t3, fontSize: 10)),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isLunas ? _greenDim : _roseDim,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: (isLunas ? _green : _rose).withOpacity(0.25)),
),
child: Text(item['status_label']?.toUpperCase() ?? '-',
style: TextStyle(
color: isLunas ? _green : _rose,
fontSize: 10,
fontWeight: FontWeight.w900,
letterSpacing: 0.5)),
),
],
),
);
}
}