You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
// frontend/components/dashboard/DashboardClient.tsx
|
|
"use client";
|
|
import { useEffect, useRef } from "react";
|
|
import { Icon } from "@/components/Icon";
|
|
import type { IconName } from "@/components/icons/paths";
|
|
import { useDashboard } from "@/lib/dashboard/useDashboard";
|
|
import { ApprovalSummaryCard } from "./ApprovalSummaryCard";
|
|
import { DashboardSkeleton } from "./DashboardSkeleton";
|
|
import { GoalsCard } from "./GoalsCard";
|
|
import { HeroBriefing } from "./HeroBriefing";
|
|
import { InboxSummaryCard } from "./InboxSummaryCard";
|
|
import { ScheduleCard } from "./ScheduleCard";
|
|
import { TaskSummaryCard } from "./TaskSummaryCard";
|
|
|
|
export function DashboardClient() {
|
|
const { data, isLoading, error, mutate } = useDashboard();
|
|
const shellRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const el = shellRef.current;
|
|
if (!el) return;
|
|
const id = requestAnimationFrame(() => el.classList.add("entered"));
|
|
return () => cancelAnimationFrame(id);
|
|
}, [data]);
|
|
|
|
if (isLoading) return <DashboardSkeleton />;
|
|
if (error || !data) {
|
|
return (
|
|
<div className="dash-error" role="alert">
|
|
<p>대시보드를 불러오지 못했어요.</p>
|
|
<button className="mini-cta" onClick={() => mutate()}>
|
|
<Icon name="swap" /> 다시 시도
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const {
|
|
user,
|
|
briefing,
|
|
saved_today,
|
|
today_routed,
|
|
schedule,
|
|
task_summary,
|
|
goals,
|
|
approvals_summary,
|
|
inbox_recent,
|
|
badges,
|
|
} = data;
|
|
|
|
return (
|
|
<div className="dash-page" ref={shellRef}>
|
|
<div className="pagehead">
|
|
<div>
|
|
<div className="ph-eyebrow">
|
|
<span>{briefing.today}</span>
|
|
<span className="sep" />
|
|
<span className="wx">
|
|
<Icon name={briefing.weather.icon as IconName} />
|
|
{briefing.weather.cond}
|
|
</span>
|
|
</div>
|
|
<h1 className="ph-title">
|
|
좋은 아침이에요, {user.name}님 <em>☀</em>
|
|
</h1>
|
|
</div>
|
|
<div className="ph-search">
|
|
<Icon name="search" />
|
|
<input placeholder="아리에게 무엇이든…" aria-label="검색" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="work">
|
|
<div className="board">
|
|
<HeroBriefing briefing={briefing} />
|
|
<ApprovalSummaryCard
|
|
items={approvals_summary}
|
|
savedToday={saved_today}
|
|
pending={badges.appr}
|
|
/>
|
|
<InboxSummaryCard items={inbox_recent} todayRouted={today_routed} />
|
|
<ScheduleCard items={schedule} />
|
|
<TaskSummaryCard summary={task_summary} />
|
|
<GoalsCard goals={goals} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|