"use client"; import { CheckIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { useMemo, useState } from "react"; import { CatalogFood, PortionUnit, Recipe, calcRecipe, classifyRecipe } from "@/lib/hellth-data"; import { trpc } from "@/lib/trpc"; type SubmissionStatus = "pending" | "accepted" | "rejected"; type SubmissionForReview = { id: string; name: string; portions: number; minutes: number; notes: string | null; ingredients: Array<{ id: string; foodId: string; amountG: unknown; displayAmount: unknown | null; displayUnit: PortionUnit | null; portionId: string | null; food: Parameters[0] | null; }>; }; function toNumber(value: unknown) { return Number(value) || 0; } function foodFromSubmission(food: { id: string; name: string; brand: string | null; category: string; kcalPer100g: unknown; proteinPer100g: unknown; carbsPer100g: unknown; fatPer100g: unknown; servingG: unknown; preferredUnit: PortionUnit | null; densityGPerMl: unknown | null; source: "seed" | "manual" | "openfoodfacts" | "ocr" | "community"; barcode: string | null; availableEverywhere: boolean; }): CatalogFood { return { id: food.id, name: food.name, brand: food.brand ?? undefined, category: food.category, kcalPer100g: toNumber(food.kcalPer100g), proteinPer100g: toNumber(food.proteinPer100g), carbsPer100g: toNumber(food.carbsPer100g), fatPer100g: toNumber(food.fatPer100g), servingG: toNumber(food.servingG) || 100, preferredUnit: food.preferredUnit ?? "g", densityGPerMl: food.densityGPerMl == null ? undefined : toNumber(food.densityGPerMl), source: food.source, barcode: food.barcode ?? undefined, availableEverywhere: food.availableEverywhere, }; } function SubmissionNutrition({ submission }: { submission: SubmissionForReview }) { const foods = submission.ingredients.map((ingredient) => ingredient.food ? foodFromSubmission(ingredient.food) : null).filter(Boolean) as CatalogFood[]; const recipe: Recipe = { id: submission.id, name: submission.name, portions: submission.portions, minutes: submission.minutes, notes: submission.notes ?? undefined, ingredients: submission.ingredients.map((ingredient) => ({ foodId: ingredient.foodId, amountG: toNumber(ingredient.amountG), amount: ingredient.displayAmount == null ? undefined : toNumber(ingredient.displayAmount), unit: ingredient.displayUnit ?? undefined, portionId: ingredient.portionId ?? undefined, })), }; const totals = calcRecipe(recipe, foods); const classification = classifyRecipe(recipe, foods); return (
{classification.tags.map((tag) => {tag})}

{totals.kcal}

kcal

{totals.carbs} g

KH {classification.distribution.carbs}%

{totals.fat} g

Fett {classification.distribution.fat}%

{totals.protein} g

Eiweiß {classification.distribution.protein}%

{classification.criteria.map((criterion) => (
{criterion.ok ? "OK" : "!"} {criterion.label} {criterion.detail}
))}
); } export default function RezeptPruefungPage() { const utils = trpc.useUtils(); const [status, setStatus] = useState("pending"); const [reviewNotes, setReviewNotes] = useState>({}); const submissionsQuery = trpc.health.recipeSubmissionsForReview.useQuery({ status }); const reviewMutation = trpc.health.reviewRecipeSubmission.useMutation({ onSuccess: async () => { await utils.health.recipeSubmissionsForReview.invalidate({ status }); await utils.health.listRecipes.invalidate(); }, }); const submissions = useMemo(() => submissionsQuery.data ?? [], [submissionsQuery.data]); function review(id: string, action: "accept" | "reject") { reviewMutation.mutate({ id, action, reviewNote: reviewNotes[id]?.trim() || null, }); } return (

Moderation

Rezeptvorschläge prüfen

Akzeptierte Vorschläge werden als öffentliche Rezepte in den Rezeptkatalog übernommen.

{(["pending", "accepted", "rejected"] as const).map((item) => ( ))}
{submissions.map((submission) => (

{submission.name}

{submission.minutes} min · {submission.portions} Portionen {submission.mealPrepDays ? ` · ${submission.mealPrepDays} Meal-Prep-Tag(e)` : ""}

{submission.notes ?

{submission.notes}

: null}
{submission.status === "accepted" ? "akzeptiert" : submission.status === "rejected" ? "abgelehnt" : "offen"}
{submission.ingredients.map((ingredient) => (
{ingredient.food?.name ?? "Unbekanntes Lebensmittel"} {toNumber(ingredient.displayAmount) || toNumber(ingredient.amountG)} {ingredient.displayUnit ?? "g"} · {Math.round(toNumber(ingredient.amountG))} g
))}
{submission.status === "pending" ? (
setReviewNotes((current) => ({ ...current, [submission.id]: event.target.value }))} placeholder="Prüfnotiz optional" className="rounded-2xl border border-gray-200 px-4 py-3 text-sm font-semibold" />
) : submission.reviewNote ? (

{submission.reviewNote}

) : null}
))} {!submissions.length && !submissionsQuery.isLoading ? (

Keine Rezeptvorschläge in dieser Ansicht.

) : null}
); }