- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선 - FormField, TabChip, Select 등 공통 컴포넌트 개선 - 가격배분 edit 페이지 제거 및 상세 페이지 통합 - 체크리스트, 근태, 급여, 권한 관리 등 폼 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import { memo } from 'react';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { Button } from '@/components/ui/button';
|
|
import { ChevronRight, ChevronDown, Plus, SquarePen, Trash2 } from 'lucide-react';
|
|
import type { DepartmentTreeItemProps } from './types';
|
|
|
|
/**
|
|
* 트리 행 (재귀 렌더링)
|
|
* - 무제한 깊이 지원
|
|
* - depth에 따른 동적 들여쓰기
|
|
*/
|
|
export const DepartmentTreeItem = memo(function DepartmentTreeItem({
|
|
department,
|
|
depth,
|
|
expandedIds,
|
|
selectedIds,
|
|
onToggleExpand,
|
|
onToggleSelect,
|
|
onAdd,
|
|
onEdit,
|
|
onDelete
|
|
}: DepartmentTreeItemProps) {
|
|
const hasChildren = department.children && department.children.length > 0;
|
|
const isExpanded = expandedIds.has(department.id);
|
|
const isSelected = selectedIds.has(department.id);
|
|
|
|
// 들여쓰기 계산 (depth * 24px)
|
|
const paddingLeft = depth * 24;
|
|
|
|
return (
|
|
<>
|
|
{/* 현재 행 */}
|
|
<div
|
|
className="group px-4 py-3 hover:bg-muted/50 transition-colors"
|
|
style={{ paddingLeft: `${paddingLeft + 16}px` }}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{/* 펼침/접힘 버튼 */}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className={`h-6 w-6 p-0 shrink-0 ${!hasChildren ? 'invisible' : ''}`}
|
|
onClick={() => onToggleExpand(department.id)}
|
|
>
|
|
{isExpanded ? (
|
|
<ChevronDown className="h-4 w-4" />
|
|
) : (
|
|
<ChevronRight className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
|
|
{/* 체크박스 */}
|
|
<Checkbox
|
|
checked={isSelected}
|
|
onCheckedChange={() => onToggleSelect(department.id)}
|
|
aria-label={`${department.name} 선택`}
|
|
className="shrink-0"
|
|
/>
|
|
|
|
{/* 부서명 */}
|
|
<span className="break-words">{department.name}</span>
|
|
</div>
|
|
|
|
{/* 작업 버튼 (선택 시 부서명 아래에 표시, 데스크톱: 호버 시에도 표시) */}
|
|
{isSelected && (
|
|
<div className="flex items-center gap-1 mt-2 ml-10">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 w-7 p-0"
|
|
onClick={() => onAdd(department.id)}
|
|
title="하위 부서 추가"
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
size="icon"
|
|
className="h-7 w-7"
|
|
onClick={() => onEdit(department)}
|
|
title="수정"
|
|
>
|
|
<SquarePen className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 w-7 p-0 text-destructive hover:text-destructive"
|
|
onClick={() => onDelete(department)}
|
|
title="삭제"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 하위 부서 (재귀) */}
|
|
{hasChildren && isExpanded && (
|
|
<>
|
|
{department.children!.map(child => (
|
|
<DepartmentTreeItem
|
|
key={child.id}
|
|
department={child}
|
|
depth={depth + 1}
|
|
expandedIds={expandedIds}
|
|
selectedIds={selectedIds}
|
|
onToggleExpand={onToggleExpand}
|
|
onToggleSelect={onToggleSelect}
|
|
onAdd={onAdd}
|
|
onEdit={onEdit}
|
|
onDelete={onDelete}
|
|
/>
|
|
))}
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
}); |