forked from baron/baron-sso
린트 적용
This commit is contained in:
@@ -14,12 +14,12 @@ import {
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
import { ScrollArea } from "../../../components/ui/scroll-area";
|
||||
import {
|
||||
bulkUpdateUsers,
|
||||
fetchGroups,
|
||||
fetchTenants,
|
||||
import {
|
||||
type GroupSummary,
|
||||
type TenantSummary,
|
||||
type GroupSummary
|
||||
bulkUpdateUsers,
|
||||
fetchGroups,
|
||||
fetchTenants,
|
||||
} from "../../../lib/adminApi";
|
||||
import { t } from "../../../lib/i18n";
|
||||
|
||||
@@ -28,12 +28,16 @@ interface UserBulkMoveGroupModalProps {
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroupModalProps) {
|
||||
export function UserBulkMoveGroupModal({
|
||||
userIds,
|
||||
onSuccess,
|
||||
}: UserBulkMoveGroupModalProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [selectedTenantSlug, setSelectedTenantSlug] = React.useState<string>("");
|
||||
const [selectedTenantSlug, setSelectedTenantSlug] =
|
||||
React.useState<string>("");
|
||||
const [selectedGroupName, setSelectedGroupName] = React.useState<string>("");
|
||||
const [searchTerm, setSearchTerm] = React.useState("");
|
||||
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: tenantsData } = useQuery({
|
||||
@@ -43,9 +47,10 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
});
|
||||
const tenants = tenantsData?.items ?? [];
|
||||
|
||||
const selectedTenantId = React.useMemo(() =>
|
||||
tenants.find(t => t.slug === selectedTenantSlug)?.id ?? "",
|
||||
[tenants, selectedTenantSlug]);
|
||||
const selectedTenantId = React.useMemo(
|
||||
() => tenants.find((t) => t.slug === selectedTenantSlug)?.id ?? "",
|
||||
[tenants, selectedTenantSlug],
|
||||
);
|
||||
|
||||
const { data: groups, isLoading: isGroupsLoading } = useQuery({
|
||||
queryKey: ["tenant-groups", selectedTenantId],
|
||||
@@ -56,7 +61,12 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
const mutation = useMutation({
|
||||
mutationFn: bulkUpdateUsers,
|
||||
onSuccess: () => {
|
||||
toast.success(t("msg.admin.users.bulk.move_success", "사용자들의 부서가 이동되었습니다."));
|
||||
toast.success(
|
||||
t(
|
||||
"msg.admin.users.bulk.move_success",
|
||||
"사용자들의 부서가 이동되었습니다.",
|
||||
),
|
||||
);
|
||||
setOpen(false);
|
||||
onSuccess?.();
|
||||
},
|
||||
@@ -79,27 +89,41 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
const filteredGroups = React.useMemo(() => {
|
||||
if (!groups) return [];
|
||||
if (!searchTerm) return groups;
|
||||
return groups.filter(g => g.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
return groups.filter((g) =>
|
||||
g.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
}, [groups, searchTerm]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="text-background hover:bg-background/10 h-8">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-background hover:bg-background/10 h-8"
|
||||
>
|
||||
{t("ui.admin.users.bulk.move_group", "부서 이동")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("ui.admin.users.bulk.move_title", "사용자 부서 이동")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{t("ui.admin.users.bulk.move_title", "사용자 부서 이동")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("msg.admin.users.bulk.move_description", "선택한 {{count}}명의 사용자를 이동할 테넌트와 부서를 선택하세요.", { count: userIds.length })}
|
||||
{t(
|
||||
"msg.admin.users.bulk.move_description",
|
||||
"선택한 {{count}}명의 사용자를 이동할 테넌트와 부서를 선택하세요.",
|
||||
{ count: userIds.length },
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">{t("ui.admin.users.create.form.tenant", "테넌트 선택")}</label>
|
||||
<label className="text-sm font-medium">
|
||||
{t("ui.admin.users.create.form.tenant", "테넌트 선택")}
|
||||
</label>
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
value={selectedTenantSlug}
|
||||
@@ -110,14 +134,18 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
>
|
||||
<option value="">{t("ui.common.select", "선택하세요...")}</option>
|
||||
{tenants.map((t) => (
|
||||
<option key={t.id} value={t.slug}>{t.name} ({t.slug})</option>
|
||||
<option key={t.id} value={t.slug}>
|
||||
{t.name} ({t.slug})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{selectedTenantSlug && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">{t("ui.admin.users.bulk.select_group", "부서 선택")}</label>
|
||||
<label className="text-sm font-medium">
|
||||
{t("ui.admin.users.bulk.select_group", "부서 선택")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
@@ -133,14 +161,18 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
type="button"
|
||||
onClick={() => setSelectedGroupName("")}
|
||||
className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition ${
|
||||
selectedGroupName === "" ? "bg-primary text-primary-foreground" : "hover:bg-muted"
|
||||
selectedGroupName === ""
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "hover:bg-muted"
|
||||
}`}
|
||||
>
|
||||
<FolderTree size={14} />
|
||||
{t("ui.admin.users.bulk.no_department", "(부서 없음)")}
|
||||
</button>
|
||||
{isGroupsLoading ? (
|
||||
<div className="flex justify-center py-4"><Loader2 className="animate-spin text-muted-foreground" /></div>
|
||||
<div className="flex justify-center py-4">
|
||||
<Loader2 className="animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
filteredGroups.map((group) => (
|
||||
<button
|
||||
@@ -148,7 +180,9 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
type="button"
|
||||
onClick={() => setSelectedGroupName(group.name)}
|
||||
className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition ${
|
||||
selectedGroupName === group.name ? "bg-primary text-primary-foreground" : "hover:bg-muted"
|
||||
selectedGroupName === group.name
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "hover:bg-muted"
|
||||
}`}
|
||||
>
|
||||
<FolderTree size={14} />
|
||||
@@ -166,11 +200,13 @@ export function UserBulkMoveGroupModal({ userIds, onSuccess }: UserBulkMoveGroup
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
{t("ui.common.cancel", "취소")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleMove}
|
||||
<Button
|
||||
onClick={handleMove}
|
||||
disabled={!selectedTenantSlug || mutation.isPending}
|
||||
>
|
||||
{mutation.isPending && <Loader2 size={16} className="mr-2 animate-spin" />}
|
||||
{mutation.isPending && (
|
||||
<Loader2 size={16} className="mr-2 animate-spin" />
|
||||
)}
|
||||
{t("ui.admin.users.bulk.do_move", "이동 실행")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user