Skip to content

Commit

Permalink
finishing up meal page and nutrition card basics
Browse files Browse the repository at this point in the history
  • Loading branch information
trevorpfiz committed Jan 30, 2024
1 parent 5487219 commit cfe0fe5
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { Suspense } from "react";

import { MacrosDonut } from "~/components/dashboard/macros-donut";
import { MacrosProgress } from "~/components/dashboard/macros-progress";
import { NutrientsProgress } from "~/components/dashboard/nutrients-progress";
import Foods from "~/components/meal/foods";
import { MealTopBar } from "~/components/meal/meal-top-bar";
import { api } from "~/trpc/server";

export const runtime = "edge";

export default async function MealPage({
params,
}: {
params: { mealId: string };
params: { mealId: number };
}) {
const meal = api.meal.byId({ id: params.mealId });

return (
<div className="relative z-0 flex h-full w-full overflow-hidden bg-white">
<div className="relative flex h-full max-w-2xl flex-1 flex-col overflow-hidden bg-white">
Expand All @@ -25,7 +30,9 @@ export default async function MealPage({
<MacrosProgress />
{/* TODO: Meal Images */}
{/* Foods */}
<Foods />
<Suspense fallback={<div>Loading...</div>}>
<Foods meal={meal} mealId={params.mealId} />
</Suspense>
{/* Nutrient progress charts */}
<NutrientsProgress />
</main>
Expand Down
4 changes: 2 additions & 2 deletions apps/nextjs/src/components/dashboard/meal-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Link from "next/link";
import { format } from "date-fns";
import { Clock } from "lucide-react";

import type { MealWithNutritionWithFoodItem } from "@nourish/db/src/schema";
import type { RouterOutputs } from "@nourish/api";
import { cn } from "@nourish/ui";
import { Badge } from "@nourish/ui/badge";
import {
Expand All @@ -15,7 +15,7 @@ import {
type CardProps = React.ComponentProps<typeof Card>;

interface MealProps extends CardProps {
meal: MealWithNutritionWithFoodItem;
meal: RouterOutputs["meal"]["byDay"][number];
}

export function MealCard({ meal, className, ...props }: MealProps) {
Expand Down
30 changes: 22 additions & 8 deletions apps/nextjs/src/components/dashboard/meal-cards.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as React from "react";
"use client";

import { ScrollArea, ScrollBar } from "@nourish/ui/scroll-area";

import { MealCard } from "~/components/dashboard/meal-card";
import { api } from "~/trpc/server";
import { api } from "~/trpc/react";

export interface Meal {
time: string;
foodItems: string[];
}

export const meals: Meal[] = [
export const mealsData: Meal[] = [
{
time: "2023-11-09T07:01:00Z",
foodItems: ["Chicken Breast", "Strawberries"],
Expand All @@ -23,17 +23,31 @@ export const meals: Meal[] = [
{ time: "2023-11-09T18:01:00Z", foodItems: ["Honey"] }, // Dinner
];

export async function MealCards() {
export function MealCards() {
const currentDate = new Date().toISOString().split("T")[0]; // yyyy-mm-dd format
const meals = await api.meal.byDay({ date: currentDate! });
const { isPending, isError, data, error } = api.meal.byDay.useQuery({
date: currentDate!,
});

if (isError) {
return <span>Error: {error.message}</span>;
}

return (
<div className="relative w-full">
<ScrollArea className="w-full whitespace-nowrap">
<div className="flex w-max space-x-4 px-4 py-2">
{meals.map((meal, index) => (
<MealCard key={index} meal={meal} />
))}
{isPending ? (
<>
<div>Loading...</div>
<div>Loading...</div>
<div>Loading...</div>
</>
) : data?.length === 0 ? (
<div>No meals found</div>
) : (
data?.map((meal, index) => <MealCard key={index} meal={meal} />)
)}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
Expand Down
60 changes: 54 additions & 6 deletions apps/nextjs/src/components/meal/foods.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,60 @@
"use client";

import { use } from "react";
import Link from "next/link";
import { atom } from "jotai";
import { atom, useAtom } from "jotai";
import { Plus, Trash } from "lucide-react";

import type { RouterOutputs } from "@nourish/api";
import { Button } from "@nourish/ui/button";
import { toast } from "@nourish/ui/toast";

import { NutritionCard } from "~/components/meal/nutrition-card";
import { api } from "~/trpc/react";

export const selectedNutritionIdsAtom = atom<number[]>([]);

interface FoodsProps {
mealId: number;
meal: Promise<RouterOutputs["meal"]["byId"]>;
}

export default function Foods(props: FoodsProps) {
const initialData = use(props.meal);
const [selectedNutritionIds, setSelectedNutritionIds] = useAtom(
selectedNutritionIdsAtom,
);

const utils = api.useUtils();
const { data: meal } = api.meal.byId.useQuery(
{ id: props.mealId },
{
initialData,
},
);

const { isPending, submittedAt, variables, mutate, isError } =
api.nutrition.deleteMany.useMutation({
onError: (err) => {
toast.error(
err?.data?.code === "UNAUTHORIZED"
? "You must be logged in to delete a post"
: "Failed to delete post",
);
},
onSettled: async () => {
await utils.meal.invalidate();
},
});

export const selectedMealItemsAtom = atom<string[]>([]);
const handleDelete = () => {
if (selectedNutritionIds.length > 0) {
mutate(selectedNutritionIds);
} else {
toast.error("No items selected");
}
};

export default function Foods() {
return (
<div className="flex flex-col items-center gap-4">
<div className="flex items-center justify-between gap-2">
Expand All @@ -20,12 +66,14 @@ export default function Foods() {
</Link>
</Button>
</div>
<Trash size={24} />
<Button onClick={() => handleDelete()}>
<Trash size={24} />
</Button>
</div>
{/* Nutrition cards */}
<div>
{data.map((item, index) => (
<NutritionCard key={index} index={index} />
{meal?.nutrition.map((item, index) => (
<NutritionCard key={item.id} nutritionItem={item} />
))}
</div>
</div>
Expand Down
80 changes: 42 additions & 38 deletions apps/nextjs/src/components/meal/nutrition-card.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
"use client";

import { useMutationState } from "@tanstack/react-query";
import { getQueryKey } from "@trpc/react-query";
import { useAtom } from "jotai";
import { Check, X } from "lucide-react";
import { Check } from "lucide-react";

import type { ReviewFoodsForm } from "@nourish/validators";
import type { NutritionWithFoodItem } from "@nourish/db/src/schema";
import { cn } from "@nourish/ui";
import { Badge } from "@nourish/ui/badge";
import { Button } from "@nourish/ui/button";
import { Card, CardContent, CardFooter } from "@nourish/ui/card";
import {
FormControl,
FormField,
FormItem,
FormLabel,
useFieldArrayFormContext,
} from "@nourish/ui/form";
import { Input } from "@nourish/ui/input";
import { Label } from "@nourish/ui/label";
import {
Expand All @@ -25,50 +19,57 @@ import {
SelectValue,
} from "@nourish/ui/select";

import { selectedMealItemsAtom } from "~/components/meal/foods";
import { selectedNutritionIdsAtom } from "~/components/meal/foods";
import { api } from "~/trpc/react";

type CardProps = React.ComponentProps<typeof Card>;

interface NutritionCardProps extends CardProps {
index: number;
nutritionItem: NutritionWithFoodItem;
}

function NutritionCard({ index, className, ...props }: NutritionCardProps) {
const [selectedMealItems, setSelectedMealItems] = useAtom(
selectedMealItemsAtom,
function NutritionCard({
nutritionItem,
className,
...props
}: NutritionCardProps) {
const [selectedNutritionIds, setSelectedNutritionIds] = useAtom(
selectedNutritionIdsAtom,
);
const foodItem = /* retrieve food item details based on index or some identifier */;
const isSelected = selectedMealItems.some(item => item.id === foodItem.id);
const utils = api.useUtils();
const deleteManyNutritionKey = getQueryKey(api.nutrition.deleteMany);
const variables = useMutationState<string>({
filters: { mutationKey: deleteManyNutritionKey, status: "pending" },
select: (mutation) => mutation.state.variables,
});

const foodItem = nutritionItem.foodItem;
const isSelected = selectedNutritionIds.includes(foodItem.id);

const toggleSelection = () => {
if (isSelected) {
const newSelection = selectedMealItems.filter(item => item.id !== foodItem.id);
setSelectedMealItems(newSelection);
} else {
setSelectedMealItems([...selectedMealItems, {
id: foodItem.id,
name: foodItem.name,
description: foodItem.description,
size: "", // default size
quantity: 1, // default quantity
}]);
}
setSelectedNutritionIds((currentSelected) => {
if (isSelected) {
return currentSelected.filter((id) => id !== foodItem.id);
} else {
return [...currentSelected, foodItem.id];
}
});
};

// Function to handle size change
const handleSizeChange = (size) => {
const updatedItems = selectedMealItems.map(item =>
item.id === foodItem.id ? { ...item, size: size } : item
const updatedItems = selectedNutritionIds.map((item) =>
item.id === foodItem.id ? { ...item, size: size } : item,
);
setSelectedMealItems(updatedItems);
setSelectedNutritionIds(updatedItems);
};

// Function to handle quantity change
const handleQuantityChange = (e) => {
const updatedItems = selectedMealItems.map(item =>
item.id === foodItem.id ? { ...item, quantity: e.target.value } : item
const updatedItems = selectedNutritionIds.map((item) =>
item.id === foodItem.id ? { ...item, quantity: e.target.value } : item,
);
setSelectedMealItems(updatedItems);
setSelectedNutritionIds(updatedItems);
};

return (
Expand Down Expand Up @@ -106,7 +107,10 @@ function NutritionCard({ index, className, ...props }: NutritionCardProps) {
{/* Size */}
<div className="flex flex-[2] flex-row items-center gap-2 space-y-0">
<Label htmlFor="size">Size</Label>
<Select onValueChange={handleSizeChange} defaultValue={/* get default size from selectedMealItems */}>
<Select
onValueChange={handleSizeChange}
defaultValue={nutritionItem.serving_size}
>
<SelectTrigger id="size">
<SelectValue placeholder="Size" />
</SelectTrigger>
Expand All @@ -126,8 +130,8 @@ function NutritionCard({ index, className, ...props }: NutritionCardProps) {
id="quantity"
type="number"
placeholder="Quantity"
value={/* get quantity from selectedMealItems */}
onChange={handleQuantityChange}
value={nutritionItem.servings}
onChange={handleQuantityChange}
/>
</div>
</CardFooter>
Expand Down
13 changes: 13 additions & 0 deletions packages/api/src/router/meal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export const mealRouter = createTRPCRouter({
.query(({ ctx, input }) => {
return ctx.db.query.meal.findFirst({
where: eq(schema.meal.id, input.id),
with: {
nutrition: {
with: {
foodItem: true,
},
},
},
});
}),

Expand Down Expand Up @@ -49,4 +56,10 @@ export const mealRouter = createTRPCRouter({

return meals;
}),

delete: protectedProcedure
.input(z.number())
.mutation(async ({ ctx, input }) => {
return ctx.db.delete(schema.meal).where(eq(schema.meal.id, input));
}),
});
20 changes: 19 additions & 1 deletion packages/api/src/router/nutrition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import { differenceInMinutes } from "date-fns";
import { z } from "zod";

import type { MealWithNutrition } from "@nourish/db/src/schema";
import { and, eq, gte, schema } from "@nourish/db";
import { and, desc, eq, gte, inArray, schema } from "@nourish/db";
import { meal } from "@nourish/db/src/schema";
import { ReviewFoodsFormSchema } from "@nourish/validators";

import { createTRPCRouter, protectedProcedure } from "../trpc";

export const nutritionRouter = createTRPCRouter({
all: protectedProcedure.query(({ ctx }) => {
return ctx.db.query.nutrition.findMany({
orderBy: desc(schema.nutrition.id),
limit: 10,
with: {
foodItem: true,
},
});
}),

createMany: protectedProcedure
.input(
z.object({
Expand Down Expand Up @@ -106,4 +116,12 @@ export const nutritionRouter = createTRPCRouter({
.delete(schema.nutrition)
.where(eq(schema.nutrition.id, input));
}),

deleteMany: protectedProcedure
.input(z.array(z.number()))
.mutation(async ({ ctx, input }) => {
return ctx.db
.delete(schema.nutrition)
.where(inArray(schema.nutrition.id, input));
}),
});
Loading

0 comments on commit cfe0fe5

Please sign in to comment.