feat(mobile): MIKRO WEEK-4 byt ut måltid i veckoplanen (swap-vy)
This commit is contained in:
@@ -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" },
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user