import 'package:aioa_mobile/features/tasks/application/approval_task_controller.dart'; import 'package:aioa_mobile/features/tasks/application/notification_controller.dart'; import 'package:aioa_mobile/features/tasks/data/approval_task_repository.dart'; import 'package:aioa_mobile/features/tasks/data/notification_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; class TasksPage extends ConsumerWidget { const TasksPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final notifications = ref.watch(notificationProvider); final tasks = ref.watch(approvalTaskProvider); final unread = notifications.value?.where((item) => !item.isRead).length ?? 0; final taskCount = tasks.value?.length ?? 0; return DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title: const Text('待办与通知'), bottom: TabBar( tabs: [ Tab(text: taskCount == 0 ? '待办' : '待办 ($taskCount)'), Tab(text: unread == 0 ? '通知' : '通知 ($unread)'), ], ), ), body: TabBarView( children: [ _ApprovalTaskList(tasks: tasks), _NotificationList(notifications: notifications), ], ), ), ); } } class _ApprovalTaskList extends ConsumerWidget { const _ApprovalTaskList({required this.tasks}); final AsyncValue> tasks; @override Widget build(BuildContext context, WidgetRef ref) => tasks.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('待办加载失败:$error'), const SizedBox(height: 8), FilledButton( onPressed: ref.read(approvalTaskProvider.notifier).refresh, child: const Text('重试'), ), ], ), ), data: (items) { if (items.isEmpty) return const Center(child: Text('暂无待办')); return RefreshIndicator( onRefresh: ref.read(approvalTaskProvider.notifier).refresh, child: ListView.separated( padding: const EdgeInsets.all(12), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (context, index) => _ApprovalTaskCard(task: items[index]), ), ); }, ); } class _ApprovalTaskCard extends ConsumerWidget { const _ApprovalTaskCard({required this.task}); final ApprovalTaskItem task; @override Widget build(BuildContext context, WidgetRef ref) { final request = task.leaveRequest; final formatter = DateFormat('MM-dd HH:mm'); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ const CircleAvatar(child: Icon(Icons.assignment_ind_outlined)), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( task.name, style: const TextStyle(fontWeight: FontWeight.w700), ), Text(_typeLabel(request.type)), ], ), ), ], ), const SizedBox(height: 12), Text( '${formatter.format(request.startsAt.toLocal())} — ${formatter.format(request.endsAt.toLocal())}', ), const SizedBox(height: 6), Text(request.reason), const SizedBox(height: 14), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => _decide(context, ref, approved: false), child: const Text('驳回'), ), ), const SizedBox(width: 10), Expanded( child: FilledButton( onPressed: () => _decide(context, ref, approved: true), child: const Text('批准'), ), ), ], ), ], ), ), ); } Future _decide( BuildContext context, WidgetRef ref, { required bool approved, }) async { final comment = await showDialog( context: context, builder: (context) => _DecisionDialog(approved: approved), ); if (comment == null || !context.mounted) return; final error = await ref .read(approvalTaskProvider.notifier) .decide(task: task, approved: approved, comment: comment); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(error ?? (approved ? '已批准' : '已驳回'))), ); } String _typeLabel(String type) => switch (type) { 'PERSONAL' => '事假', 'SICK' => '病假', 'ANNUAL' => '年假', _ => type, }; } class _DecisionDialog extends StatefulWidget { const _DecisionDialog({required this.approved}); final bool approved; @override State<_DecisionDialog> createState() => _DecisionDialogState(); } class _DecisionDialogState extends State<_DecisionDialog> { final controller = TextEditingController(); @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) => AlertDialog( title: Text(widget.approved ? '批准申请' : '驳回申请'), content: TextField( controller: controller, maxLength: 1000, maxLines: 3, decoration: InputDecoration( labelText: widget.approved ? '审批意见(选填)' : '驳回原因', ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), FilledButton( onPressed: () => Navigator.pop(context, controller.text), child: Text(widget.approved ? '确认批准' : '确认驳回'), ), ], ); } class _NotificationList extends ConsumerWidget { const _NotificationList({required this.notifications}); final AsyncValue> notifications; @override Widget build(BuildContext context, WidgetRef ref) => notifications.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('通知加载失败:$error'), const SizedBox(height: 8), FilledButton( onPressed: ref.read(notificationProvider.notifier).refresh, child: const Text('重试'), ), ], ), ), data: (items) { if (items.isEmpty) return const Center(child: Text('暂无通知')); return RefreshIndicator( onRefresh: ref.read(notificationProvider.notifier).refresh, child: ListView.separated( padding: const EdgeInsets.all(12), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (context, index) { final item = items[index]; return Card( color: item.isRead ? null : Theme.of( context, ).colorScheme.primaryContainer.withValues(alpha: 0.35), child: ListTile( leading: Icon(_icon(item.type)), title: Text( item.title, style: TextStyle( fontWeight: item.isRead ? FontWeight.w500 : FontWeight.w700, ), ), subtitle: Text( '${item.body}\n${DateFormat('MM-dd HH:mm').format(item.createdAt.toLocal())}', ), isThreeLine: true, trailing: item.isRead ? null : const Badge(), onTap: item.isRead ? null : () => ref .read(notificationProvider.notifier) .markRead(item.id), ), ); }, ), ); }, ); IconData _icon(String type) => switch (type) { 'APPROVAL_TASK_ASSIGNED' => Icons.assignment_outlined, 'LEAVE_APPROVED' => Icons.check_circle_outline, 'LEAVE_REJECTED' => Icons.cancel_outlined, _ => Icons.notifications_none, }; }