전체적인 리팩토링

This commit is contained in:
2026-03-12 09:26:27 +09:00
parent 406af7408a
commit e51d767878
97 changed files with 13651 additions and 363 deletions

View File

@@ -1,4 +1,5 @@
import Link from "next/link";
import type { ReactNode } from "react";
import { Activity, RefreshCcw, Settings2, Wifi } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
@@ -58,153 +59,155 @@ export function StatusHeader({
? "수신 대기중"
: "연결됨"
: "미연결";
const displayGrossTotalAmount = hasApiTotalAmount
? summary?.apiReportedTotalAmount ?? 0
: summary?.totalAmount ?? 0;
return (
<Card className="relative overflow-hidden border-brand-200 shadow-sm dark:border-brand-800/50">
{/* ========== BACKGROUND DECORATION ========== */}
<div className="pointer-events-none absolute inset-x-0 top-0 h-20 bg-linear-to-r from-brand-100/70 via-brand-50/50 to-transparent dark:from-brand-900/35 dark:via-brand-900/20" />
<Card className="relative overflow-hidden border-brand-200/80 bg-linear-to-br from-brand-50/80 via-background to-brand-50/20 shadow-sm dark:border-brand-800/45 dark:from-brand-900/25 dark:via-background dark:to-background">
<div className="pointer-events-none absolute -right-14 -top-14 h-52 w-52 rounded-full bg-brand-300/30 blur-3xl dark:bg-brand-700/20" />
<div className="pointer-events-none absolute -left-16 bottom-0 h-44 w-44 rounded-full bg-brand-200/25 blur-3xl dark:bg-brand-800/20" />
<CardContent className="relative grid gap-3 p-4 md:grid-cols-[1fr_1fr_1fr_auto]">
{/* ========== TOTAL ASSET ========== */}
<div className="rounded-xl border border-border/70 bg-background/90 px-4 py-3">
<p className="text-xs font-medium text-muted-foreground"> ( )</p>
<p className="mt-1 text-xl font-semibold tracking-tight">
{summary ? `${formatCurrency(summary.totalAmount)}` : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
() {summary ? `${formatCurrency(summary.cashBalance)}` : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{" "}
{summary ? `${formatCurrency(summary.evaluationAmount)}` : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
(KIS){" "}
{summary ? `${formatCurrency(summary.totalDepositAmount)}` : "-"}
</p>
<p className="mt-1 text-[11px] text-muted-foreground/80">
.
</p>
<p className="mt-1 text-xs text-muted-foreground">
( ){" "}
{summary ? `${formatCurrency(summary.netAssetAmount)}` : "-"}
</p>
{hasApiTotalAmount && isApiTotalAmountDifferent ? (
<p className="mt-1 text-xs text-muted-foreground">
KIS {formatCurrency(summary?.apiReportedTotalAmount ?? 0)}
<CardContent className="relative space-y-3 p-4 md:p-5">
<div className="grid gap-3 xl:grid-cols-[1fr_1fr_auto]">
<div className="rounded-2xl border border-brand-200/70 bg-background/85 p-4 shadow-sm dark:border-brand-800/60 dark:bg-brand-950/20">
<p className="text-xs font-semibold tracking-wide text-muted-foreground">
TOTAL ASSET
</p>
) : null}
{hasApiNetAssetAmount ? (
<p className="mt-1 text-xs text-muted-foreground">
KIS {formatCurrency(summary?.apiReportedNetAssetAmount ?? 0)}
<p className="mt-2 text-2xl font-bold tracking-tight text-foreground md:text-3xl">
{summary ? `${formatCurrency(displayGrossTotalAmount)}` : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{summary ? `${formatCurrency(summary.netAssetAmount)}` : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{summary ? `${formatCurrency(summary.cashBalance)}` : "-"} · {" "}
{summary ? `${formatCurrency(summary.evaluationAmount)}` : "-"}
</p>
) : null}
</div>
{/* ========== PROFIT/LOSS ========== */}
<div className="rounded-xl border border-border/70 bg-background/90 px-4 py-3">
<p className="text-xs font-medium text-muted-foreground"> </p>
<p
className={cn(
"mt-1 text-xl font-semibold tracking-tight",
toneClass,
)}
>
{summary ? `${formatSignedCurrency(summary.totalProfitLoss)}` : "-"}
</p>
<p className={cn("mt-1 text-xs font-medium", toneClass)}>
{summary ? formatSignedPercent(summary.totalProfitRate) : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{" "}
{summary ? `${formatCurrency(summary.evaluationAmount)}` : "-"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{" "}
{summary ? `${formatCurrency(summary.purchaseAmount)}` : "-"}
</p>
</div>
{/* ========== CONNECTION STATUS ========== */}
<div className="rounded-xl border border-border/70 bg-background/90 px-4 py-3">
<p className="text-xs font-medium text-muted-foreground"> </p>
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs font-medium">
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-1",
isKisRestConnected
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
: "bg-red-500/10 text-red-600 dark:text-red-400",
)}
>
<Wifi className="h-3.5 w-3.5" />
{isKisRestConnected ? "연결됨" : "연결 끊김"}
</span>
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-1",
isWebSocketReady
? isRealtimePending
? "bg-amber-500/10 text-amber-700 dark:text-amber-400"
: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
: "bg-muted text-muted-foreground",
)}
>
<Activity className="h-3.5 w-3.5" />
{realtimeStatusLabel}
</span>
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-1",
isProfileVerified
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
: "bg-amber-500/10 text-amber-700 dark:text-amber-400",
)}
>
<Activity className="h-3.5 w-3.5" />
{isProfileVerified ? "완료" : "미완료"}
</span>
</div>
<p className="mt-2 text-xs text-muted-foreground">
{updatedLabel}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{maskAccountNo(verifiedAccountNo)}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{summary ? `${formatCurrency(summary.loanAmount)}` : "-"}
</p>
<div className="rounded-2xl border border-border/70 bg-background/85 p-4 shadow-sm">
<p className="text-xs font-semibold tracking-wide text-muted-foreground">
TODAY P/L
</p>
<p className={cn("mt-2 text-2xl font-bold tracking-tight md:text-3xl", toneClass)}>
{summary ? `${formatSignedCurrency(summary.totalProfitLoss)}` : "-"}
</p>
<p className={cn("mt-1 text-sm font-semibold", toneClass)}>
{summary ? formatSignedPercent(summary.totalProfitRate) : "-"}
</p>
<p className="mt-2 text-xs text-muted-foreground">
{summary ? `${formatCurrency(summary.purchaseAmount)}` : "-"} · {" "}
{summary ? `${formatCurrency(summary.loanAmount)}` : "-"}
</p>
</div>
<div className="flex flex-col gap-2 rounded-2xl border border-border/70 bg-background/85 p-3">
<Button
type="button"
variant="outline"
onClick={onRefresh}
disabled={isRefreshing}
className="w-full border-brand-200 text-brand-700 hover:bg-brand-50 dark:border-brand-700 dark:text-brand-300 dark:hover:bg-brand-900/30"
>
<RefreshCcw className={cn("mr-2 h-4 w-4", isRefreshing ? "animate-spin" : "")} />
</Button>
<Button
asChild
className="w-full bg-brand-600 text-white hover:bg-brand-700 dark:bg-brand-600 dark:text-white dark:hover:bg-brand-500"
>
<Link href="/settings">
<Settings2 className="mr-2 h-4 w-4" />
</Link>
</Button>
<div className="mt-1 rounded-xl border border-border/70 bg-muted/30 px-2.5 py-2 text-[11px] text-muted-foreground">
<p> {updatedLabel}</p>
<p> {maskAccountNo(verifiedAccountNo)}</p>
</div>
</div>
</div>
{/* ========== QUICK ACTIONS ========== */}
<div className="flex flex-col gap-2 sm:flex-row md:flex-col md:items-stretch md:justify-center">
<Button
type="button"
variant="outline"
onClick={onRefresh}
disabled={isRefreshing}
className="w-full border-brand-200 text-brand-700 hover:bg-brand-50 dark:border-brand-700 dark:text-brand-300 dark:hover:bg-brand-900/30"
>
<RefreshCcw
className={cn("h-4 w-4 mr-2", isRefreshing ? "animate-spin" : "")}
/>
</Button>
<Button
asChild
className="w-full bg-brand-600 text-white hover:bg-brand-700 dark:bg-brand-600 dark:text-white dark:hover:bg-brand-500"
>
<Link href="/settings">
<Settings2 className="h-4 w-4 mr-2" />
</Link>
</Button>
<div className="grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
<OverviewMetric
icon={<Wifi className="h-3.5 w-3.5" />}
label="REST 연결"
value={isKisRestConnected ? "정상" : "끊김"}
toneClass={
isKisRestConnected
? "border-emerald-300/70 bg-emerald-50/60 text-emerald-700 dark:border-emerald-700/50 dark:bg-emerald-950/30 dark:text-emerald-300"
: "border-red-300/70 bg-red-50/60 text-red-700 dark:border-red-700/50 dark:bg-red-950/30 dark:text-red-300"
}
/>
<OverviewMetric
icon={<Activity className="h-3.5 w-3.5" />}
label="실시간 시세"
value={realtimeStatusLabel}
toneClass={
isWebSocketReady
? isRealtimePending
? "border-amber-300/70 bg-amber-50/60 text-amber-700 dark:border-amber-700/50 dark:bg-amber-950/30 dark:text-amber-300"
: "border-emerald-300/70 bg-emerald-50/60 text-emerald-700 dark:border-emerald-700/50 dark:bg-emerald-950/30 dark:text-emerald-300"
: "border-slate-300/70 bg-slate-50/60 text-slate-700 dark:border-slate-700/50 dark:bg-slate-900/30 dark:text-slate-300"
}
/>
<OverviewMetric
icon={<Activity className="h-3.5 w-3.5" />}
label="계좌 인증"
value={isProfileVerified ? "완료" : "미완료"}
toneClass={
isProfileVerified
? "border-emerald-300/70 bg-emerald-50/60 text-emerald-700 dark:border-emerald-700/50 dark:bg-emerald-950/30 dark:text-emerald-300"
: "border-amber-300/70 bg-amber-50/60 text-amber-700 dark:border-amber-700/50 dark:bg-amber-950/30 dark:text-amber-300"
}
/>
<OverviewMetric
icon={<Activity className="h-3.5 w-3.5" />}
label="총예수금(KIS)"
value={summary ? `${formatCurrency(summary.totalDepositAmount)}` : "-"}
toneClass="border-brand-200/80 bg-brand-50/70 text-brand-700 dark:border-brand-700/60 dark:bg-brand-900/35 dark:text-brand-300"
/>
</div>
{hasApiTotalAmount && isApiTotalAmountDifferent ? (
<p className="text-xs text-muted-foreground">
{formatCurrency(summary?.totalAmount ?? 0)} · KIS {" "}
{formatCurrency(summary?.apiReportedTotalAmount ?? 0)}
</p>
) : null}
{hasApiNetAssetAmount ? (
<p className="text-xs text-muted-foreground">
KIS {formatCurrency(summary?.apiReportedNetAssetAmount ?? 0)}
</p>
) : null}
</CardContent>
</Card>
);
}
function OverviewMetric({
icon,
label,
value,
toneClass,
}: {
icon: ReactNode;
label: string;
value: string;
toneClass: string;
}) {
return (
<div className={cn("rounded-xl border px-3 py-2", toneClass)}>
<p className="flex items-center gap-1 text-[11px] font-medium opacity-85">
{icon}
{label}
</p>
<p className="mt-0.5 text-xs font-semibold">{value}</p>
</div>
);
}
/**
* @description 계좌번호를 마스킹해 표시합니다.
* @param value 계좌번호(8-2)