diff --git a/src/components/hr/CardManagement/CardDetail.tsx b/src/components/hr/CardManagement/CardDetail.tsx index 759de542..29853597 100644 --- a/src/components/hr/CardManagement/CardDetail.tsx +++ b/src/components/hr/CardManagement/CardDetail.tsx @@ -25,6 +25,7 @@ import { PageHeader } from '@/components/organisms/PageHeader'; import { ContentSkeleton } from '@/components/ui/skeleton'; import { toast } from 'sonner'; import { formatAmountWon as formatCurrency } from '@/lib/utils/amount'; +import type { FieldErrors } from '@/lib/api/execute-server-action'; import type { Card as CardType, CardFormData, CardStatus } from './types'; import { CARD_COMPANIES, @@ -59,6 +60,25 @@ function getCardTypeLabel(value: string): string { return option?.label || value || '-'; } +// 폼 필드(camelCase) → API 필드(snake_case) 매핑 +const FORM_TO_API_FIELD: Record = { + cardCompany: 'card_company', + cardType: 'card_type', + cardNumber: 'card_number', + cardName: 'card_name', + alias: 'alias', + expiryDate: 'expiry_date', + csv: 'csv', + paymentDay: 'payment_day', + pinPrefix: 'card_password', + totalLimit: 'total_limit', + usedAmount: 'used_amount', + remainingLimit: 'remaining_limit', + status: 'status', + userId: 'assigned_user_id', + memo: 'memo', +}; + interface CardDetailProps { card?: CardType; mode: 'create' | 'view' | 'edit'; @@ -72,6 +92,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro const [isSaving, setIsSaving] = useState(false); const [isLoadingApproval, setIsLoadingApproval] = useState(false); const [employees, setEmployees] = useState>([]); + const [fieldErrors, setFieldErrors] = useState({}); useEffect(() => { const urlMode = searchParams.get('mode'); @@ -112,37 +133,46 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro const handleChange = useCallback((field: keyof CardFormData, value: string | number) => { setFormData(prev => ({ ...prev, [field]: value })); + const apiField = FORM_TO_API_FIELD[field] || field; + setFieldErrors(prev => { + if (!prev[apiField]) return prev; + const next = { ...prev }; + delete next[apiField]; + return next; + }); }, []); + // API 필드명으로 에러 메시지 조회 + const getError = (apiField: string) => fieldErrors[apiField]?.[0]; + const handleBack = () => { router.push('/ko/hr/card-management'); }; const handleSubmit = async () => { if (!formData.cardCompany) { + setFieldErrors({ card_company: ['카드사를 선택해주세요.'] }); toast.error('카드사를 선택해주세요.'); return; } + setFieldErrors({}); setIsSaving(true); try { - if (isCreateMode) { - const result = await createCard(formData); - if (result.success) { - toast.success('카드가 등록되었습니다.'); - router.push('/ko/hr/card-management'); - } else { - toast.error(result.error || '카드 등록에 실패했습니다.'); - } + const result = isCreateMode + ? await createCard(formData) + : card?.id ? await updateCard(card.id, formData) : null; + + if (!result) return; + + if (result.success) { + toast.success(isCreateMode ? '카드가 등록되었습니다.' : '카드가 수정되었습니다.'); + router.push('/ko/hr/card-management'); } else { - if (!card?.id) return; - const result = await updateCard(card.id, formData); - if (result.success) { - toast.success('카드가 수정되었습니다.'); - router.push('/ko/hr/card-management'); - } else { - toast.error(result.error || '카드 수정에 실패했습니다.'); + if (result.fieldErrors) { + setFieldErrors(result.fieldErrors); } + toast.error(result.error || (isCreateMode ? '카드 등록에 실패했습니다.' : '카드 수정에 실패했습니다.')); } } catch { toast.error('저장 중 오류가 발생했습니다.'); @@ -158,6 +188,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro }); const handleCancel = () => { + setFieldErrors({}); if (isCreateMode) { router.push('/ko/hr/card-management'); } else { @@ -398,7 +429,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro value={formData.cardCompany} onValueChange={(v) => handleChange('cardCompany', v)} > - + @@ -407,6 +438,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro ))} + {getError('card_company') &&

{getError('card_company')}

}
@@ -414,7 +446,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro value={formData.cardType || '_none'} onValueChange={(v) => handleChange('cardType', v === '_none' ? '' : v)} > - + @@ -424,6 +456,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro ))} + {getError('card_type') &&

{getError('card_type')}

}
@@ -431,14 +464,17 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro value={formData.cardNumber} onChange={(v) => handleChange('cardNumber', v)} /> + {getError('card_number') &&

{getError('card_number')}

}
handleChange('cardName', e.target.value)} placeholder="카드명" /> + {getError('card_name') &&

{getError('card_name')}

}
@@ -447,28 +483,34 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
handleChange('alias', e.target.value)} placeholder="별칭" /> + {getError('alias') &&

{getError('alias')}

}
handleChange('expiryDate', e.target.value)} placeholder="MMYY" maxLength={4} /> + {getError('expiry_date') &&

{getError('expiry_date')}

}
handleChange('csv', e.target.value)} placeholder="CSV" maxLength={4} /> + {getError('csv') &&

{getError('csv')}

}
@@ -476,7 +518,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro value={formData.paymentDay || '_none'} onValueChange={(v) => handleChange('paymentDay', v === '_none' ? '' : v)} > - + @@ -486,6 +528,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro ))} + {getError('payment_day') &&

{getError('payment_day')}

}
@@ -494,29 +537,35 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
handleChange('totalLimit', Number(e.target.value) || 0)} placeholder="0" /> + {getError('total_limit') &&

{getError('total_limit')}

}
handleChange('usedAmount', Number(e.target.value) || 0)} placeholder="0" /> + {getError('used_amount') &&

{getError('used_amount')}

}
handleChange('remainingLimit', Number(e.target.value) || 0)} placeholder="0" /> + {getError('remaining_limit') &&

{getError('remaining_limit')}

}
@@ -524,7 +573,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro value={formData.status} onValueChange={(v) => handleChange('status', v as CardStatus)} > - + @@ -532,6 +581,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro {CARD_STATUS_LABELS.suspended} + {getError('status') &&

{getError('status')}

}
@@ -550,7 +600,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro value={formData.userId || '_none'} onValueChange={(v) => handleChange('userId', v === '_none' ? '' : v)} > - + @@ -560,15 +610,18 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro ))} + {getError('assigned_user_id') &&

{getError('assigned_user_id')}

}