import { ThemedText } from '@/components/ThemedText'; import { useAppDispatch } from '@/hooks/redux'; import { takeMedicationAction } from '@/store/medicationsSlice'; import type { MedicationDisplayItem } from '@/types/medication'; import { Ionicons } from '@expo/vector-icons'; import dayjs, { Dayjs } from 'dayjs'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { Image } from 'expo-image'; import React, { useEffect, useState } from 'react'; import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native'; export type MedicationCardProps = { medication: MedicationDisplayItem; colors: (typeof import('@/constants/Colors').Colors)[keyof typeof import('@/constants/Colors').Colors]; selectedDate: Dayjs; onOpenDetails?: (medication: MedicationDisplayItem) => void; }; export function MedicationCard({ medication, colors, selectedDate, onOpenDetails }: MedicationCardProps) { const dispatch = useAppDispatch(); const [isSubmitting, setIsSubmitting] = useState(false); const [imageError, setImageError] = useState(false); const scheduledDate = dayjs(`${selectedDate.format('YYYY-MM-DD')} ${medication.scheduledTime}`); const timeDiffMinutes = scheduledDate.diff(dayjs(), 'minute'); // 当药品变化时重置图片错误状态 useEffect(() => { setImageError(false); }, [medication.id]); /** * 处理服药操作 */ const handleTakeMedication = async () => { // 检查 recordId 是否存在 if (!medication.recordId || isSubmitting) { return; } // 判断是否早于服药时间1小时以上 if (timeDiffMinutes > 60) { // 显示二次确认弹窗 Alert.alert( '尚未到服药时间', `该用药计划在 ${medication.scheduledTime},现在还早于1小时以上。\n\n是否确认已服用此药物?`, [ { text: '取消', style: 'cancel', onPress: () => { // 用户取消,不执行任何操作 console.log('用户取消提前服药'); }, }, { text: '确认已服用', style: 'default', onPress: () => { // 用户确认,执行服药逻辑 executeTakeMedication(medication.recordId!); }, }, ] ); } else { // 在正常时间范围内,直接执行服药逻辑 executeTakeMedication(medication.recordId); } }; /** * 执行服药操作(提取公共逻辑) */ const executeTakeMedication = async (recordId: string) => { setIsSubmitting(true); try { // 调用 Redux action 标记为已服用 await dispatch(takeMedicationAction({ recordId: recordId, actualTime: new Date().toISOString(), })).unwrap(); // 可选:显示成功提示 // Alert.alert('服药成功', '已记录本次服药'); } catch (error) { console.error('[MEDICATION_CARD] 服药操作失败', error); Alert.alert( '操作失败', error instanceof Error ? error.message : '记录服药时发生错误,请稍后重试', [{ text: '确定' }] ); } finally { setIsSubmitting(false); } }; const renderStatusBadge = () => { if (medication.status === 'missed') { return ( 已错过 ); } if (medication.status === 'upcoming') { if (timeDiffMinutes <= 0) { return ( 到服药时间 ); } const hours = Math.floor(timeDiffMinutes / 60); const minutes = timeDiffMinutes % 60; const formatted = hours > 0 ? `${hours}小时${minutes > 0 ? `${minutes}分钟` : ''}` : `${minutes}分钟`; return ( 剩余 {formatted} ); } return null; }; const renderAction = () => { if (medication.status === 'taken') { return ( 已服用 ); } // 只要没有服药,都可以显示立即服用 return ( {isLiquidGlassAvailable() ? ( {isSubmitting ? '提交中...' : '立即服用'} ) : ( {isSubmitting ? '提交中...' : '立即服用'} )} ); }; const statusChip = renderStatusBadge(); return ( onOpenDetails?.(medication)} disabled={!onOpenDetails} > {statusChip ? {statusChip} : null} setImageError(true)} key={medication.id} // 重新渲染时重置状态 /> {medication.name} {medication.dosage} {medication.scheduledTime} | {medication.frequency} {renderAction()} ); } const styles = StyleSheet.create({ card: { borderRadius: 18, position: 'relative', }, cardSurface: { borderRadius: 18, overflow: 'hidden', }, cardBody: { paddingHorizontal: 10, paddingBottom: 10, paddingTop: 10, }, cardContent: { flexDirection: 'row', alignItems: 'center', gap: 20, }, thumbnailWrapper: { width: 148, height: 110, }, thumbnailSurface: { flex: 1, backgroundColor: '#F1F4FF', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', borderRadius: 18, }, thumbnailImage: { width: '70%', height: '70%', resizeMode: 'contain', }, infoSection: { flex: 1, }, cardTitle: { fontSize: 16, fontWeight: '700', }, cardDosage: { fontSize: 12, marginTop: 4, }, scheduleRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, cardSchedule: { fontSize: 12, }, scheduleIcon: { marginTop: -1, }, actionContainer: { marginTop: 8, }, actionButton: { alignSelf: 'stretch', flexDirection: 'row', alignItems: 'center', gap: 6, justifyContent: 'center', height: 38, borderRadius: 10, overflow: 'hidden', }, actionButtonUpcoming: { backgroundColor: '#1363FF', }, actionButtonTaken: { backgroundColor: '#1FBF4B', }, actionButtonMissed: { backgroundColor: '#9CA3AF', }, fallbackActionButton: { borderWidth: 1, borderColor: 'rgba(19, 99, 255, 0.3)', backgroundColor: 'rgba(19, 99, 255, 0.9)', }, fallbackActionButtonMissed: { borderWidth: 1, borderColor: 'rgba(156, 163, 175, 0.3)', backgroundColor: 'rgba(156, 163, 175, 0.9)', }, actionButtonText: { fontSize: 14, fontWeight: '700', color: '#fff', }, actionButtonTextMissed: { fontSize: 14, fontWeight: '700', color: '#fff', }, statusChipWrapper: { position: 'absolute', top: 0, right: 0, }, statusChip: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 10, height: 28, borderBottomLeftRadius: 20, borderTopRightRadius: 0, borderBottomRightRadius: 0, backgroundColor: '#1363FF', }, statusChipUpcoming: { backgroundColor: '#1363FF', }, statusChipMissed: { backgroundColor: '#FF3B30', }, statusChipText: { fontSize: 10, fontWeight: '600', color: '#fff', }, });