107 lines
3.7 KiB
TypeScript
107 lines
3.7 KiB
TypeScript
import type { Metadata } from "next";
|
||
import { headers } from "next/headers";
|
||
import Link from "next/link";
|
||
import { ChevronLeft, ChevronRight, ShoppingCart, Printer } from "lucide-react";
|
||
import { auth } from "@/lib/auth/server";
|
||
import { db, mealPlans, recipes, eq, and, desc } from "@epicure/db";
|
||
import { buttonVariants } from "@/components/ui/button";
|
||
import { MealPlanner } from "@/components/meal-plan/meal-planner";
|
||
import { WeeklyNutritionBar } from "@/components/nutrition/weekly-nutrition-bar";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
export const metadata: Metadata = { title: "Meal Plan" };
|
||
|
||
function getMonday(dateStr?: string): Date {
|
||
const d = dateStr ? new Date(dateStr) : new Date();
|
||
const day = d.getDay();
|
||
const diff = (day === 0 ? -6 : 1 - day);
|
||
d.setDate(d.getDate() + diff);
|
||
d.setHours(0, 0, 0, 0);
|
||
return d;
|
||
}
|
||
|
||
function toDateStr(d: Date): string {
|
||
return d.toISOString().slice(0, 10);
|
||
}
|
||
|
||
function addWeeks(d: Date, n: number): Date {
|
||
const copy = new Date(d);
|
||
copy.setDate(copy.getDate() + n * 7);
|
||
return copy;
|
||
}
|
||
|
||
export default async function MealPlanPage({
|
||
searchParams,
|
||
}: {
|
||
searchParams: Promise<{ week?: string }>;
|
||
}) {
|
||
const { week } = await searchParams;
|
||
const session = await auth.api.getSession({ headers: await headers() });
|
||
if (!session) return null;
|
||
|
||
const monday = getMonday(week);
|
||
const weekStart = toDateStr(monday);
|
||
const prevWeek = toDateStr(addWeeks(monday, -1));
|
||
const nextWeek = toDateStr(addWeeks(monday, 1));
|
||
|
||
const sunday = addWeeks(monday, 1);
|
||
sunday.setDate(sunday.getDate() - 1);
|
||
|
||
const [plan, userRecipes] = await Promise.all([
|
||
db.query.mealPlans.findFirst({
|
||
where: and(eq(mealPlans.userId, session.user.id), eq(mealPlans.weekStart, weekStart)),
|
||
with: {
|
||
entries: {
|
||
with: { recipe: { with: { photos: true } } },
|
||
},
|
||
},
|
||
}),
|
||
db.query.recipes.findMany({
|
||
where: eq(recipes.authorId, session.user.id),
|
||
orderBy: desc(recipes.updatedAt),
|
||
columns: { id: true, title: true },
|
||
}),
|
||
]);
|
||
|
||
const entries = (plan?.entries ?? []).map((e) => ({
|
||
id: e.id,
|
||
day: e.day,
|
||
mealType: e.mealType,
|
||
servings: e.servings,
|
||
note: e.note,
|
||
recipe: e.recipe ? { id: e.recipe.id, title: e.recipe.title } : null,
|
||
}));
|
||
|
||
const label = `${monday.toLocaleDateString("en-US", { month: "short", day: "numeric" })} – ${sunday.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold tracking-tight">Meal Plan</h1>
|
||
<p className="text-muted-foreground mt-1">{label}</p>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Link href="/shopping-lists" className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
|
||
<ShoppingCart className="h-4 w-4" />
|
||
Shopping lists
|
||
</Link>
|
||
<Link href={`/print/meal-plan?week=${weekStart}`} target="_blank" className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
|
||
<Printer className="h-4 w-4" />
|
||
Print
|
||
</Link>
|
||
<Link href={`/meal-plan?week=${prevWeek}`} className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
|
||
<ChevronLeft className="h-4 w-4" />
|
||
</Link>
|
||
<Link href={`/meal-plan?week=${nextWeek}`} className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
|
||
<ChevronRight className="h-4 w-4" />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
|
||
<WeeklyNutritionBar weekStart={weekStart} />
|
||
<MealPlanner weekStart={weekStart} initialEntries={entries} userRecipes={userRecipes} />
|
||
</div>
|
||
);
|
||
}
|