185 lines
5.4 KiB
TypeScript
185 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Trash2 } from "lucide-react";
|
|
import { RepeaterColumnConfig } from "./types";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface RepeaterTableProps {
|
|
columns: RepeaterColumnConfig[];
|
|
data: any[];
|
|
onDataChange: (newData: any[]) => void;
|
|
onRowChange: (index: number, newRow: any) => void;
|
|
onRowDelete: (index: number) => void;
|
|
}
|
|
|
|
export function RepeaterTable({
|
|
columns,
|
|
data,
|
|
onDataChange,
|
|
onRowChange,
|
|
onRowDelete,
|
|
}: RepeaterTableProps) {
|
|
const [editingCell, setEditingCell] = useState<{
|
|
rowIndex: number;
|
|
field: string;
|
|
} | null>(null);
|
|
|
|
// 데이터 변경 감지 (필요시 활성화)
|
|
// useEffect(() => {
|
|
// console.log("📊 RepeaterTable 데이터 업데이트:", data.length, "개 행");
|
|
// }, [data]);
|
|
|
|
const handleCellEdit = (rowIndex: number, field: string, value: any) => {
|
|
const newRow = { ...data[rowIndex], [field]: value };
|
|
onRowChange(rowIndex, newRow);
|
|
};
|
|
|
|
const renderCell = (
|
|
row: any,
|
|
column: RepeaterColumnConfig,
|
|
rowIndex: number
|
|
) => {
|
|
const isEditing =
|
|
editingCell?.rowIndex === rowIndex && editingCell?.field === column.field;
|
|
const value = row[column.field];
|
|
|
|
// 계산 필드는 편집 불가
|
|
if (column.calculated || !column.editable) {
|
|
return (
|
|
<div className="px-2 py-1">
|
|
{column.type === "number"
|
|
? typeof value === "number"
|
|
? value.toLocaleString()
|
|
: value || "0"
|
|
: value || "-"}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 편집 가능한 필드
|
|
switch (column.type) {
|
|
case "number":
|
|
return (
|
|
<Input
|
|
type="number"
|
|
value={value || ""}
|
|
onChange={(e) =>
|
|
handleCellEdit(rowIndex, column.field, parseFloat(e.target.value) || 0)
|
|
}
|
|
className="h-7 text-xs"
|
|
/>
|
|
);
|
|
|
|
case "date":
|
|
return (
|
|
<Input
|
|
type="date"
|
|
value={value || ""}
|
|
onChange={(e) => handleCellEdit(rowIndex, column.field, e.target.value)}
|
|
className="h-7 text-xs"
|
|
/>
|
|
);
|
|
|
|
case "select":
|
|
return (
|
|
<Select
|
|
value={value || ""}
|
|
onValueChange={(newValue) =>
|
|
handleCellEdit(rowIndex, column.field, newValue)
|
|
}
|
|
>
|
|
<SelectTrigger className="h-7 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{column.selectOptions?.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
|
|
default: // text
|
|
return (
|
|
<Input
|
|
type="text"
|
|
value={value || ""}
|
|
onChange={(e) => handleCellEdit(rowIndex, column.field, e.target.value)}
|
|
className="h-7 text-xs"
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="border rounded-md overflow-hidden">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-xs sm:text-sm">
|
|
<thead className="bg-muted">
|
|
<tr>
|
|
<th className="px-4 py-2 text-left font-medium text-muted-foreground w-12">
|
|
#
|
|
</th>
|
|
{columns.map((col) => (
|
|
<th
|
|
key={col.field}
|
|
className="px-4 py-2 text-left font-medium text-muted-foreground"
|
|
style={{ width: col.width }}
|
|
>
|
|
{col.label}
|
|
{col.required && <span className="text-destructive ml-1">*</span>}
|
|
</th>
|
|
))}
|
|
<th className="px-4 py-2 text-left font-medium text-muted-foreground w-20">
|
|
삭제
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.length === 0 ? (
|
|
<tr>
|
|
<td
|
|
colSpan={columns.length + 2}
|
|
className="px-4 py-8 text-center text-muted-foreground"
|
|
>
|
|
추가된 항목이 없습니다
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
data.map((row, rowIndex) => (
|
|
<tr key={rowIndex} className="border-t hover:bg-accent/50">
|
|
<td className="px-4 py-2 text-center text-muted-foreground">
|
|
{rowIndex + 1}
|
|
</td>
|
|
{columns.map((col) => (
|
|
<td key={col.field} className="px-2 py-1">
|
|
{renderCell(row, col, rowIndex)}
|
|
</td>
|
|
))}
|
|
<td className="px-4 py-2 text-center">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onRowDelete(rowIndex)}
|
|
className="h-7 w-7 p-0 text-destructive hover:text-destructive"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|