import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../core/theme/app_colors.dart'; import '../../core/utils/date_utils.dart' as du; import '../../core/utils/responsive.dart'; import '../../shared/widgets/app_card.dart'; import '../../shared/widgets/status_tag.dart'; import '../../shared/widgets/empty_state.dart'; import '../../shared/widgets/loading_widget.dart'; import 'overtime_list_controller.dart'; import 'overtime_model.dart'; class OvertimeListPage extends ConsumerStatefulWidget { const OvertimeListPage({super.key}); @override ConsumerState createState() => _OvertimeListPageState(); } class _OvertimeListPageState extends ConsumerState { int _page = 1; @override Widget build(BuildContext context) { final status = ref.watch(overtimeStatusFilterProvider); final itemsAsync = ref.watch(overtimeListProvider(_page)); final r = ResponsiveHelper.of(context); return Scaffold( appBar: AppBar( title: const Text('加班申请'), actions: [ IconButton( icon: const Icon(Icons.add, color: Colors.white), onPressed: () => context.push('/overtime/apply'), ), ], ), body: Column( children: [ _buildStatusFilter(status, r), Expanded( child: Center( child: ConstrainedBox( constraints: BoxConstraints(maxWidth: r.listMaxWidth), child: itemsAsync.when( loading: () => const LoadingWidget(), error: (_, __) => const EmptyState(message: '加载失败'), data: (items) => items.isEmpty ? const EmptyState(message: '暂无加班申请') : ListView.builder( padding: const EdgeInsets.symmetric(vertical: 4), itemCount: items.length, itemBuilder: (_, index) => _buildItem(items[index]), ), ), ), ), ), ], ), ); } Widget _buildStatusFilter(String current, ResponsiveHelper r) { final statuses = [ {'key': '', 'label': '全部'}, {'key': 'pending', 'label': '待审批'}, {'key': 'approved', 'label': '已通过'}, {'key': 'rejected', 'label': '已拒绝'}, ]; final filterBar = SingleChildScrollView( scrollDirection: Axis.horizontal, padding: EdgeInsets.zero, child: Row( children: statuses.map((s) { final isSelected = current == s['key']; return Padding( padding: const EdgeInsets.only(right: 8), child: GestureDetector( onTap: () { ref.read(overtimeStatusFilterProvider.notifier).state = s['key']!; _page = 1; }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: isSelected ? AppColors.primaryLight : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: isSelected ? AppColors.primary : const Color(0xFFDDDDDD), ), ), child: Text(s['label']!, style: TextStyle( color: isSelected ? AppColors.primary : AppColors.textSecondary, fontSize: 12)), ), ), ); }).toList(), ), ); return Padding( padding: const EdgeInsets.only(top: 4, bottom: 4), child: r.isWide ? Center( child: SizedBox(width: r.listMaxWidth, child: filterBar)) : filterBar, ); } Widget _buildItem(OvertimeModel item) { return Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), children: [ SlidableAction( onPressed: (_) => context.push('/overtime/apply?id=${item.id}'), backgroundColor: AppColors.primary, foregroundColor: Colors.white, icon: Icons.edit, label: '编辑', ), ], ), child: AppCard( onTap: () => context.push('/overtime/detail/${item.id}'), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item.applicationNo, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, color: AppColors.textPrimary)), StatusTag(status: item.status), ], ), const SizedBox(height: 4), Text( '${item.otType} · ${item.otHours.toStringAsFixed(1)}h · ${item.compensationType}', style: const TextStyle( color: AppColors.textSecondary, fontSize: 12), ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item.applicantName, style: const TextStyle( color: AppColors.textHint, fontSize: 11)), Text(du.DateUtils.formatDateTime(item.createTime), style: const TextStyle( color: AppColors.textHint, fontSize: 11)), ], ), ], ), ), ); } }