ERP-node/frontend/components/barcode/BarcodeListTable.tsx

245 lines
7.8 KiB
TypeScript
Raw Normal View History

2026-03-04 20:51:00 +09:00
"use client";
import { useState } from "react";
import { BarcodeLabelMaster } from "@/types/barcode";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Copy, Trash2, Loader2 } from "lucide-react";
import { barcodeApi } from "@/lib/api/barcodeApi";
import { useToast } from "@/hooks/use-toast";
import { useRouter } from "next/navigation";
import { format } from "date-fns";
interface BarcodeListTableProps {
labels: BarcodeLabelMaster[];
total: number;
page: number;
limit: number;
isLoading: boolean;
onPageChange: (page: number) => void;
onRefresh: () => void;
}
export function BarcodeListTable({
labels,
total,
page,
limit,
isLoading,
onPageChange,
onRefresh,
}: BarcodeListTableProps) {
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const [isCopying, setIsCopying] = useState(false);
const { toast } = useToast();
const router = useRouter();
const totalPages = Math.ceil(total / limit);
const handleEdit = (labelId: string) => {
router.push(`/admin/screenMng/barcodeList/designer/${labelId}`);
};
const handleCopy = async (labelId: string) => {
setIsCopying(true);
try {
const response = await barcodeApi.copyLabel(labelId);
if (response.success) {
toast({
title: "성공",
description: "바코드 라벨이 복사되었습니다.",
});
onRefresh();
}
} catch (error: any) {
toast({
title: "오류",
description: error.message || "바코드 라벨 복사에 실패했습니다.",
variant: "destructive",
});
} finally {
setIsCopying(false);
}
};
const handleDeleteClick = (labelId: string) => {
setDeleteTarget(labelId);
};
const handleDeleteConfirm = async () => {
if (!deleteTarget) return;
setIsDeleting(true);
try {
const response = await barcodeApi.deleteLabel(deleteTarget);
if (response.success) {
toast({
title: "성공",
description: "바코드 라벨이 삭제되었습니다.",
});
setDeleteTarget(null);
onRefresh();
}
} catch (error: any) {
toast({
title: "오류",
description: error.message || "바코드 라벨 삭제에 실패했습니다.",
variant: "destructive",
});
} finally {
setIsDeleting(false);
}
};
const formatDate = (dateString: string | null) => {
if (!dateString) return "-";
try {
return format(new Date(dateString), "yyyy-MM-dd");
} catch {
return dateString;
}
};
if (isLoading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
</div>
);
}
if (labels.length === 0) {
return (
<div className="text-muted-foreground flex h-64 flex-col items-center justify-center">
<p> .</p>
</div>
);
}
return (
<>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[80px]">No</TableHead>
<TableHead></TableHead>
<TableHead className="w-[120px]">릿 </TableHead>
<TableHead className="w-[120px]"></TableHead>
<TableHead className="w-[120px]"></TableHead>
<TableHead className="w-[200px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{labels.map((label, index) => {
const rowNumber = (page - 1) * limit + index + 1;
return (
<TableRow
key={label.label_id}
onClick={() => handleEdit(label.label_id)}
className="cursor-pointer hover:bg-muted/50"
>
<TableCell className="font-medium">{rowNumber}</TableCell>
<TableCell>
<div>
<div className="font-medium">{label.label_name_kor}</div>
{label.label_name_eng && (
<div className="text-muted-foreground text-sm">{label.label_name_eng}</div>
)}
</div>
</TableCell>
<TableCell>
{label.width_mm != null && label.height_mm != null
? `${label.width_mm}×${label.height_mm}mm`
: label.template_type || "-"}
</TableCell>
<TableCell>{label.created_by || "-"}</TableCell>
<TableCell>{formatDate(label.updated_at || label.created_at)}</TableCell>
<TableCell>
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
<Button
size="icon"
variant="outline"
onClick={() => handleCopy(label.label_id)}
disabled={isCopying}
className="h-8 w-8"
title="복사"
>
<Copy className="h-4 w-4" />
</Button>
<Button
size="icon"
variant="destructive"
onClick={() => handleDeleteClick(label.label_id)}
className="h-8 w-8"
title="삭제"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
{totalPages > 1 && (
<div className="flex items-center justify-center gap-2 p-4">
<Button variant="outline" size="sm" onClick={() => onPageChange(page - 1)} disabled={page === 1}>
</Button>
<span className="text-muted-foreground text-sm">
{page} / {totalPages}
</span>
<Button variant="outline" size="sm" onClick={() => onPageChange(page + 1)} disabled={page === totalPages}>
</Button>
</div>
)}
<AlertDialog open={deleteTarget !== null} onOpenChange={(open) => !open && setDeleteTarget(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
?
<br />
.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}></AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteConfirm}
disabled={isDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeleting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
"삭제"
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}