import { useActionQuery, useActionMutation, } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import type { Meal, Exercise } from "@shared/types"; import { IconChevronLeft, IconChevronRight, IconToolsKitchen2, IconBarbell, } from "@tabler/icons-react"; import { format, addDays, subDays, isSameDay } from "date-fns"; import { useState, useEffect } from "react"; import { toast } from "sonner"; import { AddExerciseDialog } from "@/components/AddExerciseDialog"; import { AddMealDialog } from "@/components/AddMealDialog"; import { DailyProgress } from "@/components/DailyProgress"; import { ExerciseCard } from "@/components/ExerciseCard"; import { MealCard } from "@/components/MealCard"; import { QueryErrorState } from "@/components/QueryErrorState"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { VoiceDictation } from "@/components/VoiceDictation"; import { WeightTracker } from "@/components/WeightTracker"; import { getLogRowKey, isOptimisticLogRow, useOptimisticLogRows, } from "@/hooks/use-optimistic-log-rows"; import messages from "@/i18n/en-US"; import { apiFetch } from "@/lib/api"; import { formatLocalDate } from "@/lib/utils"; const SEO_TITLE = messages.seo.homeTitle; const SEO_DESCRIPTION = messages.seo.homeDescription; export function meta() { return [ { title: SEO_TITLE }, { name: "description", content: SEO_DESCRIPTION, }, { property: "og:title", content: SEO_TITLE }, { property: "og:description", content: SEO_DESCRIPTION }, { name: "twitter:card", content: "summary" }, { name: "twitter:title", content: SEO_TITLE }, { name: "twitter:description", content: SEO_DESCRIPTION }, ]; } export default function IndexPage() { const t = useT(); const [date, setDate] = useState(new Date()); const [editingMeal, setEditingMeal] = useState(null); const [editMealDialogOpen, setEditMealDialogOpen] = useState(false); const [editingExercise, setEditingExercise] = useState(null); const [editExerciseDialogOpen, setEditExerciseDialogOpen] = useState(false); const dateStr = formatLocalDate(date); // Sync current date to navigation state so the agent knows what day the user is viewing useEffect(() => { apiFetch("/_agent-native/application-state/navigation", { method: "PUT", body: JSON.stringify({ view: "entry", date: dateStr }), }).catch(() => {}); }, [dateStr]); const mealsQuery = useActionQuery("list-meals", { date: dateStr }); const { data: rawMeals, isLoading: mealsLoading } = mealsQuery; const serverMeals = Array.isArray(rawMeals) ? rawMeals : []; const { rows: meals, hasOptimisticRows: hasOptimisticMeals } = useOptimisticLogRows("meal", serverMeals, dateStr); const exercisesQuery = useActionQuery("list-exercises", { date: dateStr }); const { data: rawExercises, isLoading: exercisesLoading } = exercisesQuery; const serverExercises = Array.isArray(rawExercises) ? rawExercises : []; const { rows: exercises, hasOptimisticRows: hasOptimisticExercises } = useOptimisticLogRows("exercise", serverExercises, dateStr); const deleteMealMutation = useActionMutation("delete-meal", { onSuccess: () => { toast.success(t("meals.deleted")); }, onError: () => toast.error(t("meals.deleteFailed")), }); const deleteExerciseMutation = useActionMutation("delete-exercise", { onSuccess: () => { toast.success(t("exercise.deleted")); }, onError: () => toast.error(t("exercise.deleteFailed")), }); const mealTotals = meals.reduce( (acc, meal) => ({ calories: acc.calories + meal.calories, protein: acc.protein + (meal.protein || 0), carbs: acc.carbs + (meal.carbs || 0), fat: acc.fat + (meal.fat || 0), }), { calories: 0, protein: 0, carbs: 0, fat: 0 }, ); const exerciseTotals = exercises.reduce( (acc, exercise) => ({ burned: acc.burned + exercise.calories_burned, }), { burned: 0 }, ); const GOAL_CALORIES = 2000; const hasOptimisticLogs = hasOptimisticMeals || hasOptimisticExercises; const isLoading = (mealsLoading || exercisesLoading) && !hasOptimisticLogs; return (
{/* Date Navigation */}
{isSameDay(date, new Date()) ? t("entry.today") : format(date, "EEE, MMM d")}
{/* Daily Summary Hero */}
{isLoading ? ( ) : mealsQuery.isError || exercisesQuery.isError ? ( { void mealsQuery.refetch(); void exercisesQuery.refetch(); }} /> ) : ( )}
{/* Triple Column Layout */}
{/* Meals */}

{t("meals.title")}

{editingMeal ? ( { setEditMealDialogOpen(open); if (!open) setEditingMeal(null); }} currentDate={date} /> ) : ( )}
{mealsLoading && !hasOptimisticMeals ? ( <> ) : mealsQuery.isError ? ( void mealsQuery.refetch()} /> ) : meals.length === 0 ? (

{t("meals.noneLogged")}

{t("meals.emptyDescription")}

) : ( meals.map((meal) => ( deleteMealMutation.mutate({ id: String(id) }) } onEdit={(meal) => { setEditingMeal(meal); setEditMealDialogOpen(true); }} isDeleting={ deleteMealMutation.isPending && deleteMealMutation.variables?.id === String(meal.id) } isPending={isOptimisticLogRow(meal)} /> )) )}
{/* Exercises */}

{t("exercise.title")}

{editingExercise ? ( { setEditExerciseDialogOpen(open); if (!open) setEditingExercise(null); }} currentDate={date} /> ) : ( )}
{exercisesLoading && !hasOptimisticExercises ? ( ) : exercisesQuery.isError ? ( void exercisesQuery.refetch()} /> ) : exercises.length === 0 ? (

{t("exercise.noneLogged")}

{t("exercise.emptyDescription")}

) : ( exercises.map((exercise) => ( deleteExerciseMutation.mutate({ id: String(id) }) } onEdit={(exercise) => { setEditingExercise(exercise); setEditExerciseDialogOpen(true); }} isDeleting={ deleteExerciseMutation.isPending && deleteExerciseMutation.variables?.id === String(exercise.id) } isPending={isOptimisticLogRow(exercise)} /> )) )}
{/* Weight */}
); }