SIPDAM/samooflutter/lib/main.dart

836 lines
32 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:device_preview/device_preview.dart';
import 'package:flutter/foundation.dart';
import 'login/login.dart';
import '../api/LoginApi.dart';
import '../absensi/absensi.dart';
import '../api/AbsensiApi.dart';
import 'models/Absensi_model.dart';
import 'tugas/Penugasan.dart';
import 'tugas/Progress.dart';
import '../api/PenugasanApi.dart';
import 'models/Penugasan_model.dart';
import 'keuangan/FinanceMain.dart';
import 'profil/Profil.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
// ── Palette (konsisten dengan login screen) ──────────────────────────────────
const _bg = Color(0xFFF9FAFB);
const _bg1 = Color(0xFFFFFFFF);
const _bg2 = Color(0xFFF3F4F6);
const _green = Color(0xFF10B981);
const _greenDim = Color(0x1A10B981);
const _greenGlow = Color(0x4D10B981);
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 _violet = Color(0xFF8B5CF6);
const _violetDim = Color(0x1A8B5CF6);
const _t1 = Color(0xFF111827);
const _t2 = Color(0xFF6B7280);
const _t3 = Color(0xFF9CA3AF);
const _line2 = Color(0xFFE5E7EB);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting('id_ID', null);
runApp(
DevicePreview(
enabled: !kReleaseMode,
builder: (context) => const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
useInheritedMediaQuery: true,
builder: DevicePreview.appBuilder,
locale: DevicePreview.locale(context),
debugShowCheckedModeBanner: false,
title: 'PDAM Teknisi',
theme: ThemeData(
fontFamily: 'Roboto',
brightness: Brightness.light,
scaffoldBackgroundColor: _bg,
primarySwatch: Colors.blue,
colorScheme: ColorScheme.fromSeed(
seedColor: _green,
brightness: Brightness.light,
primary: _green,
surface: _bg1,
background: _bg,
),
useMaterial3: true,
),
initialRoute: '/',
routes: {
'/': (context) => const LoginScreen(),
'/dashboard': (context) => const DashboardScreen(),
},
);
}
}
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
int _selectedIndex = 0;
final ApiService _apiService = ApiService();
Map<String, dynamic>? _userData;
Map<String, dynamic>? _dashboardData;
bool _isLoadingProfile = false;
bool _isLoadingDashboard = false;
@override
void initState() {
super.initState();
_loadUserData();
_loadDashboardData();
}
Future<void> _loadUserData() async {
setState(() { _isLoadingProfile = true; });
final localUserData = await _apiService.getUserData();
if (localUserData != null) {
setState(() { _userData = localUserData; });
}
final result = await _apiService.getProfile();
setState(() { _isLoadingProfile = false; });
if (result['success'] == true && result['data'] != null) {
setState(() { _userData = result['data']; });
} else if (result['logout'] == true) {
if (mounted) {
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
}
}
}
Future<void> _loadDashboardData() async {
setState(() { _isLoadingDashboard = true; });
final result = await _apiService.getDashboardData();
if (mounted) {
setState(() {
_isLoadingDashboard = false;
if (result['success'] == true) {
_dashboardData = result['data'];
}
});
}
}
List<Widget> get _pages => [
HomePage(
userData: _userData,
dashboardData: _dashboardData,
isLoading: _isLoadingProfile || _isLoadingDashboard,
onNavigate: _onItemTapped,
),
const AbsensiScreen(),
PenugasanScreen(),
ProgressScreen(),
const FinanceMainScreen(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bg,
body: _pages[_selectedIndex],
bottomNavigationBar: _buildBottomNav(),
);
}
Widget _buildBottomNav() {
final items = [
{'icon': Icons.grid_view_rounded, 'label': 'Dashboard'},
{'icon': Icons.fingerprint_rounded, 'label': 'Absensi'},
{'icon': Icons.assignment_rounded, 'label': 'Tugas'},
{'icon': Icons.trending_up_rounded, 'label': 'Progress'},
{'icon': Icons.account_balance_wallet_rounded, 'label': 'Gaji'},
];
return Container(
decoration: const BoxDecoration(
color: _bg1,
border: Border(top: BorderSide(color: _line2, width: 1)),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(items.length, (i) {
final active = i == _selectedIndex;
return GestureDetector(
onTap: () => _onItemTapped(i),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 6),
decoration: active
? BoxDecoration(
color: _greenDim,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _green.withOpacity(0.3)),
)
: null,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
items[i]['icon'] as IconData,
size: 22,
color: active ? _green : _t3,
),
const SizedBox(height: 3),
Text(
items[i]['label'] as String,
style: TextStyle(
fontSize: 10,
fontWeight: active
? FontWeight.w700 : FontWeight.w400,
color: active ? _green : _t3,
letterSpacing: active ? 0.3 : 0,
),
),
],
),
),
);
}),
),
),
),
);
}
}
class HomePage extends StatelessWidget {
final Map<String, dynamic>? userData;
final Map<String, dynamic>? dashboardData;
final bool isLoading;
final Function(int) onNavigate;
const HomePage({
super.key,
this.userData,
this.dashboardData,
this.isLoading = false,
required this.onNavigate,
});
Future<void> _handleLogout(BuildContext context) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => Dialog(
backgroundColor: _bg1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: _line2),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 52, height: 52,
decoration: BoxDecoration(
color: _roseDim,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: _rose.withOpacity(0.3)),
),
child: const Icon(Icons.logout_rounded,
color: _rose, size: 24),
),
const SizedBox(height: 16),
const Text('Konfirmasi Logout',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w700,
color: _t1)),
const SizedBox(height: 8),
const Text('Apakah Anda yakin ingin keluar?',
style: TextStyle(fontSize: 13, color: _t2),
textAlign: TextAlign.center),
const SizedBox(height: 24),
Row(children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context, false),
style: OutlinedButton.styleFrom(
foregroundColor: _t2,
side: const BorderSide(color: _line2),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
child: const Text('Batal'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: _rose,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
child: const Text('Logout',
style: TextStyle(fontWeight: FontWeight.w700)),
),
),
]),
],
),
),
),
);
if (confirmed == true && context.mounted) {
final apiService = ApiService();
await apiService.logout();
if (context.mounted) {
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
}
}
}
@override
Widget build(BuildContext context) {
final teknisi = userData?['teknisi'];
final namaTeknisi = (teknisi?['nama'] ?? 'Teknisi') as String;
final idTeknisi = teknisi?['id_teknisi'] ?? '-';
final username = userData?['username'] ?? '-';
final initial = namaTeknisi.isNotEmpty
? namaTeknisi[0].toUpperCase() : 'T';
return Scaffold(
backgroundColor: _bg,
appBar: AppBar(
elevation: 0,
backgroundColor: _bg1,
title: Row(children: [
Container(
width: 30, height: 30,
decoration: BoxDecoration(
color: _greenDim,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: _green.withOpacity(0.35)),
),
child: const Icon(Icons.water_drop_rounded,
color: _green, size: 15),
),
const SizedBox(width: 10),
const Text('PDAM Teknisi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: _t1,
letterSpacing: 0.2)),
]),
actions: [
IconButton(
icon: const Icon(Icons.account_circle_rounded, color: _t2, size: 24),
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ProfilScreen())),
),
IconButton(
icon: const Icon(Icons.logout_rounded, color: _rose, size: 22),
onPressed: () => _handleLogout(context),
),
const SizedBox(width: 8),
],
),
body: RefreshIndicator(
color: _green,
backgroundColor: _bg1,
onRefresh: () async {
final apiService = ApiService();
await apiService.getProfile();
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 20, 20, 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// New Premium Header with Attendance Status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Halo, $namaTeknisi!',
style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: _t1, letterSpacing: -1)),
const SizedBox(height: 4),
Row(
children: [
Container(width: 8, height: 8, decoration: const BoxDecoration(color: _green, shape: BoxShape.circle)),
const SizedBox(width: 8),
const Text('Online & Siap Bertugas', style: TextStyle(fontSize: 12, color: _t2, fontWeight: FontWeight.w600)),
],
),
],
),
],
),
const SizedBox(height: 32),
// Wallet & Earnings Section (Horizontal Scroll or Compact)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.circular(28),
border: Border.all(color: _line2),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 20, offset: const Offset(0, 10))],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('RINGKASAN PENDAPATAN', style: TextStyle(fontSize: 10, fontWeight: FontWeight.w800, color: _t3, letterSpacing: 1.5)),
Icon(Icons.account_balance_wallet_rounded, color: _green.withOpacity(0.5), size: 18),
],
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Estimasi Gaji', style: TextStyle(fontSize: 11, color: _t2, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(_formatCurrency(dashboardData?['statistik']?['estimasi_gaji'] ?? 0),
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: _green)),
],
),
),
Container(width: 1, height: 40, color: _line2),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Kasbon Aktif', style: TextStyle(fontSize: 11, color: _t2, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(_formatCurrency(dashboardData?['statistik']?['total_kasbon'] ?? 0),
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: _rose)),
],
),
),
],
),
],
),
),
const SizedBox(height: 28),
// New Performance & Statistics Section
_sectionLabel('RINGKASAN KINERJA BULAN INI'),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.circular(32),
border: Border.all(color: _line2),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 30, offset: const Offset(0, 15))],
),
child: Row(
children: [
// Circular Progress
Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 86, height: 86,
child: CircularProgressIndicator(
value: ((dashboardData?['statistik']?['kehadiran'] ?? 0) as num) / 100.0,
strokeWidth: 9,
backgroundColor: _green.withOpacity(0.08),
valueColor: const AlwaysStoppedAnimation<Color>(_green),
strokeCap: StrokeCap.round,
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('${dashboardData?['statistik']?['kehadiran'] ?? 0}%', style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w900, color: _t1, letterSpacing: -0.5)),
Text('Hadir', style: TextStyle(fontSize: 9, color: _green.withOpacity(0.6), fontWeight: FontWeight.w800, letterSpacing: 0.5)),
],
),
],
),
const SizedBox(width: 24),
// Stats Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_miniRow(Icons.check_circle_outline_rounded, _green, 'Tugas Selesai', '${dashboardData?['statistik']?['tugas_selesai'] ?? 0} Item'),
const SizedBox(height: 12),
_miniRow(Icons.timer_outlined, _amber, 'Jam Kerja', '${dashboardData?['statistik']?['jam_kerja'] ?? 0} Jam'),
const SizedBox(height: 12),
_miniRow(Icons.trending_up_rounded, _cyan, 'Efisiensi', '+${dashboardData?['statistik']?['efisiensi'] ?? 0}%'),
],
),
),
],
),
),
const SizedBox(height: 28),
// Quick Actions or Status
_sectionLabel('AKTIVITAS TERAKHIR'),
const SizedBox(height: 16),
_buildModernStatCard(
'Gaji Terakhir Diterima',
_formatCurrency(dashboardData?['statistik']?['gaji_terakhir'] ?? 0),
Icons.history_rounded, _cyan,
isWide: true
),
],
),
),
),
);
}
Widget _miniRow(IconData icon, Color color, String label, String value) {
return Row(
children: [
Icon(icon, color: color, size: 14),
const SizedBox(width: 8),
Text(label, style: const TextStyle(fontSize: 11, color: _t2, fontWeight: FontWeight.w500)),
const Spacer(),
Text(value, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w800, color: _t1)),
],
);
}
Widget _buildModernStatCard(String title, String value, IconData icon, Color color, {bool isWide = false}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: _line2),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 10))
],
),
child: isWide
? Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(14)),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: _t3, letterSpacing: 0.5)),
const SizedBox(height: 4),
Text(value, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: color)),
],
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(height: 20),
Text(value, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: color)),
const SizedBox(height: 4),
Text(title, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: _t3)),
],
),
);
}
Widget _pill(String text, Color bg, Color fg) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: bg,
borderRadius: BorderRadius.circular(99),
border: Border.all(color: fg.withOpacity(0.25)),
),
child: Text(text, style: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: fg)),
);
}
Widget _sectionLabel(String label) {
return Text(label, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: _t3, letterSpacing: 1.4));
}
Widget _buildStatCard(String title, String value, IconData icon, Color color, Color dimColor) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: _line2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 34, height: 34,
decoration: BoxDecoration(
color: dimColor,
borderRadius: BorderRadius.circular(9),
border: Border.all(color: color.withOpacity(0.25)),
),
child: Icon(icon, color: color, size: 17),
),
const SizedBox(height: 10),
Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.w800, color: color, letterSpacing: -0.5)),
const SizedBox(height: 2),
Text(title, style: const TextStyle(fontSize: 11, color: _t3, fontWeight: FontWeight.w500)),
],
),
);
}
Widget _buildMenuCard(String title, IconData icon, Color color, Color dimColor, VoidCallback onTap) {
return Material(
color: _bg1, borderRadius: BorderRadius.circular(14),
child: InkWell(
onTap: onTap, borderRadius: BorderRadius.circular(14),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(14), border: Border.all(color: _line2)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 36, height: 36,
decoration: BoxDecoration(color: dimColor, borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withOpacity(0.25))),
child: Icon(icon, color: color, size: 18),
),
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: _t1)),
],
),
),
),
);
}
String _formatCurrency(dynamic amount) {
final formatter = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0);
return formatter.format(amount ?? 0);
}
}
class SalaryHistoryPage extends StatefulWidget {
const SalaryHistoryPage({super.key});
@override
State<SalaryHistoryPage> createState() => _SalaryHistoryPageState();
}
class _SalaryHistoryPageState extends State<SalaryHistoryPage> {
final ApiService _apiService = ApiService();
List<dynamic> _riwayatGaji = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
_fetchRiwayat();
}
Future<void> _fetchRiwayat() async {
setState(() => _isLoading = true);
final res = await _apiService.getGajiRiwayat();
if (mounted) {
setState(() {
_isLoading = false;
if (res['success'] == true) {
_riwayatGaji = res['data'] ?? [];
}
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bg,
appBar: AppBar(
elevation: 0, backgroundColor: _bg1,
title: const Row(children: [
Icon(Icons.account_balance_wallet_rounded, color: _amber, size: 18),
SizedBox(width: 8),
Text('Riwayat Gaji', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: _t1)),
]),
actions: [
IconButton(onPressed: _fetchRiwayat, icon: const Icon(Icons.refresh, color: _t2)),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator(color: _amber))
: _riwayatGaji.isEmpty
? const Center(child: Text('Belum ada riwayat gaji', style: TextStyle(color: _t2)))
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _riwayatGaji.length,
itemBuilder: (context, i) {
final item = _riwayatGaji[i];
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(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: _amberDim, borderRadius: BorderRadius.circular(12)),
child: const Icon(Icons.receipt_long_rounded, color: _amber, size: 20),
),
const SizedBox(width: 16),
Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('Periode: ${item['periode']}', style: const TextStyle(color: _t1, fontWeight: FontWeight.bold)),
Text('Tanggal Bayar: ${item['tanggal_bayar'] ?? '-'}', style: const TextStyle(color: _t2, fontSize: 12)),
]),
),
Text('Rp${item['gaji_bersih']}', style: const TextStyle(color: _green, fontWeight: FontWeight.bold, fontSize: 16)),
],
),
);
},
),
);
}
}
class KasbonHistoryPage extends StatefulWidget {
const KasbonHistoryPage({super.key});
@override
State<KasbonHistoryPage> createState() => _KasbonHistoryPageState();
}
class _KasbonHistoryPageState extends State<KasbonHistoryPage> {
final ApiService _apiService = ApiService();
List<dynamic> _riwayat = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
_fetch();
}
Future<void> _fetch() async {
setState(() => _isLoading = true);
final res = await _apiService.getKasbonRiwayat();
if (mounted) {
setState(() {
_isLoading = false;
if (res['success'] == true) _riwayat = res['data'] ?? [];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF9FAFB),
appBar: AppBar(
elevation: 0, backgroundColor: const Color(0xFFFFFFFF),
title: const Text('Riwayat Kasbon', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF111827))),
),
body: _isLoading
? const Center(child: CircularProgressIndicator(color: Color(0xFFF59E0B)))
: _riwayat.isEmpty
? const Center(child: Text('Belum ada riwayat kasbon', style: TextStyle(color: Color(0xFF6B7280))))
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _riwayat.length,
itemBuilder: (context, i) {
final item = _riwayat[i];
final bool isLunas = item['status'] == 'Lunas';
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFE5E7EB))),
child: Row(children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: isLunas ? const Color(0x1A10B981) : const Color(0x1AEF4444), borderRadius: BorderRadius.circular(12)),
child: Icon(isLunas ? Icons.check_circle_rounded : Icons.pending_rounded, color: isLunas ? const Color(0xFF10B981) : const Color(0xFFEF4444), size: 20),
),
const SizedBox(width: 16),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('Rp${item['jumlah']}', style: const TextStyle(color: Color(0xFF111827), fontWeight: FontWeight.bold)),
Text(item['keterangan'] ?? 'Tanpa keterangan', style: const TextStyle(color: Color(0xFF6B7280), fontSize: 12)),
Text(item['tanggal'] ?? '-', style: const TextStyle(color: Color(0xFF9CA3AF), fontSize: 11)),
])),
_statusBadge(item['status']),
]),
);
},
),
);
}
Widget _statusBadge(String status) {
final bool isLunas = status == 'Lunas';
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: isLunas ? const Color(0x1A10B981) : const Color(0x1AEF4444), borderRadius: BorderRadius.circular(8), border: Border.all(color: isLunas ? const Color(0xFF10B981).withOpacity(0.3) : const Color(0xFFEF4444).withOpacity(0.3))),
child: Text(status, style: TextStyle(color: isLunas ? const Color(0xFF10B981) : const Color(0xFFEF4444), fontSize: 10, fontWeight: FontWeight.bold)),
);
}
}