1
0
forked from baron/baron-sso

feat(adminfront): add Data Management menu to User tab

This commit introduces a 'Data Management' dropdown menu to the User list page, consolidating user CSV import, template download, and export actions. It aligns the UI with the existing Tenant list page.
This commit is contained in:
2026-05-20 13:25:21 +09:00
parent 0f61425bbf
commit 53dacda5d5
2 changed files with 74 additions and 27 deletions

View File

@@ -7,7 +7,9 @@ import {
ChevronDown,
ChevronLeft,
ChevronRight,
Download,
FileDown,
FileSpreadsheet,
LayoutDashboard,
Plus,
RefreshCw,
@@ -15,6 +17,7 @@ import {
Settings2,
ShieldCheck,
Trash2,
Upload,
} from "lucide-react";
import * as React from "react";
import { Link, useNavigate } from "react-router-dom";
@@ -90,7 +93,10 @@ import {
} from "../../lib/adminApi";
import { t } from "../../lib/i18n";
import { isSuperAdminRole } from "../../lib/roles";
import { UserBulkUploadModal } from "./components/UserBulkUploadModal";
import {
UserBulkUploadModal,
downloadUserTemplate,
} from "./components/UserBulkUploadModal";
import {
normalizeUserStatusValue,
type UserStatusValue,
@@ -485,27 +491,53 @@ function UserListPage() {
<RefreshCw size={16} />
{t("ui.common.refresh", "새로고침")}
</Button>
<Button
variant="outline"
onClick={() => handleExport(false)}
className="gap-2"
disabled={exportMutation.isPending}
data-testid="user-export-without-ids-btn"
>
<FileDown size={16} />
{t("ui.common.export_without_ids", "UUID 제외 내보내기")}
</Button>
<Button
variant="outline"
onClick={() => handleExport(true)}
className="gap-2"
disabled={exportMutation.isPending}
data-testid="user-export-with-ids-btn"
>
<FileDown size={16} />
{t("ui.common.export_with_ids", "UUID 포함")}
</Button>
<UserBulkUploadModal onSuccess={() => query.refetch()} />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
data-testid="user-data-mgmt-btn"
className="gap-2 h-9"
>
<LayoutDashboard size={16} />
{t("ui.admin.users.data_mgmt", "데이터 관리")}
<ChevronDown size={14} className="opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuItem
onClick={downloadUserTemplate}
data-testid="user-template-menu-item"
className="cursor-pointer"
>
<FileSpreadsheet size={16} className="mr-2 opacity-50" />
{t("ui.admin.users.csv_template", "템플릿 다운로드")}
</DropdownMenuItem>
<DropdownMenuSeparator />
<UserBulkUploadModal
variant="dropdown"
onSuccess={() => query.refetch()}
/>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => handleExport(false)}
disabled={exportMutation.isPending}
data-testid="user-export-menu-item"
className="cursor-pointer"
>
<FileDown size={16} className="mr-2 opacity-50" />
{t("ui.common.export_without_ids", "UUID 제외 내보내기")}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleExport(true)}
disabled={exportMutation.isPending}
data-testid="user-export-with-ids-menu-item"
className="cursor-pointer"
>
<FileDown size={16} className="mr-2 opacity-50" />
{t("ui.common.export_with_ids", "UUID 포함 내보내기")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="icon" className="h-9 w-9">

View File

@@ -18,6 +18,7 @@ import {
DialogTitle,
DialogTrigger,
} from "../../../components/ui/dialog";
import { DropdownMenuItem } from "../../../components/ui/dropdown-menu";
import { ScrollArea } from "../../../components/ui/scroll-area";
import {
type BulkUserItem,
@@ -121,6 +122,22 @@ function hanmacEmailStatusClass(preview?: HanmacImportEmailPreview) {
return "text-muted-foreground";
}
export const downloadUserTemplate = () => {
const headers =
"email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1";
const example =
"user1@example.com,홍길동,010-1234-5678,user,tenant-slug,개발팀,수석,팀장,프론트엔드,EMP001,second-tenant,센터,책임,,Architecture,EMP002";
const blob = new Blob([`${headers}\n${example}`], {
type: "text/csv;charset=utf-8;",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "user_bulk_template.csv";
a.click();
URL.revokeObjectURL(url);
};
export function UserBulkUploadModal({
onSuccess,
variant = "button",
@@ -334,16 +351,14 @@ export function UserBulkUploadModal({
const triggerNode =
variant === "dropdown" ? (
<div
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
role="menuitem"
tabIndex={-1}
<DropdownMenuItem
onClick={() => setOpen(true)}
className="cursor-pointer"
{...triggerProps}
>
<Upload size={16} className="mr-2 opacity-50" />
{t("ui.admin.users.list.bulk_import", "일괄 등록 (CSV)")}
</div>
</DropdownMenuItem>
) : (
<DialogTrigger asChild>
<Button variant="outline" className="gap-2" {...triggerProps}>