# 폼 유효성 검사 사용 가이드

## 개요

SAM MES 시스템의 모든 폼에서 공통으로 사용할 수 있는 유효성 검사 시스템입니다.

## 구성 요소

### 1. 유효성 검사 유틸리티 (`/utils/validation.ts`)
- 재사용 가능한 검사 규칙과 함수 제공
- 필수 입력, 길이, 숫자 범위, 이메일, 전화번호 등 검사

### 2. FormActions 컴포넌트 (`/components/organisms/FormActions.tsx`)
- 저장 버튼 클릭 시 자동으로 유효성 검사 실행
- 검사 실패 시 토스트 메시지로 사용자에게 알림

## 사용 방법

### 방법 1: ValidationRules 사용 (권장)

```tsx
import { FormActions } from "./components/organisms/FormActions";
import { ValidationHelpers, ValidationRule } from "./utils/validation";

function MyForm() {
  const [formData, setFormData] = useState({
    customerName: "",
    email: "",
    phone: "",
    quantity: 0,
    price: 0,
  });

  // 유효성 검사 규칙 정의
  const validationRules: ValidationRule[] = [
    ValidationHelpers.required("customerName", "고객명"),
    ValidationHelpers.requiredEmail("email", "이메일"),
    ValidationHelpers.requiredPhone("phone", "전화번호"),
    ValidationHelpers.requiredPositive("quantity", "수량"),
    ValidationHelpers.requiredPositive("price", "단가"),
  ];

  const handleSave = () => {
    // 유효성 검사는 FormActions에서 자동으로 처리됨
    console.log("저장:", formData);
    // API 호출 등
  };

  return (
    <div>
      {/* 폼 필드들... */}
      
      <FormActions
        onSave={handleSave}
        onCancel={() => navigate(-1)}
        validationRules={validationRules}
        formData={formData}
      />
    </div>
  );
}
```

### 방법 2: 커스텀 검사 추가

```tsx
const validationRules: ValidationRule[] = [
  ValidationHelpers.required("itemCode", "품목코드"),
  {
    field: "deliveryDate",
    fieldLabel: "납품일자",
    rules: {
      required: true,
      custom: (value, formData) => {
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        const deliveryDate = new Date(value);
        
        if (deliveryDate < today) {
          return "납품일자는 오늘 이후여야 합니다.";
        }
        return null; // 검사 통과
      }
    }
  },
  {
    field: "discount",
    fieldLabel: "할인율",
    rules: {
      number: true,
      min: 0,
      max: 100,
      custom: (value, formData) => {
        if (formData.customerType === "VIP" && value < 10) {
          return "VIP 고객은 최소 10% 할인이 적용되어야 합니다.";
        }
        return null;
      }
    }
  }
];
```

### 방법 3: 기존 onValidate 콜백 사용 (하위 호환성)

```tsx
const handleValidate = () => {
  const errors: string[] = [];
  
  if (!formData.customerName) {
    errors.push("고객명을 입력해주세요.");
  }
  
  if (!formData.email || !formData.email.includes("@")) {
    errors.push("올바른 이메일을 입력해주세요.");
  }
  
  return {
    isValid: errors.length === 0,
    errors
  };
};

<FormActions
  onSave={handleSave}
  onCancel={onCancel}
  onValidate={handleValidate}
/>
```

## 사용 가능한 검사 규칙

### 기본 검사

| 규칙 | 설명 | 예시 |
|------|------|------|
| `required` | 필수 입력 | `ValidationHelpers.required("name", "이름")` |
| `minLength` | 최소 길이 | `{ field: "name", fieldLabel: "이름", rules: { minLength: 2 } }` |
| `maxLength` | 최대 길이 | `{ field: "name", fieldLabel: "이름", rules: { maxLength: 50 } }` |
| `min` | 최소값 (숫자) | `{ field: "age", fieldLabel: "나이", rules: { number: true, min: 0 } }` |
| `max` | 최대값 (숫자) | `{ field: "age", fieldLabel: "나이", rules: { number: true, max: 150 } }` |
| `email` | 이메일 형식 | `ValidationHelpers.requiredEmail("email", "이메일")` |
| `phone` | 전화번호 형식 | `ValidationHelpers.requiredPhone("phone", "전화번호")` |
| `number` | 숫자 형식 | `ValidationHelpers.requiredNumber("quantity", "수량")` |
| `positive` | 양수 | `ValidationHelpers.requiredPositive("price", "가격")` |
| `integer` | 정수 | `{ field: "count", fieldLabel: "개수", rules: { number: true, integer: true } }` |
| `pattern` | 정규식 패턴 | `{ field: "code", fieldLabel: "코드", rules: { pattern: /^[A-Z0-9]+$/ } }` |
| `custom` | 커스텀 함수 | `{ field: "x", fieldLabel: "X", rules: { custom: (val) => val > 10 ? null : "10 이상" } }` |

