133 lines
4.0 KiB
TypeScript
133 lines
4.0 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;
|
|
categoryCode: string;
|
|
onEdit: () => void;
|
|
onDelete: () => void;
|
|
isDragOverlay?: boolean;
|
|
}
|
|
|
|
export function SortableCodeItem({
|
|
code,
|
|
categoryCode,
|
|
onEdit,
|
|
onDelete,
|
|
isDragOverlay = false,
|
|
}: SortableCodeItemProps) {
|
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
id: code.code_value,
|
|
disabled: isDragOverlay,
|
|
});
|
|
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>
|
|
);
|
|
}
|