Files
Scalar360_Attendence/lib/screens/team_screen.dart
T
2026-08-10 17:01:30 +05:30

120 lines
4.9 KiB
Dart

import 'package:flutter/material.dart';
import '../api_service.dart';
import '../theme.dart';
class TeamScreen extends StatefulWidget {
final ApiService api;
final VoidCallback onAuthError;
const TeamScreen({super.key, required this.api, required this.onAuthError});
@override
State<TeamScreen> createState() => _TeamScreenState();
}
class _TeamScreenState extends State<TeamScreen> {
List<TeamMember> _team = [];
List<LeaveRequest> _pending = [];
bool _loading = true;
bool _busy = false;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
try {
final t = await widget.api.team();
final p = await widget.api.teamLeave();
if (mounted) setState(() { _team = t; _pending = p; _loading = false; });
} on ApiException catch (e) {
if (e.status == 401) return widget.onAuthError();
if (mounted) setState(() => _loading = false);
}
}
Future<void> _decide(String gid, bool approve) async {
setState(() => _busy = true);
try {
approve ? await widget.api.approve(gid) : await widget.api.reject(gid);
await _load();
} on ApiException catch (e) {
if (e.status == 401) return widget.onAuthError();
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message)));
} finally {
if (mounted) setState(() => _busy = false);
}
}
Color _sc(String? s) => statusColor(s);
@override
Widget build(BuildContext context) {
if (_loading) return const Center(child: CircularProgressIndicator(color: AppColors.emerald));
return RefreshIndicator(
onRefresh: _load,
color: AppColors.emerald,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
if (_pending.isNotEmpty) ...[
const Text('Pending approvals', style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.navy)),
const SizedBox(height: 8),
..._pending.map((r) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(children: [
Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(r.employeeName ?? '—', style: const TextStyle(fontWeight: FontWeight.w600, color: AppColors.navy)),
Text('${r.leaveTypeName ?? 'Leave'} · ${r.days ?? ''}d · ${r.fromDate}${r.toDate}',
style: const TextStyle(fontSize: 12, color: AppColors.muted)),
]),
),
IconButton(
onPressed: _busy ? null : () => _decide(r.gid, true),
icon: const Icon(Icons.check_circle_outline, color: AppColors.emerald),
),
IconButton(
onPressed: _busy ? null : () => _decide(r.gid, false),
icon: const Icon(Icons.cancel_outlined, color: AppColors.danger),
),
]),
),
)),
const SizedBox(height: 16),
],
const Text('Team today', style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.navy)),
const SizedBox(height: 8),
if (_team.isEmpty)
const Text('No team members.', style: TextStyle(color: AppColors.muted))
else
..._team.map((m) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: AppColors.emerald.withValues(alpha: 0.12),
child: Text(_initials(m.employeeName), style: const TextStyle(color: AppColors.emerald, fontWeight: FontWeight.bold, fontSize: 13)),
),
title: Text(m.employeeName, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: Text('In ${m.checkIn ?? '—'} · Out ${m.checkOut ?? '—'}${m.hours != null ? ' · ${m.hours}h' : ''}'),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(color: _sc(m.status).withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20)),
child: Text((m.status).replaceAll('_', ' '), style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: _sc(m.status))),
),
),
)),
],
),
);
}
String _initials(String name) {
final p = name.trim().split(RegExp(r'\s+')).where((x) => x.isNotEmpty).toList();
return p.isEmpty ? '?' : p.take(2).map((x) => x[0].toUpperCase()).join();
}
}