import { useState, useCallback, useEffect } from "react"; import PageHeader from "@/components/common/PageHeader"; import DashboardFilter from "../components/DashboardFilter"; import type { DashboardFilterValues } from "../components/DashboardFilter"; import StatsCards from "../components/StatsCards"; import WeeklyChart from "../components/WeeklyChart"; import RecentMessages from "../components/RecentMessages"; import PlatformDonut from "../components/PlatformDonut"; import { fetchDashboard } from "@/api/dashboard.api"; import { formatNumber } from "@/utils/format"; import type { DashboardData } from "../types"; /** KPI → StatsCards props 변환 */ function mapCards(data: DashboardData) { const { kpi } = data; const successRate = kpi.total_send > 0 ? +(kpi.total_success / kpi.total_send * 100).toFixed(1) : 0; return [ { label: "오늘 발송 수", value: formatNumber(kpi.total_send), badge: { type: "trend" as const, value: `${Math.abs(kpi.today_sent_change_rate)}%` }, link: "/statistics", }, { label: "성공률", value: successRate.toFixed(1), unit: "%", badge: { type: "icon" as const, icon: "check_circle", color: "bg-green-100 text-green-600" }, link: "/statistics", }, { label: "등록 기기 수", value: formatNumber(kpi.total_devices), badge: { type: "icon" as const, icon: "devices", color: "bg-blue-50 text-primary" }, link: "/devices", }, { label: "활성 서비스", value: String(kpi.active_service_count), badge: { type: "icon" as const, icon: "grid_view", color: "bg-purple-50 text-purple-600" }, link: "/services", }, ]; } /** daily → WeeklyChart props 변환 (최근 7일 기준, 없는 날짜는 0으로 채움) */ function mapChart(data: DashboardData) { const allTrends = data.daily ?? []; // 최근 7일 날짜 목록 생성 (오늘 포함) const today = new Date(); const todayStr = today.toISOString().slice(0, 10); const last7Days = Array.from({ length: 7 }, (_, i) => { const d = new Date(today); d.setDate(today.getDate() - 6 + i); return d.toISOString().slice(0, 10); }); const dataMap = new Map(allTrends.map((t) => [t.stat_date, t])); // 없는 날짜는 send_count 0으로 채움 const trends = last7Days.map( (date) => dataMap.get(date) ?? { stat_date: date, send_count: 0, success_count: 0, fail_count: 0, open_count: 0, ctr: 0, }, ); const maxVal = Math.max( ...trends.map((t) => Math.max(t.send_count, t.success_count)), 1, ); return trends.map((t) => { const dateStr = t.stat_date ?? ""; const isToday = dateStr === todayStr; const mm = dateStr.slice(5, 7); const dd = dateStr.slice(8, 10); return { label: isToday ? "Today" : `${mm}.${dd}`, blue: 1 - t.send_count / maxVal, // Y축 비율 (0=최상단) green: 1 - t.success_count / maxVal, sent: formatNumber(t.send_count), reach: formatNumber(t.success_count), }; }); } /** top_messages → RecentMessages props 변환 */ function mapMessages(data: DashboardData) { return (data.top_messages ?? []).map((m) => ({ template: m.title ?? "", targetCount: formatNumber(m.total_send_count), status: (m.status ?? "") as "완료" | "실패" | "진행" | "예약", sentAt: "", })); } /** platform_share → PlatformDonut props 변환 */ function mapPlatform(data: DashboardData) { const shares = data.platform_share ?? []; return { ios: shares.find((p) => p.platform?.toLowerCase() === "ios")?.ratio ?? 0, android: shares.find((p) => p.platform?.toLowerCase() === "android")?.ratio ?? 0, }; } export default function DashboardPage() { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [empty, setEmpty] = useState(false); // API 성공이나 데이터 없음 const [cards, setCards] = useState | undefined>(); const [chart, setChart] = useState | undefined>(); const [messages, setMessages] = useState | undefined>(); const [platform, setPlatform] = useState | undefined>(); // 필터 상태 보관 (초기 로드 + 조회 버튼) const [filter, setFilter] = useState({ dateStart: (() => { const d = new Date(); d.setDate(d.getDate() - 30); return d.toISOString().slice(0, 10); })(), dateEnd: new Date().toISOString().slice(0, 10), }); const loadDashboard = useCallback(async (f: DashboardFilterValues) => { setLoading(true); setError(false); setEmpty(false); try { const res = await fetchDashboard( { start_date: f.dateStart, end_date: f.dateEnd }, ); const d = res.data.data; // 데이터 비어있는지 판단 (서비스·기기가 있으면 KPI 표시) const hasData = d.kpi.total_send > 0 || d.kpi.total_devices > 0 || d.kpi.active_service_count > 0 || (d.daily ?? []).length > 0 || (d.top_messages ?? []).length > 0; if (!hasData) { setEmpty(true); return; } setCards(mapCards(d)); setChart(mapChart(d)); setMessages(mapMessages(d)); setPlatform(mapPlatform(d)); } catch { setError(true); } finally { setLoading(false); } }, []); // 초기 로드 useEffect(() => { loadDashboard(filter); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 조회 버튼 핸들러 const handleSearch = useCallback( (f: DashboardFilterValues) => { setFilter(f); loadDashboard(f); }, [loadDashboard], ); // 스켈레톤 표시 조건 const showSkeleton = loading || error || empty || !cards; return ( <> {/* 페이지 헤더 */} {/* 필터 */} {/* 데이터 영역 */}
{/* API 에러 오버레이 */} {error && !loading && (
cloud_off

데이터를 불러올 수 없습니다

네트워크 상태를 확인하거나 다시 조회해 주세요

)} {/* 조회 결과 없음 오버레이 */} {empty && !loading && (
inbox

조회된 데이터가 없습니다

기간이나 서비스를 변경하여 다시 조회해 주세요

)}
); }