Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions app/api/shopping-list/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { NextRequest, NextResponse } from "next/server";
import { getSession } from "@/lib/auth";
import prisma from "@/lib/prisma";

export async function POST(request: NextRequest) {
const session = await getSession();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const {start, end} = await request.json();

if (!start || !end) {
return NextResponse.json(
{ error: "Brak daty startowej lub końcowej" },
{ status: 400 }
)
}

const startDate = new Date(start)
const endDate = new Date(end)


// getting meal plans within date range

try {
const mealPlans = await prisma.mealPlan.findMany({
where: {
userId: session.user.id,
date: {
gte: startDate,
lte: endDate,
},
},
include: { meal : true },
});


const shoppingListMap = new Map<string, number>()

for (const plan of mealPlans) {
const ingredients = plan.meal.ingredients as Record<string, number>
for (const [name, amount] of Object.entries(ingredients)) {
shoppingListMap.set(
name,
(shoppingListMap.get(name) ?? 0) + amount
)
}
}

const shoppingListItems = Array.from(shoppingListMap.entries())
.map(([name, amount]) => ({name,amount,}))
.sort((a, b) => a.name.localeCompare(b.name))

//

const shoppingList = await prisma.shoppingList.create({
data: {
userId: session.user.id,
startDate: startDate,
endDate: endDate,
items: shoppingListItems,
},
});

return NextResponse.json({ shoppingList }, { status: 201 });


} catch (error) {
return NextResponse.json(
{ error: "Błąd podczas tworzenia listy zakupów" },
{ status: 500 }
);
}


}
20 changes: 20 additions & 0 deletions app/planner/PlannerClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { MealPlan, Meal } from "../generated/prisma/client";
import { useFlashStore } from "@/lib/flashStore";
import FlashNotices from "../components/FlashNotices";
import { type FailedToCreate } from '../api/meal-plan-generator/services';
import { useRouter } from "next/navigation"

type MealPlanWithMeal = MealPlan & { meal: Meal };

