Files
FarmaFinder/apps/frontend-mobile/app/medicine/[id].tsx
T
Antoni Nuñez Romeu 9949b85001
Run Tests on Branches / Frontend Tests (push) Has been cancelled
Run Tests on Branches / Backend Tests (push) Has been cancelled
Refactor frontend-mobile
2026-07-07 18:08:44 +02:00

244 lines
6.8 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { View, Text, ScrollView, StyleSheet, TouchableOpacity, Linking } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { getMedicine, getMedicinePharmacies } from '../../services/medicines';
import { StockBadge } from '../../components/StockBadge';
import { LoadingSpinner } from '../../components/LoadingSpinner';
import { colors, spacing, borderRadius } from '../../constants/theme';
import { Medicine, PharmacyMedicine } from '../../types';
export default function MedicineDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const [medicine, setMedicine] = useState<Medicine | null>(null);
const [pharmacies, setPharmacies] = useState<PharmacyMedicine[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (!id) return;
const fetchMedicine = async () => {
try {
const [medData, pharmData] = await Promise.all([
getMedicine(id),
getMedicinePharmacies(id),
]);
setMedicine(medData);
setPharmacies(pharmData);
} catch (error) {
console.error('Error fetching medicine:', error);
} finally {
setIsLoading(false);
}
};
fetchMedicine();
}, [id]);
const handleDirections = (latitude: number, longitude: number) => {
const url = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`;
Linking.openURL(url);
};
if (isLoading) {
return <LoadingSpinner message="Cargando medicamento..." />;
}
if (!medicine) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Medicamento no encontrado</Text>
</View>
);
}
return (
<ScrollView style={styles.container}>
<View style={styles.header}>
<Text style={styles.name}>{medicine.nombre}</Text>
<StockBadge stock={medicine.stock} />
</View>
<View style={styles.infoSection}>
<InfoRow label="Principio activo" value={medicine.principioActivo} />
<InfoRow label="Laboratorio" value={medicine.laboratorio} />
<InfoRow label="Forma farmacéutica" value={medicine.formaFarmaceutica} />
<InfoRow
label="Precio"
value={medicine.precio ? `${medicine.precio.toFixed(2)} €` : 'No disponible'}
/>
<InfoRow label="Registro" value={medicine.nregistro} />
</View>
<View style={styles.pharmaciesSection}>
<Text style={styles.sectionTitle}>
Farmacias ({pharmacies.length})
</Text>
{pharmacies.length === 0 ? (
<Text style={styles.noPharmacies}>No hay farmacias con este medicamento</Text>
) : (
pharmacies.map((pharm) => (
<View key={pharm.id} style={styles.pharmacyCard}>
<TouchableOpacity
style={styles.pharmacyCardContent}
onPress={() => router.push(`/pharmacy/${pharm.pharmacy_id}`)}
>
<View style={styles.pharmacyInfo}>
<Text style={styles.pharmacyName}>{pharm.pharmacy?.name}</Text>
<Text style={styles.pharmacyAddress}>{pharm.pharmacy?.address}</Text>
</View>
<View style={styles.pharmacyStock}>
<Text style={styles.price}>{pharm.price.toFixed(2)} </Text>
<Text style={styles.stock}>Stock: {pharm.stock}</Text>
</View>
</TouchableOpacity>
{pharm.pharmacy?.latitude != null && pharm.pharmacy?.longitude != null && (
<TouchableOpacity
style={styles.directionsButton}
onPress={() => handleDirections(pharm.pharmacy.latitude, pharm.pharmacy.longitude)}
>
<Ionicons name="navigate" size={16} color={colors.primary} />
<Text style={styles.directionsText}>Cómo llegar</Text>
</TouchableOpacity>
)}
</View>
))
)}
</View>
</ScrollView>
);
}
function InfoRow({ label, value }: { label: string; value: string }) {
return (
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>{label}</Text>
<Text style={styles.infoValue}>{value}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
padding: spacing.lg,
backgroundColor: colors.card,
},
name: {
flex: 1,
fontSize: 22,
fontWeight: 'bold',
color: colors.text,
marginRight: spacing.sm,
},
infoSection: {
backgroundColor: colors.card,
marginTop: spacing.sm,
padding: spacing.lg,
},
infoRow: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
infoLabel: {
fontSize: 14,
color: colors.textSecondary,
},
infoValue: {
fontSize: 14,
color: colors.text,
fontWeight: '500',
},
pharmaciesSection: {
marginTop: spacing.sm,
padding: spacing.lg,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: colors.text,
marginBottom: spacing.md,
},
noPharmacies: {
color: colors.textSecondary,
textAlign: 'center',
padding: spacing.xl,
},
pharmacyCard: {
backgroundColor: colors.card,
borderRadius: borderRadius.lg,
marginBottom: spacing.sm,
overflow: 'hidden',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06,
shadowRadius: 8,
elevation: 2,
},
pharmacyCardContent: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: spacing.md,
},
pharmacyInfo: {
flex: 1,
},
pharmacyName: {
fontSize: 16,
fontWeight: '600',
color: colors.text,
},
pharmacyAddress: {
fontSize: 14,
color: colors.textSecondary,
marginTop: spacing.xs,
},
pharmacyStock: {
alignItems: 'flex-end',
},
price: {
fontSize: 16,
fontWeight: '600',
color: colors.primary,
},
stock: {
fontSize: 12,
color: colors.textSecondary,
marginTop: spacing.xs,
},
directionsButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: spacing.xs,
paddingVertical: spacing.sm,
borderTopWidth: 1,
borderTopColor: colors.border,
},
directionsText: {
color: colors.primary,
fontSize: 14,
fontWeight: '600',
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorText: {
fontSize: 16,
color: colors.textSecondary,
},
});