import 'package:flutter/material.dart'; import '../../api/oa_client.dart'; import '../../device/device_bridge.dart'; import '../../labels.dart'; import '../desk_theme.dart'; import '../widgets/desk_data_table.dart'; import '../widgets/desk_widgets.dart'; import 'desk_record_detail_page.dart'; const _kinds = [ ('PERSONAL', '请假'), ('OVERTIME', '加班'), ('BUSINESS', '出差'), ('OUT', '外出'), ('REGULARIZE', '转正'), ('RESIGN', '离职'), ]; class DeskHrApplyPage extends StatefulWidget { const DeskHrApplyPage({super.key, required this.api}); final OaClient api; @override State createState() => _DeskHrApplyPageState(); } class _DeskHrApplyPageState extends State { List> _items = []; bool _loading = true; String _bucket = ''; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() => _loading = true); try { final q = _bucket.isEmpty ? null : {'bucket': _bucket}; final data = await widget.api.get('/leave-requests', query: q); if (!mounted) return; setState(() { _items = asMaps(data); _loading = false; }); } catch (_) { if (mounted) setState(() => _loading = false); } } Future _create() async { var kind = 'LEAVE'; final reason = TextEditingController(); final days = TextEditingController(text: '1'); final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('发起人事申请'), content: SizedBox( width: 420, child: StatefulBuilder( builder: (ctx, setSt) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Wrap( spacing: 6, runSpacing: 6, children: [ for (final k in _kinds) FilterChip( label: Text(k.$2), selected: kind == k.$1, onSelected: (_) => setSt(() => kind = k.$1), ), ], ), const SizedBox(height: 12), TextField(controller: days, keyboardType: TextInputType.number, decoration: const InputDecoration(labelText: '天数')), const SizedBox(height: 8), TextField(controller: reason, maxLines: 3, decoration: const InputDecoration(labelText: '事由')), ], ), ), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('提交')), ], ), ); if (ok != true) return; try { final created = Map.from(await widget.api.post('/leave-requests', { 'kind': kind, 'reason': reason.text.trim(), 'days': num.tryParse(days.text) ?? 1, }) as Map); final id = '${created['id'] ?? ''}'; if (id.isNotEmpty && mounted) { final add = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('添加申请材料'), content: const Text('可以上传病假证明、行程单等附件。'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('暂不添加')), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('选择附件')), ], ), ); if (add == true) { final picked = await DeviceBridge.pickFile(); if (picked != null && picked['path'] != null) { await widget.api.uploadFile( filePath: picked['path']!, filename: picked['name'] ?? '申请材料', bizType: 'HR_LEAVE', bizId: id, mime: picked['mime'], ); } } } if (mounted) { deskToast(context, '已提交,等待审批'); _load(); } } catch (e) { if (mounted) deskToast(context, '$e', error: true); } } @override Widget build(BuildContext context) { return DeskListScaffold( title: '人事申请', loading: _loading, actions: [ FilledButton.icon( style: FilledButton.styleFrom(backgroundColor: kDeskBind), onPressed: _create, icon: const Icon(Icons.add, size: 18), label: const Text('发起申请'), ), ], filters: DeskFilterChips( value: _bucket, onChanged: (v) { setState(() => _bucket = v); _load(); }, items: const [('', '全部'), ('pending', '待审批'), ('approved', '已通过'), ('rejected', '已驳回')], ), body: DeskDataTable( columns: const ['类型', '事由', '状态', '时间'], rows: [ for (final r in _items) [zh(r['kind']), '${r['reason'] ?? ''}', zh(r['status']), fmtTime(r['createdAt'])], ], emptyHint: '还没有人事申请', onRowTap: (i) => deskOpenRecord(context, widget.api, {..._items[i], 'source': 'HR'}), ), ); } }