### Helper 함수

```tsx
import { ValidationHelpers } from "./utils/validation";

// 필수 입력만
ValidationHelpers.required("fieldName", "필드명")

// 필수 + 텍스트 길이
ValidationHelpers.requiredText("name", "이름", 2, 50) // 최소 2자, 최대 50자

// 필수 + 숫자 범위
ValidationHelpers.requiredNumber("age", "나이", 0, 150) // 0~150

// 필수 + 양수
ValidationHelpers.requiredPositive("price", "가격")

// 필수 + 이메일
ValidationHelpers.requiredEmail("email", "이메일")

// 필수 + 전화번호
ValidationHelpers.requiredPhone("phone", "전화번호")
```

## 실전 예시

### 수주 등록 폼

```tsx
const validationRules: ValidationRule[] = [
  ValidationHelpers.required("quotationId", "견적서"),
  ValidationHelpers.required("customerId", "고객"),
  ValidationHelpers.requiredText("orderNumber", "수주번호", 5, 20),
  {
    field: "deliveryDate",
    fieldLabel: "납품일자",
    rules: {
      required: true,
      custom: (value) => {
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        const deliveryDate = new Date(value);
        
        if (deliveryDate < today) {
          return "납품일자는 오늘 이후여야 합니다.";
        }
        
        const maxDate = new Date();
        maxDate.setFullYear(maxDate.getFullYear() + 1);
        if (deliveryDate > maxDate) {
          return "납품일자는 1년 이내로 설정해주세요.";
        }
        
        return null;
      }
    }
  },
  ValidationHelpers.requiredPositive("totalAmount", "총 금액"),
];
```

### 품목 등록 폼

```tsx
const validationRules: ValidationRule[] = [
  {
    field: "itemCode",
    fieldLabel: "품목코드",
    rules: {
      required: true,
      pattern: /^[A-Z0-9-]+$/,
      custom: (value) => {
        if (value && value.length < 5) {
          return "품목코드는 최소 5자 이상이어야 합니다.";
        }
        return null;
      }
    }
  },
  ValidationHelpers.requiredText("itemName", "품목명", 2, 100),
  ValidationHelpers.required("itemType", "품목유형"),
  ValidationHelpers.requiredPositive("standardPrice", "표준단가"),
];
```

### 견적 등록 폼

```tsx
const validationRules: ValidationRule[] = [
  ValidationHelpers.required("customerId", "고객"),
  ValidationHelpers.requiredText("quotationNumber", "견적번호", 5, 20),
  {
    field: "validUntil",
    fieldLabel: "유효기한",
    rules: {
      required: true,
      custom: (value, formData) => {
        const issueDate = new Date(formData.issueDate);
        const validDate = new Date(value);
        
        if (validDate <= issueDate) {
          return "유효기한은 발행일보다 이후여야 합니다.";
        }
        return null;
      }
    }
  },
  {
    field: "items",
    fieldLabel: "견적 품목",
    rules: {
      custom: (value) => {
        if (!Array.isArray(value) || value.length === 0) {
          return "최소 1개 이상의 품목을 추가해주세요.";
        }
        return null;
      }
    }
  },
];
```

## 주의사항

1. **필수 입력 검사가 실패하면** 다른 검사는 실행되지 않습니다.
2. **첫 번째 에러만 표시**되므로, 중요한 검사를 앞에 배치하세요.
3. **커스텀 검사 함수**는 에러 메시지를 반환하거나 `null`을 반환해야 합니다.
4. **formData 전체**가 필요한 경우 `custom` 함수의 두 번째 인자로 접근할 수 있습니다.

## 마이그레이션 가이드

기존 `onValidate` 방식을 사용 중이라면:

```tsx
// Before
<FormActions
  onSave={handleSave}
  onCancel={onCancel}
  onValidate={() => {
    const errors = [];
    if (!formData.name) errors.push("이름 필수");
    return { isValid: errors.length === 0, errors };
  }}
/>

// After (권장)
const validationRules = [
  ValidationHelpers.required("name", "이름")
];

<FormActions
  onSave={handleSave}
  onCancel={onCancel}
  validationRules={validationRules}
  formData={formData}
/>
```
