회사 관리 - 등록 페이지 수정

This commit is contained in:
dohyeons
2025-11-03 14:31:21 +09:00
parent d536fd01da
commit fd7fc754f4
7 changed files with 410 additions and 16 deletions

View File

@@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
import { validateBusinessNumber, formatBusinessNumber } from "@/lib/validation/businessNumber";
interface CompanyFormModalProps {
modalState: CompanyModalState;
@@ -29,6 +30,7 @@ export function CompanyFormModal({
onClearError,
}: CompanyFormModalProps) {
const [isSaving, setIsSaving] = useState(false);
const [businessNumberError, setBusinessNumberError] = useState<string>("");
// 모달이 열려있지 않으면 렌더링하지 않음
if (!modalState.isOpen) return null;
@@ -36,15 +38,43 @@ export function CompanyFormModal({
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("");
}
};
// 저장 처리
const handleSave = async () => {
// 입력값 검증
// 입력값 검증 (필수 필드)
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;
}
setIsSaving(true);
onClearError();
setBusinessNumberError("");
try {
const success = await onSave();
@@ -81,7 +111,7 @@ export function CompanyFormModal({
</DialogHeader>
<div className="space-y-4 py-4">
{/* 회사명 입력 */}
{/* 회사명 입력 (필수) */}
<div className="space-y-2">
<Label htmlFor="company_name">
<span className="text-destructive">*</span>
@@ -97,10 +127,94 @@ export function CompanyFormModal({
/>
</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>
{/* 에러 메시지 */}
{error && (
<div className="bg-destructive/10 rounded-md p-3">
<p className="text-destructive text-sm">{error}</p>
<div className="rounded-md bg-destructive/10 p-3">
<p className="text-sm text-destructive">{error}</p>
</div>
)}
@@ -129,7 +243,13 @@ export function CompanyFormModal({
</Button>
<Button
onClick={handleSave}
disabled={isLoading || isSaving || !formData.company_name.trim()}
disabled={
isLoading ||
isSaving ||
!formData.company_name.trim() ||
!formData.business_registration_number.trim() ||
!!businessNumberError
}
className="min-w-[80px]"
>
{(isLoading || isSaving) && <LoadingSpinner className="mr-2 h-4 w-4" />}

View File

@@ -74,6 +74,12 @@ export const MOCK_COMPANIES: Company[] = [
// 새 회사 등록 시 기본값
export const DEFAULT_COMPANY_FORM_DATA = {
company_name: "",
business_registration_number: "",
representative_name: "",
representative_phone: "",
email: "",
website: "",
address: "",
};
// 페이징 관련 상수

View File

@@ -144,6 +144,12 @@ export const useCompanyManagement = () => {
selectedCompany: company,
formData: {
company_name: company.company_name,
business_registration_number: company.business_registration_number || "",
representative_name: company.representative_name || "",
representative_phone: company.representative_phone || "",
email: company.email || "",
website: company.website || "",
address: company.address || "",
},
});
}, []);
@@ -175,6 +181,10 @@ export const useCompanyManagement = () => {
setError("회사명을 입력해주세요.");
return false;
}
if (!modalState.formData.business_registration_number.trim()) {
setError("사업자등록번호를 입력해주세요.");
return false;
}
setIsLoading(true);
setError(null);
@@ -199,6 +209,10 @@ export const useCompanyManagement = () => {
setError("올바른 데이터를 입력해주세요.");
return false;
}
if (!modalState.formData.business_registration_number.trim()) {
setError("사업자등록번호를 입력해주세요.");
return false;
}
setIsLoading(true);
setError(null);
@@ -206,6 +220,12 @@ export const useCompanyManagement = () => {
try {
await companyAPI.update(modalState.selectedCompany.company_code, {
company_name: modalState.formData.company_name,
business_registration_number: modalState.formData.business_registration_number,
representative_name: modalState.formData.representative_name,
representative_phone: modalState.formData.representative_phone,
email: modalState.formData.email,
website: modalState.formData.website,
address: modalState.formData.address,
status: modalState.selectedCompany.status,
});
closeModal();

View File

@@ -0,0 +1,74 @@
/**
* 사업자등록번호 유효성 검사 유틸리티
*/
/**
* 사업자등록번호 포맷 검증 (000-00-00000 형식)
*/
export function validateBusinessNumberFormat(value: string): boolean {
if (!value || value.trim() === "") {
return false;
}
// 하이픈 제거
const cleaned = value.replace(/-/g, "");
// 숫자 10자리인지 확인
if (!/^\d{10}$/.test(cleaned)) {
return false;
}
return true;
}
/**
* 사업자등록번호 종합 검증 (포맷만 검사)
* 실제 국세청 검증은 백엔드에서 API 호출로 처리하는 것을 권장
*/
export function validateBusinessNumber(value: string): {
isValid: boolean;
message: string;
} {
if (!value || value.trim() === "") {
return {
isValid: false,
message: "사업자등록번호를 입력해주세요.",
};
}
if (!validateBusinessNumberFormat(value)) {
return {
isValid: false,
message: "사업자등록번호는 10자리 숫자여야 합니다.",
};
}
// 포맷만 검증하고 통과
return {
isValid: true,
message: "",
};
}
/**
* 사업자등록번호 포맷팅 (자동 하이픈 추가)
*/
export function formatBusinessNumber(value: string): string {
if (!value) return "";
// 숫자만 추출
const cleaned = value.replace(/\D/g, "");
// 최대 10자리까지만
const limited = cleaned.slice(0, 10);
// 하이픈 추가 (000-00-00000)
if (limited.length <= 3) {
return limited;
} else if (limited.length <= 5) {
return `${limited.slice(0, 3)}-${limited.slice(3)}`;
} else {
return `${limited.slice(0, 3)}-${limited.slice(3, 5)}-${limited.slice(5)}`;
}
}

View File

@@ -6,6 +6,12 @@
export interface Company {
company_code: string; // 회사 코드 (varchar 32) - PK
company_name: string; // 회사명 (varchar 64)
business_registration_number?: string; // 사업자등록번호 (varchar 20)
representative_name?: string; // 대표자명 (varchar 100)
representative_phone?: string; // 대표 연락처 (varchar 20)
email?: string; // 이메일 (varchar 255)
website?: string; // 웹사이트 (varchar 500)
address?: string; // 회사 주소 (text)
writer: string; // 등록자 (varchar 32)
regdate: string; // 등록일시 (timestamp -> ISO string)
status: string; // 상태 (varchar 32)
@@ -20,7 +26,13 @@ export interface Company {
// 회사 등록/수정 폼 데이터
export interface CompanyFormData {
company_name: string; // 등록 시에는 회사명만 필요
company_name: string; // 회사명 (필수)
business_registration_number: string; // 사업자등록번호 (필수)
representative_name?: string; // 대표자명
representative_phone?: string; // 대표 연락처
email?: string; // 이메일
website?: string; // 웹사이트
address?: string; // 회사 주소
}
// 회사 검색 필터