* Extended functionalities

* Added different UIs
This commit is contained in:
2026-06-24 21:43:40 +02:00
parent 282f4864c4
commit f15bb7a916
348 changed files with 59057 additions and 498 deletions

View File

@@ -0,0 +1,126 @@
import { useMemo, useState } from 'react';
import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { useQuery } from '@tanstack/react-query';
import * as Print from 'expo-print';
import * as Sharing from 'expo-sharing';
import { useTranslation } from 'react-i18next';
import { fetchShoppingList } from '@/src/api/meal-plan.api';
import { Button } from '@/src/components/Button';
import { ErrorView } from '@/src/components/ErrorView';
import { Screen } from '@/src/components/Screen';
import { useAppearance } from '@/src/context/AppearanceContext';
import { addDays, formatDisplayDate, weekRange } from '@/src/utils/date';
import { buildShoppingListHtml, formatShoppingQuantity } from '@/src/utils/pdfExport';
export default function ShoppingScreen() {
const { t, i18n } = useTranslation();
const { colors } = useAppearance();
const [weekAnchor, setWeekAnchor] = useState(() => weekRange().from);
const { from, to } = useMemo(() => weekRange(weekAnchor), [weekAnchor]);
const [checked, setChecked] = useState<Record<string, boolean>>({});
const [exporting, setExporting] = useState(false);
const listQuery = useQuery({
queryKey: ['shopping-list', from, to],
queryFn: () => fetchShoppingList(from, to),
});
async function handleExportPdf() {
const items = listQuery.data ?? [];
if (items.length === 0) {
Alert.alert(t('errors.pdfNothingToExport'));
return;
}
setExporting(true);
try {
const html = buildShoppingListHtml(
items,
t('pdf.shoppingList'),
t('pdf.generatedOn', { date: new Date().toLocaleDateString(), count: items.length }),
t('pdf.toTaste'),
);
const { uri } = await Print.printToFileAsync({ html });
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(uri, { UTI: '.pdf', mimeType: 'application/pdf' });
}
} catch {
Alert.alert(t('errors.pdfGenerateFailed'));
} finally {
setExporting(false);
}
}
return (
<Screen title={t('shopping.title')} subtitle={t('shopping.subtitle')}>
<View style={styles.header}>
<Pressable
onPress={() => setWeekAnchor(addDays(from, -7))}
style={[styles.navBtn, { borderColor: colors.border, backgroundColor: colors.surface }]}
>
<Text style={{ color: colors.text }}></Text>
</Pressable>
<Text style={{ color: colors.text, fontWeight: '700', flex: 1, textAlign: 'center' }}>
{formatDisplayDate(from, i18n.language)} {formatDisplayDate(to, i18n.language)}
</Text>
<Pressable
onPress={() => setWeekAnchor(addDays(from, 7))}
style={[styles.navBtn, { borderColor: colors.border, backgroundColor: colors.surface }]}
>
<Text style={{ color: colors.text }}></Text>
</Pressable>
</View>
<Button
title={exporting ? t('pdf.generating') : t('pdf.generate')}
variant="secondary"
loading={exporting}
onPress={() => void handleExportPdf()}
/>
{listQuery.isLoading ? (
<Text style={{ color: colors.textMuted }}>{t('common.loading')}</Text>
) : listQuery.isError ? (
<ErrorView message={t('errors.loadShoppingFailed')} onRetry={() => listQuery.refetch()} />
) : (listQuery.data?.length ?? 0) === 0 ? (
<Text style={{ color: colors.textMuted }}>{t('shopping.empty')}</Text>
) : (
<ScrollView contentContainerStyle={styles.list}>
{listQuery.data!.map((item) => {
const key = item.name;
const isChecked = checked[key] ?? false;
return (
<Pressable
key={key}
onPress={() => setChecked((prev) => ({ ...prev, [key]: !isChecked }))}
style={[styles.row, { borderColor: colors.border, backgroundColor: colors.surface }]}
>
<View style={[styles.checkbox, { borderColor: colors.brand, backgroundColor: isChecked ? colors.brand : 'transparent' }]}>
{isChecked ? <Text style={{ color: '#fff', fontSize: 12 }}></Text> : null}
</View>
<View style={{ flex: 1 }}>
<Text style={{ color: isChecked ? colors.textMuted : colors.text, textDecorationLine: isChecked ? 'line-through' : 'none', fontWeight: '600' }}>
{item.name}
</Text>
{item.breakdown.length > 0 ? (
<Text style={{ color: colors.textMuted, fontSize: 11 }}>{item.breakdown.join(' · ')}</Text>
) : null}
</View>
<Text style={{ color: colors.textMuted, fontSize: 13 }}>
{formatShoppingQuantity(item, t('pdf.toTaste'))}
</Text>
</Pressable>
);
})}
</ScrollView>
)}
</Screen>
);
}
const styles = StyleSheet.create({
header: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 8 },
navBtn: { borderWidth: 1, borderRadius: 8, width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
list: { gap: 8, paddingBottom: 24 },
row: { flexDirection: 'row', alignItems: 'center', gap: 10, borderWidth: 1, borderRadius: 12, padding: 12 },
checkbox: { width: 22, height: 22, borderRadius: 6, borderWidth: 2, alignItems: 'center', justifyContent: 'center' },
});