import React, { useState, useMemo } from 'react'; import { View, Text, FlatList, TextInput, TouchableOpacity, StyleSheet, Modal, KeyboardAvoidingView, Platform, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { MaterialIcons } from '@expo/vector-icons'; import { useHouseholdStore } from '../../src/hooks/useHousehold'; import { ShoppingItem } from '../../src/components/shopping/ShoppingItem'; import { addToShoppingList, checkOffItem, removeShoppingEntry, clearCheckedItems, } from '../../src/services/shopping'; import { COLORS } from '../../src/constants'; import { ShoppingListEntry } from '../../src/types'; import Toast from 'react-native-toast-message'; type ListRow = | { type: 'section'; id: string; title: string; count: number; clearable?: boolean } | { type: 'item'; id: string; entry: ShoppingListEntry }; export default function ShoppingScreen() { const shoppingList = useHouseholdStore((s) => s.shoppingList); const household = useHouseholdStore((s) => s.household); const deviceId = useHouseholdStore((s) => s.deviceId); const [manualItem, setManualItem] = useState(''); const [search, setSearch] = useState(''); const [checkoffEntry, setCheckoffEntry] = useState(null); const [quantityBought, setQuantityBought] = useState('1'); const listData = useMemo(() => { const term = search.toLowerCase().trim(); const matches = (e: ShoppingListEntry) => !term || e.name.toLowerCase().includes(term); const pending = shoppingList.filter((e) => !e.isChecked && matches(e)); const checked = shoppingList.filter((e) => e.isChecked && matches(e)); const rows: ListRow[] = []; if (pending.length > 0) { rows.push({ type: 'section', id: 'sec-pending', title: 'Zu kaufen', count: pending.length }); pending.forEach((e) => rows.push({ type: 'item', id: e.id, entry: e })); } if (checked.length > 0) { rows.push({ type: 'section', id: 'sec-checked', title: 'Im Wagen', count: checked.length, clearable: true }); checked.forEach((e) => rows.push({ type: 'item', id: e.id, entry: e })); } return rows; }, [shoppingList, search]); const handleAddManual = async () => { if (!household || !manualItem.trim()) return; await addToShoppingList(household.id, { itemId: null, name: manualItem.trim(), suggestedQuantity: 1, unit: 'Stück', autoAdded: false, }); setManualItem(''); }; const handleCheckOff = (entry: ShoppingListEntry) => { setCheckoffEntry(entry); setQuantityBought(String(entry.suggestedQuantity)); }; const confirmCheckOff = async () => { if (!household || !checkoffEntry || !deviceId) return; const qty = parseFloat(quantityBought.replace(',', '.')); if (isNaN(qty) || qty < 0) { Toast.show({ type: 'error', text1: 'Ungültige Menge' }); return; } try { await checkOffItem(household.id, checkoffEntry.id, deviceId, qty); } catch { Toast.show({ type: 'error', text1: 'Fehler beim Abhaken' }); } setCheckoffEntry(null); }; const handleRemove = async (entryId: string) => { if (!household) return; await removeShoppingEntry(household.id, entryId); }; const handleClearChecked = async () => { if (!household) return; try { await clearCheckedItems(household.id); } catch { Toast.show({ type: 'error', text1: 'Fehler beim Löschen' }); } }; const renderItem = ({ item }: { item: ListRow }) => { if (item.type === 'section') { return ( {item.title} {item.count} {item.clearable && ( Alle löschen )} ); } return ( handleCheckOff(item.entry)} onRemove={() => handleRemove(item.entry.id)} /> ); }; return ( {shoppingList.length > 0 && ( {search.length > 0 && Platform.OS === 'android' && ( setSearch('')} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}> )} )} item.id} renderItem={renderItem} contentContainerStyle={[styles.list, listData.length === 0 && styles.listEmpty]} ListEmptyComponent={ {search ? 'Keine Treffer' : 'Alles erledigt!'} {search ? `Kein Artikel mit „${search}" gefunden.` : 'Die Einkaufsliste ist leer.'} } /> Wie viele wurden gekauft? {checkoffEntry?.name} {checkoffEntry?.unit} setCheckoffEntry(null)} > Abbrechen Bestätigen ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.surface }, addRow: { flexDirection: 'row', gap: 10, paddingHorizontal: 16, paddingVertical: 12, backgroundColor: COLORS.white, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: COLORS.border, }, addInputWrapper: { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: COLORS.surface, borderRadius: 12, paddingHorizontal: 12, gap: 8, height: 46, }, addInput: { flex: 1, fontSize: 15, color: COLORS.text }, addBtn: { width: 46, height: 46, borderRadius: 14, backgroundColor: COLORS.primary, justifyContent: 'center', alignItems: 'center', }, addBtnDisabled: { opacity: 0.4 }, searchRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: COLORS.white, borderRadius: 12, marginHorizontal: 16, marginTop: 10, marginBottom: 2, paddingHorizontal: 12, height: 42, gap: 8, shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 4, elevation: 1, }, searchInput: { flex: 1, fontSize: 15, color: COLORS.text }, list: { paddingVertical: 8, paddingBottom: 32 }, listEmpty: { flex: 1 }, sectionHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 20, paddingTop: 16, paddingBottom: 6, }, sectionTitle: { fontSize: 12, fontWeight: '600', color: COLORS.textSecondary, textTransform: 'uppercase', letterSpacing: 0.6, }, sectionBadge: { backgroundColor: COLORS.border, borderRadius: 10, paddingHorizontal: 7, paddingVertical: 1, }, sectionBadgeText: { fontSize: 12, fontWeight: '600', color: COLORS.textSecondary }, clearBtn: { marginLeft: 'auto' }, clearBtnText: { fontSize: 12, fontWeight: '600', color: COLORS.danger }, empty: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 48, gap: 10, }, emptyIcon: { width: 72, height: 72, borderRadius: 36, backgroundColor: '#E8F5E9', justifyContent: 'center', alignItems: 'center', marginBottom: 8, }, emptyTitle: { fontSize: 17, fontWeight: '600', color: COLORS.text }, emptyText: { fontSize: 14, color: COLORS.textSecondary, textAlign: 'center' }, overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'flex-end', }, dialog: { backgroundColor: COLORS.white, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, paddingBottom: 40, alignItems: 'center', gap: 10, }, dialogHandle: { width: 36, height: 4, borderRadius: 2, backgroundColor: COLORS.border, marginBottom: 6, }, dialogTitle: { fontSize: 18, fontWeight: '700', color: COLORS.text, textAlign: 'center' }, dialogItem: { fontSize: 15, color: COLORS.textSecondary }, dialogInput: { borderWidth: 2, borderColor: COLORS.primary, borderRadius: 14, paddingHorizontal: 24, paddingVertical: 12, fontSize: 34, fontWeight: '700', width: 140, textAlign: 'center', color: COLORS.text, marginTop: 4, }, dialogUnit: { fontSize: 14, color: COLORS.textSecondary, marginTop: -2 }, dialogActions: { flexDirection: 'row', gap: 12, marginTop: 8, width: '100%' }, dialogBtnCancel: { flex: 1, backgroundColor: COLORS.surface, borderRadius: 14, paddingVertical: 14, alignItems: 'center', borderWidth: 1, borderColor: COLORS.border, }, dialogBtnConfirm: { flex: 1, backgroundColor: COLORS.primary, borderRadius: 14, paddingVertical: 14, alignItems: 'center', }, dialogBtnCancelText: { color: COLORS.text, fontWeight: '600', fontSize: 15 }, dialogBtnConfirmText: { color: COLORS.white, fontWeight: '600', fontSize: 15 }, });