1
0
forked from baron/baron-sso

연동 앱 페이지 UI 정리

This commit is contained in:
2026-06-01 15:47:18 +09:00
parent d40e443d48
commit d2a7ebd82f
5 changed files with 84 additions and 134 deletions

View File

@@ -46,7 +46,6 @@ import {
type ClientSummary,
fetchClients,
fetchDeveloperRequestStatus,
fetchDevStats,
fetchMyTenants,
requestDeveloperAccess,
} from "../../lib/devApi";
@@ -79,12 +78,6 @@ function ClientsPage() {
enabled: hasAccessToken,
});
const { data: statsData, isLoading: isLoadingStats } = useQuery({
queryKey: ["dev-stats"],
queryFn: fetchDevStats,
enabled: hasAccessToken,
});
const { data: me, isLoading: isLoadingMe } = useQuery({
queryKey: ["userMe"],
queryFn: fetchMe,
@@ -172,9 +165,6 @@ function ClientsPage() {
typeFilter,
]);
const totalClients = statsData?.total_clients ?? clients.length;
const activeSessions = statsData?.active_sessions ?? 0;
const authFailures = statsData?.auth_failures_24h ?? 0;
const hasFilterResult = filteredClients.length > 0;
const isFilteredOut = clients.length > 0 && !hasFilterResult;
const visibleClients = useMemo(() => {
@@ -198,49 +188,8 @@ function ClientsPage() {
"";
const profileRoleLabel = t(`ui.admin.role.${profileRole}`, profileRole);
type StatTone = "up" | "down" | "stable";
type StatItem = {
labelKey: string;
labelFallback: string;
value: string;
deltaKey: string;
deltaFallback: string;
tone: StatTone;
};
const stats: StatItem[] = [
{
labelKey: "ui.dev.clients.stats.total",
labelFallback: "Total Applications",
value: totalClients.toString(),
deltaKey: "ui.dev.clients.stats.realtime",
deltaFallback: "Realtime",
tone: "up" as const,
},
{
labelKey: "ui.dev.clients.stats.active_sessions",
labelFallback: "Active Sessions",
value: activeSessions.toString(),
deltaKey: "ui.dev.clients.stats.realtime",
deltaFallback: "Realtime",
tone: "up" as const,
},
{
labelKey: "ui.dev.clients.stats.auth_failures",
labelFallback: "Auth Failures (24h)",
value: authFailures.toString(),
deltaKey:
authFailures > 0
? "ui.dev.clients.stats.alert"
: "ui.dev.clients.stats.stable",
deltaFallback: authFailures > 0 ? "Check Logs" : "Stable",
tone: authFailures > 0 ? ("down" as const) : ("stable" as const),
},
];
const isLoading =
isLoadingClients ||
isLoadingStats ||
isLoadingRequest ||
(hasAccessToken && !profileRole && isLoadingMe);
@@ -285,7 +234,7 @@ function ClientsPage() {
canCreateClient ? (
<Button
size="sm"
className="shadow-lg shadow-primary/30"
className="mt-1 shadow-lg shadow-primary/30"
onClick={() => navigate("/clients/new")}
>
<Plus className="h-4 w-4" />
@@ -343,7 +292,19 @@ function ClientsPage() {
/>
<Card className="glass-panel">
<CardHeader className="pb-4 pt-6">
<CardHeader className="space-y-4 pb-4 pt-6">
<div>
<CardTitle className="text-xl font-semibold">
{t("ui.dev.clients.list.title", "클라이언트 목록")}
</CardTitle>
<CardDescription>
{t(
"msg.dev.clients.showing",
"총 {{shown}}개의 애플리케이션이 등록되어 있습니다.",
{ shown: clients.length },
)}
</CardDescription>
</div>
<SearchFilterBar
primary={
<div className="relative flex-1">
@@ -360,34 +321,21 @@ function ClientsPage() {
</div>
}
actions={
<>
<Button
variant="ghost"
size="sm"
className={cn(
"gap-1 text-muted-foreground",
isAdvancedFilterOpen && "text-primary bg-primary/10",
)}
onClick={() => setIsAdvancedFilterOpen(!isAdvancedFilterOpen)}
>
<Filter className="h-4 w-4" />
{t(
"ui.dev.clients.consents.filters.advanced",
"Advanced Filters",
)}
</Button>
<div className="hidden items-center gap-2 md:flex">
<Badge variant="muted">
{t(
"ui.dev.clients.badge.tenant_selected",
"테넌트: 선택됨",
)}
</Badge>
<Badge variant="success">
{t("ui.dev.clients.badge.dev_session", "DevFront 세션")}
</Badge>
</div>
</>
<Button
variant="ghost"
size="sm"
className={cn(
"gap-1 text-muted-foreground",
isAdvancedFilterOpen && "text-primary bg-primary/10",
)}
onClick={() => setIsAdvancedFilterOpen(!isAdvancedFilterOpen)}
>
<Filter className="h-4 w-4" />
{t(
"ui.dev.clients.consents.filters.advanced",
"Advanced Filters",
)}
</Button>
}
advancedOpen={isAdvancedFilterOpen}
advanced={
@@ -447,59 +395,6 @@ function ClientsPage() {
}
/>
</CardHeader>
<CardContent className="pt-0">
<div className="grid gap-3 md:grid-cols-3">
{stats.map((item) => (
<Card
key={item.labelKey}
className="border border-border/60 bg-background/70 shadow-none"
>
<CardHeader className="space-y-1 p-4">
<CardDescription>
{t(item.labelKey, item.labelFallback)}
</CardDescription>
<div className="flex items-baseline gap-2">
<span className="text-2xl font-semibold tracking-tight">
{item.value}
</span>
<Badge
variant={
item.tone === "up"
? "success"
: item.tone === "down"
? "warning"
: "muted"
}
className={cn(
"px-2 py-0.5 text-[11px]",
item.tone === "stable" && "bg-muted/40 text-foreground",
)}
>
{t(item.deltaKey, item.deltaFallback)}
</Badge>
</div>
</CardHeader>
</Card>
))}
</div>
</CardContent>
</Card>
<Card className="glass-panel">
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
<div>
<CardTitle className="text-xl font-semibold">
{t("ui.dev.clients.list.title", "클라이언트 목록")}
</CardTitle>
<CardDescription>
{t(
"msg.dev.clients.showing",
"총 {{shown}}개의 애플리케이션이 등록되어 있습니다.",
{ shown: totalClients },
)}
</CardDescription>
</div>
</CardHeader>
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
<div className={commonTableShellClass}>
<div className={commonTableViewportClass}>