1
0
forked from baron/baron-sso
Files
baron-sso/adminfront/src/features/tenants/routes/TenantDetailPage.tsx
chan 349cdf5fcd feat(org): 조직도 렌더링 및 동기화 로직 대폭 개선, 역할(Role) 강제화, 탭 정리
- 조직도 렌더링 시 너비 동적 계산 및 스크롤 문제 해결
- 하위 조직(Leaf)을 부모 박스 내부에 임베딩하여 2열로 깔끔하게 표시되도록 조직도 UI 전면 개편
- 사용자 생성/수정 및 CSV 업로드 시 직급(Position)과 직무(JobTitle)가 정상적으로 Kratos 및 로컬 DB에 동기화되도록 백엔드 API 수정
- CSV 조직도 업로드 시 계층 구분을 '/' 대신 ' > '로 변경하여 이름에 '/'가 포함된 부서(예: 평면/셀)가 분리되지 않도록 보호
- 잘못 입력된 과거 직책 데이터(팀장, 그룹장 등)를 'user' 권한으로 일괄 초기화하고, 이후 'role' 필드에 시스템 권한(user, tenant_admin, super_admin) 외의 값이 들어오지 않도록 백엔드 정규화 로직 강화
- 사용자 목록 페이지의 페이지네이션 제한을 50명에서 1000명으로 상향 조정
- 테넌트 목록 페이지에 이름/슬러그 기반 검색 기능 추가
- 관리자 UI 전반에서 불필요한 배지(Admin only, System 등) 제거 및 테넌트 상세 페이지의 미사용 '외부 연동' 탭 삭제
2026-04-10 13:48:12 +09:00

104 lines
3.5 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { ArrowLeft } from "lucide-react";
import { Link, Outlet, useLocation, useParams } from "react-router-dom";
import { Badge } from "../../../components/ui/badge";
import { fetchMe, fetchTenant } from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
function TenantDetailPage() {
const params = useParams<{ tenantId: string }>();
const tenantId = params.tenantId ?? "";
const location = useLocation();
const tenantQuery = useQuery({
queryKey: ["tenant", tenantId],
queryFn: () => fetchTenant(tenantId),
enabled: tenantId.length > 0,
});
const { data: profile } = useQuery({
queryKey: ["me"],
queryFn: fetchMe,
});
const canAccessSchema =
profile?.role === "super_admin" || profile?.role === "tenant_admin";
const isPermissionsTab = location.pathname.includes("/permissions");
const isOrganizationTab = location.pathname.includes("/organization");
return (
<div className="space-y-8">
<header className="flex flex-wrap items-start justify-between gap-4">
<div className="space-y-2">
<h2 className="text-3xl font-semibold">
{tenantQuery.data?.name ??
t("ui.admin.tenants.detail.loading", "불러오는 중...")}
</h2>
<p className="text-sm text-[var(--color-muted)]">
{t(
"ui.admin.tenants.detail.header_subtitle",
"테넌트 정보를 수정하거나 연동 설정을 관리합니다.",
)}
</p>
</div>
</header>
{/* Tabs */}
<div className="flex border-b border-border">
<Link
to={`/tenants/${tenantId}`}
className={`px-6 py-3 text-sm font-medium transition-colors relative ${
!isPermissionsTab &&
!location.pathname.includes("/schema") &&
!isOrganizationTab
? "text-primary border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground"
}`}
>
{t("ui.admin.tenants.detail.tab_profile", "프로필")}
</Link>
<Link
to={`/tenants/${tenantId}/permissions`}
className={`px-6 py-3 text-sm font-medium transition-colors relative ${
isPermissionsTab
? "text-primary border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground"
}`}
>
{t("ui.admin.tenants.detail.tab_permissions", "권한")}
</Link>
<Link
to={`/tenants/${tenantId}/organization`}
className={`px-6 py-3 text-sm font-medium transition-colors relative ${
isOrganizationTab
? "text-primary border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground"
}`}
>
{t("ui.admin.tenants.detail.tab_organization", "조직 관리")}
</Link>
{canAccessSchema && (
<Link
to={`/tenants/${tenantId}/schema`}
className={`px-6 py-3 text-sm font-medium transition-colors relative ${
location.pathname.includes("/schema")
? "text-primary border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground"
}`}
>
{t("ui.admin.tenants.detail.tab_schema", "사용자 스키마")}
</Link>
)}
</div>
{/* Outlet for nested routes */}
<div className="animate-in fade-in duration-500">
<Outlet />
</div>
</div>
);
}
export default TenantDetailPage;