feat(mobile): MIKRO WEEK-4 byt ut måltid i veckoplanen (swap-vy)

This commit is contained in:
Sven (AAMOS AI)
2026-08-17 21:50:18 +07:00
parent 6dcc404dc1
commit 375a951be1
3 changed files with 81 additions and 0 deletions
+5
View File
@@ -178,6 +178,11 @@ export default function PlanScreen() {
text: t("common.skip"),
onPress: () => patchEntry.mutate({ planId, entryId: entry.id, status: "skipped" }),
},
{
text: "Byt rätt",
onPress: () =>
router.push(`/swap-meal/${entry.id}?planId=${planId}&mealType=${entry.mealType}`),
},
{ text: t("common.cancel"), style: "cancel" },
]);
};
+1
View File
@@ -77,6 +77,7 @@ export default function RootLayout() {
options={{ title: "", presentation: "fullScreenModal" }}
/>
<Stack.Screen name="scan-review/[jobId]" options={{ title: t("scan.review.title"), fullScreenGestureEnabled: false }} />
<Stack.Screen name="swap-meal/[entryId]" options={{ presentation: "modal", title: "Byt ut" }} />
<Stack.Screen name="meal-review/[jobId]" options={{ title: t("scan.meal.title"), fullScreenGestureEnabled: false }} />
<Stack.Screen name="reconciliation" options={{ title: t("reconciliation.title") }} />
<Stack.Screen name="scan-diff-review/[jobId]" options={{ title: t("scan.review.title"), fullScreenGestureEnabled: false }} />
@@ -0,0 +1,75 @@
import { router, useLocalSearchParams } from "expo-router";
import { Alert } from "react-native";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api } from "@/lib/api";
import { t } from "@/lib/i18n";
import {
Body,
Card,
EmptyState,
ErrorView,
LoadingView,
Row,
Screen,
Small,
Tag,
Title,
} from "@/components/ui";
interface Rec {
recipeId: string;
titleSv: string;
coveragePercent: number;
}
interface WhatToEatResponse {
recommendations: Rec[];
}
export default function SwapMealScreen() {
const params = useLocalSearchParams<{ entryId: string; planId: string; mealType: string }>();
const entryId = String(params.entryId);
const planId = String(params.planId);
const mealType = String(params.mealType || "dinner");
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ["swap-options", mealType],
queryFn: () =>
api<WhatToEatResponse>(`/v1/recommendations/what-to-eat?limit=10&mealType=${mealType}`),
});
const swap = useMutation({
mutationFn: (recipeId: string) =>
api(`/v1/week-plans/${planId}/entries/${entryId}`, { method: "PATCH", body: { recipeId } }),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["week-plan"] });
router.back();
},
onError: (err) =>
Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error")),
});
const recs = query.data?.recommendations ?? [];
const mealLabel = mealType === "lunch" ? "lunchen" : mealType === "breakfast" ? "frukosten" : "middagen";
return (
<Screen>
<Title>Byt ut måltiden</Title>
<Small>Välj en annan rätt för {mealLabel}:</Small>
{query.isLoading && <LoadingView />}
{query.isError && <ErrorView onRetry={() => void query.refetch()} />}
{query.data && recs.length === 0 && <EmptyState text={t("wte.empty")} />}
{recs.map((rec) => (
<Card key={rec.recipeId} onPress={() => swap.mutate(rec.recipeId)}>
<Row style={{ justifyContent: "space-between", alignItems: "center" }}>
<Body>{rec.titleSv}</Body>
<Tag
label={t("wte.coverage", { pct: rec.coveragePercent })}
tone={rec.coveragePercent >= 80 ? "success" : "neutral"}
/>
</Row>
</Card>
))}
</Screen>
);
}