import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:tdesign_flutter/tdesign_flutter.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 { final _scrollCtrl = ScrollController(); @override void initState() { super.initState(); _scrollCtrl.addListener(_onScroll); } void _onScroll() { if (_scrollCtrl.position.pixels >= _scrollCtrl.position.maxScrollExtent - 100) { ref.read(overtimePageProvider.notifier).state++; } } @override void dispose() { _scrollCtrl.removeListener(_onScroll); _scrollCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final status = ref.watch(overtimeStatusFilterProvider); final itemsAsync = ref.watch(overtimeListProvider); final r = ResponsiveHelper.of(context); return Scaffold( appBar: TDNavBar( title: '加班申请', titleColor: Colors.white, backgroundColor: const Color(0xFF00ABF3), centerTitle: false, rightBarItems: [TDNavBarItem(icon: Icons.add, iconColor: Colors.white, action: () => context.push('/overtime/apply'))], ), body: Column(children: [ _buildStatusFilter(status), 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: '暂无加班申请') : RefreshIndicator( onRefresh: () async { ref.invalidate(overtimeListProvider); }, child: ListView.builder( controller: _scrollCtrl, padding: const EdgeInsets.symmetric(vertical: 4), itemCount: items.length, itemBuilder: (_, i) => _buildItem(items[i]), ), ), ), ), ), ), ]), ); } Widget _buildStatusFilter(String current) { final statuses = [ {'key': '', 'label': '全部'}, {'key': 'pending', 'label': '待审批'}, {'key': 'approved', 'label': '已通过'}, {'key': 'rejected', 'label': '已拒绝'}, ]; return Padding( padding: const EdgeInsets.only(top: 12, bottom: 4, left: 12), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: statuses.map((s) { final selected = current == s['key']; return Padding( padding: const EdgeInsets.only(right: 8), child: GestureDetector( onTap: () { ref.read(overtimeStatusFilterProvider.notifier).state = s['key']!; }, child: TDTag( s['label']!, size: TDTagSize.small, theme: selected ? TDTagTheme.primary : null, isLight: !selected, shape: TDTagShape.round, ), ), ); }).toList(), ), ), ); } Widget _buildItem(OvertimeModel item) { return AppCard( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), 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', 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.formatDate(item.otDate), style: const TextStyle(color: AppColors.textHint, fontSize: 11)), ]), ]), ); } }