대시보드 구현
This commit is contained in:
115
features/dashboard/components/DashboardContainer.tsx
Normal file
115
features/dashboard/components/DashboardContainer.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { DashboardAccessGate } from "@/features/dashboard/components/DashboardAccessGate";
|
||||
import { DashboardSkeleton } from "@/features/dashboard/components/DashboardSkeleton";
|
||||
import { HoldingsList } from "@/features/dashboard/components/HoldingsList";
|
||||
import { MarketSummary } from "@/features/dashboard/components/MarketSummary";
|
||||
import { StatusHeader } from "@/features/dashboard/components/StatusHeader";
|
||||
import { StockDetailPreview } from "@/features/dashboard/components/StockDetailPreview";
|
||||
import { useDashboardData } from "@/features/dashboard/hooks/use-dashboard-data";
|
||||
import { useKisRuntimeStore } from "@/features/settings/store/use-kis-runtime-store";
|
||||
|
||||
/**
|
||||
* @description 대시보드 메인 컨테이너입니다.
|
||||
* @remarks UI 흐름: 대시보드 진입 -> useDashboardData API 호출 -> StatusHeader/MarketSummary/HoldingsList/StockDetailPreview 순으로 렌더링
|
||||
* @see app/(main)/dashboard/page.tsx 로그인 완료 후 이 컴포넌트를 렌더링합니다.
|
||||
* @see features/dashboard/hooks/use-dashboard-data.ts 대시보드 데이터 조회/갱신 상태를 관리합니다.
|
||||
*/
|
||||
export function DashboardContainer() {
|
||||
const {
|
||||
verifiedCredentials,
|
||||
isKisVerified,
|
||||
_hasHydrated,
|
||||
wsApprovalKey,
|
||||
wsUrl,
|
||||
} = useKisRuntimeStore(
|
||||
useShallow((state) => ({
|
||||
verifiedCredentials: state.verifiedCredentials,
|
||||
isKisVerified: state.isKisVerified,
|
||||
_hasHydrated: state._hasHydrated,
|
||||
wsApprovalKey: state.wsApprovalKey,
|
||||
wsUrl: state.wsUrl,
|
||||
})),
|
||||
);
|
||||
|
||||
const canAccess = isKisVerified && Boolean(verifiedCredentials);
|
||||
|
||||
const {
|
||||
balance,
|
||||
indices,
|
||||
selectedHolding,
|
||||
selectedSymbol,
|
||||
setSelectedSymbol,
|
||||
isLoading,
|
||||
isRefreshing,
|
||||
balanceError,
|
||||
indicesError,
|
||||
lastUpdatedAt,
|
||||
refresh,
|
||||
} = useDashboardData(canAccess ? verifiedCredentials : null);
|
||||
|
||||
const isKisRestConnected = useMemo(() => {
|
||||
if (indices.length > 0) return true;
|
||||
if (balance && !balanceError) return true;
|
||||
return false;
|
||||
}, [balance, balanceError, indices.length]);
|
||||
|
||||
if (!_hasHydrated) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!canAccess) {
|
||||
return <DashboardAccessGate canAccess={canAccess} />;
|
||||
}
|
||||
|
||||
if (isLoading && !balance && indices.length === 0) {
|
||||
return <DashboardSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="mx-auto flex w-full max-w-7xl flex-col gap-4 p-4 md:p-6">
|
||||
{/* ========== STATUS HEADER ========== */}
|
||||
<StatusHeader
|
||||
summary={balance?.summary ?? null}
|
||||
isKisRestConnected={isKisRestConnected}
|
||||
isWebSocketReady={Boolean(wsApprovalKey && wsUrl)}
|
||||
isRefreshing={isRefreshing}
|
||||
lastUpdatedAt={lastUpdatedAt}
|
||||
onRefresh={() => {
|
||||
void refresh();
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== MAIN CONTENT GRID ========== */}
|
||||
<div className="grid gap-4 lg:grid-cols-[1.15fr_1fr]">
|
||||
<HoldingsList
|
||||
holdings={balance?.holdings ?? []}
|
||||
selectedSymbol={selectedSymbol}
|
||||
isLoading={isLoading}
|
||||
error={balanceError}
|
||||
onSelect={setSelectedSymbol}
|
||||
/>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<MarketSummary
|
||||
items={indices}
|
||||
isLoading={isLoading}
|
||||
error={indicesError}
|
||||
/>
|
||||
|
||||
<StockDetailPreview
|
||||
holding={selectedHolding}
|
||||
totalAmount={balance?.summary.totalAmount ?? 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user