ERP-node/frontend/components/admin/CompanyFormModal.tsx

281 lines
9.2 KiB
TypeScript
Raw Normal View History

2025-08-21 09:41:46 +09:00
import { useState } from "react";
import { CompanyModalState, CompanyFormData } from "@/types/company";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
2025-11-05 16:36:32 +09:00
import {
ResizableDialog,
ResizableDialogContent,
ResizableDialogHeader,
ResizableDialogTitle,
ResizableDialogDescription,
ResizableDialogFooter,
} from "@/components/ui/resizable-dialog";
2025-08-21 09:41:46 +09:00
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
import { validateBusinessNumber, formatBusinessNumber } from "@/lib/validation/businessNumber";
2025-08-21 09:41:46 +09:00
interface CompanyFormModalProps {
modalState: CompanyModalState;
isLoading: boolean;
error: string | null;
onClose: () => void;
onSave: () => Promise<boolean>;
onFormChange: (field: keyof CompanyFormData, value: string) => void;
onClearError: () => void;
}
/**
* /
*/
export function CompanyFormModal({
modalState,
isLoading,
error,
onClose,
onSave,
onFormChange,
onClearError,
}: CompanyFormModalProps) {
const [isSaving, setIsSaving] = useState(false);
const [businessNumberError, setBusinessNumberError] = useState<string>("");
2025-08-21 09:41:46 +09:00
// 모달이 열려있지 않으면 렌더링하지 않음
if (!modalState.isOpen) return null;
const { mode, formData, selectedCompany } = modalState;
const isEditMode = mode === "edit";
// 사업자등록번호 변경 처리
const handleBusinessNumberChange = (value: string) => {
// 자동 포맷팅
const formatted = formatBusinessNumber(value);
onFormChange("business_registration_number", formatted);
// 유효성 검사 (10자리가 다 입력되었을 때만)
const cleaned = formatted.replace(/-/g, "");
if (cleaned.length === 10) {
const validation = validateBusinessNumber(formatted);
setBusinessNumberError(validation.isValid ? "" : validation.message);
} else if (cleaned.length < 10 && businessNumberError) {
// 10자리 미만이면 에러 초기화
setBusinessNumberError("");
}
};
2025-08-21 09:41:46 +09:00
// 저장 처리
const handleSave = async () => {
// 입력값 검증 (필수 필드)
2025-08-21 09:41:46 +09:00
if (!formData.company_name.trim()) {
return;
}
if (!formData.business_registration_number.trim()) {
return;
}
// 사업자등록번호 최종 검증
const validation = validateBusinessNumber(formData.business_registration_number);
if (!validation.isValid) {
setBusinessNumberError(validation.message);
return;
}
2025-08-21 09:41:46 +09:00
setIsSaving(true);
onClearError();
setBusinessNumberError("");
2025-08-21 09:41:46 +09:00
try {
const success = await onSave();
if (success) {
// 성공 시 모달 닫기
onClose();
}
} catch (err) {
// 에러는 부모 컴포넌트에서 처리
} finally {
setIsSaving(false);
}
};
// 취소 처리
const handleCancel = () => {
onClearError();
onClose();
};
// Enter 키 처리
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !isLoading && !isSaving) {
e.preventDefault();
handleSave();
}
};
return (
2025-11-05 16:36:32 +09:00
<ResizableDialog open={modalState.isOpen} onOpenChange={handleCancel}>
<ResizableDialogContent
className="sm:max-w-[425px]"
onKeyDown={handleKeyDown}
defaultWidth={500}
defaultHeight={600}
minWidth={400}
minHeight={500}
maxWidth={700}
maxHeight={800}
modalId="company-form"
userId={modalState.companyCode}
>
<ResizableDialogHeader>
<ResizableDialogTitle>{isEditMode ? "회사 정보 수정" : "새 회사 등록"}</ResizableDialogTitle>
</ResizableDialogHeader>
2025-08-21 09:41:46 +09:00
<div className="space-y-4 py-4">
{/* 회사명 입력 (필수) */}
2025-08-21 09:41:46 +09:00
<div className="space-y-2">
<Label htmlFor="company_name">
<span className="text-destructive">*</span>
</Label>
<Input
id="company_name"
value={formData.company_name}
onChange={(e) => onFormChange("company_name", e.target.value)}
placeholder="회사명을 입력하세요"
disabled={isLoading || isSaving}
className={error ? "border-destructive" : ""}
autoFocus
/>
</div>
{/* 사업자등록번호 입력 (필수) */}
<div className="space-y-2">
<Label htmlFor="business_registration_number">
<span className="text-destructive">*</span>
</Label>
<Input
id="business_registration_number"
value={formData.business_registration_number || ""}
onChange={(e) => handleBusinessNumberChange(e.target.value)}
placeholder="000-00-00000"
disabled={isLoading || isSaving}
maxLength={12}
className={businessNumberError ? "border-destructive" : ""}
/>
{businessNumberError ? (
<p className="text-xs text-destructive">{businessNumberError}</p>
) : (
<p className="text-xs text-muted-foreground">10 ( )</p>
)}
</div>
{/* 대표자명 입력 */}
<div className="space-y-2">
<Label htmlFor="representative_name"></Label>
<Input
id="representative_name"
value={formData.representative_name || ""}
onChange={(e) => onFormChange("representative_name", e.target.value)}
placeholder="대표자명을 입력하세요"
disabled={isLoading || isSaving}
/>
</div>
{/* 대표 연락처 입력 */}
<div className="space-y-2">
<Label htmlFor="representative_phone"> </Label>
<Input
id="representative_phone"
value={formData.representative_phone || ""}
onChange={(e) => onFormChange("representative_phone", e.target.value)}
placeholder="010-0000-0000"
disabled={isLoading || isSaving}
type="tel"
/>
</div>
{/* 이메일 입력 */}
<div className="space-y-2">
<Label htmlFor="email"></Label>
<Input
id="email"
value={formData.email || ""}
onChange={(e) => onFormChange("email", e.target.value)}
placeholder="company@example.com"
disabled={isLoading || isSaving}
type="email"
/>
</div>
{/* 웹사이트 입력 */}
<div className="space-y-2">
<Label htmlFor="website"></Label>
<Input
id="website"
value={formData.website || ""}
onChange={(e) => onFormChange("website", e.target.value)}
placeholder="https://example.com"
disabled={isLoading || isSaving}
type="url"
/>
</div>
{/* 회사 주소 입력 */}
<div className="space-y-2">
<Label htmlFor="address"> </Label>
<Input
id="address"
value={formData.address || ""}
onChange={(e) => onFormChange("address", e.target.value)}
placeholder="서울특별시 강남구..."
disabled={isLoading || isSaving}
/>
</div>
2025-08-21 09:41:46 +09:00
{/* 에러 메시지 */}
{error && (
<div className="rounded-md bg-destructive/10 p-3">
<p className="text-sm text-destructive">{error}</p>
2025-08-21 09:41:46 +09:00
</div>
)}
{/* 수정 모드일 때 추가 정보 표시 */}
{isEditMode && modalState.selectedCompany && (
<div className="bg-muted/50 rounded-md p-3">
<div className="space-y-1 text-sm">
<p>
<span className="font-medium"> :</span> {modalState.selectedCompany.company_code}
</p>
<p>
<span className="font-medium">:</span> {modalState.selectedCompany.writer}
</p>
<p>
<span className="font-medium">:</span>{" "}
{new Date(modalState.selectedCompany.regdate).toLocaleDateString("ko-KR")}
</p>
</div>
</div>
)}
</div>
2025-11-05 16:36:32 +09:00
<ResizableDialogFooter>
2025-08-21 09:41:46 +09:00
<Button variant="outline" onClick={handleCancel} disabled={isLoading || isSaving}>
</Button>
<Button
onClick={handleSave}
disabled={
isLoading ||
isSaving ||
!formData.company_name.trim() ||
!formData.business_registration_number.trim() ||
!!businessNumberError
}
2025-08-21 09:41:46 +09:00
className="min-w-[80px]"
>
{(isLoading || isSaving) && <LoadingSpinner className="mr-2 h-4 w-4" />}
{isEditMode ? "수정" : "등록"}
</Button>
2025-11-05 16:36:32 +09:00
</ResizableDialogFooter>
</ResizableDialogContent>
</ResizableDialog>
2025-08-21 09:41:46 +09:00
);
}