diff options
Diffstat (limited to 'components/ship-vendor-document/revision-validation.tsx')
| -rw-r--r-- | components/ship-vendor-document/revision-validation.tsx | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/components/ship-vendor-document/revision-validation.tsx b/components/ship-vendor-document/revision-validation.tsx new file mode 100644 index 00000000..4ff621a0 --- /dev/null +++ b/components/ship-vendor-document/revision-validation.tsx @@ -0,0 +1,266 @@ +import React from "react" +import { z } from "zod" +import { Input } from "@/components/ui/input" +import { Alert, AlertDescription } from "@/components/ui/alert" +import { Info } from "lucide-react" + +// 파일 검증 스키마 +export const MAX_FILE_SIZE = 1024 * 1024 * 1024 // 1GB + +// B3 리비전 검증 함수 +export const validateB3Revision = (value: string) => { + // B3 리비전 패턴: 단일 알파벳(A-Z) 또는 R01-R99 + const alphabetPattern = /^[A-Z]$/ + const numericPattern = /^R(0[1-9]|[1-9][0-9])$/ + + return alphabetPattern.test(value) || numericPattern.test(value) +} + +// B4 리비전 검증 함수 +export const validateB4Revision = (value: string) => { + // B4 리비전 패턴: R01-R99 + const numericPattern = /^R(0[1-9]|[1-9][0-9])$/ + return numericPattern.test(value) +} + +// 리비전 검증 함수 (drawingKind에 따라) +export const validateRevision = (value: string, drawingKind: string) => { + if (drawingKind === 'B3') { + return validateB3Revision(value) + } else { + return validateB4Revision(value) + } +} + +// 리비전 수정용 스키마 생성 +export const createEditRevisionSchema = (drawingKind: string) => { + const baseSchema = { + usage: z.string().min(1, "Please select a usage"), + comment: z.string().optional(), + } + + // drawingKind에 따른 리비전 검증 추가 + const revisionField = drawingKind === 'B3' + ? z.string() + .min(1, "Please enter a revision") + .max(3, "Revision must be 3 characters or less") + .refine( + validateB3Revision, + "Invalid format. Use A-Z or R01-R99" + ) + : z.string() + .min(1, "Please enter a revision") + .max(3, "Revision must be 3 characters or less") + .refine( + validateB4Revision, + "Invalid format. Use R01-R99" + ) + + // B3인 경우에만 usageType 필드 추가 + if (drawingKind === 'B3') { + return z.object({ + ...baseSchema, + revision: revisionField, + usageType: z.string().min(1, "Please select a usage type"), + }) + } else { + return z.object({ + ...baseSchema, + revision: revisionField, + usageType: z.string().optional(), + }) + } +} + +// 리비전 업로드용 스키마 생성 +export const createUploadRevisionSchema = (drawingKind: string) => { + const baseSchema = { + usage: z.string().min(1, "Please select a usage"), + comment: z.string().optional(), + attachments: z + .array(z.instanceof(File)) + .min(1, "Please upload at least 1 file") + .refine( + (files) => files.every((file) => file.size <= MAX_FILE_SIZE), + "File size must be 50MB or less" + ), + } + + // drawingKind에 따른 리비전 검증 추가 + const revisionField = drawingKind === 'B3' + ? z.string() + .min(1, "Please enter a revision") + .max(3, "Revision must be 3 characters or less") + .refine( + validateB3Revision, + "Invalid format. Use A-Z or R01-R99" + ) + : z.string() + .min(1, "Please enter a revision") + .max(3, "Revision must be 3 characters or less") + .refine( + validateB4Revision, + "Invalid format. Use R01-R99" + ) + + // B3인 경우에만 usageType 필드 추가 + if (drawingKind === 'B3') { + return z.object({ + ...baseSchema, + revision: revisionField, + usageType: z.string().min(1, "Please select a usage type"), + }) + } else { + return z.object({ + ...baseSchema, + revision: revisionField, + usageType: z.string().optional(), + }) + } +} + +// drawingKind에 따른 용도 옵션 +export const getUsageOptions = (drawingKind: string) => { + switch (drawingKind) { + case 'B3': + return [ + { value: "Approval", label: "Approval" }, + { value: "Working", label: "Working" }, + { value: "Comments", label: "Comments" }, + ] + case 'B4': + return [ + { value: "Pre", label: "Pre" }, + { value: "Working", label: "Working" }, + ] + case 'B5': + return [ + { value: "Pre", label: "Pre" }, + { value: "Working", label: "Working" }, + ] + default: + return [ + { value: "Pre", label: "Pre" }, + { value: "Working", label: "Working" }, + ] + } +} + +// B3 전용 용도 타입 옵션 +export const getUsageTypeOptions = (usage: string) => { + switch (usage) { + case 'Approval': + return [ + { value: "Full", label: "Full" }, + { value: "Partial", label: "Partial" }, + ] + case 'Working': + return [ + { value: "Full", label: "Full" }, + { value: "Partial", label: "Partial" }, + ] + case 'Comments': + return [ + { value: "Comments", label: "Comments" }, + ] + default: + return [] + } +} + +// 리비전 형식 가이드 생성 +export const getRevisionGuide = (drawingKind: string) => { + if (drawingKind === 'B3') { + return { + placeholder: "e.g., A, B, C or R01, R02", + helpText: "Use single letter (A-Z) or R01-R99 format", + examples: [ + "A, B, C, ... Z (alphabetic revisions)", + "R01, R02, ... R99 (numeric revisions)" + ] + } + } + return { + placeholder: "e.g., R01, R02, R03", + helpText: "Enter in R01, R02, R03... format", + examples: ["R01, R02, R03, ... R99"] + } +} + +// B3 리비전 자동 포맷팅 함수 +export const formatB3RevisionInput = (value: string): string => { + // 입력값을 대문자로 변환 + const upperValue = value.toUpperCase() + + // 단일 알파벳인 경우 + if (/^[A-Z]$/.test(upperValue)) { + return upperValue + } + + // R로 시작하는 경우 + if (upperValue.startsWith('R')) { + // R 뒤의 숫자 추출 + const numPart = upperValue.slice(1).replace(/\D/g, '') + if (numPart) { + const num = parseInt(numPart, 10) + // 1-99 범위 체크 + if (num >= 1 && num <= 99) { + // 01-09는 0을 붙이고, 10-99는 그대로 + return `R${num.toString().padStart(2, '0')}` + } + } + } + + return upperValue +} + +// B3 리비전 입력 컴포넌트 +export function B3RevisionInput({ + value, + onChange, + error +}: { + value: string + onChange: (value: string) => void + error?: string +}) { + const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const rawValue = e.target.value.toUpperCase() + + // 길이 제한 (알파벳은 1자, R숫자는 3자) + if (rawValue.length <= 3) { + onChange(rawValue) + } + } + + const handleBlur = () => { + // blur 시점에 포맷팅 적용 + const formattedValue = formatB3RevisionInput(value) + onChange(formattedValue) + } + + const revisionGuide = getRevisionGuide('B3') + + return ( + <div className="space-y-2"> + <Input + value={value} + onChange={handleInputChange} + onBlur={handleBlur} + placeholder={revisionGuide.placeholder} + className={error ? "border-red-500" : ""} + /> + <Alert className="bg-blue-50 border-blue-200"> + <Info className="h-4 w-4 text-blue-600" /> + <AlertDescription className="text-xs space-y-1"> + <div className="font-medium text-blue-900">{revisionGuide.helpText}</div> + <div className="text-blue-700"> + {revisionGuide.examples.map((example, idx) => ( + <div key={idx}>• {example}</div> + ))} + </div> + </AlertDescription> + </Alert> + </div> + ) +} |
