import { useCallback, useEffect, useMemo, useState } from 'react'; import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { copyYesterdayMeals, createReminder, deleteDrink, deleteMeal, deleteReminder, fetchDailySummary, fetchDrinkCatalog, fetchFavoriteCatalogItems, fetchFavoriteRecipes, fetchGoals, fetchHistory, fetchMealSuggestions, fetchRecentMeals, fetchReminders, logDrink, logMeal, previewMeal, saveGoals, updateReminder, } from '@/src/api/diet.api'; import { fetchIngredientCatalogItems } from '@/src/api/ingredient-catalog.api'; import { fetchRecipes } from '@/src/api/recipes.api'; import { Button } from '@/src/components/Button'; import { DateNavigator } from '@/src/components/DateNavigator'; import { ErrorView } from '@/src/components/ErrorView'; import { Input } from '@/src/components/Input'; import { Screen } from '@/src/components/Screen'; import { SimpleBarChart } from '@/src/components/SimpleBarChart'; import { useAppearance } from '@/src/context/AppearanceContext'; import { CONSUMPTION_CATEGORIES, DietReminderType, REMINDER_DAYS_MASK_ALL, todayIso, type DietSuggestion, type LogMealInput, type MealPreview, type UserDailyGoals, } from '@/src/types/diet'; import { extractApiError } from '@/src/utils/apiError'; import { historyRange } from '@/src/utils/date'; import { ingredientDisplayName } from '@/src/utils/ingredient-catalog.util'; import { nextSort, sortBy, sortIndicator, type SortDirection } from '@/src/utils/sort'; type MealLogSortColumn = 'name' | 'calories'; type DrinkLogSortColumn = 'name' | 'volume'; type ReminderSortColumn = 'title' | 'time'; type Tab = 'today' | 'goals' | 'reminders' | 'history'; function mealMacroLine(m: { calories?: number | null; proteinG?: number | null; fatG?: number | null; carbsG?: number | null; }): string { if (m.calories == null) return ''; return `${m.calories} kcal · P ${Number(m.proteinG ?? 0).toFixed(1)}g · F ${Number(m.fatG ?? 0).toFixed(1)}g · C ${Number(m.carbsG ?? 0).toFixed(1)}g`; } function ProgressCard({ label, consumed, goal, remaining, unit, remainingLabel, }: { label: string; consumed: number; goal?: number | null; remaining?: number | null; unit: string; remainingLabel: string; }) { const { colors } = useAppearance(); const pct = goal && goal > 0 ? Math.min((consumed / goal) * 100, 100) : 0; return ( {label} {consumed} {goal != null ? ( {' '} / {goal} {unit} ) : null} {remaining != null ? ( {remainingLabel}: {remaining} {unit} ) : null} {goal != null ? ( ) : null} ); } export default function DietScreen() { const { t, i18n } = useTranslation(); const { colors } = useAppearance(); const queryClient = useQueryClient(); const [tab, setTab] = useState('today'); const [date, setDate] = useState(todayIso()); const [mealCategory, setMealCategory] = useState(2); const [recipeSearch, setRecipeSearch] = useState(''); const [selectedRecipeId, setSelectedRecipeId] = useState(null); const [customMealName, setCustomMealName] = useState(''); const [portions, setPortions] = useState('1'); const [catalogItemId, setCatalogItemId] = useState(null); const [grams, setGrams] = useState('150'); const [mealPreview, setMealPreview] = useState(null); const [goalsForm, setGoalsForm] = useState({}); const [reminderTitle, setReminderTitle] = useState(''); const [reminderTime, setReminderTime] = useState('09:00'); const [reminderType, setReminderType] = useState(DietReminderType.Water); const [editingReminderId, setEditingReminderId] = useState(null); const [mealSort, setMealSort] = useState<{ column: MealLogSortColumn; direction: SortDirection }>({ column: 'name', direction: 'asc', }); const [drinkSort, setDrinkSort] = useState<{ column: DrinkLogSortColumn; direction: SortDirection }>({ column: 'name', direction: 'asc', }); const [reminderSort, setReminderSort] = useState<{ column: ReminderSortColumn; direction: SortDirection }>({ column: 'time', direction: 'asc', }); const summaryQuery = useQuery({ queryKey: ['diet', 'summary', date], queryFn: () => fetchDailySummary(date), }); const suggestionsQuery = useQuery({ queryKey: ['diet', 'suggestions', date], queryFn: () => fetchMealSuggestions(date), enabled: tab === 'today' && !!summaryQuery.data, }); const catalogQuery = useQuery({ queryKey: ['diet', 'catalog'], queryFn: fetchDrinkCatalog, }); const ingredientItemsQuery = useQuery({ queryKey: ['ingredient-catalog', 'all'], queryFn: () => fetchIngredientCatalogItems(), }); const recipesQuery = useQuery({ queryKey: ['recipes', 'diet', recipeSearch], queryFn: () => fetchRecipes({ search: recipeSearch }), enabled: recipeSearch.trim().length >= 2, }); const goalsQuery = useQuery({ queryKey: ['diet', 'goals'], queryFn: fetchGoals, enabled: tab === 'goals', }); const remindersQuery = useQuery({ queryKey: ['diet', 'reminders'], queryFn: fetchReminders, enabled: tab === 'reminders', }); const recentsQuery = useQuery({ queryKey: ['diet', 'recents'], queryFn: () => fetchRecentMeals(8), enabled: tab === 'today', }); const favoriteRecipesQuery = useQuery({ queryKey: ['diet', 'favorites', 'recipes'], queryFn: fetchFavoriteRecipes, enabled: tab === 'today', }); const favoriteCatalogQuery = useQuery({ queryKey: ['diet', 'favorites', 'catalog'], queryFn: fetchFavoriteCatalogItems, enabled: tab === 'today', }); const historyRangeValues = historyRange(14); const historyQuery = useQuery({ queryKey: ['diet', 'history', historyRangeValues.from, historyRangeValues.to], queryFn: () => fetchHistory(historyRangeValues.from, historyRangeValues.to), enabled: tab === 'history', }); useEffect(() => { if (goalsQuery.data) setGoalsForm(goalsQuery.data); }, [goalsQuery.data]); const invalidate = () => void queryClient.invalidateQueries({ queryKey: ['diet'] }); const resetMealForm = () => { setRecipeSearch(''); setSelectedRecipeId(null); setCustomMealName(''); setCatalogItemId(null); setPortions('1'); setGrams('150'); setMealPreview(null); }; const buildMealPayload = useCallback((): LogMealInput | null => { if (selectedRecipeId) { return { recipeId: selectedRecipeId, mealCategory, logDate: date, portions: Number(portions) || 1, }; } const gramsNum = Number(grams); if (catalogItemId && gramsNum > 0) { return { catalogItemId, grams: gramsNum, mealCategory, logDate: date }; } if (customMealName.trim()) { return { name: customMealName.trim(), mealCategory, logDate: date }; } return null; }, [catalogItemId, customMealName, date, grams, mealCategory, portions, selectedRecipeId]); useEffect(() => { const payload = buildMealPayload(); if (!payload) { setMealPreview(null); return; } const timer = setTimeout(() => { void previewMeal(payload, date) .then(setMealPreview) .catch(() => setMealPreview(null)); }, 250); return () => clearTimeout(timer); }, [buildMealPayload, date]); const logMealMutation = useMutation({ mutationFn: logMeal, onSuccess: () => { resetMealForm(); invalidate(); }, }); const logDrinkMutation = useMutation({ mutationFn: logDrink, onSuccess: invalidate }); const saveGoalsMutation = useMutation({ mutationFn: saveGoals, onSuccess: invalidate }); const createReminderMutation = useMutation({ mutationFn: createReminder, onSuccess: () => { setReminderTitle(''); setEditingReminderId(null); void queryClient.invalidateQueries({ queryKey: ['diet', 'reminders'] }); }, }); const updateReminderMutation = useMutation({ mutationFn: ({ id, input }: { id: number; input: Parameters[1] }) => updateReminder(id, input), onSuccess: () => { setReminderTitle(''); setEditingReminderId(null); void queryClient.invalidateQueries({ queryKey: ['diet', 'reminders'] }); }, }); const copyYesterdayMutation = useMutation({ mutationFn: () => copyYesterdayMeals(date), onSuccess: () => invalidate(), }); const deleteMealMutation = useMutation({ mutationFn: deleteMeal, onSuccess: invalidate }); const deleteDrinkMutation = useMutation({ mutationFn: deleteDrink, onSuccess: invalidate }); const deleteReminderMutation = useMutation({ mutationFn: deleteReminder, onSuccess: () => void queryClient.invalidateQueries({ queryKey: ['diet', 'reminders'] }), }); const tabs: { id: Tab; label: string }[] = [ { id: 'today', label: t('diet.tabToday') }, { id: 'history', label: t('diet.tabHistory') }, { id: 'goals', label: t('diet.tabGoals') }, { id: 'reminders', label: t('diet.tabReminders') }, ]; const summary = summaryQuery.data; const suggestions = suggestionsQuery.data ?? []; const ingredientItems = ingredientItemsQuery.data ?? []; const suggestionName = (s: DietSuggestion) => i18n.language.startsWith('pl') && s.namePl ? s.namePl : s.name; const canLogMeal = buildMealPayload() != null; const sortedMeals = useMemo(() => { if (!summary) return []; const { column, direction } = mealSort; return sortBy(summary.meals, direction, (meal) => (column === 'name' ? meal.name : meal.calories)); }, [summary, mealSort]); const sortedDrinks = useMemo(() => { if (!summary) return []; const { column, direction } = drinkSort; return sortBy(summary.drinks, direction, (drink) => column === 'name' ? drink.name : drink.volumeMl, ); }, [summary, drinkSort]); const sortedReminders = useMemo(() => { const { column, direction } = reminderSort; return sortBy(remindersQuery.data ?? [], direction, (reminder) => column === 'title' ? reminder.title : reminder.timeOfDay, ); }, [remindersQuery.data, reminderSort]); return ( {tabs.map(({ id, label }) => ( setTab(id)} style={[ styles.tab, { backgroundColor: tab === id ? colors.brand : colors.surface, borderColor: tab === id ? colors.brand : colors.border, }, ]} > {label} ))} {tab === 'today' && ( <> {summaryQuery.isLoading ? ( {t('common.loading')} ) : summaryQuery.isError ? ( summaryQuery.refetch()} /> ) : summary ? ( <> {t('diet.quickLog')}