import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../api_service.dart'; import '../theme.dart'; class LeaveScreen extends StatefulWidget { final ApiService api; final VoidCallback onAuthError; const LeaveScreen({super.key, required this.api, required this.onAuthError}); @override State createState() => _LeaveScreenState(); } class _LeaveScreenState extends State { List _balances = []; List _requests = []; bool _loading = true; @override void initState() { super.initState(); _load(); } Future _load() async { try { final b = await widget.api.leaveBalances(); final r = await widget.api.myLeave(); if (mounted) setState(() { _balances = b; _requests = r; _loading = false; }); } on ApiException catch (e) { if (e.status == 401) return widget.onAuthError(); if (mounted) setState(() => _loading = false); } } Future _apply() async { final types = await widget.api.leaveTypes().catchError((_) => []); if (!mounted || types.isEmpty) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('No leave types set up'))); return; } final ok = await showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => _ApplySheet(api: widget.api, types: types), ); if (ok == true) _load(); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: _apply, backgroundColor: AppColors.emerald, icon: const Icon(Icons.add), label: const Text('Apply'), ), backgroundColor: Colors.transparent, body: _loading ? const Center(child: CircularProgressIndicator(color: AppColors.emerald)) : RefreshIndicator( onRefresh: _load, color: AppColors.emerald, child: ListView( padding: const EdgeInsets.fromLTRB(16, 12, 16, 90), children: [ const Text('Balances', style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.navy)), const SizedBox(height: 8), if (_balances.isEmpty) const Text('No leave types.', style: TextStyle(color: AppColors.muted)) else ..._balances.map((b) => Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( title: Text(b.leaveTypeName, style: const TextStyle(fontWeight: FontWeight.w600)), subtitle: Text('${b.used} used of ${b.allocated}'), trailing: Text('${b.available}', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.emerald)), ), )), const SizedBox(height: 16), const Text('My requests', style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.navy)), const SizedBox(height: 8), if (_requests.isEmpty) const Text('No requests yet.', style: TextStyle(color: AppColors.muted)) else ..._requests.map((r) => Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( title: Text('${r.leaveTypeName ?? 'Leave'} · ${r.days ?? ''} day(s)'), subtitle: Text('${r.fromDate} → ${r.toDate}${r.reason != null ? '\n${r.reason}' : ''}'), isThreeLine: r.reason != null, trailing: _statusChip(r.status), ), )), ], ), ), ); } } Widget _statusChip(String s) { final c = s == 'APPROVED' ? AppColors.emerald : s == 'REJECTED' ? AppColors.danger : AppColors.gold; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: c.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20)), child: Text(s, style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: c)), ); } class _ApplySheet extends StatefulWidget { final ApiService api; final List types; const _ApplySheet({required this.api, required this.types}); @override State<_ApplySheet> createState() => _ApplySheetState(); } class _ApplySheetState extends State<_ApplySheet> { String? _type; DateTime? _from, _to; final _reason = TextEditingController(); bool _busy = false; Future _pick(bool from) async { final d = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now().subtract(const Duration(days: 30)), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (d != null) setState(() => from ? _from = d : _to = d); } Future _submit() async { if (_type == null || _from == null || _to == null) return; setState(() => _busy = true); final fmt = DateFormat('yyyy-MM-dd'); try { await widget.api.applyLeave(_type!, fmt.format(_from!), fmt.format(_to!), _reason.text); if (mounted) Navigator.pop(context, true); } on ApiException catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message))); setState(() => _busy = false); } } @override Widget build(BuildContext context) { final fmt = DateFormat('dd MMM yyyy'); return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Padding( padding: const EdgeInsets.all(20), child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text('Apply for leave', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.navy)), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _type, decoration: const InputDecoration(labelText: 'Leave type'), items: widget.types.map((t) => DropdownMenuItem(value: t.gid, child: Text(t.name))).toList(), onChanged: (v) => setState(() => _type = v), ), const SizedBox(height: 12), Row(children: [ Expanded(child: OutlinedButton(onPressed: () => _pick(true), child: Text(_from == null ? 'From' : fmt.format(_from!)))), const SizedBox(width: 10), Expanded(child: OutlinedButton(onPressed: () => _pick(false), child: Text(_to == null ? 'To' : fmt.format(_to!)))), ]), const SizedBox(height: 12), TextField(controller: _reason, decoration: const InputDecoration(labelText: 'Reason'), maxLines: 2), const SizedBox(height: 16), FilledButton( onPressed: _busy ? null : _submit, child: _busy ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Text('Submit request'), ), ]), ), ); } }