import { Stack, useLocalSearchParams, router } from 'expo-router'; import { useEffect, useState } from 'react'; import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { fetchIngredientCatalogItems } from '@/src/api/ingredient-catalog.api'; import { updateRecipe } from '@/src/api/recipe-management.api'; import { fetchRecipeById } from '@/src/api/recipes.api'; import { Button } from '@/src/components/Button'; import { ErrorView } from '@/src/components/ErrorView'; import { Input } from '@/src/components/Input'; import { Screen } from '@/src/components/Screen'; import { useAppearance } from '@/src/context/AppearanceContext'; import { MealCategory } from '@/src/types/recipe'; import { extractApiError } from '@/src/utils/apiError'; import { ingredientDisplayName } from '@/src/utils/ingredient-catalog.util'; import { ALL_CATEGORIES, categoryLabel, parseOptionalNumber } from '@/src/utils/recipe'; interface IngredientRow { name: string; amountGrams: string; unit: string; catalogItemId: number | null; } interface StepRow { description: string; } export default function EditRecipeScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const recipeId = Number(id); const { t, i18n } = useTranslation(); const { colors } = useAppearance(); const queryClient = useQueryClient(); const recipeQuery = useQuery({ queryKey: ['recipe', recipeId], queryFn: () => fetchRecipeById(recipeId), enabled: Number.isFinite(recipeId), }); const catalogQuery = useQuery({ queryKey: ['ingredient-catalog', 'all'], queryFn: () => fetchIngredientCatalogItems(), }); const [name, setName] = useState(''); const [category, setCategory] = useState(MealCategory.Lunch); const [prepTime, setPrepTime] = useState(''); const [calories, setCalories] = useState(''); const [protein, setProtein] = useState(''); const [fat, setFat] = useState(''); const [carbs, setCarbs] = useState(''); const [ingredients, setIngredients] = useState([]); const [steps, setSteps] = useState([]); const [error, setError] = useState(null); useEffect(() => { const recipe = recipeQuery.data; if (!recipe) return; setName(recipe.name); setCategory(recipe.mealCategory); setPrepTime(recipe.prepTimeMinutes?.toString() ?? ''); setCalories(recipe.calories?.toString() ?? ''); setProtein(recipe.protein?.toString() ?? ''); setFat(recipe.fat?.toString() ?? ''); setCarbs(recipe.carbs?.toString() ?? ''); setIngredients( recipe.ingredients.length > 0 ? recipe.ingredients.map((i) => ({ name: i.name, amountGrams: i.amountGrams?.toString() ?? '', unit: i.unit ?? '', catalogItemId: i.catalogItemId ?? null, })) : [{ name: '', amountGrams: '', unit: '', catalogItemId: null }], ); setSteps( recipe.steps.length > 0 ? recipe.steps.map((s) => ({ description: s.description })) : [{ description: '' }], ); }, [recipeQuery.data]); const saveMutation = useMutation({ mutationFn: () => { const filteredSteps = steps.filter((s) => s.description.trim()); return updateRecipe(recipeId, { name: name.trim(), mealCategory: category, calories: parseOptionalNumber(calories), protein: parseOptionalNumber(protein), fat: parseOptionalNumber(fat), carbs: parseOptionalNumber(carbs), prepTimeMinutes: parseOptionalNumber(prepTime), ingredients: ingredients .filter((i) => i.name.trim()) .map((i, index) => ({ name: i.name.trim(), amountGrams: parseOptionalNumber(i.amountGrams), unit: i.unit, catalogItemId: i.catalogItemId, sortOrder: index, })), steps: filteredSteps.map((s, index) => ({ stepNumber: index + 1, description: s.description.trim() })), }); }, onSuccess: (recipe) => { queryClient.invalidateQueries({ queryKey: ['recipes'] }); queryClient.invalidateQueries({ queryKey: ['recipe', recipeId] }); Alert.alert(t('manage.title'), t('manage.updatedSuccess', { name: recipe.name })); router.back(); }, onError: (err) => setError(extractApiError(err, t('errors.saveRecipeFailed'))), }); if (!Number.isFinite(recipeId)) { return ( ); } if (recipeQuery.isLoading) return ; if (recipeQuery.isError || !recipeQuery.data) { return ( recipeQuery.refetch()} /> ); } const catalogItems = catalogQuery.data ?? []; return ( <> {t('common.category')} {ALL_CATEGORIES.map((cat) => ( setCategory(cat)} style={[styles.chip, { backgroundColor: category === cat ? colors.brand : colors.surface, borderColor: category === cat ? colors.brand : colors.border }]} > {categoryLabel(cat, t)} ))} {t('recipes.ingredients')} {ingredients.map((ing, index) => ( { const next = [...ingredients]; next[index] = { ...next[index], name: v }; setIngredients(next); }} /> {t('manage.catalogItem')} { const next = [...ingredients]; next[index] = { ...next[index], catalogItemId: null }; setIngredients(next); }} style={[styles.chip, { backgroundColor: ing.catalogItemId == null ? colors.brand : colors.surface, borderColor: ing.catalogItemId == null ? colors.brand : colors.border }]}> {t('diet.noCatalogItem')} {catalogItems.map((item) => ( { const next = [...ingredients]; next[index] = { ...next[index], catalogItemId: item.id, name: ingredientDisplayName(item, i18n.language) }; setIngredients(next); }} style={[styles.chip, { backgroundColor: ing.catalogItemId === item.id ? colors.brand : colors.surface, borderColor: ing.catalogItemId === item.id ? colors.brand : colors.border }]}> {ingredientDisplayName(item, i18n.language)} ))} { const next = [...ingredients]; next[index] = { ...next[index], amountGrams: v }; setIngredients(next); }} /> { const next = [...ingredients]; next[index] = { ...next[index], unit: v }; setIngredients(next); }} /> ))} {t('manage.preparationSteps')} {steps.map((step, index) => ( { const next = [...steps]; next[index] = { description: v }; setSteps(next); }} multiline /> ))} {error ? {error} : null}