import { useMemo, useState } from 'react'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import PrimaryButton from '../components/PrimaryButton'; import SecondaryButton from '../components/SecondaryButton'; import AppHeader from '../components/layout/AppHeader'; import ScreenContainer from '../components/layout/ScreenContainer'; import SectionTitle from '../components/layout/SectionTitle'; import ProductImage from '../components/product/ProductImage'; import ProductScoreBadge from '../components/product/ProductScoreBadge'; import RatingCategoryRow from '../components/product/RatingCategoryRow'; import { calculateProductRating } from '../services/ratingService'; import { colors } from '../utils/constants'; import { formatPrice } from '../utils/formatPrice'; export default function ProductDetailScreen({ product, preferences, onBack, onAddToList, onOpenList, onSaveManualPrice, onShowRatingCriteria, onShowAlternatives }) { const [savedMessage, setSavedMessage] = useState(''); const [manualPrice, setManualPrice] = useState(''); const [priceError, setPriceError] = useState(''); const hasProductPrice = typeof product.price === 'number'; const currency = product.currency || preferences.currency; const parsedManualPrice = parseManualPrice(manualPrice); const pricedProduct = useMemo(() => ({ ...product, currency, price: hasProductPrice ? product.price : parsedManualPrice }), [currency, hasProductPrice, parsedManualPrice, product]); const rating = useMemo(() => calculateProductRating(pricedProduct, preferences), [pricedProduct, preferences]); const recommendation = rating.overallStatus === 'good' ? 'Passt gut zu dir' : rating.overallStatus === 'medium' ? 'Genauer prüfen' : 'Lieber meiden'; function addToList() { if (!hasProductPrice && manualPrice.trim() && parsedManualPrice === undefined) { setSavedMessage(''); setPriceError('Bitte gib einen gültigen Preis ein oder lasse das Feld leer.'); return; } if (!hasProductPrice && typeof pricedProduct.price === 'number') { onSaveManualPrice(pricedProduct, pricedProduct.price); } onAddToList(pricedProduct); setPriceError(''); setSavedMessage('Zur Einkaufsliste hinzugefügt.'); } return ( {product.brand || 'Ohne Marke'} {product.name} {product.category || 'Produkt'} · {formatPrice(pricedProduct.price, currency)} {sourceLabel(product.dataSource)} {recommendation} Tippe unten auf ein Kriterium, um die Bewertung nachzuvollziehen. {!hasProductPrice ? ( Ladenpreis ergänzen Optional. Ohne Preis kannst du das Produkt speichern; die Einkaufssumme bleibt dann unvollständig. {currency} { setManualPrice(value); setPriceError(''); setSavedMessage(''); }} placeholder="z. B. 3.50" placeholderTextColor={colors.muted} style={styles.priceInput} value={manualPrice} /> {priceError ? {priceError} : null} ) : null} {savedMessage ? ( {savedMessage} Einkaufsliste ansehen › ) : null} onShowAlternatives(product)} /> Bewertungskriterien {Object.entries(rating.categoryScores).map(([category, categoryRating]) => ( onShowRatingCriteria(pricedProduct, category)} rating={categoryRating} /> ))} Produktdaten Barcode: {product.barcode} Zutaten: {(product.ingredients || []).join(', ') || 'Keine Angaben'} Allergene: {(product.allergens || []).join(', ') || 'Keine Angaben'} Nutri-Score: {product.nutrition?.nutriScore || 'Keine Angabe'} Eco-Score: {product.sustainability?.ecoScore || 'Keine Angabe'} ); } function parseManualPrice(value) { const normalizedValue = value.trim().replace(',', '.'); const parsedValue = Number(normalizedValue); if (!normalizedValue || !Number.isFinite(parsedValue) || parsedValue <= 0) { return undefined; } return Math.round(parsedValue * 100) / 100; } function sourceLabel(source) { const labels = { cache: 'Quelle: lokaler Cache', local: 'Quelle: lokale Beispieldaten', 'open-food-facts': 'Quelle: Open Food Facts' }; return labels[source] || 'Quelle: unbekannt'; } const styles = StyleSheet.create({ productPanel: { backgroundColor: colors.surface, borderColor: colors.border, borderRadius: 20, borderWidth: 1, gap: 14, padding: 18 }, topRow: { alignItems: 'flex-start', flexDirection: 'row', gap: 12 }, copy: { flex: 1 }, brand: { color: colors.primary, fontSize: 13, fontWeight: '800', textTransform: 'uppercase' }, name: { color: colors.text, fontSize: 25, fontWeight: '800', lineHeight: 31, marginTop: 4 }, meta: { color: colors.muted, fontSize: 15, marginTop: 6 }, source: { alignSelf: 'flex-start', backgroundColor: '#eef3ef', borderRadius: 8, color: colors.primaryDark, fontSize: 12, fontWeight: '800', marginTop: 10, paddingHorizontal: 8, paddingVertical: 4 }, recommendation: { borderRadius: 14, gap: 4, padding: 14 }, goodRecommendation: { backgroundColor: '#e4f4e9' }, mediumRecommendation: { backgroundColor: '#fff3d7' }, criticalRecommendation: { backgroundColor: '#fee9e7' }, recommendationTitle: { fontSize: 17, fontWeight: '800' }, goodText: { color: colors.success }, mediumText: { color: '#8a5914' }, criticalText: { color: colors.danger }, recommendationCopy: { color: colors.text, fontSize: 13, lineHeight: 19 }, pricePanel: { backgroundColor: colors.surface, borderColor: colors.border, borderRadius: 18, borderWidth: 1, gap: 10, padding: 16 }, priceTitle: { color: colors.text, fontSize: 17, fontWeight: '800' }, priceCopy: { color: colors.muted, fontSize: 14, lineHeight: 20 }, priceInputRow: { alignItems: 'center', flexDirection: 'row', gap: 10 }, currency: { color: colors.muted, fontSize: 16, fontWeight: '800' }, priceInput: { borderColor: colors.border, borderRadius: 12, borderWidth: 1, color: colors.text, flex: 1, fontSize: 16, paddingHorizontal: 12, paddingVertical: 10 }, errorText: { color: colors.danger, fontSize: 13, fontWeight: '800' }, dataPanel: { backgroundColor: colors.surface, borderColor: colors.border, borderRadius: 18, borderWidth: 1, gap: 8, padding: 14 }, dataText: { color: colors.muted, fontSize: 14, lineHeight: 20 }, saved: { color: colors.success, fontSize: 14, fontWeight: '800' }, savedPanel: { backgroundColor: colors.surfaceTint, borderRadius: 14, gap: 6, padding: 14 }, savedLink: { color: colors.primary, fontSize: 14, fontWeight: '800' } });