summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/additional-info/join-form.tsx594
-rw-r--r--lib/items-tech/table/update-items-sheet.tsx32
2 files changed, 365 insertions, 261 deletions
diff --git a/components/additional-info/join-form.tsx b/components/additional-info/join-form.tsx
index 220547df..8dca4b61 100644
--- a/components/additional-info/join-form.tsx
+++ b/components/additional-info/join-form.tsx
@@ -126,6 +126,7 @@ const cashFlowRatingScaleMap: Record<string, string[]> = {
}
const MAX_FILE_SIZE = 3e9
+const IMAGE_FILE_SIZE = 5e6 // 5MB
// 첨부파일 타입 정의
const ATTACHMENT_TYPES = [
@@ -183,6 +184,7 @@ export function InfoForm() {
// 서명/직인 업로드 관련 상태
const [signatureFiles, setSignatureFiles] = React.useState<File[]>([])
const [hasSignature, setHasSignature] = React.useState(false)
+ const [signaturePreviewUrl, setSignaturePreviewUrl] = React.useState<string | null>(null)
// React Hook Form
const form = useForm<UpdateVendorInfoSchema>({
@@ -280,6 +282,21 @@ export function InfoForm() {
setExistingCreditFiles(creditFiles)
setExistingCashFlowFiles(cashFlowFiles)
setExistingSignatureFiles(signatureFiles) // 서명/직인 파일들
+
+ // 기존 서명/직인 파일이 이미지인 경우 미리보기 URL 생성
+ if (signatureFiles.length > 0) {
+ const signatureFile = signatureFiles[0]
+ const fileName = signatureFile.fileName.toLowerCase()
+ const isImage = fileName.includes('.jpg') || fileName.includes('.jpeg') ||
+ fileName.includes('.png') || fileName.includes('.gif') ||
+ fileName.includes('.webp')
+
+ if (isImage) {
+ // 실제 파일 경로 사용 (DB에 저장된 filePath)
+ // filePath는 이미 /vendors/{vendorId}/{hashedFileName} 형태
+ setSignaturePreviewUrl(signatureFile.filePath)
+ }
+ }
}
// 폼 기본값 설정 (연락처 포함)
@@ -352,6 +369,15 @@ export function InfoForm() {
fetchVendorData()
}, [companyId, form, replaceContacts])
+ // 컴포넌트 언마운트 시 미리보기 URL 정리 (blob URL만)
+ React.useEffect(() => {
+ return () => {
+ if (signaturePreviewUrl && signaturePreviewUrl.startsWith('blob:')) {
+ URL.revokeObjectURL(signaturePreviewUrl)
+ }
+ }
+ }, [signaturePreviewUrl])
+
// 보안 다운로드 유틸리티를 사용한 개별 파일 다운로드
const handleDownloadFile = async (file: AttachmentFile) => {
try {
@@ -529,6 +555,13 @@ export function InfoForm() {
// 삭제할 ID 목록에 추가
setFilesToDelete([...filesToDelete, fileId])
+ // 서명/직인 파일을 삭제하는 경우 미리보기도 정리
+ const isSignatureFile = existingSignatureFiles.some(file => file.id === fileId)
+ if (isSignatureFile && signaturePreviewUrl) {
+ // 서버 URL인 경우 revokeObjectURL을 호출하지 않음 (blob URL이 아니므로)
+ setSignaturePreviewUrl(null)
+ }
+
// UI에서 제거
setExistingFiles(existingFiles.filter(file => file.id !== fileId))
setExistingCreditFiles(existingCreditFiles.filter(file => file.id !== fileId))
@@ -543,6 +576,12 @@ export function InfoForm() {
+ // 이미지 파일 검증 함수 (서명용)
+ const isImageFile = (file: File): boolean => {
+ const imageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']
+ return imageTypes.includes(file.type)
+ }
+
// 서명/직인 업로드 핸들러들 (한 개만 허용)
const handleSignatureDropAccepted = (acceptedFiles: File[]) => {
// 첫 번째 파일만 사용 (한 개만 허용)
@@ -560,9 +599,22 @@ export function InfoForm() {
})
}
+ // 기존 미리보기 URL 정리 (blob URL만)
+ if (signaturePreviewUrl && signaturePreviewUrl.startsWith('blob:')) {
+ URL.revokeObjectURL(signaturePreviewUrl)
+ }
+
setSignatureFiles([newFile]) // 새 파일 설정
setHasSignature(true)
+ // 이미지 파일인 경우 미리보기 생성
+ if (isImageFile(newFile)) {
+ const previewUrl = URL.createObjectURL(newFile)
+ setSignaturePreviewUrl(previewUrl)
+ } else {
+ setSignaturePreviewUrl(null)
+ }
+
if (acceptedFiles.length > 1) {
toast({
title: "파일 제한",
@@ -585,6 +637,10 @@ export function InfoForm() {
const removeSignatureFile = () => {
setSignatureFiles([])
setHasSignature(false)
+ if (signaturePreviewUrl && signaturePreviewUrl.startsWith('blob:')) {
+ URL.revokeObjectURL(signaturePreviewUrl)
+ }
+ setSignaturePreviewUrl(null)
}
// 파일 타입 라벨 가져오기
@@ -755,112 +811,306 @@ export function InfoForm() {
<Separator />
- {/* 서명/직인 등록 섹션 - 독립적으로 사용 가능 */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <CheckCircle className="w-5 h-5" />
- 회사 서명/직인 등록 (선택사항)
- </CardTitle>
- <CardDescription>
- 회사의 공식 서명이나 직인을 등록하여 계약서 및 공식 문서에 사용할 수 있습니다.
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-4">
- {/* 현재 등록된 서명/직인 파일 표시 (한 개만) */}
- {(existingSignatureFiles.length > 0 || signatureFiles.length > 0) && (
- <div className="space-y-2">
- <div className="flex items-center gap-2 p-2 border rounded-lg bg-green-50">
- <CheckCircle className="w-4 h-4 text-green-600" />
- <span className="text-sm text-green-800">서명/직인 등록됨</span>
- </div>
-
- {/* 기존 등록된 서명/직인 (첫 번째만 표시) */}
- {existingSignatureFiles.length > 0 && signatureFiles.length === 0 && (
- <div className="p-2 border rounded-lg">
- {(() => {
- const file = existingSignatureFiles[0];
- const fileInfo = getFileInfo(file.fileName);
- return (
- <div className="flex items-center gap-2">
- <FileListIcon />
- <div className="flex-1">
- <div className="text-xs font-medium">{fileInfo.icon} {file.fileName}</div>
- <div className="text-xs text-muted-foreground">
- {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
+ {/* 서명/직인 등록과 첨부파일 요약을 하나의 행으로 배치 */}
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+ {/* 서명/직인 등록 섹션 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <CheckCircle className="w-5 h-5" />
+ 회사 서명/직인 등록 (선택사항)
+ </CardTitle>
+ <CardDescription>
+ 회사의 공식 서명이나 직인을 등록하여 계약서 및 공식 문서에 사용할 수 있습니다.
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {/* 현재 등록된 서명/직인 파일 표시 (한 개만) */}
+ {(existingSignatureFiles.length > 0 || signatureFiles.length > 0) && (
+ <div className="space-y-2">
+ {/* 기존 등록된 서명/직인 (첫 번째만 표시) */}
+ {existingSignatureFiles.length > 0 && signatureFiles.length === 0 && (
+ <div className="space-y-2">
+ <div className="p-2 border rounded-lg">
+ {(() => {
+ const file = existingSignatureFiles[0];
+ const fileInfo = getFileInfo(file.fileName);
+ return (
+ <div className="flex items-center gap-2">
+ <FileListIcon />
+ <div className="flex-1">
+ <div className="text-xs font-medium">{fileInfo.icon} {file.fileName}</div>
+ <div className="text-xs text-muted-foreground">
+ {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
+ </div>
+ </div>
+ <div className="flex items-center space-x-1">
+ <FileListAction
+ onClick={() => handleDownloadFile(file)}
+ disabled={isDownloading}
+ >
+ {isDownloading ? <Loader2 className="h-3 w-3 animate-spin" /> : <Download className="h-3 w-3" />}
+ </FileListAction>
+ <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
+ <X className="h-3 w-3" />
+ </FileListAction>
+ </div>
</div>
- </div>
- <div className="flex items-center space-x-1">
- <FileListAction
- onClick={() => handleDownloadFile(file)}
- disabled={isDownloading}
- >
- {isDownloading ? <Loader2 className="h-3 w-3 animate-spin" /> : <Download className="h-3 w-3" />}
- </FileListAction>
- <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
- <X className="h-3 w-3" />
- </FileListAction>
+ );
+ })()}
+ </div>
+
+ {/* 기존 서명 미리보기 (이미지인 경우만) */}
+ {signaturePreviewUrl && !signatureFiles.length && (
+ <div className="p-2 border rounded-lg bg-gray-50">
+ <div className="text-xs font-medium mb-2">등록된 서명/직인</div>
+ <div className="flex justify-center">
+ <img
+ src={signaturePreviewUrl}
+ alt="등록된 서명/직인"
+ className="max-w-full max-h-32 object-contain rounded border"
+ onError={() => {
+ console.error("기존 서명 이미지 로드 실패")
+ setSignaturePreviewUrl(null)
+ }}
+ // 추가 보안: referrer policy 설정
+ referrerPolicy="no-referrer"
+ />
</div>
</div>
- );
- })()}
- </div>
- )}
-
- {/* 새로 업로드된 서명/직인 */}
- {signatureFiles.length > 0 && (
- <div className="p-2 border rounded-lg bg-blue-50">
- {(() => {
- const file = signatureFiles[0];
- return (
- <div className="flex items-center gap-2">
- <FileListIcon />
- <div className="flex-1">
- <div className="text-xs font-medium">{file.name}</div>
- <div className="text-xs text-muted-foreground">
- 서명/직인 (새 파일) | {prettyBytes(file.size)}
+ )}
+ </div>
+ )}
+
+ {/* 새로 업로드된 서명/직인 */}
+ {signatureFiles.length > 0 && (
+ <div className="space-y-2">
+ <div className="p-2 border rounded-lg bg-blue-50">
+ {(() => {
+ const file = signatureFiles[0];
+ return (
+ <div className="flex items-center gap-2">
+ <FileListIcon />
+ <div className="flex-1">
+ <div className="text-xs font-medium">{file.name}</div>
+ <div className="text-xs text-muted-foreground">
+ 서명/직인 (새 파일) | {prettyBytes(file.size)}
+ </div>
+ </div>
+ <FileListAction onClick={removeSignatureFile}>
+ <X className="h-3 w-3" />
+ </FileListAction>
</div>
+ );
+ })()}
+ </div>
+
+ {/* 서명 미리보기 (이미지인 경우만) */}
+ {signaturePreviewUrl && (
+ <div className="p-2 border rounded-lg bg-gray-50">
+ <div className="text-xs font-medium mb-2">미리보기</div>
+ <div className="flex justify-center">
+ <img
+ src={signaturePreviewUrl}
+ alt="서명/직인 미리보기"
+ className="max-w-full max-h-32 object-contain rounded border"
+ onError={() => {
+ toast({
+ variant: "destructive",
+ title: "미리보기 오류",
+ description: "이미지를 불러올 수 없습니다.",
+ })
+ setSignaturePreviewUrl(null)
+ }}
+ // 추가 보안: referrer policy 설정
+ referrerPolicy="no-referrer"
+ />
</div>
- <FileListAction onClick={removeSignatureFile}>
- <X className="h-3 w-3" />
- </FileListAction>
</div>
- );
- })()}
- </div>
- )}
- </div>
- )}
-
- {/* 서명/직인 업로드 드롭존 */}
- <Dropzone
- maxSize={MAX_FILE_SIZE}
- onDropAccepted={handleSignatureDropAccepted}
- onDropRejected={handleSignatureDropRejected}
- disabled={isSubmitting}
- >
- {({ maxSize }) => (
- <DropzoneZone className="flex justify-center min-h-[50px]">
- <DropzoneInput />
- <div className="flex items-center gap-2">
- <Upload className="w-4 h-4" />
- <div className="text-sm">
- <DropzoneTitle>
- {existingSignatureFiles.length > 0 || signatureFiles.length > 0
- ? "서명/직인 교체"
- : "서명/직인 업로드"
- }
- </DropzoneTitle>
- <DropzoneDescription>
- 한 개 파일만 업로드 가능 {maxSize ? ` | 최대: ${prettyBytes(maxSize)}` : ""}
- </DropzoneDescription>
+ )}
</div>
- </div>
- </DropzoneZone>
+ )}
+ </div>
)}
- </Dropzone>
- </CardContent>
- </Card>
+
+ {/* 서명/직인 업로드 드롭존 */}
+ <Dropzone
+ maxSize={IMAGE_FILE_SIZE}
+ onDropAccepted={handleSignatureDropAccepted}
+ onDropRejected={handleSignatureDropRejected}
+ disabled={isSubmitting}
+ >
+ {({ maxSize }) => (
+ <DropzoneZone className="flex justify-center min-h-[50px]">
+ <DropzoneInput />
+ <div className="flex items-center gap-2">
+ <Upload className="w-4 h-4" />
+ <div className="text-sm">
+ <DropzoneTitle>
+ {existingSignatureFiles.length > 0 || signatureFiles.length > 0
+ ? "서명/직인 교체"
+ : "서명/직인 업로드"
+ }
+ </DropzoneTitle>
+ <DropzoneDescription>
+ 한 개 파일만 업로드 가능 {maxSize ? ` | 최대: ${prettyBytes(maxSize)}` : ""}
+ </DropzoneDescription>
+ </div>
+ </div>
+ </DropzoneZone>
+ )}
+ </Dropzone>
+ </CardContent>
+ </Card>
+
+ {/* 첨부파일 요약 카드 - 기존 파일 있는 경우만 표시 */}
+ {(existingFiles.length > 0 || existingCreditFiles.length > 0 || existingCashFlowFiles.length > 0) && (
+ <Card>
+ <CardHeader>
+ <CardTitle>첨부파일 요약</CardTitle>
+ <CardDescription>
+ 현재 등록된 파일 목록입니다. 전체 다운로드하거나 개별 파일을 다운로드할 수 있습니다.
+ </CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="grid gap-4">
+ {existingFiles.length > 0 && (
+ <div>
+ <h4 className="font-medium mb-2">첨부파일</h4>
+ <ScrollArea className="h-48">
+ <FileList className="gap-2">
+ {existingFiles.map((file) => {
+ const fileInfo = getFileInfo(file.fileName);
+ return (
+ <FileListItem key={file.id}>
+ <FileListHeader>
+ <FileListIcon />
+ <FileListInfo>
+ <FileListName>
+ {fileInfo.icon} {file.fileName}
+ </FileListName>
+ <FileListDescription>
+ {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
+ </FileListDescription>
+ </FileListInfo>
+ <div className="flex items-center space-x-2">
+ <FileListAction
+ onClick={() => handleDownloadFile(file)}
+ disabled={isDownloading}
+ >
+ {isDownloading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
+ </FileListAction>
+ <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
+ <X className="h-4 w-4" />
+ </FileListAction>
+ </div>
+ </FileListHeader>
+ </FileListItem>
+ );
+ })}
+ </FileList>
+ </ScrollArea>
+ </div>
+ )}
+
+ {existingCreditFiles.length > 0 && (
+ <div>
+ <h4 className="font-medium mb-2">신용평가 첨부파일</h4>
+ <ScrollArea className="h-24">
+ <FileList className="gap-2">
+ {existingCreditFiles.map((file) => {
+ const fileInfo = getFileInfo(file.fileName);
+ return (
+ <FileListItem key={file.id}>
+ <FileListHeader>
+ <FileListIcon />
+ <FileListInfo>
+ <FileListName>
+ {fileInfo.icon} {file.fileName}
+ </FileListName>
+ <FileListDescription>
+ {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
+ </FileListDescription>
+ </FileListInfo>
+ <div className="flex items-center space-x-2">
+ <FileListAction
+ onClick={() => handleDownloadFile(file)}
+ disabled={isDownloading}
+ >
+ {isDownloading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
+ </FileListAction>
+ <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
+ <X className="h-4 w-4" />
+ </FileListAction>
+ </div>
+ </FileListHeader>
+ </FileListItem>
+ );
+ })}
+ </FileList>
+ </ScrollArea>
+ </div>
+ )}
+
+ {existingCashFlowFiles.length > 0 && (
+ <div>
+ <h4 className="font-medium mb-2">현금흐름 첨부파일</h4>
+ <ScrollArea className="h-24">
+ <FileList className="gap-2">
+ {existingCashFlowFiles.map((file) => {
+ const fileInfo = getFileInfo(file.fileName);
+ return (
+ <FileListItem key={file.id}>
+ <FileListHeader>
+ <FileListIcon />
+ <FileListInfo>
+ <FileListName>
+ {fileInfo.icon} {file.fileName}
+ </FileListName>
+ <FileListDescription>
+ {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
+ </FileListDescription>
+ </FileListInfo>
+ <div className="flex items-center space-x-2">
+ <FileListAction
+ onClick={() => handleDownloadFile(file)}
+ disabled={isDownloading}
+ >
+ {isDownloading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
+ </FileListAction>
+ <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
+ <X className="h-4 w-4" />
+ </FileListAction>
+ </div>
+ </FileListHeader>
+ </FileListItem>
+ );
+ })}
+ </FileList>
+ </ScrollArea>
+ </div>
+ )}
+ </div>
+ </CardContent>
+ <CardFooter>
+ {(existingFiles.length + existingCreditFiles.length + existingCashFlowFiles.length) > 1 && (
+ <Button
+ variant="outline"
+ onClick={handleDownloadAllFiles}
+ disabled={isDownloading}
+ >
+ {isDownloading ? (
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ ) : (
+ <Download className="mr-2 h-4 w-4" />
+ )}
+ 전체 다운로드
+ </Button>
+ )}
+ </CardFooter>
+ </Card>
+ )}
+ </div>
{/* 정규업체 등록 현황 섹션 */}
{registrationData ? (
@@ -930,154 +1180,6 @@ export function InfoForm() {
</Card>
)}
- {/* 첨부파일 요약 카드 - 기존 파일 있는 경우만 표시 */}
- {(existingFiles.length > 0 || existingCreditFiles.length > 0 || existingCashFlowFiles.length > 0) && (
- <Card>
- <CardHeader>
- <CardTitle>첨부파일 요약</CardTitle>
- <CardDescription>
- 현재 등록된 파일 목록입니다. 전체 다운로드하거나 개별 파일을 다운로드할 수 있습니다.
- </CardDescription>
- </CardHeader>
- <CardContent>
- <div className="grid gap-4">
- {existingFiles.length > 0 && (
- <div>
- <h4 className="font-medium mb-2">첨부파일</h4>
- <ScrollArea className="h-48">
- <FileList className="gap-2">
- {existingFiles.map((file) => {
- const fileInfo = getFileInfo(file.fileName);
- return (
- <FileListItem key={file.id}>
- <FileListHeader>
- <FileListIcon />
- <FileListInfo>
- <FileListName>
- {fileInfo.icon} {file.fileName}
- </FileListName>
- <FileListDescription>
- {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
- </FileListDescription>
- </FileListInfo>
- <div className="flex items-center space-x-2">
- <FileListAction
- onClick={() => handleDownloadFile(file)}
- disabled={isDownloading}
- >
- {isDownloading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
- </FileListAction>
- <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
- <X className="h-4 w-4" />
- </FileListAction>
- </div>
- </FileListHeader>
- </FileListItem>
- );
- })}
- </FileList>
- </ScrollArea>
- </div>
- )}
-
- {existingCreditFiles.length > 0 && (
- <div>
- <h4 className="font-medium mb-2">신용평가 첨부파일</h4>
- <ScrollArea className="h-24">
- <FileList className="gap-2">
- {existingCreditFiles.map((file) => {
- const fileInfo = getFileInfo(file.fileName);
- return (
- <FileListItem key={file.id}>
- <FileListHeader>
- <FileListIcon />
- <FileListInfo>
- <FileListName>
- {fileInfo.icon} {file.fileName}
- </FileListName>
- <FileListDescription>
- {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
- </FileListDescription>
- </FileListInfo>
- <div className="flex items-center space-x-2">
- <FileListAction
- onClick={() => handleDownloadFile(file)}
- disabled={isDownloading}
- >
- {isDownloading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
- </FileListAction>
- <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
- <X className="h-4 w-4" />
- </FileListAction>
- </div>
- </FileListHeader>
- </FileListItem>
- );
- })}
- </FileList>
- </ScrollArea>
- </div>
- )}
-
- {existingCashFlowFiles.length > 0 && (
- <div>
- <h4 className="font-medium mb-2">현금흐름 첨부파일</h4>
- <ScrollArea className="h-24">
- <FileList className="gap-2">
- {existingCashFlowFiles.map((file) => {
- const fileInfo = getFileInfo(file.fileName);
- return (
- <FileListItem key={file.id}>
- <FileListHeader>
- <FileListIcon />
- <FileListInfo>
- <FileListName>
- {fileInfo.icon} {file.fileName}
- </FileListName>
- <FileListDescription>
- {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
- </FileListDescription>
- </FileListInfo>
- <div className="flex items-center space-x-2">
- <FileListAction
- onClick={() => handleDownloadFile(file)}
- disabled={isDownloading}
- >
- {isDownloading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
- </FileListAction>
- <FileListAction onClick={() => handleDeleteExistingFile(file.id)}>
- <X className="h-4 w-4" />
- </FileListAction>
- </div>
- </FileListHeader>
- </FileListItem>
- );
- })}
- </FileList>
- </ScrollArea>
- </div>
- )}
- </div>
- </CardContent>
- <CardFooter>
- {(existingFiles.length + existingCreditFiles.length + existingCashFlowFiles.length) > 1 && (
- <Button
- variant="outline"
- onClick={handleDownloadAllFiles}
- disabled={isDownloading}
- >
- {isDownloading ? (
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- ) : (
- <Download className="mr-2 h-4 w-4" />
- )}
- 전체 다운로드
- </Button>
- )}
- </CardFooter>
- </Card>
- )}
-
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
diff --git a/lib/items-tech/table/update-items-sheet.tsx b/lib/items-tech/table/update-items-sheet.tsx
index 91108ba0..7662884c 100644
--- a/lib/items-tech/table/update-items-sheet.tsx
+++ b/lib/items-tech/table/update-items-sheet.tsx
@@ -111,6 +111,7 @@ export function UpdateItemSheet({ item, itemType, open, onOpenChange }: UpdateIt
// 초기값 설정
const form = useForm<UpdateItemSchema>({
defaultValues: {
+ itemCode: item.itemCode,
workType: item.workType,
...getItemTypeSpecificDefaults(item, itemType),
},
@@ -152,22 +153,19 @@ export function UpdateItemSheet({ item, itemType, open, onOpenChange }: UpdateIt
case 'shipbuilding':
result = await modifyShipbuildingItem({
...data,
- id: item.id,
- itemCode: item.itemCode // itemCode는 변경 불가, 원래 값 사용
+ id: item.id
});
break;
case 'offshoreTop':
result = await modifyOffshoreTopItem({
...data,
- id: item.id,
- itemCode: item.itemCode // itemCode는 변경 불가, 원래 값 사용
+ id: item.id
});
break;
case 'offshoreHull':
result = await modifyOffshoreHullItem({
...data,
- id: item.id,
- itemCode: item.itemCode // itemCode는 변경 불가, 원래 값 사용
+ id: item.id
});
break;
default:
@@ -227,15 +225,19 @@ export function UpdateItemSheet({ item, itemType, open, onOpenChange }: UpdateIt
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
- {/* Material Group을 Form 안으로 이동 */}
- <div className="mt-4">
- <div className="grid gap-2">
- <label className="text-sm font-medium leading-none">
- 자재 그룹 (수정 불가)
- </label>
- <Input value={item.itemCode} disabled readOnly />
- </div>
- </div>
+ <FormField
+ control={form.control}
+ name="itemCode"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>자재 그룹 <span style={{ color: 'red' }}>*</span></FormLabel>
+ <FormControl>
+ <Input placeholder="자재 그룹을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
<FormField
control={form.control}