cdc55dfd48
SUPER_ADMIN cross-tenant 모드에서 회사당 cap 200 에 걸리거나 한 회사 조회 실패 시 화면 상단에 안내 배너 노출. 아무 메타 없으면 자리 안 잡음. 신규 - components/common/CrossTenantBanner.tsx — amber(truncated) + red(failed) v5 토큰 (surface-solid + glow-sm) 기반 솔리드 배너. blur 안 씀 API 클라이언트 4개에 cross_tenant_meta 노출 - lib/api/user.ts — userAPI.getList 응답에 cross_tenant_meta 추가 - lib/api/role.ts — roleAPI.getList 동일 - lib/api/batch.ts — BatchAPI.getBatchConfigs 동일 - lib/api/multilang.ts — getLangKeys 동일 (i18nList 페이지는 아직 직접 호출 패턴이라 자동 적용 X — 후속에서 페이지를 getLangKeys 로 통일하면 동작) 페이지 마운트 (3개) - userMng/userMngList — useUserManagement hook 에 crossTenantMeta state 추가 - userMng/rolesList — loadRoleGroups 에서 메타 set - automaticMng/batchmngList — loadBatchConfigs 에서 메타 set - systemMng/i18nList — 스킵 (cross-tenant aggregation 미적용 상태, 별도 작업) 설계서 §11 검증 (직전 §11.2 부분 실패 시뮬) 결과: failed 배너가 header X-CrossTenant-Failed 와 동일 정보로 화면에 노출됨. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
3.1 KiB
TypeScript
72 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { AlertTriangle, Info } from "lucide-react";
|
|
|
|
/**
|
|
* SUPER_ADMIN cross-tenant 응답에 truncated/failed 정보가 있을 때 보여주는 안내 배너.
|
|
*
|
|
* 동작:
|
|
* - meta 가 없거나 truncated/failed 둘 다 0 이면 아무것도 렌더링 안 함 (자리 안 잡음)
|
|
* - truncated_company_codes 가 있으면 amber 톤 안내 ("N개 회사가 cap 에 걸려 일부만 표시")
|
|
* - failed_company_codes 가 있으면 red 톤 경고 ("N개 회사 조회 실패")
|
|
* - 두 가지 동시에 있을 수 있어 각각 독립 배너로 노출
|
|
*
|
|
* v5 디자인 토큰만 사용 — primary-color glow + amber/red 액센트, 솔리드 배경 (blur 금지).
|
|
*
|
|
* @see notes/hjjeong/2026-04-28-cross-tenant-execution-log.md §3.5
|
|
*/
|
|
export function CrossTenantBanner({ meta }: { meta?: Record<string, any> | null }) {
|
|
if (!meta) return null;
|
|
|
|
const truncated: string[] = Array.isArray(meta.truncated_company_codes) ? meta.truncated_company_codes : [];
|
|
const failed: string[] = Array.isArray(meta.failed_company_codes) ? meta.failed_company_codes : [];
|
|
const cap: number | undefined = meta.per_company_limit;
|
|
|
|
if (truncated.length === 0 && failed.length === 0) return null;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1.5 mb-2">
|
|
{truncated.length > 0 && (
|
|
<div
|
|
className="flex items-start gap-2 px-3 py-2 rounded-[10px] border text-[0.8125rem]"
|
|
style={{
|
|
background: "var(--v5-surface-solid)",
|
|
borderColor: "rgba(var(--v5-amber-rgb),0.4)",
|
|
boxShadow: "0 0 16px rgba(var(--v5-amber-rgb),0.15)",
|
|
color: "var(--v5-text)",
|
|
}}
|
|
>
|
|
<Info size={14} style={{ color: "rgb(var(--v5-amber-rgb))", marginTop: 2, flexShrink: 0 }} />
|
|
<div className="flex-1 leading-snug">
|
|
<span style={{ fontWeight: 600 }}>
|
|
{truncated.length}개 회사가 회사당 {cap ?? 200}건 cap 에 걸려 일부만 표시 중
|
|
</span>
|
|
<span style={{ color: "var(--v5-text-sec)", marginLeft: 6 }}>
|
|
({truncated.join(", ")}) — 더 보려면 검색을 좁히거나 회사 도메인으로 전환
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{failed.length > 0 && (
|
|
<div
|
|
className="flex items-start gap-2 px-3 py-2 rounded-[10px] border text-[0.8125rem]"
|
|
style={{
|
|
background: "var(--v5-surface-solid)",
|
|
borderColor: "rgba(var(--v5-red-rgb),0.4)",
|
|
boxShadow: "var(--v5-glow-danger)",
|
|
color: "var(--v5-text)",
|
|
}}
|
|
>
|
|
<AlertTriangle size={14} style={{ color: "rgb(var(--v5-red-rgb))", marginTop: 2, flexShrink: 0 }} />
|
|
<div className="flex-1 leading-snug">
|
|
<span style={{ fontWeight: 600 }}>{failed.length}개 회사 조회 실패</span>
|
|
<span style={{ color: "var(--v5-text-sec)", marginLeft: 6 }}>
|
|
({failed.join(", ")}) — 다른 회사 결과는 정상 표시. 백엔드 로그 확인 필요
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|