forked from baron/baron-sso
조직도 기능 추가
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { OrgChartUploadModal } from "../components/OrgChartUploadModal";
|
||||
import { Checkbox } from "../../../components/ui/checkbox";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -25,13 +25,17 @@ import {
|
||||
import {
|
||||
type TenantSummary,
|
||||
deleteTenant,
|
||||
deleteTenantsBulk,
|
||||
fetchMe,
|
||||
fetchTenants,
|
||||
} from "../../../lib/adminApi";
|
||||
import { t } from "../../../lib/i18n";
|
||||
import { OrgChartUploadModal } from "../components/OrgChartUploadModal";
|
||||
|
||||
function TenantListPage() {
|
||||
const navigate = useNavigate();
|
||||
const [selectedIds, setSelectedIds] = React.useState<string[]>([]);
|
||||
|
||||
const { data: profile } = useQuery({
|
||||
queryKey: ["me"],
|
||||
queryFn: fetchMe,
|
||||
@@ -41,7 +45,6 @@ function TenantListPage() {
|
||||
React.useEffect(() => {
|
||||
if (profile?.role === "tenant_admin") {
|
||||
const manageableCount = profile.manageableTenants?.length ?? 0;
|
||||
// If only 1 in array, OR array is empty but we have a primary tenantId
|
||||
if (
|
||||
(manageableCount === 1 || manageableCount === 0) &&
|
||||
profile.tenantId
|
||||
@@ -67,6 +70,14 @@ function TenantListPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const deleteBulkMutation = useMutation({
|
||||
mutationFn: (ids: string[]) => deleteTenantsBulk(ids),
|
||||
onSuccess: () => {
|
||||
setSelectedIds([]);
|
||||
query.refetch();
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
profile &&
|
||||
profile.role !== "super_admin" &&
|
||||
@@ -84,7 +95,6 @@ function TenantListPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// While redirecting (only if exactly one manageable tenant)
|
||||
if (
|
||||
profile?.role === "tenant_admin" &&
|
||||
(profile.manageableTenants?.length ?? 0) <= 1
|
||||
@@ -101,8 +111,40 @@ function TenantListPage() {
|
||||
|
||||
const tenants = query.data?.items ?? [];
|
||||
|
||||
// [New] Find a primary COMPANY_GROUP tenant to act as the root for matrix org charts
|
||||
const rootTenant = tenants.find((t) => t.type === "COMPANY_GROUP") || tenants[0];
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedIds(tenants.map((t) => t.id));
|
||||
} else {
|
||||
setSelectedIds([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (id: string, checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedIds((prev) => [...prev, id]);
|
||||
} else {
|
||||
setSelectedIds((prev) => prev.filter((i) => i !== id));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteBulk = () => {
|
||||
if (selectedIds.length === 0) return;
|
||||
if (
|
||||
!window.confirm(
|
||||
t(
|
||||
"msg.admin.tenants.delete_bulk_confirm",
|
||||
"선택한 {{count}}개 테넌트를 삭제할까요?",
|
||||
{ count: selectedIds.length },
|
||||
),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
deleteBulkMutation.mutate(selectedIds);
|
||||
};
|
||||
|
||||
const rootTenant =
|
||||
tenants.find((t) => t.type === "COMPANY_GROUP") || tenants[0];
|
||||
|
||||
const handleDelete = (tenantId: string, tenantName: string) => {
|
||||
if (
|
||||
@@ -134,16 +176,34 @@ function TenantListPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* [New] Add Upload Modal to global list page, visible to Super Admin */}
|
||||
<RoleGuard roles={["super_admin"]}>
|
||||
{rootTenant && (
|
||||
<OrgChartUploadModal
|
||||
tenantId={rootTenant.id}
|
||||
onSuccess={() => query.refetch()}
|
||||
/>
|
||||
{selectedIds.length > 0 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteBulk}
|
||||
disabled={deleteBulkMutation.isPending}
|
||||
className="gap-2"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
{t("ui.admin.tenants.delete_selected", "선택 삭제")} (
|
||||
{selectedIds.length})
|
||||
</Button>
|
||||
)}
|
||||
</RoleGuard>
|
||||
|
||||
<RoleGuard roles={["super_admin"]}>
|
||||
<OrgChartUploadModal
|
||||
tenantId={rootTenant?.id || "root"}
|
||||
onSuccess={() => query.refetch()}
|
||||
/>
|
||||
</RoleGuard>
|
||||
|
||||
<Button asChild variant="outline" className="gap-2">
|
||||
<Link to="/tenants/org-chart">
|
||||
{t("ui.admin.tenants.view_org_chart", "전체 조직도 보기")}
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => query.refetch()}
|
||||
@@ -191,6 +251,17 @@ function TenantListPage() {
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10 bg-secondary shadow-sm">
|
||||
<TableRow>
|
||||
<TableHead className="w-[40px]">
|
||||
<Checkbox
|
||||
checked={
|
||||
tenants.length > 0 &&
|
||||
selectedIds.length === tenants.length
|
||||
}
|
||||
onCheckedChange={(checked) =>
|
||||
handleSelectAll(!!checked)
|
||||
}
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.admin.tenants.table.name", "NAME")}
|
||||
</TableHead>
|
||||
@@ -217,7 +288,7 @@ function TenantListPage() {
|
||||
<TableBody>
|
||||
{query.isLoading && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7}>
|
||||
<TableCell colSpan={8}>
|
||||
{t("msg.common.loading", "로딩 중...")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -225,7 +296,7 @@ function TenantListPage() {
|
||||
{!query.isLoading && tenants.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={7}
|
||||
colSpan={8}
|
||||
className="text-center py-8 text-muted-foreground"
|
||||
>
|
||||
{t(
|
||||
@@ -237,6 +308,14 @@ function TenantListPage() {
|
||||
)}
|
||||
{tenants.map((tenant) => (
|
||||
<TableRow key={tenant.id}>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selectedIds.includes(tenant.id)}
|
||||
onCheckedChange={(checked) =>
|
||||
handleSelect(tenant.id, !!checked)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-semibold">
|
||||
{tenant.name}
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user