ERP-node/frontend/components/admin/SortableCodeItem.tsx

123 lines
3.9 KiB
TypeScript

"use client";
import React from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Edit, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { useUpdateCode } from "@/hooks/queries/useCodes";
import type { CodeInfo } from "@/types/commonCode";
interface SortableCodeItemProps {
code: CodeInfo;
onEdit: () => void;
onDelete: () => void;
categoryCode: string;
}
export function SortableCodeItem({ code, onEdit, onDelete, categoryCode }: SortableCodeItemProps) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: code.code_value });
const updateCodeMutation = useUpdateCode();
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
// 활성/비활성 토글 핸들러
const handleToggleActive = async (checked: boolean) => {
try {
await updateCodeMutation.mutateAsync({
categoryCode,
codeValue: code.code_value,
data: {
codeName: code.code_name,
codeNameEng: code.code_name_eng || "",
description: code.description || "",
sortOrder: code.sort_order,
isActive: checked ? "Y" : "N",
},
});
} catch (error) {
console.error("코드 활성 상태 변경 실패:", error);
}
};
return (
<div
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
className={cn(
"group cursor-grab rounded-lg border p-3 transition-all hover:shadow-sm",
"border-gray-200 bg-white hover:bg-gray-50",
isDragging && "cursor-grabbing opacity-50",
)}
>
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<h3 className="font-medium text-gray-900">{code.code_name}</h3>
<Badge
variant={code.is_active === "Y" ? "default" : "secondary"}
className={cn(
"cursor-pointer transition-colors",
code.is_active === "Y"
? "bg-green-100 text-green-800 hover:bg-green-200 hover:text-green-900"
: "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-700",
updateCodeMutation.isPending && "cursor-not-allowed opacity-50",
)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (!updateCodeMutation.isPending) {
handleToggleActive(code.is_active !== "Y");
}
}}
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
>
{code.is_active === "Y" ? "활성" : "비활성"}
</Badge>
</div>
<p className="mt-1 text-sm text-gray-600">{code.code_value}</p>
{code.description && <p className="mt-1 text-sm text-gray-500">{code.description}</p>}
</div>
{/* 액션 버튼 */}
<div
className="flex items-center gap-1"
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
>
<Button
size="sm"
variant="ghost"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onEdit();
}}
>
<Edit className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDelete();
}}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
</div>
);
}