diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx index 99b0849..8c74779 100644 --- a/apps/mobile/src/app/(tabs)/index.tsx +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { Pressable, View } from "react-native"; import { router } from "expo-router"; -import { useQuery } from "@tanstack/react-query"; -import { api } from "@/lib/api"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { api, ApiError } from "@/lib/api"; import { useAnalytics } from "@/lib/analytics"; import { recommendationsViewed } from "@app/analytics"; import { t } from "@/lib/i18n"; @@ -49,42 +49,71 @@ interface WhatToEatResponse { mealBoxSuggestions: MealBoxSuggestion[]; context: { activeHolidays: string[]; remainingKcal: number; remainingProteinG: number }; } +interface BrowseRecipe { + id: string; + titleSv: string; + title?: string; + totalTimeMinutes: number | null; + nutritionPerPortion: { kcal?: number } | null; + ratingAverage: number | null; + ratingCount: number; +} -// Bläddra-genvägar direkt på förstasidan (öppnar recept-bläddraren förvald på -// kategorin). Etiketter återanvänder myday.mealType.* där de finns. -const BROWSE_CATS: ReadonlyArray<{ +// Kategori-flikar direkt på förstasidan. null = personliga rekommendationer; +// annars filtreras recepten IN-PLACE på samma sida (ingen navigering bort). +// De flesta filtrerar på måltidstyp; "Baka" på taggen "baking". +const CATS: ReadonlyArray<{ key: string; labelKey: string | null; label?: string; + mealType?: string; + tag?: string; glyph: string; }> = [ - { key: "breakfast", labelKey: "myday.mealType.breakfast", glyph: "🥣" }, - { key: "lunch", labelKey: "myday.mealType.lunch", glyph: "🥗" }, - { key: "dinner", labelKey: "myday.mealType.dinner", glyph: "🍽️" }, - { key: "snack", labelKey: "myday.mealType.snack", glyph: "🍎" }, - { key: "dessert", labelKey: "myday.mealType.dessert", glyph: "🍰" }, - { key: "baking", labelKey: null, label: "Baka", glyph: "🥐" }, + { key: "breakfast", labelKey: "myday.mealType.breakfast", mealType: "breakfast", glyph: "🥣" }, + { key: "lunch", labelKey: "myday.mealType.lunch", mealType: "lunch", glyph: "🥗" }, + { key: "dinner", labelKey: "myday.mealType.dinner", mealType: "dinner", glyph: "🍽️" }, + { key: "snack", labelKey: "myday.mealType.snack", mealType: "snack", glyph: "🍎" }, + { key: "dessert", labelKey: "myday.mealType.dessert", mealType: "dessert", glyph: "🍰" }, + { key: "baking", labelKey: null, label: "Baka", tag: "baking", glyph: "🥐" }, ]; +const errText = (e: unknown): string | undefined => + e instanceof ApiError ? `[${e.status}] ${e.message}` : e instanceof Error ? e.message : undefined; + export default function WhatToEatScreen() { const [craving, setCraving] = useState(""); const [submittedCraving, setSubmittedCraving] = useState(""); const [page, setPage] = useState(0); + const [cat, setCat] = useState(null); // null = Rekommenderat const { track } = useAnalytics(); const hour = new Date().getHours(); const currentMeal = hour < 10 ? "breakfast" : hour < 14 ? "lunch" : "dinner"; - // view=default ger en balanserad rankning (täckning väger tungt men inte - // allenarådande som i pantry-vyn) → mer varierade, relevanta förslag i stället - // för samma triviala högtäckningsrätter. Större pool (30) så "Visa fler" - // bläddrar längre innan den upprepar. + // Personliga rekommendationer (bara i "Rekommenderat"-läget). view=default ger + // en balanserad rankning; limit=20 är schemats maxgräns. const query = useQuery({ queryKey: ["what-to-eat", submittedCraving, currentMeal], queryFn: () => api( - `/v1/recommendations/what-to-eat?limit=30&view=default&mealType=${currentMeal}${submittedCraving ? `&craving=${encodeURIComponent(submittedCraving)}` : ""}`, + `/v1/recommendations/what-to-eat?limit=20&view=default&mealType=${currentMeal}${submittedCraving ? `&craving=${encodeURIComponent(submittedCraving)}` : ""}`, ), + enabled: cat === null, + }); + + // Kategori-bläddring in-place (när en flik är vald). + const browse = useQuery({ + queryKey: ["wte-browse", cat], + queryFn: () => { + const c = CATS.find((x) => x.key === cat); + let path = "/v1/recipes?limit=50"; + if (c?.mealType) path += `&mealType=${c.mealType}`; + if (c?.tag) path += `&tags=${c.tag}`; + return api<{ recipes: BrowseRecipe[] }>(path); + }, + enabled: cat !== null, + placeholderData: keepPreviousData, }); const allRecs = query.data?.recommendations ?? []; @@ -115,125 +144,174 @@ export default function WhatToEatScreen() { {t("wte.subtitle")} - - - { - setSubmittedCraving(craving); - setPage(0); - }} - returnKeyType="search" - /> - - - - - {/* Bläddra bland recept per kategori direkt från förstasidan. */} + {/* Kategori-flikar – filtrerar på DENNA sida, ingen navigering bort. */} - {BROWSE_CATS.map((c) => ( +