"use client" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Check, Edit, Trash2 } from "lucide-react" import { useOptimistic, useState } from "react" interface CrudColumn { key: keyof T label: string type?: "text" | "number" | "checkbox" | "select" | "color" options?: string[] defaultValue?: string | boolean editable?: boolean } interface CrudProps { items: T[] columns: CrudColumn[] onDelete: (id: string) => Promise<{ success: boolean; error?: string }> onAdd: (data: Partial) => Promise<{ success: boolean; error?: string }> onEdit?: (id: string, data: Partial) => Promise<{ success: boolean; error?: string }> } export function CrudTable({ items, columns, onDelete, onAdd, onEdit }: CrudProps) { const [isAdding, setIsAdding] = useState(false) const [editingId, setEditingId] = useState(null) const [newItem, setNewItem] = useState>(itemDefaults(columns)) const [editingItem, setEditingItem] = useState>(itemDefaults(columns)) const [optimisticItems, addOptimisticItem] = useOptimistic(items, (state, newItem: T) => [...state, newItem]) const FormCell = (item: T, column: CrudColumn) => { if (column.type === "checkbox") { return item[column.key] ? : "" } if (column.type === "color" || column.key === "color") { const value = (item[column.key] as string) || "" return (
{value}
) } return item[column.key] } const EditFormCell = (item: T, column: CrudColumn) => { if (column.type === "checkbox") { return ( setEditingItem({ ...editingItem, [column.key]: e.target.checked, }) } /> ) } else if (column.type === "select") { return ( ) } else if (column.type === "color" || column.key === "color") { return (
setEditingItem({ ...editingItem, [column.key]: e.target.value, }) } />
setEditingItem({ ...editingItem, [column.key]: e.target.value, }) } placeholder="#FFFFFF" />
) } return ( setEditingItem({ ...editingItem, [column.key]: e.target.value, }) } /> ) } const AddFormCell = (column: CrudColumn) => { if (column.type === "checkbox") { return ( setNewItem({ ...newItem, [column.key]: e.target.checked, }) } /> ) } else if (column.type === "select") { return ( ) } else if (column.type === "color" || column.key === "color") { return (
setNewItem({ ...newItem, [column.key]: e.target.value, }) } />
setNewItem({ ...newItem, [column.key]: e.target.value, }) } placeholder="#FFFFFF" />
) } return ( setNewItem({ ...newItem, [column.key]: e.target.value, }) } /> ) } const handleAdd = async () => { try { const result = await onAdd(newItem) if (result.success) { setIsAdding(false) setNewItem(itemDefaults(columns)) } else { alert(result.error) } } catch (error) { console.error("Failed to add item:", error) } } const handleEdit = async (id: string) => { if (!onEdit) return try { const result = await onEdit(id, editingItem) if (result.success) { setEditingId(null) setEditingItem({}) } else { alert(result.error) } } catch (error) { console.error("Failed to edit item:", error) } } const startEditing = (item: T) => { setEditingId(item.code || item.id) setEditingItem(item) } const handleDelete = async (id: string) => { try { const result = await onDelete(id) if (!result.success) { alert(result.error) } } catch (error) { console.error("Failed to delete item:", error) } } return (
{columns.map((column) => ( {column.label} ))} Actions {optimisticItems.map((item, index) => ( {columns.map((column) => ( {editingId === (item.code || item.id) && column.editable ? EditFormCell(item, column) : FormCell(item, column)} ))}
{editingId === (item.code || item.id) ? ( <> ) : ( <> {onEdit && ( )} {item.isDeletable && ( )} )}
))} {isAdding && ( {columns.map((column) => ( {column.editable && AddFormCell(column)} ))}
)}
{!isAdding && ( )}
) } function itemDefaults(columns: CrudColumn[]) { return columns.reduce((acc, column) => { acc[column.key] = column.defaultValue as T[keyof T] return acc }, {} as Partial) }