Update styling and accessibility, refine product and shopping list functionalities
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
|
||||
import PrimaryButton from '../components/PrimaryButton';
|
||||
import SecondaryButton from '../components/SecondaryButton';
|
||||
@@ -18,6 +18,7 @@ export default function ProductDetailScreen({
|
||||
preferences,
|
||||
onBack,
|
||||
onAddToList,
|
||||
onOpenList,
|
||||
onSaveManualPrice,
|
||||
onShowRatingCriteria,
|
||||
onShowAlternatives
|
||||
@@ -34,26 +35,27 @@ export default function ProductDetailScreen({
|
||||
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 && parsedManualPrice === undefined) {
|
||||
if (!hasProductPrice && manualPrice.trim() && parsedManualPrice === undefined) {
|
||||
setSavedMessage('');
|
||||
setPriceError('Bitte gib einen Preis ein, bevor du das Produkt hinzufügst.');
|
||||
setPriceError('Bitte gib einen gültigen Preis ein oder lasse das Feld leer.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof pricedProduct.price === 'number') {
|
||||
if (!hasProductPrice && typeof pricedProduct.price === 'number') {
|
||||
onSaveManualPrice(pricedProduct, pricedProduct.price);
|
||||
}
|
||||
|
||||
onAddToList(pricedProduct);
|
||||
setPriceError('');
|
||||
setSavedMessage('Produkt wurde zur Einkaufsliste hinzugefügt.');
|
||||
setSavedMessage('Zur Einkaufsliste hinzugefügt.');
|
||||
}
|
||||
|
||||
return (
|
||||
<ScreenContainer>
|
||||
<AppHeader title="Produktbewertung" subtitle="Persönliche Einschätzung" onBack={onBack} />
|
||||
<AppHeader title="Produkt im Check" subtitle="Bewertung nach deinen Einstellungen" onBack={onBack} />
|
||||
|
||||
<View style={styles.productPanel}>
|
||||
<ProductImage imageUrl={product.imageUrl} productName={product.name} size="large" />
|
||||
@@ -68,24 +70,27 @@ export default function ProductDetailScreen({
|
||||
</View>
|
||||
<ProductScoreBadge status={rating.overallStatus} score={rating.overallScore} />
|
||||
</View>
|
||||
<Text style={styles.summary}>
|
||||
Empfehlung: {rating.overallStatus === 'good' ? 'kaufen' : rating.overallStatus === 'medium' ? 'prüfen' : 'meiden'}.
|
||||
</Text>
|
||||
<View style={[styles.recommendation, styles[`${rating.overallStatus}Recommendation`]]}>
|
||||
<Text style={[styles.recommendationTitle, styles[`${rating.overallStatus}Text`]]}>{recommendation}</Text>
|
||||
<Text style={styles.recommendationCopy}>Tippe unten auf ein Kriterium, um die Bewertung nachzuvollziehen.</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{!hasProductPrice ? (
|
||||
<View style={styles.pricePanel}>
|
||||
<Text style={styles.priceTitle}>Preis ergänzen</Text>
|
||||
<Text style={styles.priceTitle}>Ladenpreis ergänzen</Text>
|
||||
<Text style={styles.priceCopy}>
|
||||
Open Food Facts liefert keine Preise. Gib den Ladenpreis ein, damit die Einkaufsliste und Budgetprüfung stimmen.
|
||||
Optional. Ohne Preis kannst du das Produkt speichern; die Einkaufssumme bleibt dann unvollständig.
|
||||
</Text>
|
||||
<View style={styles.priceInputRow}>
|
||||
<Text style={styles.currency}>{currency}</Text>
|
||||
<TextInput
|
||||
accessibilityLabel="Ladenpreis"
|
||||
keyboardType="decimal-pad"
|
||||
onChangeText={(value) => {
|
||||
setManualPrice(value);
|
||||
setPriceError('');
|
||||
setSavedMessage('');
|
||||
}}
|
||||
placeholder="z. B. 3.50"
|
||||
placeholderTextColor={colors.muted}
|
||||
@@ -97,6 +102,17 @@ export default function ProductDetailScreen({
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{savedMessage ? (
|
||||
<View style={styles.savedPanel}>
|
||||
<Text style={styles.saved}>{savedMessage}</Text>
|
||||
<Pressable accessibilityRole="button" onPress={onOpenList}>
|
||||
<Text style={styles.savedLink}>Einkaufsliste ansehen ›</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : null}
|
||||
<PrimaryButton label="Zur Einkaufsliste hinzufügen" onPress={addToList} />
|
||||
<SecondaryButton label="Bessere Alternativen suchen" onPress={() => onShowAlternatives(product)} />
|
||||
|
||||
<SectionTitle>Bewertungskriterien</SectionTitle>
|
||||
{Object.entries(rating.categoryScores).map(([category, categoryRating]) => (
|
||||
<RatingCategoryRow
|
||||
@@ -111,14 +127,11 @@ export default function ProductDetailScreen({
|
||||
<View style={styles.dataPanel}>
|
||||
<Text style={styles.dataText}>Barcode: {product.barcode}</Text>
|
||||
<Text style={styles.dataText}>Zutaten: {(product.ingredients || []).join(', ') || 'Keine Angaben'}</Text>
|
||||
<Text style={styles.dataText}>Allergene: {(product.allergens || []).join(', ') || 'Keine'}</Text>
|
||||
<Text style={styles.dataText}>Allergene: {(product.allergens || []).join(', ') || 'Keine Angaben'}</Text>
|
||||
<Text style={styles.dataText}>Nutri-Score: {product.nutrition?.nutriScore || 'Keine Angabe'}</Text>
|
||||
<Text style={styles.dataText}>Eco-Score: {product.sustainability?.ecoScore || 'Keine Angabe'}</Text>
|
||||
</View>
|
||||
|
||||
{savedMessage ? <Text style={styles.saved}>{savedMessage}</Text> : null}
|
||||
<PrimaryButton label="Zur Einkaufsliste hinzufügen" onPress={addToList} />
|
||||
<SecondaryButton label="Alternative anzeigen" onPress={() => onShowAlternatives(product)} />
|
||||
</ScreenContainer>
|
||||
);
|
||||
}
|
||||
@@ -148,7 +161,7 @@ const styles = StyleSheet.create({
|
||||
productPanel: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
gap: 14,
|
||||
padding: 18
|
||||
@@ -190,15 +203,30 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4
|
||||
},
|
||||
summary: {
|
||||
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: 16,
|
||||
fontWeight: '700'
|
||||
fontSize: 13,
|
||||
lineHeight: 19
|
||||
},
|
||||
pricePanel: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderRadius: 18,
|
||||
borderWidth: 1,
|
||||
gap: 10,
|
||||
padding: 16
|
||||
@@ -225,7 +253,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
priceInput: {
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
color: colors.text,
|
||||
flex: 1,
|
||||
@@ -241,7 +269,7 @@ const styles = StyleSheet.create({
|
||||
dataPanel: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderRadius: 8,
|
||||
borderRadius: 18,
|
||||
borderWidth: 1,
|
||||
gap: 8,
|
||||
padding: 14
|
||||
@@ -255,5 +283,16 @@ const styles = StyleSheet.create({
|
||||
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'
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user