fix(skan+sok): robust kvitto-enheter, titel-/fuzzy-sok, hogtidsfilter
Kvitto-skanning (rapporterad krasch 'svar matchar inte schema … invalid value'): - AI-skan-enheter (kvitto/kyl/maltid) normaliseras nu fore validering: 'st'->COUNT, 'kg'->KILOGRAM, 'forp'/'pase'->PACKAGE osv. Okand enhet -> null i stallet for att falla hela svaret. Skarpt kvitto-prompt med exakt enhetslista. 17 nya tester bevisar normaliseringen. Sok pa 'Vad ska vi ata': - Endast TITELN (inte beskrivningen) -> 'pannkaka' ger inte langre appelkompott (som bara namnde pannkakor i beskrivningen). - Stavningstolerant via trigram-similarity: 'pankakor' hittar pannkakor. Hogtidsknapparna (kraftskiva/grill/jul ...): - Filtrerar nu pa recept-holidayTag (slug) i stallet for en titelsok som gav tomt. Har event inga recept an visas ett arligt tomt-lage. Full typecheck gron, ai-contracts 24 tester grona.
This commit is contained in:
@@ -47,7 +47,11 @@ interface MealBoxSuggestion {
|
||||
interface WhatToEatResponse {
|
||||
recommendations: Recommendation[];
|
||||
mealBoxSuggestions: MealBoxSuggestion[];
|
||||
context: { activeHolidays: string[]; remainingKcal: number; remainingProteinG: number };
|
||||
context: {
|
||||
activeHolidays: Array<{ slug: string; name: string }>;
|
||||
remainingKcal: number;
|
||||
remainingProteinG: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Kategori-flikar. Alla använder SAMMA rekommendations-motor (what-to-eat) så
|
||||
@@ -79,6 +83,7 @@ export default function WhatToEatScreen() {
|
||||
const [submittedSearch, setSubmittedSearch] = useState("");
|
||||
const [page, setPage] = useState(0);
|
||||
const [cat, setCat] = useState<string | null>(null); // null = Rekommenderat
|
||||
const [holiday, setHoliday] = useState<string | null>(null); // vald högtid (slug)
|
||||
const { track } = useAnalytics();
|
||||
|
||||
const hour = new Date().getHours();
|
||||
@@ -87,7 +92,7 @@ export default function WhatToEatScreen() {
|
||||
const activeCat = cat === null ? null : CATS.find((c) => c.key === cat);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["what-to-eat", cat, submittedSearch, currentMeal],
|
||||
queryKey: ["what-to-eat", cat, holiday, submittedSearch, currentMeal],
|
||||
queryFn: () => {
|
||||
let path = "/v1/recommendations/what-to-eat?limit=20&view=default";
|
||||
if (activeCat?.tag) {
|
||||
@@ -95,20 +100,33 @@ export default function WhatToEatScreen() {
|
||||
} else {
|
||||
path += `&mealType=${activeCat?.mealType ?? currentMeal}`;
|
||||
}
|
||||
// Sök bland alla recept (titel/beskrivning) oavsett vald flik.
|
||||
if (holiday) path += `&holiday=${encodeURIComponent(holiday)}`;
|
||||
// Sök bland alla recept (på titel) oavsett vald flik/högtid.
|
||||
if (submittedSearch) path += `&search=${encodeURIComponent(submittedSearch)}`;
|
||||
return api<WhatToEatResponse>(path);
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
// Sök, flik och högtid är ömsesidigt uteslutande – ett aktivt filter i taget.
|
||||
const submitSearch = (value: string) => {
|
||||
setSearchText(value);
|
||||
setSubmittedSearch(value.trim());
|
||||
setHoliday(null);
|
||||
setPage(0);
|
||||
};
|
||||
const selectCat = (value: string | null) => {
|
||||
setCat(value);
|
||||
setHoliday(null);
|
||||
setSearchText("");
|
||||
setSubmittedSearch("");
|
||||
setPage(0);
|
||||
};
|
||||
const selectHoliday = (slug: string) => {
|
||||
setHoliday((cur) => (cur === slug ? null : slug)); // toggla av/på
|
||||
setCat(null);
|
||||
setSearchText("");
|
||||
setSubmittedSearch("");
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
@@ -155,9 +173,9 @@ export default function WhatToEatScreen() {
|
||||
<>
|
||||
<Spacer size={spacing.xs} />
|
||||
<Row style={{ flexWrap: "wrap" }}>
|
||||
{query.data.context.activeHolidays.map((holiday) => (
|
||||
<Pressable key={holiday} onPress={() => submitSearch(holiday)}>
|
||||
<Tag label={`🎉 ${holiday}`} tone="accent" />
|
||||
{query.data.context.activeHolidays.map((h) => (
|
||||
<Pressable key={h.slug} onPress={() => selectHoliday(h.slug)}>
|
||||
<Tag label={`🎉 ${h.name}`} tone={holiday === h.slug ? "success" : "accent"} />
|
||||
</Pressable>
|
||||
))}
|
||||
</Row>
|
||||
@@ -192,28 +210,37 @@ export default function WhatToEatScreen() {
|
||||
|
||||
{query.data && (
|
||||
<>
|
||||
{cat === null && !submittedSearch && query.data.mealBoxSuggestions.length > 0 && (
|
||||
<>
|
||||
<Heading>{t("wte.mealBoxFirst")}</Heading>
|
||||
{query.data.mealBoxSuggestions.map((box) => (
|
||||
<Card key={box.mealBoxId} onPress={() => router.push("/meal-boxes")}>
|
||||
<Row style={{ justifyContent: "space-between" }}>
|
||||
<Body>🍱 {box.titleSv}</Body>
|
||||
<Tag
|
||||
label={t("mealbox.portionsLeft", { count: box.portionsRemaining })}
|
||||
tone="success"
|
||||
/>
|
||||
</Row>
|
||||
<Small>{box.whySv}</Small>
|
||||
</Card>
|
||||
))}
|
||||
<Spacer size={spacing.sm} />
|
||||
</>
|
||||
)}
|
||||
{cat === null &&
|
||||
!submittedSearch &&
|
||||
!holiday &&
|
||||
query.data.mealBoxSuggestions.length > 0 && (
|
||||
<>
|
||||
<Heading>{t("wte.mealBoxFirst")}</Heading>
|
||||
{query.data.mealBoxSuggestions.map((box) => (
|
||||
<Card key={box.mealBoxId} onPress={() => router.push("/meal-boxes")}>
|
||||
<Row style={{ justifyContent: "space-between" }}>
|
||||
<Body>🍱 {box.titleSv}</Body>
|
||||
<Tag
|
||||
label={t("mealbox.portionsLeft", { count: box.portionsRemaining })}
|
||||
tone="success"
|
||||
/>
|
||||
</Row>
|
||||
<Small>{box.whySv}</Small>
|
||||
</Card>
|
||||
))}
|
||||
<Spacer size={spacing.sm} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{allRecs.length === 0 && (
|
||||
<EmptyState
|
||||
text={submittedSearch ? "Inga recept matchar din sökning." : t("wte.empty")}
|
||||
text={
|
||||
submittedSearch
|
||||
? "Inga recept matchar din sökning."
|
||||
: holiday
|
||||
? "Inga recept för den här högtiden än."
|
||||
: t("wte.empty")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -221,7 +248,7 @@ export default function WhatToEatScreen() {
|
||||
<Card key={rec.recipeId} onPress={() => router.push(`/recipe/${rec.recipeId}`)}>
|
||||
<Row style={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Heading>{rec.titleSv}</Heading>
|
||||
{page === 0 && index === 0 && !submittedSearch && (
|
||||
{page === 0 && index === 0 && !submittedSearch && !holiday && (
|
||||
<Tag label="Toppval" tone="accent" />
|
||||
)}
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user