forked from baron/baron-sso
feat: implement native drag and drop for organization hierarchy and add hover info tooltips
This commit is contained in:
@@ -580,13 +580,15 @@ const TenantTreeRow: React.FC<{
|
||||
level: number;
|
||||
isRoot: boolean;
|
||||
onRemove: (id: string, name: string) => void;
|
||||
onMove: (id: string, newParentId: string) => void;
|
||||
isUpdating: boolean;
|
||||
searchTerm?: string;
|
||||
}> = ({ node, level, isRoot, onRemove, isUpdating, searchTerm }) => {
|
||||
}> = ({ node, level, isRoot, onRemove, onMove, isUpdating, searchTerm }) => {
|
||||
const navigate = useNavigate();
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const [isUserAddOpen, setIsUserAddOpen] = useState(false);
|
||||
const [isMemberListOpen, setIsMemberListOpen] = useState(false);
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
|
||||
// Auto expand if search matches children
|
||||
@@ -613,10 +615,44 @@ const TenantTreeRow: React.FC<{
|
||||
|
||||
const TypeIcon = getTenantIcon(node.type);
|
||||
|
||||
// DnD Handlers
|
||||
const handleDragStart = (e: React.DragEvent) => {
|
||||
if (isRoot) return;
|
||||
e.dataTransfer.setData("nodeId", node.id);
|
||||
e.dataTransfer.setData("nodeName", node.name);
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (isUpdating) return;
|
||||
setIsDragOver(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = () => {
|
||||
setIsDragOver(false);
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
const draggedId = e.dataTransfer.getData("nodeId");
|
||||
if (!draggedId || draggedId === node.id) return;
|
||||
onMove(draggedId, node.id);
|
||||
};
|
||||
|
||||
const hoverTitle = `${node.name} (${node.type})\n${t("ui.admin.tenants.members.direct", "소속 멤버")}: ${node.memberCount || 0}\n${t("ui.admin.tenants.members.total", "총 멤버")}: ${node.recursiveMemberCount || 0}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow
|
||||
className={`group hover:bg-muted/30 transition-colors ${isRoot ? "bg-primary/5 font-bold" : ""} ${isMatching ? "bg-primary/10 ring-1 ring-primary/20" : ""}`}
|
||||
draggable={!isRoot}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
className={`group hover:bg-muted/30 transition-colors ${isRoot ? "bg-primary/5 font-bold" : ""} ${isMatching ? "bg-primary/10 ring-1 ring-primary/20" : ""} ${isDragOver ? "bg-primary/20 border-2 border-dashed border-primary" : ""}`}
|
||||
title={hoverTitle}
|
||||
>
|
||||
<TableCell style={{ paddingLeft: `${1 + level * 2}rem` }}>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -676,6 +712,7 @@ const TenantTreeRow: React.FC<{
|
||||
type="button"
|
||||
className="flex items-center gap-2 cursor-pointer hover:bg-muted p-1.5 rounded-md transition-all group/members w-full text-left"
|
||||
onClick={() => setIsMemberListOpen(true)}
|
||||
title={t("msg.admin.org.hover_member_info", "클릭하여 멤버 상세 조회")}
|
||||
>
|
||||
<div className="bg-primary/10 p-1.5 rounded text-primary">
|
||||
<Users size={16} />
|
||||
@@ -772,6 +809,7 @@ const TenantTreeRow: React.FC<{
|
||||
level={level + 1}
|
||||
isRoot={false}
|
||||
onRemove={onRemove}
|
||||
onMove={onMove}
|
||||
isUpdating={isUpdating}
|
||||
searchTerm={searchTerm}
|
||||
/>
|
||||
@@ -841,6 +879,10 @@ function TenantUserGroupsTab() {
|
||||
|
||||
const handleAdd = (id: string) =>
|
||||
updateParentMutation.mutate({ id, parentId: tenantId });
|
||||
const handleMove = (id: string, newParentId: string) => {
|
||||
if (id === newParentId) return;
|
||||
updateParentMutation.mutate({ id, parentId: newParentId });
|
||||
};
|
||||
const handleRemove = (id: string, name: string) => {
|
||||
if (
|
||||
window.confirm(
|
||||
@@ -1084,6 +1126,7 @@ function TenantUserGroupsTab() {
|
||||
level={0}
|
||||
isRoot={true}
|
||||
onRemove={handleRemove}
|
||||
onMove={handleMove}
|
||||
isUpdating={updateParentMutation.isPending}
|
||||
searchTerm={treeSearchTerm}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user