Files
sam-react-prod/src/components/accounting/BillManagement/sections/HistorySection.tsx
유병철 1675f3edcf feat: 어음관리 리팩토링 및 CEO 대시보드 SummaryNavBar 추가
- BillManagement: BillDetail 리팩토링, sections/hooks 분리, constants 추가
- BillManagement types 대폭 확장, actions 개선
- GiftCertificateManagement: actions/types 확장
- CEO 대시보드: SummaryNavBar 컴포넌트 추가, useSectionSummary 훅
- bill-prototype 개발 페이지 업데이트
2026-03-05 20:47:43 +09:00

151 lines
7.7 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import { Plus, Trash2, AlertTriangle } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { DatePicker } from '@/components/ui/date-picker';
import { Label } from '@/components/ui/label';
import { CurrencyInput } from '@/components/ui/currency-input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from '@/components/ui/select';
import {
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
} from '@/components/ui/table';
import type { BillFormData } from '../types';
import { HISTORY_TYPE_OPTIONS } from '../constants';
interface HistorySectionProps {
formData: BillFormData;
updateField: <K extends keyof BillFormData>(field: K, value: BillFormData[K]) => void;
isViewMode: boolean;
isElectronic: boolean;
maxSplitCount: number;
onAddInstallment: () => void;
onRemoveInstallment: (id: string) => void;
onUpdateInstallment: (id: string, field: string, value: string | number) => void;
}
export function HistorySection({
formData, updateField, isViewMode, isElectronic, maxSplitCount,
onAddInstallment, onRemoveInstallment, onUpdateInstallment,
}: HistorySectionProps) {
const splitEndorsementStats = useMemo(() => {
const splits = formData.installments.filter(inst => inst.type === 'splitEndorsement');
const totalAmount = splits.reduce((sum, inst) => sum + inst.amount, 0);
return { count: splits.length, totalAmount, remaining: formData.amount - totalAmount };
}, [formData.installments, formData.amount]);
return (
<Card className="mb-6">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-lg"> </CardTitle>
{!isViewMode && (
<Button variant="outline" size="sm" onClick={onAddInstallment} className="text-orange-500 border-orange-300 hover:bg-orange-50">
<Plus className="h-4 w-4 mr-1" />
</Button>
)}
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* 분할배서 토글 */}
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Switch checked={formData.isSplit} onCheckedChange={(c) => updateField('isSplit', c)} disabled={isViewMode} />
<Label> </Label>
{formData.isSplit && (
<Badge variant="outline" className="text-xs text-amber-600 border-amber-300 bg-amber-50">
{maxSplitCount}
</Badge>
)}
</div>
{formData.isSplit && isElectronic && (
<div className="flex items-center gap-2 text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded-md px-3 py-2">
<AlertTriangle className="h-3.5 w-3.5 flex-shrink-0" />
<span> 분할배서: 최초 5 ( 6)</span>
</div>
)}
{formData.isSplit && splitEndorsementStats.count > 0 && (
<div className="flex items-center gap-4 text-sm bg-gray-50 rounded-md px-3 py-2">
<span className="text-muted-foreground">:</span>
<span className="font-semibold"> {formData.amount.toLocaleString()}</span>
<span className="text-muted-foreground">| :</span>
<span className="font-semibold text-blue-600"> {splitEndorsementStats.totalAmount.toLocaleString()}</span>
<span className="text-muted-foreground">| :</span>
<span className={`font-semibold ${splitEndorsementStats.remaining < 0 ? 'text-red-600' : 'text-green-600'}`}>
{splitEndorsementStats.remaining.toLocaleString()}
</span>
{splitEndorsementStats.remaining < 0 && (
<span className="text-red-500 text-xs flex items-center gap-1"><AlertTriangle className="h-3 w-3" /> </span>
)}
</div>
)}
</div>
{/* 이력 테이블 */}
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]">No</TableHead>
<TableHead className="min-w-[130px]"></TableHead>
<TableHead className="min-w-[130px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
{!isViewMode && <TableHead className="w-[60px]"></TableHead>}
</TableRow>
</TableHeader>
<TableBody>
{formData.installments.length === 0 ? (
<TableRow>
<TableCell colSpan={isViewMode ? 6 : 7} className="text-center text-gray-500 py-8"> </TableCell>
</TableRow>
) : formData.installments.map((inst, idx) => (
<TableRow key={inst.id} className={inst.type === 'splitEndorsement' ? 'bg-amber-50/50' : ''}>
<TableCell className="text-center">{idx + 1}</TableCell>
<TableCell>
<DatePicker value={inst.date} onChange={(d) => onUpdateInstallment(inst.id, 'date', d)} size="sm" disabled={isViewMode} />
</TableCell>
<TableCell>
<Select value={inst.type} onValueChange={(v) => onUpdateInstallment(inst.id, 'type', v)} disabled={isViewMode}>
<SelectTrigger className="h-8 text-sm"><SelectValue /></SelectTrigger>
<SelectContent>
{HISTORY_TYPE_OPTIONS
.filter(o => o.value !== 'splitEndorsement' || formData.isSplit)
.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)
}
</SelectContent>
</Select>
</TableCell>
<TableCell>
<CurrencyInput value={inst.amount} onChange={(v) => onUpdateInstallment(inst.id, 'amount', v ?? 0)} className="h-8 text-sm" disabled={isViewMode} />
</TableCell>
<TableCell>
<Input value={inst.counterparty} onChange={(e) => onUpdateInstallment(inst.id, 'counterparty', e.target.value)} placeholder="거래처/은행" className="h-8 text-sm" disabled={isViewMode} />
</TableCell>
<TableCell>
<Input value={inst.note} onChange={(e) => onUpdateInstallment(inst.id, 'note', e.target.value)} className="h-8 text-sm" disabled={isViewMode} />
</TableCell>
{!isViewMode && (
<TableCell>
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-500 hover:text-red-600 hover:bg-red-50" onClick={() => onRemoveInstallment(inst.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
</Card>
);
}