diff --git a/README.md b/README.md
index 24c3328..621ab9c 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ Die App enthält jetzt einen MVP-Flow mit Bottom-Tabs:
- Produktdetail: Ampelbewertung nach Allergien, Budget, Ernährung und Nachhaltigkeit
- Alternativen: bessere Produktvorschläge aus dem lokalen Beispieldatensatz
- Einkaufsliste: Produkte hinzufügen, abhaken, löschen und Gesamtsumme sehen
-- Profil: Präferenzen, Allergien, Budget, Bio-Vorliebe und Offline-Modus verwalten
+- Profil: Präferenzen, Allergien, Budget mit Zeitraum, Bio-Vorliebe und Offline-Modus verwalten
Beispiel-Barcodes für Tests über Open Food Facts:
diff --git a/src/components/form/BudgetInput.js b/src/components/form/BudgetInput.js
index 379e062..be80bd6 100644
--- a/src/components/form/BudgetInput.js
+++ b/src/components/form/BudgetInput.js
@@ -1,8 +1,10 @@
-import { StyleSheet, Text, TextInput, View } from 'react-native';
+import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
-import { colors } from '../../utils/constants';
+import { budgetPeriodLabels, colors } from '../../utils/constants';
-export default function BudgetInput({ value, currency, onChangeValue }) {
+const budgetPeriods = ['day', 'week', 'month', 'shoppingTrip'];
+
+export default function BudgetInput({ value, currency, period, onChangePeriod, onChangeValue }) {
return (
Maximales Einkaufsbudget
@@ -15,6 +17,23 @@ export default function BudgetInput({ value, currency, onChangeValue }) {
value={String(value ?? '')}
/>
+
+ {budgetPeriods.map((option) => {
+ const isActive = period === option;
+ return (
+ onChangePeriod(option)}
+ style={[styles.periodChip, isActive ? styles.activePeriodChip : null]}
+ >
+
+ pro {budgetPeriodLabels[option]}
+
+
+ );
+ })}
+
);
}
@@ -25,7 +44,7 @@ const styles = StyleSheet.create({
borderColor: colors.border,
borderRadius: 8,
borderWidth: 1,
- gap: 8,
+ gap: 10,
padding: 14
},
label: {
@@ -52,5 +71,31 @@ const styles = StyleSheet.create({
fontSize: 16,
paddingHorizontal: 12,
paddingVertical: 10
+ },
+ periodGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 8
+ },
+ periodChip: {
+ alignItems: 'center',
+ borderColor: colors.border,
+ borderRadius: 8,
+ borderWidth: 1,
+ flexGrow: 1,
+ paddingHorizontal: 10,
+ paddingVertical: 9
+ },
+ activePeriodChip: {
+ backgroundColor: colors.primary,
+ borderColor: colors.primary
+ },
+ periodText: {
+ color: colors.text,
+ fontSize: 13,
+ fontWeight: '800'
+ },
+ activePeriodText: {
+ color: colors.surface
}
});
diff --git a/src/screens/ProductDetailScreen.js b/src/screens/ProductDetailScreen.js
index 7dff833..62bff3b 100644
--- a/src/screens/ProductDetailScreen.js
+++ b/src/screens/ProductDetailScreen.js
@@ -17,24 +17,24 @@ export default function ProductDetailScreen({ product, preferences, onBack, onAd
const [savedMessage, setSavedMessage] = useState('');
const [manualPrice, setManualPrice] = useState('');
const [priceError, setPriceError] = useState('');
- const rating = useMemo(() => calculateProductRating(product, preferences), [product, preferences]);
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]);
function addToList() {
- const parsedManualPrice = parseManualPrice(manualPrice);
-
if (!hasProductPrice && parsedManualPrice === undefined) {
setSavedMessage('');
setPriceError('Bitte gib einen Preis ein, bevor du das Produkt hinzufügst.');
return;
}
- onAddToList({
- ...product,
- currency,
- price: hasProductPrice ? product.price : parsedManualPrice
- });
+ onAddToList(pricedProduct);
setPriceError('');
setSavedMessage('Produkt wurde zur Einkaufsliste hinzugefügt.');
}
@@ -50,7 +50,7 @@ export default function ProductDetailScreen({ product, preferences, onBack, onAd
{product.brand || 'Ohne Marke'}
{product.name}
- {product.category || 'Produkt'} · {formatPrice(product.price, currency)}
+ {product.category || 'Produkt'} · {formatPrice(pricedProduct.price, currency)}
diff --git a/src/screens/ProfileScreen.js b/src/screens/ProfileScreen.js
index 107fd7b..42bb8fa 100644
--- a/src/screens/ProfileScreen.js
+++ b/src/screens/ProfileScreen.js
@@ -68,7 +68,9 @@ export default function ProfileScreen({ preferences, onChangePreferences }) {
updatePreference('budgetPeriod', value)}
onChangeValue={(value) => updatePreference('maxBudgetPerShoppingTrip', value)}
+ period={preferences.budgetPeriod || 'week'}
value={preferences.maxBudgetPerShoppingTrip}
/>
diff --git a/src/services/ratingService.js b/src/services/ratingService.js
index 118f27e..399b373 100644
--- a/src/services/ratingService.js
+++ b/src/services/ratingService.js
@@ -134,14 +134,26 @@ function rateBudget(product, preferences) {
}
const share = product.price / preferences.maxBudgetPerShoppingTrip;
+ const periodLabel = budgetPeriodLabel(preferences.budgetPeriod);
if (share <= 0.08) {
- return { status: 'good', message: 'Preis passt gut zu deinem Einkaufsbudget.' };
+ return { status: 'good', message: `Preis passt gut zu deinem Budget pro ${periodLabel}.` };
}
if (share <= 0.18) {
- return { status: 'medium', message: 'Preis ist spürbar, aber noch im Rahmen.' };
+ return { status: 'medium', message: `Preis ist für dein Budget pro ${periodLabel} spürbar, aber noch im Rahmen.` };
}
- return { status: 'critical', message: 'Preis belastet dein Einkaufsbudget stark.' };
+ return { status: 'critical', message: `Preis belastet dein Budget pro ${periodLabel} stark.` };
+}
+
+function budgetPeriodLabel(period) {
+ const labels = {
+ day: 'Tag',
+ week: 'Woche',
+ month: 'Monat',
+ shoppingTrip: 'Einkauf'
+ };
+
+ return labels[period] || labels.week;
}
diff --git a/src/utils/constants.js b/src/utils/constants.js
index 8795a3d..ecb4de3 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -17,6 +17,7 @@ export const defaultPreferences = {
preferredDiet: 'lowSugar',
preferBio: true,
maxBudgetPerShoppingTrip: 45,
+ budgetPeriod: 'week',
currency: 'CHF',
offlineModeEnabled: false,
sustainabilityPriority: 'medium',
@@ -30,3 +31,10 @@ export const dietLabels = {
lowSugar: 'Wenig Zucker',
highProtein: 'Proteinreich'
};
+
+export const budgetPeriodLabels = {
+ day: 'Tag',
+ week: 'Woche',
+ month: 'Monat',
+ shoppingTrip: 'Einkauf'
+};