128 lines
4.6 KiB
TypeScript
Executable File
128 lines
4.6 KiB
TypeScript
Executable File
"use client";
|
|
|
|
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|
import { useState } from "react";
|
|
import { AppShell } from "@/components/layout/app-shell";
|
|
import { CharacterCard } from "@/components/features/character-card";
|
|
import { TodaysAdventure } from "@/components/features/todays-adventure";
|
|
import { DailyReflection } from "@/components/features/daily-reflection";
|
|
import { QuestGiverSidebar, ReadingWidget } from "@/components/features/sidebar-widgets";
|
|
import { DaySwitcher } from "@/components/features/day-switcher";
|
|
import { CatchUpCard } from "@/components/features/catch-up-card";
|
|
import { QuickLogPanel } from "@/components/features/quick-log-panel";
|
|
import { ResourceLinkCapture } from "@/components/features/resource-link-capture";
|
|
import { formatDisplayDate, todayString } from "@/lib/dates-client";
|
|
|
|
async function fetchDashboard(date?: string) {
|
|
const url = date ? `/api/dashboard?date=${date}` : "/api/dashboard";
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error("Failed to load dashboard");
|
|
return res.json();
|
|
}
|
|
|
|
export default function HomePage() {
|
|
const [activeDate, setActiveDate] = useState<string | undefined>(undefined);
|
|
|
|
const { data, isLoading, error } = useQuery({
|
|
queryKey: ["dashboard", activeDate],
|
|
queryFn: () => fetchDashboard(activeDate),
|
|
placeholderData: keepPreviousData,
|
|
staleTime: 30_000,
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<AppShell>
|
|
<div className="p-8 text-center">Loading your adventure...</div>
|
|
</AppShell>
|
|
);
|
|
}
|
|
|
|
if (error || !data) {
|
|
return (
|
|
<AppShell>
|
|
<div className="p-8 text-center text-[var(--muted-rose)]">
|
|
Could not load dashboard. Is the database running?
|
|
</div>
|
|
</AppShell>
|
|
);
|
|
}
|
|
|
|
const date = data.today?.date ?? data.dateContext?.logicalToday ?? todayString();
|
|
const ctx = data.dateContext;
|
|
|
|
return (
|
|
<AppShell>
|
|
<div className="p-4 pb-20 md:pb-4">
|
|
{ctx && (
|
|
<DaySwitcher
|
|
activeDate={date}
|
|
logicalToday={ctx.logicalToday}
|
|
logicalYesterday={ctx.logicalYesterday}
|
|
isGraceWindow={ctx.isGraceWindow}
|
|
onSelectDate={setActiveDate}
|
|
/>
|
|
)}
|
|
<p className="text-sm text-[var(--warm-grey)] mb-4">
|
|
{formatDisplayDate(date)}
|
|
</p>
|
|
{data.catchUpGaps?.length > 0 && (
|
|
<CatchUpCard gaps={data.catchUpGaps} onSelectDate={setActiveDate} />
|
|
)}
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4">
|
|
<div className="lg:col-span-4">
|
|
<CharacterCard
|
|
displayName={data.user.displayName}
|
|
currentTitle={data.user.currentTitle}
|
|
level={data.progress.level}
|
|
totalXp={data.progress.totalXp}
|
|
portraitConfig={data.user.portraitConfig}
|
|
scores={{
|
|
consistencyScore: data.progress.consistencyScore,
|
|
disciplineScore: data.progress.disciplineScore,
|
|
learningScore: data.progress.learningScore,
|
|
spiritualScore: data.progress.spiritualScore,
|
|
healthScore: data.progress.healthScore,
|
|
readingScore: data.progress.readingScore,
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="lg:col-span-5 space-y-4">
|
|
{data.today && (
|
|
<TodaysAdventure
|
|
date={date}
|
|
chapter={data.progress.currentChapter}
|
|
journeyDay={data.progress.journeyDay}
|
|
isRestDay={data.today.isRestDay}
|
|
isCustomized={data.today.isCustomized}
|
|
workHoursTarget={data.today.workHoursTarget}
|
|
dayMode={data.today.dayMode ?? "normal"}
|
|
isBackfilled={data.today.isBackfilled ?? false}
|
|
items={data.today.items}
|
|
todos={data.today.todos}
|
|
/>
|
|
)}
|
|
<QuickLogPanel date={date} />
|
|
<ResourceLinkCapture date={date} />
|
|
<DailyReflection
|
|
date={date}
|
|
initial={data.reflection}
|
|
dateLabel={formatDisplayDate(date)}
|
|
/>
|
|
</div>
|
|
<div className="lg:col-span-3 space-y-4">
|
|
<QuestGiverSidebar suggestions={data.suggestions} />
|
|
<ReadingWidget
|
|
activeBooks={data.reading.activeBooks}
|
|
streak={data.reading.streak}
|
|
weeklyPages={data.reading.weeklyPages}
|
|
weeklyGoal={data.reading.weeklyGoal}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AppShell>
|
|
);
|
|
}
|