From ca4a9671e7f8c17c7e5cf97fff3b0c7ae4ef7b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Mon, 13 Jul 2026 15:33:34 +0200 Subject: [PATCH] feat(mobile): add product detail screen for CIMA and OFF products --- .../app/product/[source]/[id].tsx | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 apps/frontend-mobile/app/product/[source]/[id].tsx diff --git a/apps/frontend-mobile/app/product/[source]/[id].tsx b/apps/frontend-mobile/app/product/[source]/[id].tsx new file mode 100644 index 0000000..3659e67 --- /dev/null +++ b/apps/frontend-mobile/app/product/[source]/[id].tsx @@ -0,0 +1,249 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, ScrollView, StyleSheet, Image, ActivityIndicator } from 'react-native'; +import { useLocalSearchParams } from 'expo-router'; +import { getProduct, Product } from '../../../services/products'; +import { LoadingSpinner } from '../../../components/LoadingSpinner'; +import { useThemeContext } from '../../../components/ThemeProvider'; +import { spacing, borderRadius } from '../../../constants/theme'; + +export default function ProductDetailScreen() { + const { source, id } = useLocalSearchParams<{ source: string; id: string }>(); + const { colors } = useThemeContext(); + const [product, setProduct] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(false); + + useEffect(() => { + if (!source || !id) return; + + const fetchProduct = async () => { + try { + const data = await getProduct(source, id); + if (data) { + setProduct(data); + } else { + setError(true); + } + } catch { + setError(true); + } finally { + setIsLoading(false); + } + }; + + fetchProduct(); + }, [source, id]); + + if (isLoading) { + return ; + } + + if (error || !product) { + return ( + + Producto no encontrado + + ); + } + + const isCima = product.source === 'cima'; + + return ( + + {product.image_url ? ( + + ) : ( + + Sin imagen + + )} + + + + {product.name} + + {isCima ? 'CIMA' : 'OFF'} + + + {product.brand ? ( + {product.brand} + ) : null} + {product.category ? ( + {product.category} + ) : null} + + + {isCima ? ( + + Detalles CIMA + {product.active_ingredient && ( + + )} + {product.dosage && ( + + )} + {product.form && ( + + )} + {product.prescription && ( + + )} + + + ) : ( + + Información nutricional + {product.nutriscore && ( + + )} + {product.nova_group != null && ( + + )} + {product.eco_score && ( + + )} + {product.ingredients && ( + + Ingredientes + {product.ingredients} + + )} + + )} + + {isCima && product.photos && product.photos.length > 0 && ( + + Imágenes + + {product.photos.map((photo, i) => ( + + + {photo.tipo} + + ))} + + + )} + + ); +} + +function InfoRow({ label, value, colors }: { label: string; value: string; colors: any }) { + return ( + + {label} + {value} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + image: { + width: '100%', + height: 260, + backgroundColor: '#f5f5f5', + }, + imagePlaceholder: { + width: '100%', + height: 160, + alignItems: 'center', + justifyContent: 'center', + }, + placeholderText: { + fontSize: 14, + }, + header: { + padding: spacing.lg, + }, + nameRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + gap: spacing.sm, + }, + name: { + flex: 1, + fontSize: 22, + fontWeight: 'bold', + }, + badge: { + paddingHorizontal: spacing.sm, + paddingVertical: spacing.xs, + borderRadius: borderRadius.full, + overflow: 'hidden', + }, + badgeText: { + color: '#fff', + fontSize: 12, + fontWeight: '700', + }, + brand: { + fontSize: 15, + marginTop: spacing.xs, + }, + category: { + fontSize: 13, + marginTop: spacing.xs, + }, + infoSection: { + marginTop: spacing.sm, + padding: spacing.lg, + }, + sectionTitle: { + fontSize: 16, + fontWeight: '600', + marginBottom: spacing.sm, + }, + infoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: spacing.sm, + borderBottomWidth: 1, + }, + infoLabel: { + fontSize: 13, + flexShrink: 0, + marginRight: spacing.sm, + }, + infoValue: { + fontSize: 13, + fontWeight: '500', + flex: 1, + textAlign: 'right', + }, + infoValueMultiline: { + fontSize: 13, + fontWeight: '500', + flex: 1, + textAlign: 'right', + }, + photosScroll: { + marginTop: spacing.xs, + }, + photoItem: { + marginRight: spacing.md, + alignItems: 'center', + width: 120, + }, + photoImage: { + width: 120, + height: 120, + borderRadius: borderRadius.md, + backgroundColor: '#f5f5f5', + }, + photoLabel: { + fontSize: 11, + marginTop: spacing.xs, + }, + errorContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + errorText: { + fontSize: 16, + }, +});