- BillManagement: BillDetail 리팩토링, sections/hooks 분리, constants 추가 - BillManagement types 대폭 확장, actions 개선 - GiftCertificateManagement: actions/types 확장 - CEO 대시보드: SummaryNavBar 컴포넌트 추가, useSectionSummary 훅 - bill-prototype 개발 페이지 업데이트
289 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|