fix(veckoplan): byt-ratt-fel, navigera veckor, lagg till maltid manuellt
- Byt rätt gav 'något gick fel': limit=30 översteg schemats tak (20). Höjt taket till 50, byt rätt hämtar 40 + sök vilken rätt som helst. - Veckonavigering: pilarna byter nu VECKA (weekOffset) i stället för att bara flytta markerad dag -> går att planera nästa vecka. Titel visar veckans datumintervall; bakåt spärras före denna vecka. - Lägg till måltid: ny POST /v1/week-plans/:id/entries + knapp 'Lägg till måltid' (väljer måltidstyp -> receptväljaren i lägg-till-läge) + knapp 'Planera om veckan'. Går nu att fylla en tom dag igen. Verifierat: swap 200 (40 varierade), add-entry 201, delete 200, api 104/104.
This commit is contained in:
@@ -2,6 +2,7 @@ import type { FastifyInstance } from "fastify";
|
||||
import { and, desc, eq, gte, sql } from "drizzle-orm";
|
||||
import { schema } from "@app/database";
|
||||
import {
|
||||
addPlanEntryInputSchema,
|
||||
generateWeekPlanInputSchema,
|
||||
idParamSchema,
|
||||
updatePlanEntryInputSchema,
|
||||
@@ -178,6 +179,64 @@ export async function planningRoutes(app: FastifyInstance) {
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// Lägg till en enskild måltid i planen (t.ex. på en tom dag).
|
||||
app.post("/v1/week-plans/:id/entries", auth, async (req, reply) => {
|
||||
const params = req.params as { id: string };
|
||||
const [plan] = await app.db
|
||||
.select()
|
||||
.from(schema.weekPlans)
|
||||
.where(eq(schema.weekPlans.id, params.id))
|
||||
.limit(1);
|
||||
if (!plan) throw errors.notFound("Planen finns inte.");
|
||||
await requireMembership(app.db, plan.householdId, req.userId);
|
||||
|
||||
const input = parse(addPlanEntryInputSchema, req.body);
|
||||
const [recipe] = await app.db
|
||||
.select({
|
||||
id: schema.recipes.id,
|
||||
titleSv: schema.recipes.titleSv,
|
||||
portions: schema.recipes.portions,
|
||||
})
|
||||
.from(schema.recipes)
|
||||
.where(and(eq(schema.recipes.id, input.recipeId), eq(schema.recipes.status, "published")))
|
||||
.limit(1);
|
||||
if (!recipe) throw errors.notFound("Receptet finns inte.");
|
||||
|
||||
const sameDay = await app.db
|
||||
.select({ sortOrder: schema.weekPlanEntries.sortOrder })
|
||||
.from(schema.weekPlanEntries)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.weekPlanEntries.weekPlanId, params.id),
|
||||
eq(schema.weekPlanEntries.date, input.date),
|
||||
),
|
||||
);
|
||||
const nextSort = sameDay.reduce((m, e) => Math.max(m, e.sortOrder ?? 0), -1) + 1;
|
||||
|
||||
const [row] = await app.db
|
||||
.insert(schema.weekPlanEntries)
|
||||
.values({
|
||||
weekPlanId: params.id,
|
||||
date: input.date,
|
||||
mealType: input.mealType,
|
||||
recipeId: recipe.id,
|
||||
titleSv: recipe.titleSv,
|
||||
portions: recipe.portions ?? 2,
|
||||
status: "planned",
|
||||
sortOrder: nextSort,
|
||||
})
|
||||
.returning();
|
||||
|
||||
await emitEvent(app.db, {
|
||||
type: "WEEK_PLAN_UPDATED",
|
||||
payload: { weekPlanId: params.id, reason: "entry_added" },
|
||||
userId: req.userId,
|
||||
householdId: plan.householdId,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
return reply.status(201).send(row);
|
||||
});
|
||||
|
||||
app.post("/v1/week-plans/:id/activate", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const [plan] = await app.db
|
||||
|
||||
@@ -69,21 +69,38 @@ export default function PlanScreen() {
|
||||
const queryClient = useQueryClient();
|
||||
const [selectedKey, setSelectedKey] = useState<string>(() => toKey(new Date()));
|
||||
const [actionEntry, setActionEntry] = useState<WeekPlanEntry | null>(null);
|
||||
const [addMealOpen, setAddMealOpen] = useState(false);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [weekOffset, setWeekOffset] = useState(0); // 0 = denna vecka, 1 = nästa vecka …
|
||||
const todayKey = toKey(new Date());
|
||||
|
||||
const selected = useMemo(() => new Date(`${selectedKey}T00:00:00`), [selectedKey]);
|
||||
// Veckoplanen startar alltid idag och går 7 dagar framåt (aldrig bakåt i tiden).
|
||||
const monday = useMemo(() => {
|
||||
// Planeringsfönster: 7 dagar från veckostart (idag + veckoförskjutning).
|
||||
// Aldrig bakåt i tiden – weekOffset klampas till >= 0.
|
||||
const todayMidnight = useMemo(() => {
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
}, []);
|
||||
const mondayKey = toKey(monday);
|
||||
const weekDays = useMemo(() => Array.from({ length: 7 }, (_, i) => addDays(monday, i)), [monday]);
|
||||
const weekStart = useMemo(
|
||||
() => addDays(todayMidnight, weekOffset * 7),
|
||||
[todayMidnight, weekOffset],
|
||||
);
|
||||
const weekStartKey = toKey(weekStart);
|
||||
const weekDays = useMemo(
|
||||
() => Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)),
|
||||
[weekStart],
|
||||
);
|
||||
// Byt vecka (framåt/bakåt). Sätter markerad dag till första dagen i veckan.
|
||||
const goWeek = (delta: number) =>
|
||||
setWeekOffset((o) => {
|
||||
const next = Math.max(0, o + delta);
|
||||
setSelectedKey(toKey(addDays(todayMidnight, next * 7)));
|
||||
return next;
|
||||
});
|
||||
// Lunch + middag varje dag som default (fler fyllda måltider), går att toggla.
|
||||
const [mealSlots, setMealSlots] = useState<string[][]>(() =>
|
||||
weekDays.map(() => ["lunch", "dinner"]),
|
||||
Array.from({ length: 7 }, () => ["lunch", "dinner"]),
|
||||
);
|
||||
const toggleSlot = (dayIdx: number, meal: string) =>
|
||||
setMealSlots((prev) =>
|
||||
@@ -93,8 +110,8 @@ export default function PlanScreen() {
|
||||
);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["week-plan", mondayKey],
|
||||
queryFn: () => api<WeekPlansResponse>(`/v1/week-plans?weekStartDate=${mondayKey}`),
|
||||
queryKey: ["week-plan", weekStartKey],
|
||||
queryFn: () => api<WeekPlansResponse>(`/v1/week-plans?weekStartDate=${weekStartKey}`),
|
||||
refetchInterval: generating ? 3000 : false,
|
||||
});
|
||||
const plan = query.data?.plans?.[0] ?? null;
|
||||
@@ -129,7 +146,7 @@ export default function PlanScreen() {
|
||||
return () => clearTimeout(timer);
|
||||
}, [generating]);
|
||||
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["week-plan", mondayKey] });
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["week-plan", weekStartKey] });
|
||||
const onMutationError = (err: unknown) =>
|
||||
Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error"));
|
||||
|
||||
@@ -137,7 +154,7 @@ export default function PlanScreen() {
|
||||
mutationFn: () =>
|
||||
api("/v1/week-plans/generate", {
|
||||
method: "POST",
|
||||
body: { weekStartDate: mondayKey, mealTypes: ["lunch", "dinner"], mealSlots },
|
||||
body: { weekStartDate: weekStartKey, mealTypes: ["lunch", "dinner"], mealSlots },
|
||||
}),
|
||||
onSuccess: () => setGenerating(true),
|
||||
onError: onMutationError,
|
||||
@@ -183,9 +200,11 @@ export default function PlanScreen() {
|
||||
if (planId) setActionEntry(entry);
|
||||
};
|
||||
|
||||
const monthLabel = cap(
|
||||
new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(selected),
|
||||
);
|
||||
const weekRangeLabel = `${new Intl.DateTimeFormat(locale, { day: "numeric" }).format(
|
||||
weekStart,
|
||||
)}–${new Intl.DateTimeFormat(locale, { day: "numeric", month: "short" }).format(
|
||||
addDays(weekStart, 6),
|
||||
)}`;
|
||||
const dayHeader = cap(
|
||||
new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long" }).format(
|
||||
selected,
|
||||
@@ -196,13 +215,18 @@ export default function PlanScreen() {
|
||||
return (
|
||||
<Screen>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Pressable onPress={() => setSelectedKey(toKey(addDays(selected, -7)))} hitSlop={8}>
|
||||
<Pressable
|
||||
onPress={() => goWeek(-1)}
|
||||
hitSlop={8}
|
||||
disabled={weekOffset === 0}
|
||||
style={{ opacity: weekOffset === 0 ? 0.3 : 1 }}
|
||||
>
|
||||
<Text style={{ fontSize: 22, color: colors.textMuted, paddingHorizontal: spacing.sm }}>
|
||||
‹
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Title>{monthLabel}</Title>
|
||||
<Pressable onPress={() => setSelectedKey(toKey(addDays(selected, 7)))} hitSlop={8}>
|
||||
<Title>{weekRangeLabel}</Title>
|
||||
<Pressable onPress={() => goWeek(1)} hitSlop={8}>
|
||||
<Text style={{ fontSize: 22, color: colors.textMuted, paddingHorizontal: spacing.sm }}>
|
||||
›
|
||||
</Text>
|
||||
@@ -210,7 +234,10 @@ export default function PlanScreen() {
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
onPress={() => setSelectedKey(todayKey)}
|
||||
onPress={() => {
|
||||
setWeekOffset(0);
|
||||
setSelectedKey(todayKey);
|
||||
}}
|
||||
style={{ alignSelf: "flex-start", paddingVertical: spacing.xs }}
|
||||
>
|
||||
<Text style={{ color: colors.primary, fontSize: 13, fontWeight: "600" }}>
|
||||
@@ -356,6 +383,18 @@ export default function PlanScreen() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Lägg till en måltid på vald dag (även tom dag), så länge en plan finns. */}
|
||||
{planId && !generating ? (
|
||||
<>
|
||||
<Spacer size={spacing.xs} />
|
||||
<Button
|
||||
label={t("plan.addMeal")}
|
||||
variant="secondary"
|
||||
onPress={() => setAddMealOpen(true)}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{hasEntries ? (
|
||||
<>
|
||||
<Spacer size={spacing.sm} />
|
||||
@@ -365,9 +404,59 @@ export default function PlanScreen() {
|
||||
onPress={() => planId && createList.mutate(planId)}
|
||||
loading={createList.isPending}
|
||||
/>
|
||||
<Spacer size={spacing.xs} />
|
||||
<Button
|
||||
label={t("plan.regenerate")}
|
||||
variant="ghost"
|
||||
onPress={() => generate.mutate()}
|
||||
loading={generate.isPending}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* Välj måltidstyp att lägga till – navigerar till receptväljaren i "lägg till"-läge. */}
|
||||
<Modal
|
||||
visible={addMealOpen}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setAddMealOpen(false)}
|
||||
>
|
||||
<Pressable
|
||||
onPress={() => setAddMealOpen(false)}
|
||||
style={{ flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,0.4)" }}
|
||||
>
|
||||
<Pressable
|
||||
onPress={() => {}}
|
||||
style={{
|
||||
backgroundColor: colors.surface,
|
||||
borderTopLeftRadius: radius.lg,
|
||||
borderTopRightRadius: radius.lg,
|
||||
padding: spacing.lg,
|
||||
paddingBottom: spacing.xl,
|
||||
}}
|
||||
>
|
||||
<Heading>{t("plan.addMeal")}</Heading>
|
||||
<Small>{dayHeader}</Small>
|
||||
<Spacer size={spacing.sm} />
|
||||
{(["breakfast", "lunch", "dinner", "snack", "dessert"] as const).map((meal) => (
|
||||
<View key={meal}>
|
||||
<Button
|
||||
label={mealTypeLabel(meal)}
|
||||
variant="secondary"
|
||||
onPress={() => {
|
||||
setAddMealOpen(false);
|
||||
router.push(
|
||||
`/swap-meal/new?planId=${planId}&mealType=${meal}&date=${selectedKey}`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Spacer size={spacing.xs} />
|
||||
</View>
|
||||
))}
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
|
||||
{/* Åtgärdspanel för en måltid – tryck utanför för att stänga, ingen Avbryt. */}
|
||||
<Modal
|
||||
visible={actionEntry !== null}
|
||||
|
||||
@@ -30,10 +30,17 @@ interface WhatToEatResponse {
|
||||
}
|
||||
|
||||
export default function SwapMealScreen() {
|
||||
const params = useLocalSearchParams<{ entryId: string; planId: string; mealType: string }>();
|
||||
const params = useLocalSearchParams<{
|
||||
entryId: string;
|
||||
planId: string;
|
||||
mealType: string;
|
||||
date?: string;
|
||||
}>();
|
||||
const entryId = String(params.entryId);
|
||||
const planId = String(params.planId);
|
||||
const mealType = String(params.mealType || "dinner");
|
||||
const date = typeof params.date === "string" ? params.date : "";
|
||||
const addMode = date.length > 0; // "lägg till" i stället för "byt ut"
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
@@ -44,7 +51,7 @@ export default function SwapMealScreen() {
|
||||
// Med sökning: byt till VILKEN rätt som helst (mealType filtreras då bort på
|
||||
// servern). Utan sökning: breda förslag för slotten (view=swap, låg täckningsvikt).
|
||||
queryFn: () => {
|
||||
const base = "/v1/recommendations/what-to-eat?limit=30&view=swap";
|
||||
const base = "/v1/recommendations/what-to-eat?limit=40&view=swap";
|
||||
const path = submitted
|
||||
? `${base}&search=${encodeURIComponent(submitted)}`
|
||||
: `${base}&mealType=${mealType}`;
|
||||
@@ -54,7 +61,15 @@ export default function SwapMealScreen() {
|
||||
|
||||
const swap = useMutation({
|
||||
mutationFn: (recipeId: string) =>
|
||||
api(`/v1/week-plans/${planId}/entries/${entryId}`, { method: "PATCH", body: { recipeId } }),
|
||||
addMode
|
||||
? api(`/v1/week-plans/${planId}/entries`, {
|
||||
method: "POST",
|
||||
body: { date, mealType, recipeId },
|
||||
})
|
||||
: api(`/v1/week-plans/${planId}/entries/${entryId}`, {
|
||||
method: "PATCH",
|
||||
body: { recipeId },
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["week-plan"] });
|
||||
router.back();
|
||||
@@ -69,8 +84,11 @@ export default function SwapMealScreen() {
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<Title>Byt ut måltiden</Title>
|
||||
<Small>Välj en annan rätt för {mealLabel} – eller sök efter vilken rätt som helst.</Small>
|
||||
<Title>{addMode ? "Lägg till måltid" : "Byt ut måltiden"}</Title>
|
||||
<Small>
|
||||
{addMode ? `Välj en rätt för ${mealLabel}` : `Välj en annan rätt för ${mealLabel}`} – eller
|
||||
sök efter vilken rätt som helst.
|
||||
</Small>
|
||||
<Spacer size={spacing.sm} />
|
||||
<Input
|
||||
placeholder="Sök bland alla recept …"
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Ja, jeg lavede den",
|
||||
"cookedLog.confirm": "Træk råvarer for {count} portioner fra dit lager?",
|
||||
"cookedLog.doneTitle": "Registreret!",
|
||||
"cookedLog.doneBody": "Råvarerne er trukket fra dit lager, og måltidet er registreret."
|
||||
"cookedLog.doneBody": "Råvarerne er trukket fra dit lager, og måltidet er registreret.",
|
||||
"plan.addMeal": "Tilføj et måltid",
|
||||
"plan.regenerate": "Planlæg ugen igen"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Ja, ich habe es gekocht",
|
||||
"cookedLog.confirm": "Zutaten für {count} Portionen von deinem Vorrat abziehen?",
|
||||
"cookedLog.doneTitle": "Erfasst!",
|
||||
"cookedLog.doneBody": "Die Zutaten wurden von deinem Vorrat abgezogen und die Mahlzeit ist erfasst."
|
||||
"cookedLog.doneBody": "Die Zutaten wurden von deinem Vorrat abgezogen und die Mahlzeit ist erfasst.",
|
||||
"plan.addMeal": "Mahlzeit hinzufügen",
|
||||
"plan.regenerate": "Woche neu planen"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Yes, I cooked it",
|
||||
"cookedLog.confirm": "Deduct ingredients for {count} servings from your stock?",
|
||||
"cookedLog.doneTitle": "Logged!",
|
||||
"cookedLog.doneBody": "The ingredients have been deducted from your stock and the meal is logged."
|
||||
"cookedLog.doneBody": "The ingredients have been deducted from your stock and the meal is logged.",
|
||||
"plan.addMeal": "Add a meal",
|
||||
"plan.regenerate": "Re-plan the week"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Sí, lo cociné",
|
||||
"cookedLog.confirm": "¿Descontar ingredientes para {count} raciones de tu despensa?",
|
||||
"cookedLog.doneTitle": "¡Registrado!",
|
||||
"cookedLog.doneBody": "Los ingredientes se han descontado de tu despensa y la comida queda registrada."
|
||||
"cookedLog.doneBody": "Los ingredientes se han descontado de tu despensa y la comida queda registrada.",
|
||||
"plan.addMeal": "Añadir una comida",
|
||||
"plan.regenerate": "Replanificar la semana"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Kyllä, tein sen",
|
||||
"cookedLog.confirm": "Vähennetäänkö ainekset {count} annokselle varastostasi?",
|
||||
"cookedLog.doneTitle": "Merkitty!",
|
||||
"cookedLog.doneBody": "Ainekset on vähennetty varastostasi ja ateria on kirjattu."
|
||||
"cookedLog.doneBody": "Ainekset on vähennetty varastostasi ja ateria on kirjattu.",
|
||||
"plan.addMeal": "Lisää ateria",
|
||||
"plan.regenerate": "Suunnittele viikko uudelleen"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Oui, je l'ai cuisiné",
|
||||
"cookedLog.confirm": "Déduire les ingrédients pour {count} portions de votre stock ?",
|
||||
"cookedLog.doneTitle": "Enregistré !",
|
||||
"cookedLog.doneBody": "Les ingrédients ont été déduits de votre stock et le repas est enregistré."
|
||||
"cookedLog.doneBody": "Les ingrédients ont été déduits de votre stock et le repas est enregistré.",
|
||||
"plan.addMeal": "Ajouter un repas",
|
||||
"plan.regenerate": "Replanifier la semaine"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Sì, l'ho cucinato",
|
||||
"cookedLog.confirm": "Scalare gli ingredienti per {count} porzioni dalla tua dispensa?",
|
||||
"cookedLog.doneTitle": "Registrato!",
|
||||
"cookedLog.doneBody": "Gli ingredienti sono stati scalati dalla tua dispensa e il pasto è registrato."
|
||||
"cookedLog.doneBody": "Gli ingredienti sono stati scalati dalla tua dispensa e il pasto è registrato.",
|
||||
"plan.addMeal": "Aggiungi un pasto",
|
||||
"plan.regenerate": "Ripianifica la settimana"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Ja, jeg laget den",
|
||||
"cookedLog.confirm": "Trekke råvarer for {count} porsjoner fra lageret ditt?",
|
||||
"cookedLog.doneTitle": "Registrert!",
|
||||
"cookedLog.doneBody": "Råvarene er trukket fra lageret ditt, og måltidet er logget."
|
||||
"cookedLog.doneBody": "Råvarene er trukket fra lageret ditt, og måltidet er logget.",
|
||||
"plan.addMeal": "Legg til et måltid",
|
||||
"plan.regenerate": "Planlegg uken på nytt"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Ja, ik heb het gemaakt",
|
||||
"cookedLog.confirm": "Ingrediënten voor {count} porties van je voorraad aftrekken?",
|
||||
"cookedLog.doneTitle": "Vastgelegd!",
|
||||
"cookedLog.doneBody": "De ingrediënten zijn van je voorraad afgetrokken en de maaltijd is vastgelegd."
|
||||
"cookedLog.doneBody": "De ingrediënten zijn van je voorraad afgetrokken en de maaltijd is vastgelegd.",
|
||||
"plan.addMeal": "Maaltijd toevoegen",
|
||||
"plan.regenerate": "Week opnieuw plannen"
|
||||
}
|
||||
|
||||
@@ -500,5 +500,7 @@
|
||||
"cookedLog.button": "Tak, ugotowałem",
|
||||
"cookedLog.confirm": "Odjąć składniki na {count} porcji z Twojej spiżarni?",
|
||||
"cookedLog.doneTitle": "Zapisano!",
|
||||
"cookedLog.doneBody": "Składniki zostały odjęte z Twojej spiżarni, a posiłek zapisano."
|
||||
"cookedLog.doneBody": "Składniki zostały odjęte z Twojej spiżarni, a posiłek zapisano.",
|
||||
"plan.addMeal": "Dodaj posiłek",
|
||||
"plan.regenerate": "Zaplanuj tydzień ponownie"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Sim, cozinhei",
|
||||
"cookedLog.confirm": "Descontar ingredientes para {count} porções do seu stock?",
|
||||
"cookedLog.doneTitle": "Registado!",
|
||||
"cookedLog.doneBody": "Os ingredientes foram descontados do seu stock e a refeição fica registada."
|
||||
"cookedLog.doneBody": "Os ingredientes foram descontados do seu stock e a refeição fica registada.",
|
||||
"plan.addMeal": "Adicionar uma refeição",
|
||||
"plan.regenerate": "Replanear a semana"
|
||||
}
|
||||
|
||||
@@ -486,5 +486,7 @@
|
||||
"cookedLog.button": "Ja, jag lagade den",
|
||||
"cookedLog.confirm": "Räkna av råvaror för {count} portioner från ditt lager?",
|
||||
"cookedLog.doneTitle": "Registrerat!",
|
||||
"cookedLog.doneBody": "Råvarorna är avräknade från ditt lager och måltiden är loggad."
|
||||
"cookedLog.doneBody": "Råvarorna är avräknade från ditt lager och måltiden är loggad.",
|
||||
"plan.addMeal": "Lägg till måltid",
|
||||
"plan.regenerate": "Planera om veckan"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user