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,
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Download,
|
||||||
FileDown,
|
FileDown,
|
||||||
|
FileSpreadsheet,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Plus,
|
Plus,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
@@ -15,6 +17,7 @@ import {
|
|||||||
Settings2,
|
Settings2,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
Upload,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
@@ -90,7 +93,10 @@ import {
|
|||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { isSuperAdminRole } from "../../lib/roles";
|
import { isSuperAdminRole } from "../../lib/roles";
|
||||||
import { UserBulkUploadModal } from "./components/UserBulkUploadModal";
|
import {
|
||||||
|
UserBulkUploadModal,
|
||||||
|
downloadUserTemplate,
|
||||||
|
} from "./components/UserBulkUploadModal";
|
||||||
import {
|
import {
|
||||||
normalizeUserStatusValue,
|
normalizeUserStatusValue,
|
||||||
type UserStatusValue,
|
type UserStatusValue,
|
||||||
@@ -485,27 +491,53 @@ function UserListPage() {
|
|||||||
<RefreshCw size={16} />
|
<RefreshCw size={16} />
|
||||||
{t("ui.common.refresh", "새로고침")}
|
{t("ui.common.refresh", "새로고침")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<DropdownMenu>
|
||||||
variant="outline"
|
<DropdownMenuTrigger asChild>
|
||||||
onClick={() => handleExport(false)}
|
<Button
|
||||||
className="gap-2"
|
variant="outline"
|
||||||
disabled={exportMutation.isPending}
|
data-testid="user-data-mgmt-btn"
|
||||||
data-testid="user-export-without-ids-btn"
|
className="gap-2 h-9"
|
||||||
>
|
>
|
||||||
<FileDown size={16} />
|
<LayoutDashboard size={16} />
|
||||||
{t("ui.common.export_without_ids", "UUID 제외 내보내기")}
|
{t("ui.admin.users.data_mgmt", "데이터 관리")}
|
||||||
</Button>
|
<ChevronDown size={14} className="opacity-50" />
|
||||||
<Button
|
</Button>
|
||||||
variant="outline"
|
</DropdownMenuTrigger>
|
||||||
onClick={() => handleExport(true)}
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
className="gap-2"
|
<DropdownMenuItem
|
||||||
disabled={exportMutation.isPending}
|
onClick={downloadUserTemplate}
|
||||||
data-testid="user-export-with-ids-btn"
|
data-testid="user-template-menu-item"
|
||||||
>
|
className="cursor-pointer"
|
||||||
<FileDown size={16} />
|
>
|
||||||
{t("ui.common.export_with_ids", "UUID 포함")}
|
<FileSpreadsheet size={16} className="mr-2 opacity-50" />
|
||||||
</Button>
|
{t("ui.admin.users.csv_template", "템플릿 다운로드")}
|
||||||
<UserBulkUploadModal onSuccess={() => query.refetch()} />
|
</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>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" size="icon" className="h-9 w-9">
|
<Button variant="outline" size="icon" className="h-9 w-9">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
|
import { DropdownMenuItem } from "../../../components/ui/dropdown-menu";
|
||||||
import { ScrollArea } from "../../../components/ui/scroll-area";
|
import { ScrollArea } from "../../../components/ui/scroll-area";
|
||||||
import {
|
import {
|
||||||
type BulkUserItem,
|
type BulkUserItem,
|
||||||
@@ -121,6 +122,22 @@ function hanmacEmailStatusClass(preview?: HanmacImportEmailPreview) {
|
|||||||
return "text-muted-foreground";
|
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({
|
export function UserBulkUploadModal({
|
||||||
onSuccess,
|
onSuccess,
|
||||||
variant = "button",
|
variant = "button",
|
||||||
@@ -334,16 +351,14 @@ export function UserBulkUploadModal({
|
|||||||
|
|
||||||
const triggerNode =
|
const triggerNode =
|
||||||
variant === "dropdown" ? (
|
variant === "dropdown" ? (
|
||||||
<div
|
<DropdownMenuItem
|
||||||
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}
|
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
|
className="cursor-pointer"
|
||||||
{...triggerProps}
|
{...triggerProps}
|
||||||
>
|
>
|
||||||
<Upload size={16} className="mr-2 opacity-50" />
|
<Upload size={16} className="mr-2 opacity-50" />
|
||||||
{t("ui.admin.users.list.bulk_import", "일괄 등록 (CSV)")}
|
{t("ui.admin.users.list.bulk_import", "일괄 등록 (CSV)")}
|
||||||
</div>
|
</DropdownMenuItem>
|
||||||
) : (
|
) : (
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" className="gap-2" {...triggerProps}>
|
<Button variant="outline" className="gap-2" {...triggerProps}>
|
||||||
|
|||||||
Reference in New Issue
Block a user