102 lines
3.8 KiB
Dart
102 lines
3.8 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:intl/intl.dart';
|
||
import '../api_service.dart';
|
||
import '../theme.dart';
|
||
|
||
class PayslipsScreen extends StatefulWidget {
|
||
final ApiService api;
|
||
final VoidCallback onAuthError;
|
||
const PayslipsScreen({super.key, required this.api, required this.onAuthError});
|
||
|
||
@override
|
||
State<PayslipsScreen> createState() => _PayslipsScreenState();
|
||
}
|
||
|
||
class _PayslipsScreenState extends State<PayslipsScreen> {
|
||
List<Payslip> _rows = [];
|
||
bool _loading = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_load();
|
||
}
|
||
|
||
Future<void> _load() async {
|
||
try {
|
||
final r = await widget.api.payslips();
|
||
if (mounted) setState(() { _rows = r; _loading = false; });
|
||
} on ApiException catch (e) {
|
||
if (e.status == 401) return widget.onAuthError();
|
||
if (mounted) setState(() => _loading = false);
|
||
}
|
||
}
|
||
|
||
String _month(String m) {
|
||
try {
|
||
return DateFormat('MMMM yyyy').format(DateTime.parse('$m-01'));
|
||
} catch (_) {
|
||
return m;
|
||
}
|
||
}
|
||
|
||
String _inr(num v) => '₹${NumberFormat('#,##,###').format(v)}';
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (_loading) return const Center(child: CircularProgressIndicator(color: AppColors.emerald));
|
||
if (_rows.isEmpty) {
|
||
return const Center(child: Text('No payslips yet.', style: TextStyle(color: AppColors.muted)));
|
||
}
|
||
return RefreshIndicator(
|
||
onRefresh: _load,
|
||
color: AppColors.emerald,
|
||
child: ListView.separated(
|
||
padding: const EdgeInsets.all(16),
|
||
itemCount: _rows.length,
|
||
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
||
itemBuilder: (_, i) {
|
||
final p = _rows[i];
|
||
return Card(
|
||
child: ListTile(
|
||
title: Text(_month(p.periodMonth), style: const TextStyle(fontWeight: FontWeight.w600)),
|
||
subtitle: const Text('Net pay'),
|
||
trailing: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||
Text(_inr(p.netPay), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.emerald)),
|
||
Text(p.status, style: const TextStyle(fontSize: 11, color: AppColors.muted)),
|
||
]),
|
||
onTap: () => showModalBottomSheet(
|
||
context: context,
|
||
builder: (_) => Padding(
|
||
padding: const EdgeInsets.all(20),
|
||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||
Text(_month(p.periodMonth), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.navy)),
|
||
const SizedBox(height: 16),
|
||
_row('Gross earnings', _inr(p.gross)),
|
||
_row('Total deductions', '− ${_inr(p.totalDeductions)}'),
|
||
const Divider(height: 24),
|
||
_row('Net pay', _inr(p.netPay), bold: true),
|
||
const SizedBox(height: 16),
|
||
const Text('Full PDF payslip is available on the Scalar360 web portal.',
|
||
style: TextStyle(fontSize: 12, color: AppColors.muted)),
|
||
]),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _row(String l, String v, {bool bold = false}) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||
Text(l, style: TextStyle(color: bold ? AppColors.navy : AppColors.muted, fontWeight: bold ? FontWeight.bold : FontWeight.normal)),
|
||
Text(v, style: TextStyle(fontWeight: FontWeight.w600, color: bold ? AppColors.emerald : AppColors.navy, fontSize: bold ? 18 : 14)),
|
||
]),
|
||
);
|
||
}
|
||
}
|