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}>

View File

@@ -37,6 +37,7 @@ test("clients page loads correctly", async ({ page }) => {
// 페이지 내 주요 텍스트 확인
await expect(page.getByText("연동 앱 목록")).toBeVisible();
await expect(page.getByText("Total Applications", { exact: true })).toHaveCount(0);
// 테이블 헤더 확인
await expect(

View File

@@ -595,6 +595,10 @@ description = "Quickly review application types and headless login usage."
empty = "Review the RPs this account can access."
none = "No linked applications are available."
[msg.dev.dashboard.recent_changes]
description = "Review trends for changed or deleted applications on the dashboard."
empty = "There are no recent change logs yet."
[msg.dev.dashboard.notice]
consent_audit = "Consent Audit"
dev_scope = "Dev Scope"
@@ -2308,6 +2312,20 @@ title = "Quick links"
[ui.dev.dashboard.recent]
title = "My Applications"
[ui.dev.dashboard.recent_changes]
aria = "Recent changed application status"
deleted_group = "Deleted RPs"
period = "Recent change aggregation period"
series = "Changes {{changes}} / Actors {{actors}}"
title = "Recent Changed Apps"
y_axis = "Y axis: change count"
[ui.dev.dashboard.recent_changes.summary]
changed_clients = "Changed applications"
deleted_clients = "Deleted RPs"
latest_change = "Latest change"
total_changes = "Recent change count"
[ui.dev.dashboard.stack]
notes = "Setup notes"
subtitle = "Devfront baseline"

View File

@@ -1087,6 +1087,10 @@ description = "애플리케이션 유형과 headless login 사용 현황을 빠
empty = "현재 계정이 접근할 수 있는 RP를 확인합니다."
none = "표시할 연동 앱이 없습니다."
[msg.dev.dashboard.recent_changes]
description = "변경 또는 삭제된 애플리케이션을 대시보드에서 추이를 확인합니다."
empty = "최근 변경 로그가 아직 없습니다."
[msg.dev.dashboard.notice]
consent_audit = "Consent 회수는 감사 로그와 연계"
dev_scope = "RP 정책은 dev scope에서만 적용"
@@ -2772,6 +2776,20 @@ title = "빠른 이동"
[ui.dev.dashboard.recent]
title = "내 애플리케이션"
[ui.dev.dashboard.recent_changes]
aria = "최근 변경된 앱 현황"
deleted_group = "삭제된 RP"
period = "최근 변경 집계 단위"
series = "변경 {{changes}} / 작업자 {{actors}}"
title = "최근 변경된 앱"
y_axis = "Y축: 변경 수"
[ui.dev.dashboard.recent_changes.summary]
changed_clients = "변경된 앱 수"
deleted_clients = "삭제된 RP 수"
latest_change = "마지막 변경일"
total_changes = "최근 변경 건수"
[ui.dev.dashboard.stack]
notes = "Setup notes"
subtitle = "Devfront baseline"

View File

@@ -947,6 +947,10 @@ description = ""
empty = ""
none = ""
[msg.dev.dashboard.recent_changes]
description = ""
empty = ""
[msg.dev.dashboard.notice]
consent_audit = ""
dev_scope = ""
@@ -2653,6 +2657,20 @@ title = ""
[ui.dev.dashboard.recent]
title = ""
[ui.dev.dashboard.recent_changes]
aria = ""
deleted_group = ""
period = ""
series = ""
title = ""
y_axis = ""
[ui.dev.dashboard.recent_changes.summary]
changed_clients = ""
deleted_clients = ""
latest_change = ""
total_changes = ""
[ui.dev.dashboard.stack]
notes = ""
subtitle = ""