feat(WEB): CEO 대시보드 리팩토링, 캘린더 강화, validation 모듈 분리, Git Workflow 정립
- CEO 대시보드 전 섹션 공통 컴포넌트 기반 리팩토링 (SectionCard, StatItem 등) - CalendarSection 일정 CRUD 기능 확장 - validation.ts → validation/ 모듈 분리 (item-schemas, form-schemas, common, utils) - CLAUDE.md Git Workflow 섹션 추가 (develop/main 플로우 정의) - Jenkinsfile CI/CD 파이프라인 정비 (Slack 알림 추가) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
110
src/lib/utils/validation/common.ts
Normal file
110
src/lib/utils/validation/common.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 공통 Zod 검증 스키마
|
||||
*
|
||||
* 품목명, 품목유형, 날짜, 숫자, BOM 등 여러 스키마에서 공유하는 기본 블록
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ===== 내부 전용 스키마 =====
|
||||
|
||||
/**
|
||||
* 품목 코드 검증
|
||||
* 형식: {업체코드}-{품목유형}-{일련번호}
|
||||
* 예: KD-FG-001
|
||||
*
|
||||
* 현재 사용하지 않음 (품목 코드 자동 생성)
|
||||
*/
|
||||
export const _itemCodeSchema = z.string()
|
||||
.min(1, '품목 코드를 입력해주세요')
|
||||
.regex(
|
||||
/^[A-Z0-9]+-[A-Z]{2}-\d+$/,
|
||||
'품목 코드 형식이 올바르지 않습니다 (예: KD-FG-001)'
|
||||
);
|
||||
|
||||
// ===== 공통 필드 스키마 =====
|
||||
|
||||
/**
|
||||
* 품목명 검증
|
||||
*/
|
||||
export const itemNameSchema = z.preprocess(
|
||||
(val) => val === undefined || val === null ? "" : val,
|
||||
z.string().min(1, '품목명을 입력해주세요').max(200, '품목명은 200자 이내로 입력해주세요')
|
||||
);
|
||||
|
||||
/**
|
||||
* 품목 유형 검증
|
||||
*/
|
||||
export const itemTypeSchema = z.enum(['FG', 'PT', 'SM', 'RM', 'CS'], {
|
||||
message: '품목 유형을 선택해주세요',
|
||||
});
|
||||
|
||||
/**
|
||||
* 단위 검증
|
||||
*
|
||||
* 현재 사용하지 않음 (materialUnitSchema로 대체)
|
||||
*/
|
||||
export const _unitSchema = z.string()
|
||||
.min(1, '단위를 입력해주세요')
|
||||
.max(20, '단위는 20자 이내로 입력해주세요');
|
||||
|
||||
/**
|
||||
* 양수 검증 (가격, 수량 등)
|
||||
* undefined나 빈 문자열은 검증하지 않음
|
||||
*/
|
||||
export const positiveNumberSchema = z.union([
|
||||
z.number().positive('0보다 큰 값을 입력해주세요'),
|
||||
z.string().transform((val) => parseFloat(val)).pipe(z.number().positive('0보다 큰 값을 입력해주세요')),
|
||||
z.undefined(),
|
||||
z.null(),
|
||||
z.literal('')
|
||||
]).optional();
|
||||
|
||||
/**
|
||||
* 날짜 검증 (YYYY-MM-DD)
|
||||
* 빈 문자열이나 undefined는 검증하지 않음
|
||||
*/
|
||||
export const dateSchema = z.preprocess(
|
||||
(val) => {
|
||||
if (val === undefined || val === null || val === '') return undefined;
|
||||
return val;
|
||||
},
|
||||
z.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/, '날짜 형식이 올바르지 않습니다 (YYYY-MM-DD)')
|
||||
.optional()
|
||||
);
|
||||
|
||||
// ===== BOM 라인 스키마 =====
|
||||
|
||||
/**
|
||||
* 절곡품 전개도 상세 스키마
|
||||
*/
|
||||
export const bendingDetailSchema = z.object({
|
||||
id: z.string(),
|
||||
no: z.number().int().positive(),
|
||||
input: z.number(),
|
||||
elongation: z.number().default(-1),
|
||||
calculated: z.number(),
|
||||
sum: z.number(),
|
||||
shaded: z.boolean().default(false),
|
||||
aAngle: z.number().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* BOM 라인 스키마
|
||||
*/
|
||||
export const bomLineSchema = z.object({
|
||||
id: z.string(),
|
||||
childItemCode: z.string().min(1, '하위 품목 코드를 입력해주세요'),
|
||||
childItemName: z.string().min(1, '하위 품목명을 입력해주세요'),
|
||||
quantity: z.number().positive('수량은 0보다 커야 합니다'),
|
||||
unit: z.string().min(1, '단위를 입력해주세요'),
|
||||
unitPrice: positiveNumberSchema,
|
||||
quantityFormula: z.string().optional(),
|
||||
note: z.string().max(500).optional(),
|
||||
|
||||
// 절곡품 관련
|
||||
isBending: z.boolean().optional(),
|
||||
bendingDiagram: z.string().url().optional(),
|
||||
bendingDetails: z.array(bendingDetailSchema).optional(),
|
||||
});
|
||||
Reference in New Issue
Block a user