188 lines
6.1 KiB
Dart
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,
|
|
};
|
|
}
|