Files
AIOA/mobile/lib/features/requests/presentation/leave_request_detail_page.dart
T
2026-07-18 19:20:07 +08:00

188 lines
6.1 KiB
Dart

import 'package:aioa_mobile/features/requests/application/leave_request_controller.dart';
import 'package:aioa_mobile/features/requests/data/leave_request_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
class LeaveRequestDetailPage extends ConsumerWidget {
const LeaveRequestDetailPage({required this.id, super.key});
final String id;
@override
Widget build(BuildContext context, WidgetRef ref) {
final detail = ref.watch(leaveRequestDetailProvider(id));
return Scaffold(
appBar: AppBar(title: const Text('申请详情')),
body: detail.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('详情加载失败:$error')),
data: (value) => _DetailBody(id: id, detail: value),
),
);
}
}
class _DetailBody extends ConsumerWidget {
const _DetailBody({required this.id, required this.detail});
final String id;
final LeaveRequestDetail detail;
@override
Widget build(BuildContext context, WidgetRef ref) {
final request = detail.request;
final formatter = DateFormat('yyyy-MM-dd HH:mm');
return ListView(
padding: const EdgeInsets.all(16),
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_statusLabel(request.status),
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800),
),
const SizedBox(height: 14),
_row('请假类型', _typeLabel(request.type)),
_row('开始时间', formatter.format(request.startsAt.toLocal())),
_row('结束时间', formatter.format(request.endsAt.toLocal())),
_row('请假原因', request.reason),
],
),
),
),
const SizedBox(height: 14),
Text(
'流程时间线',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
if (detail.timeline.isEmpty)
const Card(
child: ListTile(
leading: Icon(Icons.edit_note),
title: Text('草稿已创建'),
),
)
else
for (final event in detail.timeline) _TimelineTile(event: event),
const SizedBox(height: 16),
if (request.status == 'DRAFT')
FilledButton.icon(
onPressed: () => _transition(context, ref, 'submit'),
icon: const Icon(Icons.send_outlined),
label: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('提交审批'),
),
),
if (request.status == 'PENDING')
OutlinedButton.icon(
onPressed: () => _transition(context, ref, 'withdraw'),
icon: const Icon(Icons.undo),
label: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('撤回申请'),
),
),
],
);
}
Future<void> _transition(
BuildContext context,
WidgetRef ref,
String action,
) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(action == 'submit' ? '提交审批' : '撤回申请'),
content: Text(action == 'submit' ? '提交后将进入审批流程,确认继续?' : '确认撤回当前申请?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确认'),
),
],
),
);
if (confirmed != true || !context.mounted) return;
final error = await ref
.read(leaveRequestDetailProvider(id).notifier)
.transition(action);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error ?? (action == 'submit' ? '已提交审批' : '已撤回'))),
);
}
Widget _row(String label, String value) => Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 78, child: Text(label)),
Expanded(
child: Text(
value,
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
],
),
);
String _typeLabel(String value) => switch (value) {
'PERSONAL' => '事假',
'SICK' => '病假',
'ANNUAL' => '年假',
_ => value,
};
String _statusLabel(String value) => switch (value) {
'DRAFT' => '草稿',
'PENDING' => '审批中',
'APPROVED' => '已通过',
'REJECTED' => '已驳回',
'WITHDRAWN' => '已撤回',
_ => value,
};
}
class _TimelineTile extends StatelessWidget {
const _TimelineTile({required this.event});
final LeaveTimelineEvent event;
@override
Widget build(BuildContext context) => Card(
child: ListTile(
leading: const Icon(Icons.radio_button_checked),
title: Text(_eventLabel(event.eventType)),
subtitle: Text(
'${event.fromStatus}${event.toStatus}\n${DateFormat('MM-dd HH:mm').format(event.occurredAt.toLocal())}',
),
isThreeLine: true,
),
);
String _eventLabel(String value) => switch (value) {
'LEAVE_REQUEST_SUBMITTED' => '申请已提交',
'LEAVE_REQUEST_WITHDRAWN' => '申请已撤回',
'LEAVE_REQUEST_APPROVED' => '申请已通过',
'LEAVE_REQUEST_REJECTED' => '申请已驳回',
'LEAVE_APPROVAL_TASK_APPROVED' => '审批节点已通过',
'LEAVE_APPROVAL_TASK_REJECTED' => '审批节点已驳回',
_ => value,
};
}