Expand All @@ -18,6 +19,8 @@ export default function PlannerClient({ meals }: PlannerClientProps) {
const [showWeeklyForm, setShowWeeklyForm] = useState(false);
const { flash } = useFlashStore();

const router = useRouter()

return (
<div>
{
Expand All @@ -42,6 +45,23 @@ export default function PlannerClient({ meals }: PlannerClientProps) {
{showWeeklyForm && (
<WeeklyPreferencesModal onClose={() => setShowWeeklyForm(false)} />
)}

{ /* Przycisk do generowania listy zakupów */ }

<div className="flex justify-center mb-6">
<button
type="button"
onClick={() => router.push("/shopping-list/new")}
className="rounded-xl border-purple-700 border-3 bg-white px-4 py-2 text-sm font-medium
text-purple-600 shadow hover:bg-purple-300 cursor-pointer transition-colors duration-300
"
>
Wygeneruj listę zakupów
</button>
</div>

</div>


);
}
3 changes: 3 additions & 0 deletions app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export default async function ProfilePage() {
return (
<div className="mt-10 flex justify-center">
<div className="w-full max-w-3xl bg-white rounded-2xl shadow p-6">
<h1 className="text-3xl font-bold mb-6 text-purple-800">
Witaj, {session.user.name}!
</h1>
<h1 className="text-2xl font-bold mb-4 text-purple-800">
Szablony preferencji na dni tygodnia
</h1>
Expand Down
43 changes: 43 additions & 0 deletions app/shopping-list/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import prisma from "@/lib/prisma"
import { getSession } from "@/lib/auth"

type Props = {
params : Promise<{ id: string }>
}

export default async function ShoppingListDetailPage({ params }: Props) {
const session = await getSession()
if (!session?.user?.id) return <p> Musisz być zalogowany</p>

const corParams = await params
const id = parseInt(corParams.id, 10)
console.log("ID listy zakupów:", id)

const list = await prisma.shoppingList.findUnique({
where: { id },
})

if (!list || list.userId !== session.user.id) {
return <p>Lista zakupów nie znaleziona</p>
}

const shoppingListItems = (list.items as { name: string; amount: number }[])

return (
<div className="flex items-center min-h-screen py-12 px-40">
<div>
<h1 className="text-m text-violet-400 font-semibold mb-6">
Lista zakupów: od {list.startDate.toISOString().slice(0,10)} do {list.endDate.toISOString().slice(0,10)}
</h1>

<ul className="space-y-2">
{shoppingListItems.map((item: any) => (
<li key={item.name}>
{item.name}: {item.amount}g
</li>
))}
</ul>
</div>
</div>
)
}
61 changes: 61 additions & 0 deletions app/shopping-list/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client"

import { useRouter } from "next/navigation"
import { useState } from "react"

export default function NewShoppingListPage() {
const router = useRouter()
const [start, setStart] = useState("")
const [end, setEnd] = useState("")

async function handleGenerate() {
if (!start || !end) {
alert("Wybierz zarówno datę początkową, jak i końcową")
return
}

try {
const res = await fetch("/api/shopping-list", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ start, end }),
cache: "no-store",
})

if (!res.ok) {
throw new Error("Nie udało się wygenerować listy zakupów")
}

router.push("/shopping-list")
} catch (err) {
console.error(err)
alert("Wystąpił błąd przy tworzeniu listy zakupów")
}
}

return (
<div className="mx-auto max-w-xl p-10">
<h1 className="text-2xl font-semibold mb-4 text-violet-600">
Nowa lista zakupów
</h1>

{/* Proste wprowadzanie dat*/}
<div className="flex flex-col gap-4 border p-4 rounded-lg">
<input
type="date"
value={start}
onChange={e => setStart(e.target.value)}
/>
<input
type="date"
value={end}
onChange={e => setEnd(e.target.value)}
/>

<button onClick={handleGenerate}>
Generuj listę
</button>
</div>
</div>
)
}
36 changes: 31 additions & 5 deletions app/shopping-list/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
export default function ShoppingListPage() {
import prisma from "@/lib/prisma";
import { getSession } from "@/lib/auth";

export default async function ShoppingListPage() {
const session = await getSession();
if (!session?.user?.id) return <p>Musisz być zalogowany</p>;

const lists = await prisma.shoppingList.findMany({
where: { userId: session.user.id },
orderBy: { createdAt: "desc" },
});


return (
<div className="mx-auto max-w-4xl p-10">
<h1 className="text-3xl font-semibold text-violet-600 mb-4">
Lista zakupów
Listy zakupowe
</h1>
<p className="text-zinc-400">
Tu będzie strona z listą zakupów.
</p>

{lists.length === 0 ? (
<p>Brak list zakupów. Wygeneruj nową listę zakupów.</p>
) : (

// bardzo proste przekierowanie do list
<ul className="space-y-2 text-xl text-violet-800">
{lists.map(list => (
<li key={list.id} className="hover:text-violet-950 hover:underline">
<a href={`/shopping-list/${list.id}`}>
▫︎ {list.startDate.toISOString().slice(0, 10)} → {list.endDate.toISOString().slice(0, 10)}
</a>
</li>
))}
</ul>

)}
</div>
);
}
14 changes: 14 additions & 0 deletions prisma/migrations/20251228224915_add_shopping_list/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "ShoppingList" (
"id" SERIAL NOT NULL,
"userId" TEXT NOT NULL,
"startDate" TIMESTAMP(3) NOT NULL,
"endDate" TIMESTAMP(3) NOT NULL,
"items" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "ShoppingList_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "ShoppingList" ADD CONSTRAINT "ShoppingList_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
27 changes: 19 additions & 8 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ model Tag {
}

model User {
id String @id @default(cuid())
email String @unique
password String
name String?
createdAt DateTime @default(now())
mealPlans MealPlan[] //one to many
preferences UserPreference[]
meals Meal[] // user-created meals
id String @id @default(cuid())
email String @unique
password String
name String?
createdAt DateTime @default(now())
mealPlans MealPlan[] //one to many
preferences UserPreference[]
meals Meal[] // user-created meals
shoppingLists ShoppingList[]
}

enum MealType {
Expand Down Expand Up @@ -81,3 +82,13 @@ model UserPreference {

@@id([userId, dayOfWeek, tagId])
}

model ShoppingList {
id Int @id @default(autoincrement())
userId String
user User @relation(fields: [userId], references: [id])
startDate DateTime
endDate DateTime
items Json
createdAt DateTime @default(now())
}
Loading