Files
sam-react-prod/src/components/accounting/BillManagement/sections/BasicInfoSection.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

289 lines
13 KiB
TypeScript

'use client';
import { useMemo } from '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 { Switch } from '@/components/ui/switch';
import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from '@/components/ui/select';
import type { SectionProps } from './types';
import {
INSTRUMENT_TYPE_OPTIONS,
DIRECTION_OPTIONS,
MEDIUM_OPTIONS,
ENDORSEMENT_OPTIONS,
BILL_CATEGORY_OPTIONS,
STORAGE_OPTIONS,
PAYMENT_METHOD_OPTIONS,
ENDORSEMENT_ORDER_PAPER,
ENDORSEMENT_ORDER_ELECTRONIC,
} from '../constants';
interface BasicInfoSectionProps extends SectionProps {
clients: { id: string; name: string }[];
conditions: {
isReceived: boolean;
isIssued: boolean;
isCheck: boolean;
isBill: boolean;
canBeElectronic: boolean;
isElectronic: boolean;
receivedStatusOptions: readonly { value: string; label: string }[];
issuedStatusOptions: readonly { value: string; label: string }[];
paymentPlaceOptions: readonly { value: string; label: string }[];
};
onInstrumentTypeChange: (v: string) => void;
onDirectionChange: (v: string) => void;
}
export function BasicInfoSection({
formData, updateField, isViewMode, clients, conditions, onInstrumentTypeChange, onDirectionChange,
}: BasicInfoSectionProps) {
const {
isReceived, isIssued, isCheck, isBill, canBeElectronic, isElectronic,
receivedStatusOptions, issuedStatusOptions, paymentPlaceOptions,
} = conditions;
const endorsementOrderOptions = useMemo(
() => isElectronic ? ENDORSEMENT_ORDER_ELECTRONIC : [...ENDORSEMENT_ORDER_PAPER],
[isElectronic]
);
return (
<Card className="mb-6">
<CardHeader>
<CardTitle className="text-lg"> </CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* 어음번호 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<Input value={formData.billNumber} onChange={(e) => updateField('billNumber', e.target.value)} placeholder="자동생성 또는 직접입력" disabled={isViewMode} />
</div>
{/* 증권종류 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<Select value={formData.instrumentType} onValueChange={onInstrumentTypeChange} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{INSTRUMENT_TYPE_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
{/* 거래방향 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<Select value={formData.direction} onValueChange={onDirectionChange} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{DIRECTION_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
{/* 전자/지류 */}
<div className="space-y-2">
<Label>/ <span className="text-red-500">*</span>
{!canBeElectronic && <span className="text-xs text-muted-foreground ml-1">(전자어음법: 약속어음만 )</span>}
</Label>
<Select value={formData.medium} onValueChange={(v) => updateField('medium', v as 'electronic' | 'paper')} disabled={isViewMode || !canBeElectronic}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{MEDIUM_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
{/* 거래처 */}
<div className="space-y-2">
<Label>{isReceived ? '거래처 (발행인)' : '수취인 (거래처)'} <span className="text-red-500">*</span></Label>
<Select
value={isReceived ? formData.vendor : formData.payee}
onValueChange={(v) => updateField(isReceived ? 'vendor' : 'payee', v)}
disabled={isViewMode}
>
<SelectTrigger><SelectValue placeholder="선택" /></SelectTrigger>
<SelectContent>
{clients.map(c => <SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>)}
</SelectContent>
</Select>
</div>
{/* 금액 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<CurrencyInput value={formData.amount} onChange={(v) => updateField('amount', v ?? 0)} disabled={isViewMode} />
</div>
{/* 발행일 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<DatePicker value={formData.issueDate} onChange={(d) => updateField('issueDate', d)} disabled={isViewMode} />
</div>
{/* 만기일 (수표는 일람출급이므로 없음) */}
{isBill && (
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<DatePicker value={formData.maturityDate} onChange={(d) => updateField('maturityDate', d)} disabled={isViewMode} />
</div>
)}
{/* 은행 */}
<div className="space-y-2">
<Label>{isReceived ? '발행은행' : '결제은행'}</Label>
<Input
value={isReceived ? formData.issuerBank : formData.settlementBank}
onChange={(e) => updateField(isReceived ? 'issuerBank' : 'settlementBank', e.target.value)}
placeholder={isReceived ? '예: 국민은행' : '예: 신한은행'}
disabled={isViewMode}
/>
</div>
{/* 지급장소 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span>
{isCheck && <span className="text-xs text-muted-foreground ml-1">(수표: 은행만)</span>}
</Label>
<Select value={formData.paymentPlace} onValueChange={(v) => updateField('paymentPlace', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue placeholder="선택" /></SelectTrigger>
<SelectContent>
{paymentPlaceOptions.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
{/* 지급장소 상세 */}
{formData.paymentPlace === 'other' && (
<div className="space-y-2">
<Label> </Label>
<Input value={formData.paymentPlaceDetail} onChange={(e) => updateField('paymentPlaceDetail', e.target.value)} placeholder="지급장소를 직접 입력" disabled={isViewMode} />
</div>
)}
{/* 어음구분 (어음만) */}
{isBill && (
<div className="space-y-2">
<Label></Label>
<Select value={formData.billCategory} onValueChange={(v) => updateField('billCategory', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{BILL_CATEGORY_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
)}
{/* ===== 받을어음 전용 필드 ===== */}
{isReceived && (
<>
<div className="space-y-2">
<Label> </Label>
<Select value={formData.endorsement} onValueChange={(v) => updateField('endorsement', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{ENDORSEMENT_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Select value={formData.endorsementOrder} onValueChange={(v) => updateField('endorsementOrder', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{endorsementOrderOptions.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Select value={formData.storagePlace} onValueChange={(v) => updateField('storagePlace', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue placeholder="선택" /></SelectTrigger>
<SelectContent>
{STORAGE_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<Select value={formData.receivedStatus} onValueChange={(v) => updateField('receivedStatus', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{receivedStatusOptions.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
{/* 할인여부 (수표 제외) */}
{isBill && (
<div className="space-y-2">
<Label></Label>
<div className="h-10 flex items-center gap-3 px-3 border rounded-md bg-gray-50">
<Switch checked={formData.isDiscounted} onCheckedChange={(c) => {
updateField('isDiscounted', c);
if (c) updateField('receivedStatus', 'discounted');
}} disabled={isViewMode} />
<span className="text-sm">{formData.isDiscounted ? '할인 적용' : '미적용'}</span>
</div>
</div>
)}
</>
)}
{/* ===== 지급어음 전용 필드 ===== */}
{isIssued && (
<>
<div className="space-y-2">
<Label></Label>
<Select value={formData.paymentMethod} onValueChange={(v) => updateField('paymentMethod', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{PAYMENT_METHOD_OPTIONS.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<Select value={formData.issuedStatus} onValueChange={(v) => updateField('issuedStatus', v)} disabled={isViewMode}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{issuedStatusOptions.map(o => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<DatePicker value={formData.actualPaymentDate} onChange={(d) => updateField('actualPaymentDate', d)} disabled={isViewMode} />
</div>
</>
)}
{/* 입출금 계좌 */}
<div className="space-y-2">
<Label>/ </Label>
<Input value={formData.bankAccountInfo} onChange={(e) => updateField('bankAccountInfo', e.target.value)} placeholder="계좌 정보" disabled={isViewMode} />
</div>
{/* 비고 */}
<div className="space-y-2 lg:col-span-2">
<Label></Label>
<Input value={formData.note} onChange={(e) => updateField('note', e.target.value)} placeholder="비고를 입력해주세요" disabled={isViewMode} />
</div>
</div>
</CardContent>
</Card>
);
}