import { 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 { createRecipe } from '@/src/api/recipes.api'; import { Button } from '@/src/components/Button'; import { ExcelImportPanel } from '@/src/components/ExcelImportPanel'; 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 { ALL_CATEGORIES, categoryLabel, parseOptionalNumber } from '@/src/utils/recipe'; import { extractApiError } from '@/src/utils/apiError'; import { ingredientDisplayName } from '@/src/utils/ingredient-catalog.util'; type ManageTab = 'manual' | 'import'; interface IngredientRow { name: string; amountGrams: string; unit: string; catalogItemId: number | null; } interface StepRow { description: string; } export default function ManageScreen() { const { t, i18n } = useTranslation(); const { colors } = useAppearance(); const queryClient = useQueryClient(); const [manageTab, setManageTab] = useState('manual'); 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([ { name: '', amountGrams: '', unit: '', catalogItemId: null }, ]); const [steps, setSteps] = useState([{ description: '' }]); const [error, setError] = useState(null); const saveMutation = useMutation({ mutationFn: createRecipe, onSuccess: (recipe) => { queryClient.invalidateQueries({ queryKey: ['recipes'] }); queryClient.invalidateQueries({ queryKey: ['categories'] }); Alert.alert(t('manage.title'), t('manage.savedSuccess', { name: recipe.name })); setName(''); setPrepTime(''); setCalories(''); setProtein(''); setFat(''); setCarbs(''); setIngredients([{ name: '', amountGrams: '', unit: '', catalogItemId: null }]); setSteps([{ description: '' }]); setError(null); }, onError: (err) => { setError(extractApiError(err, t('errors.saveRecipeFailed'))); }, }); function validate(): string | null { if (!name.trim()) return t('validation.recipeNameRequired'); const validSteps = steps.filter((s) => s.description.trim()); if (validSteps.length === 0) return t('validation.stepsMin'); return null; } function handleSave() { const validationError = validate(); if (validationError) { setError(validationError); return; } const filteredIngredients = 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, })); const filteredSteps = steps .filter((s) => s.description.trim()) .map((s, index) => ({ stepNumber: index + 1, description: s.description.trim(), })); saveMutation.mutate({ name: name.trim(), mealCategory: category, calories: parseOptionalNumber(calories), protein: parseOptionalNumber(protein), fat: parseOptionalNumber(fat), carbs: parseOptionalNumber(carbs), prepTimeMinutes: parseOptionalNumber(prepTime), ingredients: filteredIngredients, steps: filteredSteps, }); } const catalogItems = catalogQuery.data ?? []; return ( {(['manual', 'import'] as ManageTab[]).map((tab) => ( setManageTab(tab)} style={[styles.tab, { backgroundColor: manageTab === tab ? colors.brand : colors.surface, borderColor: manageTab === tab ? colors.brand : colors.border }]} > {t(tab === 'manual' ? 'manage.tabManual' : 'manage.tabImport')} ))} {manageTab === 'import' ? ( queryClient.invalidateQueries({ queryKey: ['recipes'] })} /> ) : ( {t('manage.basicInfo')} {t('common.category')} {ALL_CATEGORIES.map((cat) => { const selected = category === cat; return ( setCategory(cat)} style={[ styles.chip, { backgroundColor: selected ? colors.brand : colors.surface, borderColor: selected ? colors.brand : colors.border, }, ]} > {categoryLabel(cat, t)} ); })} {t('recipes.ingredients')} {ingredients.map((ing, index) => ( { const next = [...ingredients]; next[index] = { ...next[index], name: value }; 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: value }; setIngredients(next); }} /> { const next = [...ingredients]; next[index] = { ...next[index], unit: value }; setIngredients(next); }} /> {ingredients.length > 1 ? (