import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../core/api_client.dart'; import '../../core/app_colors.dart'; import '../../core/app_design_tokens.dart'; import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; import '../../providers/data_refresh_providers.dart'; import '../../widgets/app_error_state.dart'; import '../../widgets/app_toast.dart'; import '../../widgets/common_widgets.dart'; import '../care_plan_ui_logic.dart'; import 'medication_ui_logic.dart'; class MedicationEditPage extends ConsumerStatefulWidget { final String? id; const MedicationEditPage({super.key, this.id}); @override ConsumerState createState() => _MedicationEditPageState(); } class _MedicationEditPageState extends ConsumerState { final _nameController = TextEditingController(); final _dosageController = TextEditingController(); final _notesController = TextEditingController(); List _times = [ const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0), ]; DateTime _startDate = DateTime.now(); DateTime? _endDate; bool _saving = false; bool _loading = false; String? _loadError; bool get _editing => widget.id?.isNotEmpty == true; @override void initState() { super.initState(); if (_editing) _load(); } @override void dispose() { _nameController.dispose(); _dosageController.dispose(); _notesController.dispose(); super.dispose(); } Future _load() async { setState(() { _loading = true; _loadError = null; }); try { final medications = await ref.read(medicationServiceProvider).getList(); final medication = medications.cast?>().firstWhere( (item) => item?['id']?.toString() == widget.id, orElse: () => null, ); if (medication == null) throw const ApiException('该用药记录不存在'); _nameController.text = medication['name']?.toString() ?? ''; _dosageController.text = medication['dosage']?.toString() ?? ''; _notesController.text = medication['notes']?.toString() ?? ''; final loadedTimes = (medication['timeOfDay'] as List?) ?.map((value) => _parseTime(value)) .whereType() .toList() ?? []; if (loadedTimes.isNotEmpty) _times = loadedTimes; _startDate = DateTime.tryParse(medication['startDate']?.toString() ?? '') ?? DateTime.now(); _endDate = DateTime.tryParse(medication['endDate']?.toString() ?? ''); } catch (error) { _loadError = error is ApiException ? error.message : '用药信息加载失败'; } finally { if (mounted) setState(() => _loading = false); } } Future _save() async { final timeValues = _times.map(_timeValue).toList(); final validation = validateMedicationForm( name: _nameController.text, dosage: _dosageController.text, times: timeValues, startDate: _startDate, endDate: _endDate, ); if (validation != null) { AppToast.show(context, validation, type: AppToastType.warning); return; } setState(() => _saving = true); final payload = { 'name': _nameController.text.trim(), 'dosage': _dosageController.text.trim(), 'frequency': _frequencyForTimes(_times.length), 'notes': _notesController.text.trim(), 'timeOfDay': timeValues, 'startDate': _dateValue(_startDate), 'endDate': _endDate == null ? null : _dateValue(_endDate!), 'source': 'Manual', }; try { final service = ref.read(medicationServiceProvider); if (_editing) { await service.update(widget.id!, payload); } else { await service.create(payload); } ref.read(medicationDataRefreshSignalProvider.notifier).trigger(); if (mounted) { AppToast.show( context, _editing ? '用药已更新' : '用药已添加', type: AppToastType.success, ); popRoute(ref); } } catch (error) { if (mounted) { AppToast.show( context, error is ApiException ? error.message : '保存失败,请稍后重试', type: AppToastType.error, ); } } finally { if (mounted) setState(() => _saving = false); } } Future _deleteMedication() async { if (!_editing || _saving) return; final confirmed = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('删除用药'), content: Text('确定删除“${_nameController.text.trim()}”吗?相关服药记录也将一并删除。'), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(dialogContext, true), style: TextButton.styleFrom(foregroundColor: AppColors.errorText), child: const Text('删除'), ), ], ), ); if (confirmed != true || !mounted) return; setState(() => _saving = true); try { await ref.read(medicationServiceProvider).deleteMedication(widget.id!); ref.read(medicationDataRefreshSignalProvider.notifier).trigger(); if (mounted) { AppToast.show(context, '用药已删除', type: AppToastType.success); popRoute(ref); } } catch (error) { if (mounted) { AppToast.show( context, error is ApiException ? error.message : '删除失败,请稍后重试', type: AppToastType.error, ); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { return GradientScaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref), ), title: Text(_editing ? '编辑用药' : '添加用药'), ), bottomNavigationBar: _loading || _loadError != null ? null : SafeArea( top: false, child: Container( color: Colors.white, padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: AppGradientOutlineButton( label: _editing ? '保存修改' : '添加用药', onPressed: _saving ? null : _save, loading: _saving, ), ), ), body: _loading ? const Center(child: CircularProgressIndicator()) : _loadError != null ? AppErrorState(title: _loadError!, onRetry: _load) : ListView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, padding: const EdgeInsets.fromLTRB(16, 12, 16, 28), children: [ _FormSection( title: '药品信息', children: [ _TextFieldRow( label: '药品名称', controller: _nameController, hint: '请输入药品名称', ), const _SectionDivider(), _TextFieldRow( label: '剂量', controller: _dosageController, hint: '例如 100 mg', ), ], ), const SizedBox(height: 16), _FormSection( title: '服用安排', children: [ _PickerRow( label: '每日次数', value: '${_times.length}次', onTap: _pickCount, ), const _SectionDivider(), Padding( padding: const EdgeInsets.fromLTRB(16, 11, 12, 11), child: Row( children: [ const Text('服药时间', style: AppTextStyles.formLabel), const SizedBox(width: 12), Expanded( child: Wrap( alignment: WrapAlignment.end, spacing: 8, runSpacing: 8, children: List.generate( _times.length, (index) => _TimeButton( value: _timeValue(_times[index]), onTap: () => _pickTime(index), ), ), ), ), ], ), ), ], ), const SizedBox(height: 16), _FormSection( title: '用药周期', children: [ _PickerRow( label: '开始日期', value: _displayDate(_startDate), onTap: _pickStartDate, ), const _SectionDivider(), _PickerRow( label: '结束日期', value: _endDate == null ? '长期' : _displayDate(_endDate!), onTap: _pickEndDate, onClear: _endDate == null ? null : () => setState(() => _endDate = null), ), ], ), const SizedBox(height: 16), _FormSection( title: '备注', children: [ Padding( padding: const EdgeInsets.all(16), child: TextField( controller: _notesController, minLines: 3, maxLines: 5, maxLength: 200, decoration: const InputDecoration( hintText: '可填写饭前、饭后或其他用药要求(选填)', border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, filled: false, contentPadding: EdgeInsets.zero, ), ), ), ], ), if (_editing) ...[ const SizedBox(height: 16), Material( color: Colors.white, borderRadius: AppRadius.lgBorder, clipBehavior: Clip.antiAlias, child: InkWell( onTap: _saving ? null : _deleteMedication, child: const SizedBox( height: 54, child: Center( child: Text( '删除用药', style: TextStyle( color: AppColors.errorText, fontSize: 15, fontWeight: FontWeight.w700, ), ), ), ), ), ), ], ], ), ); } Future _pickCount() async { final count = await showAppCountPicker( context, initialValue: _times.length, min: 1, max: 4, label: '次', ); if (count == null || !mounted) return; setState(() { while (_times.length < count) { final hour = (8 + _times.length * 5).clamp(0, 23); _times.add(TimeOfDay(hour: hour, minute: 0)); } while (_times.length > count) { _times.removeLast(); } }); } Future _pickTime(int index) async { final value = await showAppTimePicker(context, initialTime: _times[index]); if (value != null && mounted) setState(() => _times[index] = value); } Future _pickStartDate() async { final value = await showAppDatePicker( context, initialDate: _startDate, firstDate: DateTime(2020), lastDate: DateTime(2035), ); if (value != null && mounted) setState(() => _startDate = value); } Future _pickEndDate() async { final value = await showAppDatePicker( context, initialDate: _endDate ?? _startDate, firstDate: DateTime(2020), lastDate: DateTime(2035), ); if (value != null && mounted) setState(() => _endDate = value); } } class _FormSection extends StatelessWidget { final String title; final List children; const _FormSection({required this.title, required this.children}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 2, bottom: 8), child: Text( title, style: AppTextStyles.sectionTitle.copyWith(fontSize: 16), ), ), Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: Colors.white, borderRadius: AppRadius.lgBorder, ), child: Column(children: children), ), ], ); } } class _TextFieldRow extends StatelessWidget { final String label; final TextEditingController controller; final String hint; const _TextFieldRow({ required this.label, required this.controller, required this.hint, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: Row( children: [ SizedBox( width: 82, child: Text(label, style: AppTextStyles.formLabel), ), Expanded( child: TextField( controller: controller, textAlign: TextAlign.right, decoration: InputDecoration( hintText: hint, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, filled: false, contentPadding: const EdgeInsets.symmetric(vertical: 14), ), style: AppTextStyles.formValue, ), ), ], ), ); } } class _PickerRow extends StatelessWidget { final String label; final String value; final VoidCallback onTap; final VoidCallback? onClear; const _PickerRow({ required this.label, required this.value, required this.onTap, this.onClear, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.fromLTRB(16, 15, 10, 15), child: Row( children: [ Expanded(child: Text(label, style: AppTextStyles.formLabel)), Text(value, style: AppTextStyles.formValue), if (onClear != null) IconButton( tooltip: '清除结束日期', visualDensity: VisualDensity.compact, onPressed: onClear, icon: const Icon(Icons.close, size: 18), ) else const Padding( padding: EdgeInsets.only(left: 6), child: Icon( LucideIcons.chevronRight, color: AppColors.textHint, size: 20, ), ), ], ), ), ); } } class _TimeButton extends StatelessWidget { final String value; final VoidCallback onTap; const _TimeButton({required this.value, required this.onTap}); @override Widget build(BuildContext context) { return OutlinedButton.icon( onPressed: onTap, icon: const Icon(LucideIcons.clock, size: 18), label: Text(value), style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, side: BorderSide(color: AppColors.primary.withValues(alpha: 0.38)), shape: RoundedRectangleBorder(borderRadius: AppRadius.smBorder), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), ), ); } } class _SectionDivider extends StatelessWidget { const _SectionDivider(); @override Widget build(BuildContext context) { return const Padding( padding: EdgeInsets.only(left: 16), child: Divider(height: 1, thickness: 0.7, color: AppColors.divider), ); } } TimeOfDay? _parseTime(Object value) { final parts = value.toString().split(':'); if (parts.length < 2) return null; final hour = int.tryParse(parts[0]); final minute = int.tryParse(parts[1]); if (hour == null || minute == null) return null; return TimeOfDay(hour: hour, minute: minute); } String _timeValue(TimeOfDay value) => '${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}'; String _dateValue(DateTime value) => '${value.year}-${value.month.toString().padLeft(2, '0')}-${value.day.toString().padLeft(2, '0')}'; String _displayDate(DateTime value) => formatMedicationFormDate(value); String _frequencyForTimes(int count) => switch (count) { 2 => 'TwiceDaily', 3 => 'ThreeTimesDaily', _ => 'Daily', };