This commit is contained in:
athul rd
2026-08-10 17:01:30 +05:30
commit 19a79d6126
16 changed files with 1790 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
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)),
]),
);
}
}