Initial commit

This commit is contained in:
2026-07-28 15:00:00 +02:00
commit 2f95eeda83
64 changed files with 5990 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
import { useEffect, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import apiClient from '../api/client'
import ProductFoundModal from '../components/ProductFoundModal'
import ConfirmDialog from '../components/ConfirmDialog'
import DishShareManager from '../components/DishShareManager'
import ThemeToggle from '../components/ThemeToggle'
export default function DishDetailPage() {
const { id } = useParams()
const navigate = useNavigate()
const [dish, setDish] = useState(null)
const [isLogging, setIsLogging] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
useEffect(() => {
apiClient.get(`/api/dishes/${id}`).then((res) => setDish(res.data))
}, [id])
async function handleConfirmDelete() {
await apiClient.delete(`/api/dishes/${id}`)
navigate('/cookbook')
}
if (!dish) {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-400">
Lädt…
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 pb-28 dark:bg-gray-900">
<header className="flex items-center justify-between bg-white px-4 py-4 shadow-sm dark:bg-gray-800">
<h1 className="text-base font-semibold text-gray-900 dark:text-gray-50">{dish.name}</h1>
<div className="flex items-center gap-4">
<Link to="/cookbook" className="text-sm font-medium text-emerald-600 dark:text-emerald-400">
Zurück
</Link>
<ThemeToggle />
</div>
</header>
<main className="mx-auto max-w-md space-y-4 p-4">
<section className="rounded-xl bg-white p-4 shadow-sm dark:bg-gray-800">
<p className="mb-3 text-sm text-gray-500 dark:text-gray-400">
Ergibt insgesamt {Math.round(dish.total_weight_g)}g · Nährwerte pro 100g
</p>
<dl className="mb-4 grid grid-cols-4 gap-2 text-center text-sm">
<div>
<dt className="text-gray-500 dark:text-gray-400">Kcal</dt>
<dd className="font-medium text-gray-900 dark:text-gray-50">{Math.round(dish.calories)}</dd>
</div>
<div>
<dt className="text-gray-500 dark:text-gray-400">Carbs</dt>
<dd className="font-medium text-gray-900 dark:text-gray-50">{Math.round(dish.carbs)}g</dd>
</div>
<div>
<dt className="text-gray-500 dark:text-gray-400">Protein</dt>
<dd className="font-medium text-gray-900 dark:text-gray-50">{Math.round(dish.protein)}g</dd>
</div>
<div>
<dt className="text-gray-500 dark:text-gray-400">Fett</dt>
<dd className="font-medium text-gray-900 dark:text-gray-50">{Math.round(dish.fat)}g</dd>
</div>
</dl>
<h2 className="mb-2 text-sm font-semibold text-gray-900 dark:text-gray-50">Zutaten</h2>
<ul className="mb-4 divide-y divide-gray-100 dark:divide-gray-700">
{dish.ingredients.map((ing) => (
<li key={ing.barcode} className="flex items-center justify-between py-1.5 text-sm">
<span className="text-gray-700 dark:text-gray-300">{ing.name}</span>
<span className="text-gray-500 dark:text-gray-400">{Math.round(ing.amount_g)}g</span>
</li>
))}
</ul>
{dish.instructions && (
<>
<h2 className="mb-2 text-sm font-semibold text-gray-900 dark:text-gray-50">Zubereitung</h2>
<p className="whitespace-pre-wrap text-sm text-gray-700 dark:text-gray-300">
{dish.instructions}
</p>
</>
)}
</section>
{dish.is_owner ? (
<>
<div className="flex gap-3">
<Link
to={`/cookbook/${id}/edit`}
className="flex-1 rounded-lg border border-gray-300 py-2.5 text-center text-sm font-medium text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
Bearbeiten
</Link>
<button
onClick={() => setIsDeleting(true)}
className="flex-1 rounded-lg border border-red-200 py-2.5 text-sm font-medium text-red-600 dark:border-red-900 dark:text-red-400"
>
Löschen
</button>
</div>
<DishShareManager dishId={id} />
</>
) : (
<p className="text-center text-sm text-gray-500 dark:text-gray-400">
Geteilt von {dish.owner_username}
</p>
)}
</main>
<button
onClick={() => setIsLogging(true)}
className="fixed bottom-6 left-1/2 -translate-x-1/2 rounded-full bg-emerald-600 px-6 py-3 text-base font-medium text-white shadow-lg active:bg-emerald-700"
>
Essen loggen
</button>
{isLogging && (
<ProductFoundModal
product={dish}
onLogged={() => navigate('/dashboard')}
onClose={() => setIsLogging(false)}
/>
)}
{isDeleting && (
<ConfirmDialog
title="Gericht löschen?"
message={`"${dish.name}" wird dauerhaft aus dem Kochbuch entfernt.`}
onConfirm={handleConfirmDelete}
onCancel={() => setIsDeleting(false)}
/>
)}
</div>
)
}