refactor: 모달 Content 컴포넌트 분리 및 파일 입력 UI 공통화

- 모달 컴포넌트에서 Content 분리하여 재사용성 향상
  - EstimateDocumentContent, DirectConstructionContent 등
  - WorkLogContent, QuotePreviewContent, ReceivingReceiptContent
- 파일 입력 공통 UI 컴포넌트 추가
  - file-dropzone, file-input, file-list, image-upload
- 폼 컴포넌트 코드 정리 및 중복 제거 (-4,056줄)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-22 15:07:17 +09:00
parent 6fa69d81f4
commit 9464a368ba
48 changed files with 3900 additions and 4063 deletions

View File

@@ -4,8 +4,8 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { FileDropzone } from '@/components/ui/file-dropzone';
import { NumberInput } from '@/components/ui/number-input';
import { FileImage, Plus, Trash2, X, Download, Loader2 } from 'lucide-react';
import type { BendingDetail } from '@/types/item';
@@ -215,11 +215,9 @@ export default function BendingDiagramSection({
<div>
<Label> </Label>
<div className="mt-2 space-y-3">
<Input
type="file"
accept="image/*,.pdf"
onChange={(e) => {
const file = e.target.files?.[0];
<FileDropzone
onFilesSelect={(files) => {
const file = files[0];
if (file && typeof window !== 'undefined') {
setBendingDiagramFile(file);
const reader = new window.FileReader();
@@ -229,13 +227,14 @@ export default function BendingDiagramSection({
reader.readAsDataURL(file);
}
}}
accept="image/*,.pdf"
maxSize={10}
disabled={isSubmitting}
title="클릭하거나 파일을 드래그하세요"
description={selectedPartType === 'ASSEMBLY'
? '조립품 전개도 이미지를 업로드하세요(JPG, PNG, PDF 등)'
: '절곡품 전개도 이미지를 업로드하세요(JPG, PNG, PDF 등)'}
/>
<p className="text-xs text-muted-foreground">
* {selectedPartType === 'ASSEMBLY'
? '조립품 전개도 이미지를 업로드하세요(JPG, PNG, PDF 등)'
: '절곡품 전개도 이미지를 업로드하세요(JPG, PNG, PDF 등)'}
</p>
</div>
{/* 전개도 이미지 미리보기 */}

View File

@@ -2,7 +2,6 @@
* 제품 (FG) 폼 컴포넌트
*/
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
@@ -14,7 +13,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { X } from 'lucide-react';
import { FileInput } from '@/components/ui/file-input';
import type { UseFormRegister, UseFormSetValue, UseFormGetValues, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
@@ -243,79 +242,29 @@ export function ProductCertificationSection({
{/* 시방서 파일 */}
<div className="space-y-2">
<Label> (PDF)</Label>
<div className="flex items-center gap-2">
<label className="cursor-pointer">
<span className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium border rounded-md bg-background hover:bg-accent hover:text-accent-foreground">
</span>
<input
type="file"
accept=".pdf"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
setSpecificationFile(file);
}
}}
className="hidden"
disabled={isSubmitting}
/>
</label>
<span className="text-sm text-muted-foreground">
{specificationFile ? specificationFile.name : '선택된 파일 없음'}
</span>
{specificationFile && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setSpecificationFile(null)}
disabled={isSubmitting}
className="h-6 w-6 p-0"
>
<X className="h-4 w-4" />
</Button>
)}
</div>
<FileInput
value={specificationFile}
onFileSelect={setSpecificationFile}
onFileRemove={() => setSpecificationFile(null)}
accept=".pdf"
disabled={isSubmitting}
buttonText="파일 선택"
placeholder="선택된 파일 없음"
/>
</div>
{/* 인정서 파일 */}
<div className="space-y-2">
<Label> (PDF)</Label>
<div className="flex items-center gap-2">
<label className="cursor-pointer">
<span className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium border rounded-md bg-background hover:bg-accent hover:text-accent-foreground">
</span>
<input
type="file"
accept=".pdf"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
setCertificationFile(file);
}
}}
className="hidden"
disabled={isSubmitting}
/>
</label>
<span className="text-sm text-muted-foreground">
{certificationFile ? certificationFile.name : '선택된 파일 없음'}
</span>
{certificationFile && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setCertificationFile(null)}
disabled={isSubmitting}
className="h-6 w-6 p-0"
>
<X className="h-4 w-4" />
</Button>
)}
</div>
<FileInput
value={certificationFile}
onFileSelect={setCertificationFile}
onFileRemove={() => setCertificationFile(null)}
accept=".pdf"
disabled={isSubmitting}
buttonText="파일 선택"
placeholder="선택된 파일 없음"
/>
</div>
{/* 비고 */}