2025-11-03 16:31:03 +09:00
|
|
|
import { Edit, Trash2, HardDrive, FileText, Users } from "lucide-react";
|
2025-08-21 09:41:46 +09:00
|
|
|
import { Company } from "@/types/company";
|
|
|
|
|
import { COMPANY_TABLE_COLUMNS } from "@/constants/company";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
2025-11-03 16:31:03 +09:00
|
|
|
import { useRouter } from "next/navigation";
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
interface CompanyTableProps {
|
|
|
|
|
companies: Company[];
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
onEdit: (company: Company) => void;
|
|
|
|
|
onDelete: (company: Company) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 회사 목록 테이블 컴포넌트
|
2025-10-22 14:52:13 +09:00
|
|
|
* 데스크톱: 테이블 뷰
|
|
|
|
|
* 모바일/태블릿: 카드 뷰
|
2025-08-21 09:41:46 +09:00
|
|
|
*/
|
|
|
|
|
export function CompanyTable({ companies, isLoading, onEdit, onDelete }: CompanyTableProps) {
|
2025-11-03 16:31:03 +09:00
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
// 부서 관리 페이지로 이동
|
|
|
|
|
const handleManageDepartments = (company: Company) => {
|
|
|
|
|
router.push(`/admin/company/${company.company_code}/departments`);
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-05 14:52:10 +09:00
|
|
|
// 디스크 사용량 포맷팅 함수
|
|
|
|
|
const formatDiskUsage = (company: Company) => {
|
|
|
|
|
if (!company.diskUsage) {
|
|
|
|
|
return (
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="text-muted-foreground flex items-center gap-1">
|
2025-09-05 14:52:10 +09:00
|
|
|
<HardDrive className="h-3 w-3" />
|
|
|
|
|
<span className="text-xs">정보 없음</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { fileCount, totalSizeMB } = company.diskUsage;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-1">
|
|
|
|
|
<div className="flex items-center gap-1">
|
2025-11-03 16:31:03 +09:00
|
|
|
<FileText className="text-primary h-3 w-3" />
|
2025-09-05 14:52:10 +09:00
|
|
|
<span className="text-xs font-medium">{fileCount}개 파일</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1">
|
2025-11-03 16:31:03 +09:00
|
|
|
<HardDrive className="text-primary h-3 w-3" />
|
2025-09-05 14:52:10 +09:00
|
|
|
<span className="text-xs">{totalSizeMB.toFixed(1)} MB</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
2025-10-22 14:52:13 +09:00
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
// 로딩 상태 렌더링
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
return (
|
2025-10-22 14:52:13 +09:00
|
|
|
<>
|
|
|
|
|
{/* 데스크톱 테이블 스켈레톤 */}
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-card hidden shadow-sm lg:block">
|
2025-10-22 14:52:13 +09:00
|
|
|
<Table>
|
|
|
|
|
<TableHeader>
|
2025-10-30 15:49:23 +09:00
|
|
|
<TableRow>
|
2025-08-21 09:41:46 +09:00
|
|
|
{COMPANY_TABLE_COLUMNS.map((column) => (
|
2025-10-30 17:02:30 +09:00
|
|
|
<TableHead key={column.key} className="h-12 px-6 py-3 text-sm font-semibold">
|
2025-10-22 14:52:13 +09:00
|
|
|
{column.label}
|
|
|
|
|
</TableHead>
|
2025-08-21 09:41:46 +09:00
|
|
|
))}
|
2025-10-30 17:02:30 +09:00
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">디스크 사용량</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
2025-08-21 09:41:46 +09:00
|
|
|
</TableRow>
|
2025-10-22 14:52:13 +09:00
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
|
|
|
|
{Array.from({ length: 10 }).map((_, index) => (
|
2025-10-30 15:39:39 +09:00
|
|
|
<TableRow key={index}>
|
2025-10-22 14:52:13 +09:00
|
|
|
<TableCell className="h-16">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-4 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="h-16">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-4 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="h-16">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-4 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="h-16">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-4 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="h-16">
|
|
|
|
|
<div className="flex gap-2">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-8 w-8 animate-pulse rounded"></div>
|
|
|
|
|
<div className="bg-muted h-8 w-8 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 모바일/태블릿 카드 스켈레톤 */}
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:hidden">
|
|
|
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
2025-11-03 16:31:03 +09:00
|
|
|
<div key={index} className="bg-card rounded-lg border p-4 shadow-sm">
|
2025-10-22 14:52:13 +09:00
|
|
|
<div className="mb-4 flex items-start justify-between">
|
|
|
|
|
<div className="flex-1 space-y-2">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-5 w-32 animate-pulse rounded"></div>
|
|
|
|
|
<div className="bg-muted h-4 w-24 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2 border-t pt-4">
|
|
|
|
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
|
|
|
<div key={i} className="flex justify-between">
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-muted h-4 w-16 animate-pulse rounded"></div>
|
|
|
|
|
<div className="bg-muted h-4 w-32 animate-pulse rounded"></div>
|
2025-10-22 14:52:13 +09:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
2025-08-21 09:41:46 +09:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 데이터가 없을 때
|
|
|
|
|
if (companies.length === 0) {
|
|
|
|
|
return (
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-card flex h-64 flex-col items-center justify-center shadow-sm">
|
2025-10-22 14:52:13 +09:00
|
|
|
<div className="flex flex-col items-center gap-2 text-center">
|
2025-11-03 16:31:03 +09:00
|
|
|
<p className="text-muted-foreground text-sm">등록된 회사가 없습니다.</p>
|
2025-10-22 14:52:13 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 실제 데이터 렌더링
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* 데스크톱 테이블 뷰 (lg 이상) */}
|
2025-11-03 16:31:03 +09:00
|
|
|
<div className="bg-card hidden lg:block">
|
2025-08-21 09:41:46 +09:00
|
|
|
<Table>
|
2025-11-03 16:31:03 +09:00
|
|
|
<TableHeader>
|
|
|
|
|
<TableRow>
|
|
|
|
|
{COMPANY_TABLE_COLUMNS.map((column) => (
|
|
|
|
|
<TableHead key={column.key} className="h-12 px-6 py-3 text-sm font-semibold">
|
|
|
|
|
{column.label}
|
|
|
|
|
</TableHead>
|
|
|
|
|
))}
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">디스크 사용량</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
|
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
2025-08-21 09:41:46 +09:00
|
|
|
<TableBody>
|
2025-10-22 14:52:13 +09:00
|
|
|
{companies.map((company) => (
|
2025-11-03 16:31:03 +09:00
|
|
|
<TableRow
|
|
|
|
|
key={company.regdate + company.company_code}
|
|
|
|
|
className="bg-background hover:bg-muted/50 transition-colors"
|
|
|
|
|
>
|
2025-10-30 17:02:30 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{company.company_code}</TableCell>
|
|
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm font-medium">{company.company_name}</TableCell>
|
|
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">{company.writer}</TableCell>
|
|
|
|
|
<TableCell className="h-16 px-6 py-3">{formatDiskUsage(company)}</TableCell>
|
|
|
|
|
<TableCell className="h-16 px-6 py-3">
|
2025-10-22 14:52:13 +09:00
|
|
|
<div className="flex gap-2">
|
2025-11-03 16:31:03 +09:00
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => handleManageDepartments(company)}
|
|
|
|
|
className="h-8 w-8"
|
|
|
|
|
aria-label="부서관리"
|
|
|
|
|
>
|
|
|
|
|
<Users className="h-4 w-4" />
|
|
|
|
|
</Button>
|
2025-10-22 14:52:13 +09:00
|
|
|
<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)}
|
2025-11-03 16:31:03 +09:00
|
|
|
className="text-destructive hover:bg-destructive/10 hover:text-destructive h-8 w-8"
|
2025-10-22 14:52:13 +09:00
|
|
|
aria-label="삭제"
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
2025-08-21 09:41:46 +09:00
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-22 14:52:13 +09:00
|
|
|
{/* 모바일/태블릿 카드 뷰 (lg 미만) */}
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:hidden">
|
|
|
|
|
{companies.map((company) => (
|
|
|
|
|
<div
|
|
|
|
|
key={company.regdate + company.company_code}
|
2025-11-03 16:31:03 +09:00
|
|
|
className="bg-card hover:bg-muted/50 rounded-lg border p-4 shadow-sm transition-colors"
|
2025-10-22 14:52:13 +09:00
|
|
|
>
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
<div className="mb-4 flex items-start justify-between">
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<h3 className="text-base font-semibold">{company.company_name}</h3>
|
2025-11-03 16:31:03 +09:00
|
|
|
<p className="text-muted-foreground mt-1 font-mono text-sm">{company.company_code}</p>
|
2025-10-22 14:52:13 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 정보 */}
|
|
|
|
|
<div className="space-y-2 border-t pt-4">
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">작성자</span>
|
|
|
|
|
<span className="font-medium">{company.writer}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">디스크 사용량</span>
|
|
|
|
|
<div>{formatDiskUsage(company)}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 액션 */}
|
|
|
|
|
<div className="mt-4 flex gap-2 border-t pt-4">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
2025-11-03 16:31:03 +09:00
|
|
|
onClick={() => handleManageDepartments(company)}
|
2025-10-22 14:52:13 +09:00
|
|
|
className="h-9 flex-1 gap-2 text-sm"
|
|
|
|
|
>
|
2025-11-03 16:31:03 +09:00
|
|
|
<Users className="h-4 w-4" />
|
|
|
|
|
부서
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="outline" size="sm" onClick={() => onEdit(company)} className="h-9 flex-1 gap-2 text-sm">
|
2025-10-22 14:52:13 +09:00
|
|
|
<Edit className="h-4 w-4" />
|
|
|
|
|
수정
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => onDelete(company)}
|
2025-11-03 16:31:03 +09:00
|
|
|
className="text-destructive hover:bg-destructive/10 hover:text-destructive h-9 flex-1 gap-2 text-sm"
|
2025-10-22 14:52:13 +09:00
|
|
|
>
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
삭제
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
2025-08-21 09:41:46 +09:00
|
|
|
);
|
|
|
|
|
}
|