ERP-node/frontend/components/admin/dashboard/widgets/yard-3d/YardLayoutList.tsx

278 lines
9.4 KiB
TypeScript

"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Search, MoreVertical, Loader2 } from "lucide-react";
interface YardLayout {
id: number;
name: string;
description: string;
placement_count: number;
updated_at: string;
}
interface YardLayoutListProps {
layouts: YardLayout[];
isLoading: boolean;
onSelect: (layout: YardLayout) => void;
onDelete: (id: number) => Promise<void>;
onDuplicate: (id: number, newName: string) => Promise<void>;
}
export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete, onDuplicate }: YardLayoutListProps) {
const [searchText, setSearchText] = useState("");
const [sortOrder, setSortOrder] = useState<"recent" | "name">("recent");
const [deleteTarget, setDeleteTarget] = useState<YardLayout | null>(null);
const [duplicateTarget, setDuplicateTarget] = useState<YardLayout | null>(null);
const [duplicateName, setDuplicateName] = useState("");
const [isDeleting, setIsDeleting] = useState(false);
const [isDuplicating, setIsDuplicating] = useState(false);
// 검색 필터링
const filteredLayouts = layouts.filter((layout) => {
if (!searchText) return true;
return (
layout.name.toLowerCase().includes(searchText.toLowerCase()) ||
layout.description?.toLowerCase().includes(searchText.toLowerCase())
);
});
// 정렬
const sortedLayouts = [...filteredLayouts].sort((a, b) => {
if (sortOrder === "recent") {
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
} else {
return a.name.localeCompare(b.name);
}
});
// 날짜 포맷팅
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
};
// 삭제 확인
const handleDeleteConfirm = async () => {
if (!deleteTarget) return;
setIsDeleting(true);
try {
await onDelete(deleteTarget.id);
setDeleteTarget(null);
} catch (error) {
console.error("삭제 실패:", error);
} finally {
setIsDeleting(false);
}
};
// 복제 실행
const handleDuplicateConfirm = async () => {
if (!duplicateTarget || !duplicateName.trim()) return;
setIsDuplicating(true);
try {
await onDuplicate(duplicateTarget.id, duplicateName);
setDuplicateTarget(null);
setDuplicateName("");
} catch (error) {
console.error("복제 실패:", error);
} finally {
setIsDuplicating(false);
}
};
// 복제 모달 열기
const handleDuplicateClick = (layout: YardLayout) => {
setDuplicateTarget(layout);
setDuplicateName(`${layout.name} (복사본)`);
};
if (isLoading) {
return (
<div className="flex h-full items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
</div>
);
}
return (
<div className="flex h-full flex-col space-y-4">
{/* 검색 및 정렬 */}
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Input
placeholder="야드 이름 또는 설명 검색..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="pl-9"
/>
</div>
<select
value={sortOrder}
onChange={(e) => setSortOrder(e.target.value as "recent" | "name")}
className="rounded-md border border-gray-300 px-3 py-2 text-sm"
>
<option value="recent"> </option>
<option value="name"></option>
</select>
</div>
{/* 테이블 */}
{sortedLayouts.length === 0 ? (
<div className="flex flex-1 items-center justify-center">
<div className="text-center text-gray-500">
{searchText ? "검색 결과가 없습니다" : "등록된 야드가 없습니다"}
</div>
</div>
) : (
<div className="flex-1 overflow-auto rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-center"> </TableHead>
<TableHead> </TableHead>
<TableHead className="w-[80px] text-center"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedLayouts.map((layout) => (
<TableRow key={layout.id} className="cursor-pointer hover:bg-gray-50" onClick={() => onSelect(layout)}>
<TableCell className="font-medium">{layout.name}</TableCell>
<TableCell className="text-gray-600">{layout.description || "-"}</TableCell>
<TableCell className="text-center">{layout.placement_count}</TableCell>
<TableCell className="text-sm text-gray-500">{formatDate(layout.updated_at)}</TableCell>
<TableCell className="text-center">
<DropdownMenu>
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
<Button variant="ghost" size="sm">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onSelect(layout)}></DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDuplicateClick(layout)}></DropdownMenuItem>
<DropdownMenuItem onClick={() => setDeleteTarget(layout)} className="text-red-600">
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{/* 총 개수 */}
<div className="text-sm text-gray-500"> {sortedLayouts.length}</div>
{/* 삭제 확인 모달 */}
<AlertDialog open={!!deleteTarget} onOpenChange={() => setDeleteTarget(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
&quot;{deleteTarget?.name}&quot; ?
<br />
.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}></AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteConfirm}
disabled={isDeleting}
className="bg-red-600 hover:bg-red-700"
>
{isDeleting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
"삭제"
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 복제 모달 */}
<Dialog open={!!duplicateTarget} onOpenChange={() => setDuplicateTarget(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle> </DialogTitle>
<DialogDescription> </DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="duplicate-name"> </Label>
<Input
id="duplicate-name"
value={duplicateName}
onChange={(e) => setDuplicateName(e.target.value)}
placeholder="야드 이름을 입력하세요"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setDuplicateTarget(null)} disabled={isDuplicating}>
</Button>
<Button onClick={handleDuplicateConfirm} disabled={!duplicateName.trim() || isDuplicating}>
{isDuplicating ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
"복제"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}