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:
@@ -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">
|
||||
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user