feat: 품목관리 기능 개선 및 문서화 업데이트
- 품목 상세/수정 페이지 파일 다운로드 기능 개선 - DynamicItemForm 파일 업로드 UI/UX 개선 (시방서, 인정서) - BendingDiagramSection 조립/절곡 부품 전개도 통합 - API proxy route 품목 타입별 라우팅 개선 - ItemListClient 파일 다운로드 유틸리티 적용 - 품목코드 중복 체크 및 다이얼로그 추가 문서화: - DynamicItemForm 훅 분리 계획서 추가 (2161줄 → 900줄 목표) - 백엔드 API 마이그레이션 문서 추가 - 대용량 파일 처리 전략 가이드 추가 - 테넌트 데이터 격리 감사 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,10 +6,11 @@ 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 { FileImage, Plus, Trash2, X } from 'lucide-react';
|
||||
import { FileImage, Plus, Trash2, X, Download, Loader2 } from 'lucide-react';
|
||||
import type { BendingDetail } from '@/types/item';
|
||||
import type { UseFormSetValue } from 'react-hook-form';
|
||||
import type { CreateItemFormData } from '@/lib/utils/validation';
|
||||
import { downloadFileById } from '@/lib/utils/fileDownload';
|
||||
|
||||
export interface BendingDiagramSectionProps {
|
||||
selectedPartType: string;
|
||||
@@ -26,6 +27,16 @@ export interface BendingDiagramSectionProps {
|
||||
widthSumFieldKey?: string;
|
||||
setValue: UseFormSetValue<CreateItemFormData>;
|
||||
isSubmitting: boolean;
|
||||
/** 기존 전개도 이미지 URL (수정 모드) */
|
||||
existingBendingDiagram?: string;
|
||||
/** 기존 전개도 파일명 */
|
||||
existingBendingDiagramFileName?: string;
|
||||
/** 기존 전개도 파일 ID (삭제용) */
|
||||
existingBendingDiagramFileId?: number | null;
|
||||
/** 기존 파일 삭제 콜백 */
|
||||
onDeleteExistingFile?: () => void;
|
||||
/** 파일 삭제 중 상태 */
|
||||
isDeletingFile?: boolean;
|
||||
}
|
||||
|
||||
export default function BendingDiagramSection({
|
||||
@@ -42,7 +53,27 @@ export default function BendingDiagramSection({
|
||||
widthSumFieldKey,
|
||||
setValue,
|
||||
isSubmitting,
|
||||
existingBendingDiagram,
|
||||
existingBendingDiagramFileName,
|
||||
existingBendingDiagramFileId,
|
||||
onDeleteExistingFile,
|
||||
isDeletingFile,
|
||||
}: BendingDiagramSectionProps) {
|
||||
// 기존 파일 다운로드 핸들러
|
||||
const handleDownloadExistingFile = async () => {
|
||||
if (!existingBendingDiagramFileId) {
|
||||
alert('파일 ID가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileName = existingBendingDiagramFileName || '전개도_이미지';
|
||||
await downloadFileById(existingBendingDiagramFileId, fileName);
|
||||
} catch (error) {
|
||||
console.error('[BendingDiagramSection] 파일 다운로드 실패:', error);
|
||||
alert('파일 다운로드에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
// 폭 합계 업데이트 헬퍼
|
||||
const updateWidthSum = (details: BendingDetail[]) => {
|
||||
const totalSum = details.reduce((acc, d) => {
|
||||
@@ -108,6 +139,76 @@ export default function BendingDiagramSection({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 기존 전개도 이미지 (수정 모드) - 파일 ID가 있으면 프록시로 이미지 로드 */}
|
||||
{existingBendingDiagramFileId && !bendingDiagram && (
|
||||
<div className="p-4 border rounded-lg bg-blue-50 border-blue-200">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileImage className="h-5 w-5 text-blue-600" />
|
||||
<span className="text-sm font-medium text-blue-900">기존 전개도 이미지</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDownloadExistingFile}
|
||||
className="text-blue-600 border-blue-300 hover:bg-blue-100"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-1" />
|
||||
다운로드
|
||||
</Button>
|
||||
{onDeleteExistingFile && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onDeleteExistingFile}
|
||||
disabled={isDeletingFile}
|
||||
className="text-red-600 border-red-300 hover:bg-red-50"
|
||||
>
|
||||
{isDeletingFile ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-1 animate-spin" />
|
||||
삭제 중...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4 mr-1" />
|
||||
삭제
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{existingBendingDiagramFileName && (
|
||||
<p className="text-xs text-blue-700 mb-2">
|
||||
파일명: {existingBendingDiagramFileName}
|
||||
</p>
|
||||
)}
|
||||
<div className="border rounded bg-white p-2">
|
||||
<img
|
||||
src={`/api/proxy/files/${existingBendingDiagramFileId}/download`}
|
||||
alt="기존 전개도"
|
||||
className="max-w-full h-auto max-h-96 mx-auto"
|
||||
onError={(e) => {
|
||||
// 이미지 로드 실패 시 대체 텍스트 표시
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = 'none';
|
||||
target.parentElement?.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
'<p class="text-center text-gray-500 py-8">이미지를 불러올 수 없습니다</p>'
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-blue-700 mt-2">
|
||||
* 새 파일을 업로드하면 기존 파일이 교체됩니다
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 파일 선택 방식 */}
|
||||
{bendingDiagramInputMethod === 'file' && (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user