Files
Epicure/apps/web/app/print/meal-plan/page.tsx
T
2026-07-01 11:10:37 +02:00

127 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { headers } from "next/headers";
import { auth } from "@/lib/auth/server";
import { db, mealPlans, eq, and } from "@epicure/db";
import { PrintTrigger } from "@/components/recipe/print-trigger";
const DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] as const;
const DAY_LABELS: Record<string, string> = {
mon: "Monday", tue: "Tuesday", wed: "Wednesday", thu: "Thursday",
fri: "Friday", sat: "Saturday", sun: "Sunday",
};
const MEAL_ORDER = ["breakfast", "lunch", "dinner", "snack"] as const;
const MEAL_LABELS: Record<string, string> = {
breakfast: "Breakfast", lunch: "Lunch", dinner: "Dinner", snack: "Snack",
};
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;
}
export default async function MealPlanPrintPage({
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 = monday.toISOString().slice(0, 10);
const sunday = new Date(monday);
sunday.setDate(sunday.getDate() + 6);
const plan = await db.query.mealPlans.findFirst({
where: and(eq(mealPlans.userId, session.user.id), eq(mealPlans.weekStart, weekStart)),
with: { entries: { with: { recipe: true } } },
});
const label = `${monday.toLocaleDateString("en-US", { month: "short", day: "numeric" })} ${sunday.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
const entries = plan?.entries ?? [];
return (
<>
<style>{`
@media print {
body { margin: 0; }
.no-print { display: none !important; }
}
body {
font-family: system-ui, sans-serif;
color: #1a1a1a;
max-width: 900px;
margin: 40px auto;
padding: 0 24px;
font-size: 13px;
line-height: 1.5;
}
h1 { font-size: 1.8em; margin: 0 0 2px; }
.subtitle { color: #666; font-size: 0.9em; margin-bottom: 24px; }
table { width: 100%; border-collapse: collapse; table-layout: fixed; }
th { text-align: left; font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.06em; color: #888; padding: 6px 8px; border-bottom: 2px solid #ccc; }
td { padding: 6px 8px; border-bottom: 1px solid #eee; border-right: 1px solid #eee; vertical-align: top; min-height: 40px; }
td:last-child { border-right: none; }
.meal-label { font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.05em; color: #aaa; display: block; margin-bottom: 2px; }
.recipe-title { font-weight: 500; }
.servings { font-size: 0.8em; color: #888; }
.empty { color: #ccc; font-size: 0.85em; }
footer { margin-top: 40px; font-size: 0.75em; color: #aaa; text-align: center; }
.print-btn {
position: fixed; top: 16px; right: 16px; padding: 8px 16px;
background: #18181b; color: white; border: none; border-radius: 6px;
cursor: pointer; font-size: 13px;
}
.print-btn:hover { background: #3f3f46; }
`}</style>
<PrintTrigger />
<h1>Meal Plan</h1>
<p className="subtitle">{label}</p>
<table>
<thead>
<tr>
<th style={{ width: "100px" }}>Day</th>
{MEAL_ORDER.map((m) => (
<th key={m}>{MEAL_LABELS[m]}</th>
))}
</tr>
</thead>
<tbody>
{DAYS.map((day) => (
<tr key={day}>
<td style={{ fontWeight: 600, color: "#555" }}>{DAY_LABELS[day]}</td>
{MEAL_ORDER.map((mealType) => {
const entry = entries.find((e) => e.day === day && e.mealType === mealType);
return (
<td key={mealType}>
{entry ? (
<>
<span className="recipe-title">{entry.recipe?.title ?? entry.note ?? "—"}</span>
{entry.servings && (
<span className="servings"> · {entry.servings} srv</span>
)}
</>
) : (
<span className="empty"></span>
)}
</td>
);
})}
</tr>
))}
</tbody>
</table>
<footer>Printed from Epicure</footer>
</>
);
}