Files
sam-react-prod/src/components/production/bending/BendingTable.tsx

261 lines
9.4 KiB
TypeScript

'use client';
import { useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Checkbox } from '@/components/ui/checkbox';
import { Plus, Trash2, Eraser, ChevronUp, ChevronDown } from 'lucide-react';
import {
type BendingData,
createEmptyBendingData,
calculateRateResult,
recalculateSums,
getWidthSum,
getBendCount,
} from './types';
interface BendingTableProps {
data: BendingData[];
onChange: (data: BendingData[]) => void;
readOnly?: boolean;
showActions?: boolean;
showSummary?: boolean;
}
export function BendingTable({
data,
onChange,
readOnly = false,
showActions = true,
showSummary = true,
}: BendingTableProps) {
const handleInputChange = useCallback(
(colIndex: number, value: number) => {
const updated = [...data];
updated[colIndex] = { ...updated[colIndex], input: isNaN(value) ? 0 : value };
onChange(recalculateSums(updated));
},
[data, onChange]
);
const handleRateChange = useCallback(
(colIndex: number, value: string) => {
const cleaned = value.trim();
// 빈값 또는 숫자(음수 포함) 허용, 입력 중간 '-' 허용
if (cleaned !== '' && cleaned !== '-' && isNaN(Number(cleaned))) return;
const updated = [...data];
updated[colIndex] = { ...updated[colIndex], rate: cleaned };
if (cleaned === '' || cleaned === '-') {
onChange(updated);
} else {
onChange(recalculateSums(updated));
}
},
[data, onChange]
);
const handleCheckChange = useCallback(
(colIndex: number, field: 'color' | 'aAngle', checked: boolean) => {
const updated = [...data];
updated[colIndex] = { ...updated[colIndex], [field]: checked };
onChange(updated);
},
[data, onChange]
);
const addColumn = useCallback(() => {
const newData = [...data, createEmptyBendingData(data.length + 1)];
onChange(recalculateSums(newData));
}, [data, onChange]);
const removeLastColumn = useCallback(() => {
if (data.length === 0) return;
const newData = data.slice(0, -1);
onChange(recalculateSums(newData));
}, [data, onChange]);
const clearAll = useCallback(() => {
const cleared = data.map((d, i) => createEmptyBendingData(i + 1));
onChange(recalculateSums(cleared));
}, [data, onChange]);
const widthSum = getWidthSum(data);
const bendCount = getBendCount(data);
return (
<div className="space-y-2">
{/* 헤더 */}
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold"> </h3>
{showActions && !readOnly && (
<div className="flex gap-1">
<Button type="button" size="sm" variant="outline" onClick={addColumn}>
<Plus className="h-3.5 w-3.5 mr-1" />
</Button>
<Button type="button" size="sm" variant="destructive" onClick={removeLastColumn}>
<Trash2 className="h-3.5 w-3.5 mr-1" />
</Button>
<Button type="button" size="sm" variant="secondary" onClick={clearAll}>
<Eraser className="h-3.5 w-3.5 mr-1" />
</Button>
</div>
)}
</div>
{/* 가로형 테이블 */}
<div className="overflow-x-auto">
<table className="w-full text-sm border-collapse">
<thead>
<tr className="bg-muted/50">
<th className="border px-2 py-1.5 text-center w-[70px] font-medium"></th>
{data.map((d) => (
<th key={d.no} className="border px-2 py-1.5 text-center min-w-[70px] font-medium">
{d.no}
</th>
))}
{!readOnly && data.length > 0 && (
<th className="border px-1 py-1.5 w-[36px]">
<Button
type="button"
size="sm"
variant="ghost"
className="h-6 w-6 p-0"
onClick={addColumn}
>
<Plus className="h-3.5 w-3.5" />
</Button>
</th>
)}
</tr>
</thead>
<tbody>
{/* 입력 */}
<tr>
<td className="border px-2 py-1 text-center font-medium bg-muted/30"></td>
{data.map((d, i) => (
<td key={i} className="border px-1 py-1 bg-yellow-50">
{readOnly ? (
<span className="block text-center">{d.input || ''}</span>
) : (
<Input
type="number"
value={d.input || ''}
onChange={(e) => handleInputChange(i, Number(e.target.value))}
className="h-7 text-center px-1 border-0 bg-transparent"
/>
)}
</td>
))}
{!readOnly && data.length > 0 && <td className="border" />}
</tr>
{/* 연신율 */}
<tr>
<td className="border px-2 py-1 text-center font-medium bg-muted/30"></td>
{data.map((d, i) => (
<td key={i} className="border px-1 py-1">
{readOnly ? (
<span className="block text-center">{d.rate ?? ''}</span>
) : (
<div className="flex items-center justify-center gap-0.5">
<button
type="button"
className="h-6 w-6 flex items-center justify-center rounded hover:bg-muted text-muted-foreground hover:text-foreground"
onClick={() => handleRateChange(i, String((Number(d.rate) || 0) + 1))}
>
<ChevronUp className="h-3.5 w-3.5" />
</button>
<span className="w-5 text-center text-sm font-medium">{d.rate || ''}</span>
<button
type="button"
className="h-6 w-6 flex items-center justify-center rounded hover:bg-muted text-muted-foreground hover:text-foreground"
onClick={() => handleRateChange(i, String((Number(d.rate) || 0) - 1))}
>
<ChevronDown className="h-3.5 w-3.5" />
</button>
</div>
)}
</td>
))}
{!readOnly && data.length > 0 && <td className="border" />}
</tr>
{/* 연신율 후 (자동 계산) */}
<tr>
<td className="border px-2 py-1 text-center font-medium bg-muted/30"> </td>
{data.map((d, i) => (
<td key={i} className="border px-1 py-1 text-center text-muted-foreground">
{d.input ? (calculateRateResult(d.input, d.rate) || '-') : '-'}
</td>
))}
{!readOnly && data.length > 0 && <td className="border" />}
</tr>
{/* 합계 (누적) */}
<tr className="bg-amber-50">
<td className="border px-2 py-1 text-center font-semibold bg-amber-100"></td>
{data.map((d, i) => {
const isLast = i === data.length - 1;
return (
<td
key={i}
className={`border px-1 py-1 text-center font-semibold ${
d.color ? 'text-red-600' : ''
} ${isLast ? 'text-blue-600' : ''}`}
>
{d.sum != null && !isNaN(d.sum) ? d.sum : '-'}
</td>
);
})}
{!readOnly && data.length > 0 && <td className="border" />}
</tr>
{/* 음영 */}
<tr>
<td className="border px-2 py-1 text-center font-medium bg-muted/30"></td>
{data.map((d, i) => (
<td key={i} className="border px-1 py-1 text-center">
<Checkbox
checked={d.color}
onCheckedChange={(checked) =>
handleCheckChange(i, 'color', checked === true)
}
disabled={readOnly}
/>
</td>
))}
{!readOnly && data.length > 0 && <td className="border" />}
</tr>
{/* A각 */}
<tr>
<td className="border px-2 py-1 text-center font-medium bg-muted/30">A각</td>
{data.map((d, i) => (
<td key={i} className="border px-1 py-1 text-center">
<Checkbox
checked={d.aAngle}
onCheckedChange={(checked) =>
handleCheckChange(i, 'aAngle', checked === true)
}
disabled={readOnly}
/>
</td>
))}
{!readOnly && data.length > 0 && <td className="border" />}
</tr>
</tbody>
</table>
</div>
{/* 합계 표시 */}
{showSummary && (
<div className="text-sm">
: <span className="font-bold text-blue-600">{widthSum}</span>
<span className="mx-2">|</span>
: <span className="font-bold text-blue-600">{bendCount}</span>
</div>
)}
</div>
);
}