SIPDAM/samooflutter/lib/keuangan/Gaji.dart

337 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../api/GajiApi.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 GajiRiwayatScreen extends StatefulWidget {
const GajiRiwayatScreen({super.key});
@override
State<GajiRiwayatScreen> createState() => _GajiRiwayatScreenState();
}
class _GajiRiwayatScreenState extends State<GajiRiwayatScreen> {
final GajiApi _apiService = GajiApi();
List<dynamic> _riwayatGaji = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
_fetchRiwayat();
}
Future<void> _fetchRiwayat() async {
setState(() => _isLoading = true);
final res = await _apiService.getRiwayat();
if (mounted) {
setState(() {
_isLoading = false;
if (res['success'] == true) {
// Laravel paginate structure is res['data']['data']
final rawData = res['data'];
if (rawData is Map && rawData.containsKey('data')) {
_riwayatGaji = rawData['data'] ?? [];
} else {
_riwayatGaji = rawData ?? [];
}
}
});
}
}
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: _amber))
: RefreshIndicator(
onRefresh: _fetchRiwayat,
color: _amber,
backgroundColor: _bg1,
child: _riwayatGaji.isEmpty
? _buildEmptyState()
: ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: _riwayatGaji.length,
itemBuilder: (context, i) => _buildGajiCard(_riwayatGaji[i]),
),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: _bg1,
shape: BoxShape.circle,
border: Border.all(color: _line2),
),
child: const Icon(Icons.receipt_long_rounded, size: 40, color: _t3),
),
const SizedBox(height: 16),
const Text('Belum Ada Riwayat',
style: TextStyle(color: _t1, fontSize: 16, fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
const Text('Slip gaji Anda akan muncul di sini',
style: TextStyle(color: _t2, fontSize: 13)),
],
),
);
}
Widget _buildGajiCard(dynamic item) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _line2),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _showGajiDetail(item['id_penggajian']),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 48, height: 48,
decoration: BoxDecoration(
color: _amberDim,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _amber.withOpacity(0.3)),
),
child: const Icon(Icons.receipt_long_rounded, color: _amber, size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Periode: ${item['nama_bulan'] ?? ''} ${item['periode_tahun'] ?? ''}',
style: const TextStyle(
color: _t1,
fontWeight: FontWeight.w700,
fontSize: 14)),
const SizedBox(height: 4),
Text('Dibayar: ${item['tanggal_bayar'] ?? '-'}',
style: const TextStyle(color: _t2, fontSize: 11)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(_formatCurrency(item['gaji_bersih']),
style: const TextStyle(
color: _green,
fontWeight: FontWeight.w800,
fontSize: 15)),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: (item['is_paid'] ?? false) ? _greenDim : _amberDim,
borderRadius: BorderRadius.circular(6),
),
child: Text((item['is_paid'] ?? false) ? 'LUNAS' : 'BELUM DIBAYAR',
style: TextStyle(
color: (item['is_paid'] ?? false) ? _green : _amber,
fontSize: 9,
fontWeight: FontWeight.w900,
letterSpacing: 0.5)),
),
],
),
],
),
),
),
),
);
}
void _showGajiDetail(int id) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => _GajiDetailSheet(id: id, apiService: _apiService),
);
}
}
class _GajiDetailSheet extends StatefulWidget {
final int id;
final GajiApi apiService;
const _GajiDetailSheet({required this.id, required this.apiService});
@override
State<_GajiDetailSheet> createState() => _GajiDetailSheetState();
}
class _GajiDetailSheetState extends State<_GajiDetailSheet> {
bool _isLoading = true;
dynamic _detail;
@override
void initState() {
super.initState();
_fetchDetail();
}
Future<void> _fetchDetail() async {
final res = await widget.apiService.getDetail(widget.id);
if (mounted) {
setState(() {
_detail = res['data']?['header']; // We use 'header' for top summary
_isLoading = false;
});
}
}
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 DraggableScrollableSheet(
initialChildSize: 0.8,
maxChildSize: 0.95,
minChildSize: 0.5,
builder: (_, controller) => Container(
decoration: const BoxDecoration(
color: _bg1,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: _isLoading
? const Center(child: CircularProgressIndicator(color: _amber))
: Column(
children: [
const SizedBox(height: 12),
Container(width: 40, height: 4, decoration: BoxDecoration(color: _line2, borderRadius: BorderRadius.circular(2))),
const SizedBox(height: 24),
Expanded(
child: ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 24),
children: [
Center(
child: Column(
children: [
const Text('SLIP GAJI TEKNISI',
style: TextStyle(color: _t2, fontSize: 11, fontWeight: FontWeight.w800, letterSpacing: 2)),
const SizedBox(height: 8),
Text(_detail['periode'] ?? '-',
style: const TextStyle(color: _t1, fontSize: 18, fontWeight: FontWeight.w800)),
const SizedBox(height: 24),
],
),
),
_buildInfoSection('RINCIAN PENDAPATAN', [
_buildDetailRow('Gaji Pokok', _formatCurrency(_detail['gaji_pokok'] ?? 0)),
_buildDetailRow('Uang Makan', _formatCurrency(_detail['potongan_makan'] ?? 0)), // It's usually a static or dynamic field
_buildDetailRow('Insentif Pekerjaan', _formatCurrency(_detail['gaji_kotor'] ?? 0), isBold: true),
]),
const SizedBox(height: 20),
_buildInfoSection('POTONGAN', [
_buildDetailRow('Potongan Kasbon', '- ${_formatCurrency(_detail['potongan_kasbon'])}', color: _rose),
_buildDetailRow('Potongan Absensi', '- ${_formatCurrency(_detail['potongan_absensi'])}', color: _rose),
]),
const Divider(height: 48, color: _line2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('TOTAL DITERIMA',
style: TextStyle(color: _t1, fontSize: 14, fontWeight: FontWeight.w800)),
Text(_formatCurrency(_detail['gaji_bersih']),
style: const TextStyle(color: _green, fontSize: 22, fontWeight: FontWeight.w900)),
],
),
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _bg2,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _line2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('CATATAN PEKERJAAN',
style: TextStyle(color: _t2, fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: 1)),
const SizedBox(height: 8),
Text(_detail['rincian_pekerjaan'] ?? 'Tidak ada rincian',
style: const TextStyle(color: _t1, fontSize: 12, height: 1.5)),
],
),
),
const SizedBox(height: 40),
],
),
),
],
),
),
);
}
Widget _buildInfoSection(String title, List<Widget> children) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(color: _t3, fontSize: 10, fontWeight: FontWeight.w900, letterSpacing: 1.2)),
const SizedBox(height: 12),
...children,
],
);
}
Widget _buildDetailRow(String label, String value, {bool isBold = false, Color? color}) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: _t2, fontSize: 13, fontWeight: isBold ? FontWeight.w700 : FontWeight.w400)),
Text(value, style: TextStyle(color: color ?? _t1, fontSize: 13, fontWeight: isBold ? FontWeight.w800 : FontWeight.w600)),
],
),
);
}
}