Update features and dependencies

This commit is contained in:
Arnaud
2026-07-01 11:10:37 +02:00
parent 9d9dfb46c6
commit 8b57a3fd87
107 changed files with 14654 additions and 458 deletions
+126
View File
@@ -0,0 +1,126 @@
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>
</>
);
}