initial commit

This commit is contained in:
2026-07-01 13:54:21 +02:00
parent ac7ba88422
commit 43ef81b8e8
16 changed files with 1061 additions and 228 deletions
+110 -9
View File
@@ -29,6 +29,7 @@ export default function App() {
const [preferences, setPreferences] = useState(defaultPreferences);
const [shoppingList, setShoppingList] = useState(initialShoppingList);
const [productCache, setProductCache] = useState({});
const [manualPrices, setManualPrices] = useState({});
const [recentProducts, setRecentProducts] = useState([]);
const [isHydrated, setIsHydrated] = useState(false);
@@ -40,11 +41,13 @@ export default function App() {
storedPreferences,
storedShoppingList,
storedProductCache,
storedManualPrices,
storedRecentProducts
] = await Promise.all([
storageService.loadPreferences(defaultPreferences),
storageService.loadShoppingList(initialShoppingList),
storageService.loadProductCache(),
storageService.loadManualPrices(),
storageService.loadRecentProducts()
]);
@@ -52,6 +55,7 @@ export default function App() {
setPreferences(storedPreferences);
setShoppingList(storedShoppingList);
setProductCache(storedProductCache);
setManualPrices(storedManualPrices);
setRecentProducts(storedRecentProducts);
setIsHydrated(true);
}
@@ -75,6 +79,10 @@ export default function App() {
if (isHydrated) storageService.saveProductCache(productCache);
}, [isHydrated, productCache]);
useEffect(() => {
if (isHydrated) storageService.saveManualPrices(manualPrices);
}, [isHydrated, manualPrices]);
useEffect(() => {
if (isHydrated) storageService.saveRecentProducts(recentProducts);
}, [isHydrated, recentProducts]);
@@ -100,6 +108,8 @@ export default function App() {
onDeleteItem={deleteListItem}
onStartScan={() => setActiveTab('scan')}
onToggleItem={toggleListItem}
onUpdateItemPrice={updateListItemPrice}
preferences={preferences}
shoppingList={shoppingList}
/>
);
@@ -122,7 +132,7 @@ export default function App() {
recentProducts={recentProducts}
/>
);
}, [activeTab, preferences, productCache, recentProducts, shoppingList]);
}, [activeTab, manualPrices, preferences, productCache, recentProducts, shoppingList]);
async function scanProductByBarcode(barcode) {
const result = await lookupProductByBarcode(
@@ -132,18 +142,18 @@ export default function App() {
);
if (result.status === 'found') {
openProduct(result.product);
addProductToCache(result.product);
addRecentProduct(result.product);
const pricedProduct = applyManualPrice(result.product);
openProduct(pricedProduct);
}
return result;
}
function openProduct(product) {
addProductToCache(product);
addRecentProduct(product);
setRoute({ name: 'productDetail', product });
const pricedProduct = applyManualPrice(product);
addProductToCache(pricedProduct);
addRecentProduct(pricedProduct);
setRoute({ name: 'productDetail', product: pricedProduct });
}
function showAlternatives(product) {
@@ -160,17 +170,73 @@ export default function App() {
function addRecentProduct(product) {
setRecentProducts((current) => {
const withoutDuplicate = current.filter((item) => item.id !== product.id);
return [product, ...withoutDuplicate].slice(0, 5);
const previous = current.find((item) => item.id === product.id);
const scanCount = (previous?.scanCount || 0) + 1;
return [{ ...product, scanCount, lastScannedAt: new Date().toISOString() }, ...withoutDuplicate].slice(0, 8);
});
}
function applyManualPrice(product) {
const manualPrice = manualPrices[priceKey(product)];
if (typeof manualPrice !== 'number') {
return product;
}
return {
...product,
currency: product.currency || preferences.currency,
price: manualPrice
};
}
function saveManualPriceForProduct(product, price) {
const key = priceKey(product);
setManualPrices((current) => ({
...current,
[key]: price
}));
setProductCache((current) => {
const cachedProduct = current[product.barcode];
if (!cachedProduct) {
return current;
}
return {
...current,
[product.barcode]: {
...cachedProduct,
currency: product.currency || preferences.currency,
price,
updatedAt: new Date().toISOString()
}
};
});
setRecentProducts((current) =>
current.map((item) =>
item.id === product.id
? { ...item, currency: product.currency || preferences.currency, price }
: item
)
);
}
function addProductToShoppingList(product) {
setShoppingList((current) => {
const existingItem = current.items.find((item) => item.productId === product.id);
const items = existingItem
? current.items.map((item) =>
item.productId === product.id
? { ...item, quantity: item.quantity + 1 }
? {
...item,
quantity: item.quantity + 1,
packageQuantityGrams: item.packageQuantityGrams ?? product.packageQuantityGrams,
packageQuantityLabel: item.packageQuantityLabel ?? product.packageQuantityLabel,
servingQuantityGrams: item.servingQuantityGrams ?? product.servingQuantityGrams,
nutrition: item.nutrition ?? product.nutrition
}
: item
)
: [
@@ -181,6 +247,12 @@ export default function App() {
productName: product.name,
quantity: 1,
estimatedPrice: product.price,
currency: product.currency || current.currency,
barcode: product.barcode,
packageQuantityGrams: product.packageQuantityGrams,
packageQuantityLabel: product.packageQuantityLabel,
servingQuantityGrams: product.servingQuantityGrams,
nutrition: product.nutrition,
checked: false,
addedAt: new Date().toISOString()
}
@@ -195,6 +267,30 @@ export default function App() {
});
}
function updateListItemPrice(itemId, price) {
const itemToUpdate = shoppingList.items.find((item) => item.id === itemId);
if (itemToUpdate) {
saveManualPriceForProduct({
id: itemToUpdate.productId,
barcode: itemToUpdate.barcode,
currency: itemToUpdate.currency || shoppingList.currency
}, price);
}
setShoppingList((current) => {
const items = current.items.map((item) =>
item.id === itemId ? { ...item, estimatedPrice: price } : item
);
return {
...current,
estimatedTotal: calculateEstimatedTotal(items),
items
};
});
}
function toggleListItem(itemId) {
setShoppingList((current) => ({
...current,
@@ -240,6 +336,7 @@ export default function App() {
<ProductDetailScreen
onAddToList={addProductToShoppingList}
onBack={goBackToTabs}
onSaveManualPrice={saveManualPriceForProduct}
onShowAlternatives={showAlternatives}
preferences={preferences}
product={routeProduct}
@@ -285,6 +382,10 @@ function calculateEstimatedTotal(items) {
}, 0);
}
function priceKey(product) {
return product.barcode || product.id;
}
const styles = StyleSheet.create({
container: {
backgroundColor: colors.background,