Files
invyone/frontend/components/admin/CompanyTable.tsx
T
johngreen 0e895a90fa feat(부서관리): V1 슬림 스코프 + 트리 컨텍스트 메뉴 UX 리디자인
백엔드:
- V018 soft-delete (deleted_at 컬럼) + 휴지통/복구 흐름
- V019 미사용 컬럼 cleanup (V1 슬림 스코프)
- DepartmentService.updateDepartment 에 parent_dept_code 사이클 가드
  (자기 자신/자손을 부모로 지정 시도 차단)
- DepartmentController, mapper 갱신

프론트:
- 부서관리 페이지(deptMngList) UX 리디자인
  - 트리 노드 ⋮ 컨텍스트 메뉴 (하위 추가, 다른 부서 아래로 이동, 정렬 4단계, 삭제)
  - 헤더 breadcrumb 으로 부서 위치 상시 표시
  - 폼의 상위부서 row 제거 (트리 ⋮ 로 진입점 일원화)
  - 빈 상태 placeholder + X 닫기 동작
  - 토글 버튼 토스 스타일 (아이콘 + 툴팁, 일정한 위치)
  - 부서유형 row 좁은 화면 가로 오버플로 fix
- DepartmentPicker 신규 재사용 컴포넌트 (자손 자동 exclude, 사이클 차단)
- 회사관리/프로비저닝 폼 개선 (Step1Basic, fields, CompanyTable, AdminPageRenderer)
- companyList/[companyCode]/departments 구버전 페이지 삭제

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:34:23 +09:00

137 lines
4.0 KiB
TypeScript

import { Edit, Trash2, HardDrive, FileText, Users } from "lucide-react";
import { Company } from "@/types/company";
import { Button } from "@/components/ui/button";
import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView";
import { useRouter } from "next/navigation";
interface CompanyTableProps {
companies: Company[];
isLoading: boolean;
onEdit: (company: Company) => void;
onDelete: (company: Company) => void;
}
/**
* 회사 목록 테이블 컴포넌트
*/
export function CompanyTable({ companies, isLoading, onEdit, onDelete }: CompanyTableProps) {
const router = useRouter();
// 부서 관리 페이지로 이동 (legacy deptMngList 가 캐노니컬 페이지)
const handleManageDepartments = (_company: Company) => {
router.push(`/admin/userMng/deptMngList`);
};
// 디스크 사용량 포맷팅 함수
const formatDiskUsage = (company: Company) => {
if (!company.disk_usage) {
return (
<div className="text-muted-foreground flex items-center gap-1">
<HardDrive className="h-3 w-3" />
<span className="text-xs"> </span>
</div>
);
}
const { file_count: fileCount, total_size_mb: totalSizeMB } = company.disk_usage;
return (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<FileText className="text-primary h-3 w-3" />
<span className="text-xs font-medium">{fileCount} </span>
</div>
<div className="flex items-center gap-1">
<HardDrive className="text-primary h-3 w-3" />
<span className="text-xs">{totalSizeMB.toFixed(1)} MB</span>
</div>
</div>
);
};
// 데스크톱 테이블 컬럼 정의
const columns: RDVColumn<Company>[] = [
{
key: "company_code",
label: "회사코드",
width: "150px",
render: (value) => <span className="font-mono">{value}</span>,
},
{
key: "company_name",
label: "회사명",
render: (value) => <span className="font-medium">{value}</span>,
},
{
key: "writer",
label: "등록자",
width: "200px",
},
{
key: "diskUsage",
label: "디스크 사용량",
hideOnMobile: true,
render: (_value, row) => formatDiskUsage(row),
},
];
// 모바일 카드 필드 정의
const cardFields: RDVCardField<Company>[] = [
{
label: "작성자",
render: (company) => <span className="font-medium">{company.writer}</span>,
},
{
label: "디스크 사용량",
render: (company) => formatDiskUsage(company),
},
];
return (
<ResponsiveDataView<Company>
data={companies}
columns={columns}
keyExtractor={(c) => c.regdate + c.company_code}
isLoading={isLoading}
emptyMessage="등록된 회사가 없습니다."
skeletonCount={10}
cardTitle={(c) => c.company_name}
cardSubtitle={(c) => <span className="font-mono">{c.company_code}</span>}
cardFields={cardFields}
actionsLabel="작업"
actionsWidth="180px"
renderActions={(company) => (
<>
<Button
variant="ghost"
size="icon"
onClick={() => handleManageDepartments(company)}
className="h-8 w-8"
aria-label="부서관리"
>
<Users className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => onEdit(company)}
className="h-8 w-8"
aria-label="수정"
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => onDelete(company)}
className="text-destructive hover:bg-destructive/10 hover:text-destructive h-8 w-8"
aria-label="삭제"
>
<Trash2 className="h-4 w-4" />
</Button>
</>
)}
/>
);
}