import 'package:flutter/material.dart'; import '../api/oa_client.dart'; import '../device/device_bridge.dart'; import '../labels.dart'; import '../theme.dart'; import '../widgets/common.dart'; import '../widgets/wecom.dart'; import 'record_detail_page.dart'; const _kinds = [ ('PERSONAL', '请假'), ('OVERTIME', '加班'), ('BUSINESS', '出差'), ('OUT', '外出'), ('REGULARIZE', '转正'), ('RESIGN', '离职'), ]; class HrApplyPage extends StatefulWidget { const HrApplyPage({super.key, required this.api}); final OaClient api; @override State createState() => _HrApplyPageState(); } class _HrApplyPageState extends State { List> _items = []; bool _loading = true; String _bucket = ''; @override void initState() { super.initState(); _load(); } Future _load() async { 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 showModalBottomSheet( context: context, isScrollControlled: true, builder: (ctx) => Padding( padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(ctx).bottom), child: StatefulBuilder( builder: (ctx, setSt) => SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text('发起人事申请', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600)), const SizedBox(height: 12), Wrap( spacing: 8, children: [ for (final k in _kinds) ChoiceChip( 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(hintText: '天数')), const SizedBox(height: 8), TextField(controller: reason, maxLines: 3, decoration: const InputDecoration(hintText: '事由')), const SizedBox(height: 16), 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) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已提交,由汇报对象和部门总监审批'))); _load(); } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e'))); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: kPaper, appBar: AppBar( title: const Text('人事申请'), actions: [IconButton(onPressed: _create, icon: const Icon(Icons.add))], ), body: Column( children: [ SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.fromLTRB(12, 8, 12, 4), child: Row( children: [ for (final it in [('', '全部'), ('pending', '待审批'), ('approved', '已通过'), ('rejected', '已驳回')]) Padding( padding: const EdgeInsets.only(right: 8), child: ChoiceChip( label: Text(it.$2), selected: _bucket == it.$1, onSelected: (_) { setState(() => _bucket = it.$1); _load(); }, ), ), ], ), ), if (_loading) const LinearProgressIndicator(minHeight: 2, color: kBind), Expanded( child: _items.isEmpty ? const EmptyHint('还没有人事申请') : RefreshIndicator( onRefresh: _load, child: ListView( children: [ for (final r in _items) ConvTile( title: '${zh(r['kind'])} · ${r['reason'] ?? ''}', preview: '${zh(r['status'])} ${fmtTime(r['createdAt'])}', avatarIcon: Icons.beach_access_outlined, avatarColor: kBind, avatarLabel: '人', onTap: () => openRecord(context, widget.api, {...r, 'source': 'HR'}), ), ], ), ), ), ], ), ); } }