- 일반전표입력, 상품권관리, 세금계산서 발행/조회 신규 페이지 추가 - 바로빌 연동 설정 페이지 추가 - 카드관리/계좌관리 리스트 UniversalListPage 공통 구조로 전환 - 카드거래조회/은행거래조회 리팩토링 (모달 분리, 액션 확장) - 계좌 상세 폼(AccountDetailForm) 신규 구현 - 카드 상세(CardDetail) 신규 구현 + CardNumberInput 적용 - DateRangeSelector, StatCards, IntegratedListTemplateV2 공통 컴포넌트 개선 - 레거시 파일 정리 (CardManagementUnified, cardConfig, _legacy 등) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
815 lines
27 KiB
TypeScript
815 lines
27 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* AccountDetailForm - 계좌 등록/수정/보기 조건부 폼
|
|
*
|
|
* 구분(category) 선택에 따라 유형(accountType) 옵션과 하단 상세 섹션이 동적 변경됨
|
|
* - 은행계좌: 계좌 정보 (계약금액, 이율, 시작일, 만기일, 이월잔액)
|
|
* - 대출계좌: 대출 정보 (대출금액, 이율, 상환방식, 거치기간 등)
|
|
* - 증권계좌: 증권 정보 (투자금액, 수익율, 평가액)
|
|
* - 보험계좌: 보험 정보 (단체/화재/CEO별 다른 필드)
|
|
*/
|
|
|
|
import { useState, useCallback, useMemo } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Landmark, Save, Trash2, ArrowLeft } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
|
import { PageLayout } from '@/components/organisms/PageLayout';
|
|
import { PageHeader } from '@/components/organisms/PageHeader';
|
|
import { FormField } from '@/components/molecules/FormField';
|
|
import type { Account, AccountCategory, AccountFormData } from './types';
|
|
import {
|
|
ACCOUNT_CATEGORY_OPTIONS,
|
|
ACCOUNT_TYPE_OPTIONS_BY_CATEGORY,
|
|
ACCOUNT_STATUS_OPTIONS,
|
|
FINANCIAL_INSTITUTION_OPTIONS_BY_CATEGORY,
|
|
BANK_LABELS,
|
|
HOLDER_LABEL_BY_CATEGORY,
|
|
} from './types';
|
|
|
|
// ===== Props =====
|
|
interface AccountDetailFormProps {
|
|
mode: 'create' | 'edit' | 'view';
|
|
initialData?: Account;
|
|
onSubmit: (data: AccountFormData) => Promise<{ success: boolean; error?: string }>;
|
|
onDelete?: () => Promise<{ success: boolean; error?: string }>;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
// ===== 초기 폼 데이터 생성 =====
|
|
function getInitialFormData(initialData?: Account): AccountFormData {
|
|
if (initialData) {
|
|
return {
|
|
category: initialData.category || 'bank_account',
|
|
accountType: initialData.accountType || '',
|
|
bankCode: initialData.bankCode || '',
|
|
bankName: initialData.bankName || '',
|
|
accountNumber: initialData.accountNumber || '',
|
|
accountName: initialData.accountName || '',
|
|
accountHolder: initialData.accountHolder || '',
|
|
status: initialData.status || 'active',
|
|
contractAmount: initialData.contractAmount,
|
|
interestRate: initialData.interestRate,
|
|
startDate: initialData.startDate,
|
|
maturityDate: initialData.maturityDate,
|
|
carryoverBalance: initialData.carryoverBalance,
|
|
loanAmount: initialData.loanAmount,
|
|
loanBalance: initialData.loanBalance,
|
|
interestPaymentCycle: initialData.interestPaymentCycle,
|
|
repaymentMethod: initialData.repaymentMethod,
|
|
gracePeriod: initialData.gracePeriod,
|
|
monthlyRepayment: initialData.monthlyRepayment,
|
|
collateral: initialData.collateral,
|
|
investmentAmount: initialData.investmentAmount,
|
|
returnRate: initialData.returnRate,
|
|
evaluationAmount: initialData.evaluationAmount,
|
|
surrenderValue: initialData.surrenderValue,
|
|
policyNumber: initialData.policyNumber,
|
|
paymentCycle: initialData.paymentCycle,
|
|
premiumPerCycle: initialData.premiumPerCycle,
|
|
premiumPerPerson: initialData.premiumPerPerson,
|
|
enrolledCount: initialData.enrolledCount,
|
|
insuredProperty: initialData.insuredProperty,
|
|
propertyAddress: initialData.propertyAddress,
|
|
beneficiary: initialData.beneficiary,
|
|
note: initialData.note,
|
|
};
|
|
}
|
|
return {
|
|
category: 'bank_account',
|
|
accountType: '',
|
|
bankCode: '',
|
|
bankName: '',
|
|
accountNumber: '',
|
|
accountName: '',
|
|
accountHolder: '',
|
|
status: 'active',
|
|
};
|
|
}
|
|
|
|
export function AccountDetailForm({
|
|
mode: initialMode,
|
|
initialData,
|
|
onSubmit,
|
|
onDelete,
|
|
isLoading,
|
|
}: AccountDetailFormProps) {
|
|
const router = useRouter();
|
|
const [mode, setMode] = useState(initialMode);
|
|
const [formData, setFormData] = useState<AccountFormData>(() => getInitialFormData(initialData));
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
|
|
const isViewMode = mode === 'view';
|
|
const isCreateMode = mode === 'create';
|
|
const disabled = isViewMode;
|
|
|
|
// ===== 구분별 동적 옵션 =====
|
|
const typeOptions = useMemo(
|
|
() => ACCOUNT_TYPE_OPTIONS_BY_CATEGORY[formData.category] || [],
|
|
[formData.category]
|
|
);
|
|
|
|
const institutionOptions = useMemo(
|
|
() => FINANCIAL_INSTITUTION_OPTIONS_BY_CATEGORY[formData.category] || [],
|
|
[formData.category]
|
|
);
|
|
|
|
const holderLabel = HOLDER_LABEL_BY_CATEGORY[formData.category] || '예금주';
|
|
|
|
// ===== 핸들러 =====
|
|
const handleChange = useCallback((field: keyof AccountFormData, value: string | number | undefined) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
}, []);
|
|
|
|
const handleCategoryChange = useCallback((value: string) => {
|
|
const category = value as AccountCategory;
|
|
setFormData(prev => ({
|
|
...prev,
|
|
category,
|
|
accountType: '',
|
|
bankCode: '',
|
|
bankName: '',
|
|
}));
|
|
}, []);
|
|
|
|
const handleBankCodeChange = useCallback((value: string) => {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
bankCode: value,
|
|
bankName: BANK_LABELS[value] || value,
|
|
}));
|
|
}, []);
|
|
|
|
const handleSubmit = useCallback(async () => {
|
|
if (!formData.category || !formData.bankCode || !formData.accountNumber) {
|
|
toast.error('필수 항목을 입력해주세요.');
|
|
return;
|
|
}
|
|
setIsSaving(true);
|
|
try {
|
|
const result = await onSubmit(formData);
|
|
if (result.success) {
|
|
toast.success(isCreateMode ? '계좌가 등록되었습니다.' : '계좌가 수정되었습니다.');
|
|
router.push('/ko/settings/accounts');
|
|
} else {
|
|
toast.error(result.error || '저장에 실패했습니다.');
|
|
}
|
|
} catch {
|
|
toast.error('서버 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}, [formData, onSubmit, isCreateMode, router]);
|
|
|
|
const handleDelete = useCallback(async () => {
|
|
if (!onDelete) return;
|
|
setIsSaving(true);
|
|
try {
|
|
const result = await onDelete();
|
|
if (result.success) {
|
|
toast.success('계좌가 삭제되었습니다.');
|
|
router.push('/ko/settings/accounts');
|
|
} else {
|
|
toast.error(result.error || '삭제에 실패했습니다.');
|
|
}
|
|
} catch {
|
|
toast.error('서버 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
setShowDeleteDialog(false);
|
|
}
|
|
}, [onDelete, router]);
|
|
|
|
const handleBack = useCallback(() => {
|
|
router.push('/ko/settings/accounts');
|
|
}, [router]);
|
|
|
|
const handleEdit = useCallback(() => {
|
|
setMode('edit');
|
|
if (initialData?.id) {
|
|
router.push(`/ko/settings/accounts/${initialData.id}?mode=edit`);
|
|
}
|
|
}, [initialData?.id, router]);
|
|
|
|
// ===== 로딩 =====
|
|
if (isLoading) {
|
|
return (
|
|
<PageLayout>
|
|
<div className="animate-pulse space-y-4">
|
|
<div className="h-8 bg-muted rounded w-1/3" />
|
|
<div className="h-64 bg-muted rounded" />
|
|
</div>
|
|
</PageLayout>
|
|
);
|
|
}
|
|
|
|
// ===== 렌더링 =====
|
|
return (
|
|
<PageLayout>
|
|
<PageHeader
|
|
title={isCreateMode ? '수기 계좌 등록' : isViewMode ? '계좌 상세' : '계좌 수정'}
|
|
description="계좌 정보를 관리합니다"
|
|
icon={Landmark}
|
|
/>
|
|
|
|
<div className="space-y-6">
|
|
{/* ===== 기본 정보 ===== */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">기본 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* 구분 & 유형 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="구분"
|
|
type="select"
|
|
required
|
|
value={formData.category}
|
|
onChange={handleCategoryChange}
|
|
options={ACCOUNT_CATEGORY_OPTIONS}
|
|
selectPlaceholder="구분 선택"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
key={`type-${formData.category}`}
|
|
label="유형"
|
|
type="select"
|
|
value={formData.accountType}
|
|
onChange={(v) => handleChange('accountType', v)}
|
|
options={typeOptions}
|
|
selectPlaceholder="유형 선택"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
|
|
{/* 금융기관 & 계좌번호 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
key={`bank-${formData.category}`}
|
|
label="금융기관"
|
|
type="select"
|
|
required
|
|
value={formData.bankCode}
|
|
onChange={handleBankCodeChange}
|
|
options={institutionOptions}
|
|
selectPlaceholder="금융기관 선택"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="계좌번호"
|
|
type="text"
|
|
required
|
|
value={formData.accountNumber}
|
|
onChange={(v) => handleChange('accountNumber', v)}
|
|
placeholder="계좌번호 입력"
|
|
disabled={disabled || mode === 'edit'}
|
|
/>
|
|
</div>
|
|
|
|
{/* 계좌명 & 예금주/계약자/피보험자 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="계좌명(상용명)"
|
|
type="text"
|
|
value={formData.accountName}
|
|
onChange={(v) => handleChange('accountName', v)}
|
|
placeholder="계좌명 입력"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label={holderLabel}
|
|
type="text"
|
|
value={formData.accountHolder}
|
|
onChange={(v) => handleChange('accountHolder', v)}
|
|
placeholder={`${holderLabel} 입력`}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
|
|
{/* 상태 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="상태"
|
|
type="select"
|
|
required
|
|
value={formData.status}
|
|
onChange={(v) => handleChange('status', v)}
|
|
options={ACCOUNT_STATUS_OPTIONS}
|
|
selectPlaceholder="상태 선택"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* ===== 구분별 상세 정보 ===== */}
|
|
{formData.category === 'bank_account' && (
|
|
<BankAccountSection formData={formData} onChange={handleChange} disabled={disabled} />
|
|
)}
|
|
{formData.category === 'loan_account' && (
|
|
<LoanAccountSection formData={formData} onChange={handleChange} disabled={disabled} />
|
|
)}
|
|
{formData.category === 'securities_account' && (
|
|
<SecuritiesAccountSection formData={formData} onChange={handleChange} disabled={disabled} />
|
|
)}
|
|
{formData.category === 'insurance_account' && (
|
|
<InsuranceAccountSection formData={formData} onChange={handleChange} disabled={disabled} />
|
|
)}
|
|
|
|
{/* ===== 하단 버튼 ===== */}
|
|
<div className="flex items-center justify-between">
|
|
<Button variant="outline" onClick={handleBack}>
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
목록으로
|
|
</Button>
|
|
<div className="flex items-center gap-2">
|
|
{isViewMode ? (
|
|
<>
|
|
{onDelete && (
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
|
>
|
|
<Trash2 className="w-4 h-4 mr-2" />
|
|
삭제
|
|
</Button>
|
|
)}
|
|
<Button onClick={handleEdit}>수정</Button>
|
|
</>
|
|
) : (
|
|
<>
|
|
{!isCreateMode && onDelete && (
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
|
>
|
|
<Trash2 className="w-4 h-4 mr-2" />
|
|
삭제
|
|
</Button>
|
|
)}
|
|
<Button onClick={handleSubmit} disabled={isSaving}>
|
|
<Save className="w-4 h-4 mr-2" />
|
|
{isCreateMode ? '등록' : '저장'}
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ===== 삭제 확인 ===== */}
|
|
<DeleteConfirmDialog
|
|
open={showDeleteDialog}
|
|
onOpenChange={setShowDeleteDialog}
|
|
onConfirm={handleDelete}
|
|
title="계좌 삭제"
|
|
description="계좌를 정말 삭제하시겠습니까? 삭제된 계좌의 과거 사용 내역은 보존됩니다."
|
|
loading={isSaving}
|
|
/>
|
|
</PageLayout>
|
|
);
|
|
}
|
|
|
|
// ============================================
|
|
// 구분별 상세 섹션 컴포넌트
|
|
// ============================================
|
|
|
|
interface SectionProps {
|
|
formData: AccountFormData;
|
|
onChange: (field: keyof AccountFormData, value: string | number | undefined) => void;
|
|
disabled: boolean;
|
|
}
|
|
|
|
// ===== 은행계좌 정보 =====
|
|
function BankAccountSection({ formData, onChange, disabled }: SectionProps) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">계좌 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="계약금액"
|
|
type="currency"
|
|
value={formData.contractAmount}
|
|
onChangeNumber={(v) => onChange('contractAmount', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="이율(%)"
|
|
type="number"
|
|
value={formData.interestRate != null ? String(formData.interestRate) : ''}
|
|
onChange={(v) => onChange('interestRate', v ? Number(v) : undefined)}
|
|
placeholder="0.00"
|
|
disabled={disabled}
|
|
allowDecimal
|
|
decimalPlaces={2}
|
|
suffix="%"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="시작일"
|
|
type="date"
|
|
value={formData.startDate || ''}
|
|
onChange={(v) => onChange('startDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="만기일"
|
|
type="date"
|
|
value={formData.maturityDate || ''}
|
|
onChange={(v) => onChange('maturityDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="이월잔액"
|
|
type="currency"
|
|
value={formData.carryoverBalance}
|
|
onChangeNumber={(v) => onChange('carryoverBalance', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="비고"
|
|
type="text"
|
|
value={formData.note || ''}
|
|
onChange={(v) => onChange('note', v)}
|
|
placeholder="비고"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ===== 대출계좌 정보 =====
|
|
function LoanAccountSection({ formData, onChange, disabled }: SectionProps) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">대출 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="대출금액"
|
|
type="currency"
|
|
value={formData.loanAmount}
|
|
onChangeNumber={(v) => onChange('loanAmount', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="이율(%)"
|
|
type="number"
|
|
value={formData.interestRate != null ? String(formData.interestRate) : ''}
|
|
onChange={(v) => onChange('interestRate', v ? Number(v) : undefined)}
|
|
placeholder="0.00"
|
|
disabled={disabled}
|
|
allowDecimal
|
|
decimalPlaces={2}
|
|
suffix="%"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="시작일"
|
|
type="date"
|
|
value={formData.startDate || ''}
|
|
onChange={(v) => onChange('startDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="만기일"
|
|
type="date"
|
|
value={formData.maturityDate || ''}
|
|
onChange={(v) => onChange('maturityDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="대출잔액"
|
|
type="currency"
|
|
value={formData.loanBalance}
|
|
onChangeNumber={(v) => onChange('loanBalance', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="이자 납입 주기"
|
|
type="select"
|
|
value={formData.interestPaymentCycle || ''}
|
|
onChange={(v) => onChange('interestPaymentCycle', v)}
|
|
options={[
|
|
{ value: 'monthly', label: '월납' },
|
|
{ value: 'quarterly', label: '분기납' },
|
|
{ value: 'semi_annual', label: '반기납' },
|
|
{ value: 'annual', label: '연납' },
|
|
]}
|
|
selectPlaceholder="주기 선택"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="상환 방식"
|
|
type="select"
|
|
value={formData.repaymentMethod || ''}
|
|
onChange={(v) => onChange('repaymentMethod', v)}
|
|
options={[
|
|
{ value: 'equal_principal', label: '원금균등' },
|
|
{ value: 'equal_installment', label: '원리금균등' },
|
|
{ value: 'bullet', label: '만기일시' },
|
|
{ value: 'other', label: '기타' },
|
|
]}
|
|
selectPlaceholder="방식 선택"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="거치 기간"
|
|
type="text"
|
|
value={formData.gracePeriod || ''}
|
|
onChange={(v) => onChange('gracePeriod', v)}
|
|
placeholder="예: 6개월"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="월 상환액"
|
|
type="currency"
|
|
value={formData.monthlyRepayment}
|
|
onChangeNumber={(v) => onChange('monthlyRepayment', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="담보물"
|
|
type="text"
|
|
value={formData.collateral || ''}
|
|
onChange={(v) => onChange('collateral', v)}
|
|
placeholder="담보물 입력"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="비고"
|
|
type="text"
|
|
value={formData.note || ''}
|
|
onChange={(v) => onChange('note', v)}
|
|
placeholder="비고"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ===== 증권계좌 정보 =====
|
|
function SecuritiesAccountSection({ formData, onChange, disabled }: SectionProps) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">증권 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="투자금액"
|
|
type="currency"
|
|
value={formData.investmentAmount}
|
|
onChangeNumber={(v) => onChange('investmentAmount', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="수익율(%)"
|
|
type="number"
|
|
value={formData.returnRate != null ? String(formData.returnRate) : ''}
|
|
onChange={(v) => onChange('returnRate', v ? Number(v) : undefined)}
|
|
placeholder="0.00"
|
|
disabled={disabled}
|
|
allowDecimal
|
|
decimalPlaces={2}
|
|
suffix="%"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="시작일"
|
|
type="date"
|
|
value={formData.startDate || ''}
|
|
onChange={(v) => onChange('startDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="만기일"
|
|
type="date"
|
|
value={formData.maturityDate || ''}
|
|
onChange={(v) => onChange('maturityDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="평가액"
|
|
type="currency"
|
|
value={formData.evaluationAmount}
|
|
onChangeNumber={(v) => onChange('evaluationAmount', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="비고"
|
|
type="text"
|
|
value={formData.note || ''}
|
|
onChange={(v) => onChange('note', v)}
|
|
placeholder="비고"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ===== 보험계좌 정보 =====
|
|
function InsuranceAccountSection({ formData, onChange, disabled }: SectionProps) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">보험 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* 공통 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="계약금액"
|
|
type="currency"
|
|
value={formData.contractAmount}
|
|
onChangeNumber={(v) => onChange('contractAmount', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="이율(%)"
|
|
type="number"
|
|
value={formData.interestRate != null ? String(formData.interestRate) : ''}
|
|
onChange={(v) => onChange('interestRate', v ? Number(v) : undefined)}
|
|
placeholder="0.00"
|
|
disabled={disabled}
|
|
allowDecimal
|
|
decimalPlaces={2}
|
|
suffix="%"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="시작일"
|
|
type="date"
|
|
value={formData.startDate || ''}
|
|
onChange={(v) => onChange('startDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="만기일"
|
|
type="date"
|
|
value={formData.maturityDate || ''}
|
|
onChange={(v) => onChange('maturityDate', v)}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="해약환급금"
|
|
type="currency"
|
|
value={formData.surrenderValue}
|
|
onChangeNumber={(v) => onChange('surrenderValue', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="증권번호"
|
|
type="text"
|
|
value={formData.policyNumber || ''}
|
|
onChange={(v) => onChange('policyNumber', v)}
|
|
placeholder="증권번호"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="납입 주기"
|
|
type="select"
|
|
value={formData.paymentCycle || ''}
|
|
onChange={(v) => onChange('paymentCycle', v)}
|
|
options={[
|
|
{ value: 'monthly', label: '월납' },
|
|
{ value: 'quarterly', label: '분기납' },
|
|
{ value: 'semi_annual', label: '반기납' },
|
|
{ value: 'annual', label: '연납' },
|
|
{ value: 'lump_sum', label: '일시납' },
|
|
]}
|
|
selectPlaceholder="주기 선택"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="납입 주기당 보험료"
|
|
type="currency"
|
|
value={formData.premiumPerCycle}
|
|
onChangeNumber={(v) => onChange('premiumPerCycle', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
|
|
{/* 단체보험 전용 */}
|
|
{formData.accountType === 'group_insurance' && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="1인당 보험료"
|
|
type="currency"
|
|
value={formData.premiumPerPerson}
|
|
onChangeNumber={(v) => onChange('premiumPerPerson', v)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="가입 인원"
|
|
type="number"
|
|
value={formData.enrolledCount != null ? String(formData.enrolledCount) : ''}
|
|
onChange={(v) => onChange('enrolledCount', v ? Number(v) : undefined)}
|
|
placeholder="0"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* 화재보험 전용 */}
|
|
{formData.accountType === 'fire_insurance' && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="보험 대상물"
|
|
type="text"
|
|
value={formData.insuredProperty || ''}
|
|
onChange={(v) => onChange('insuredProperty', v)}
|
|
placeholder="보험 대상물 입력"
|
|
disabled={disabled}
|
|
/>
|
|
<FormField
|
|
label="대상물 주소"
|
|
type="text"
|
|
value={formData.propertyAddress || ''}
|
|
onChange={(v) => onChange('propertyAddress', v)}
|
|
placeholder="주소 입력"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* CEO보험 전용 */}
|
|
{formData.accountType === 'ceo_insurance' && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="수익자"
|
|
type="text"
|
|
value={formData.beneficiary || ''}
|
|
onChange={(v) => onChange('beneficiary', v)}
|
|
placeholder="수익자 입력"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* 비고 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<FormField
|
|
label="비고"
|
|
type="text"
|
|
value={formData.note || ''}
|
|
onChange={(v) => onChange('note', v)}
|
|
placeholder="비고"
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|