파일 업로드 구조 개선
This commit is contained in:
parent
63b6e89435
commit
8489ff03c2
|
|
@ -767,8 +767,9 @@ export const previewFile = async (
|
|||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
// CORS 헤더 설정 (더 포괄적으로)
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
// CORS 헤더 설정 (credentials 모드에서는 구체적인 origin 필요)
|
||||
const origin = req.headers.origin || "http://localhost:9771";
|
||||
res.setHeader("Access-Control-Allow-Origin", origin);
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Methods",
|
||||
"GET, POST, PUT, DELETE, OPTIONS"
|
||||
|
|
|
|||
|
|
@ -26,80 +26,108 @@ import {
|
|||
// 위젯 동적 임포트
|
||||
const WeatherWidget = dynamic(() => import("@/components/dashboard/widgets/WeatherWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const ExchangeWidget = dynamic(() => import("@/components/dashboard/widgets/ExchangeWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const CalculatorWidget = dynamic(() => import("@/components/dashboard/widgets/CalculatorWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const VehicleStatusWidget = dynamic(() => import("@/components/dashboard/widgets/VehicleStatusWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const VehicleListWidget = dynamic(() => import("@/components/dashboard/widgets/VehicleListWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const VehicleMapOnlyWidget = dynamic(() => import("@/components/dashboard/widgets/VehicleMapOnlyWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 범용 지도 위젯 (차량, 창고, 고객 등 모든 위치 위젯 통합)
|
||||
const MapSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/MapSummaryWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 🧪 테스트용 지도 위젯 (REST API 지원)
|
||||
const MapTestWidget = dynamic(() => import("@/components/dashboard/widgets/MapTestWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 🧪 테스트용 지도 위젯 V2 (다중 데이터 소스)
|
||||
const MapTestWidgetV2 = dynamic(() => import("@/components/dashboard/widgets/MapTestWidgetV2"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 🧪 테스트용 차트 위젯 (다중 데이터 소스)
|
||||
const ChartTestWidget = dynamic(() => import("@/components/dashboard/widgets/ChartTestWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const ListTestWidget = dynamic(
|
||||
() => import("@/components/dashboard/widgets/ListTestWidget").then((mod) => ({ default: mod.ListTestWidget })),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const CustomMetricTestWidget = dynamic(() => import("@/components/dashboard/widgets/CustomMetricTestWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const RiskAlertTestWidget = dynamic(() => import("@/components/dashboard/widgets/RiskAlertTestWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 범용 상태 요약 위젯 (차량, 배송 등 모든 상태 위젯 통합)
|
||||
const StatusSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/StatusSummaryWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 범용 목록 위젯 (차량, 기사, 제품 등 모든 목록 위젯 통합) - 다른 분 작업 중, 임시 주석
|
||||
|
|
@ -128,22 +156,30 @@ const StatusSummaryWidget = dynamic(() => import("@/components/dashboard/widgets
|
|||
|
||||
const RiskAlertWidget = dynamic(() => import("@/components/dashboard/widgets/RiskAlertWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const TaskWidget = dynamic(() => import("@/components/dashboard/widgets/TaskWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const BookingAlertWidget = dynamic(() => import("@/components/dashboard/widgets/BookingAlertWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
const DocumentWidget = dynamic(() => import("@/components/dashboard/widgets/DocumentWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 시계 위젯 임포트
|
||||
|
|
@ -160,25 +196,33 @@ import { Button } from "@/components/ui/button";
|
|||
// 야드 관리 3D 위젯
|
||||
const YardManagement3DWidget = dynamic(() => import("./widgets/YardManagement3DWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 작업 이력 위젯
|
||||
const WorkHistoryWidget = dynamic(() => import("@/components/dashboard/widgets/WorkHistoryWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 커스텀 통계 카드 위젯
|
||||
const CustomStatsWidget = dynamic(() => import("@/components/dashboard/widgets/CustomStatsWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
// 사용자 커스텀 카드 위젯
|
||||
const CustomMetricWidget = dynamic(() => import("@/components/dashboard/widgets/CustomMetricWidget"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
|
||||
loading: () => (
|
||||
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">로딩 중...</div>
|
||||
),
|
||||
});
|
||||
|
||||
interface CanvasElementProps {
|
||||
|
|
@ -758,7 +802,7 @@ export function CanvasElement({
|
|||
<div
|
||||
ref={elementRef}
|
||||
data-element-id={element.id}
|
||||
className={`absolute min-h-[120px] min-w-[120px] cursor-move overflow-hidden rounded-lg border-2 bg-background shadow-lg ${isSelected ? "border-primary ring-2 ring-primary/20" : "border-border"} ${isDragging || isResizing ? "transition-none" : "transition-all duration-150"} `}
|
||||
className={`bg-background absolute min-h-[120px] min-w-[120px] cursor-move overflow-hidden rounded-lg border-2 shadow-lg ${isSelected ? "border-primary ring-primary/20 ring-2" : "border-border"} ${isDragging || isResizing ? "transition-none" : "transition-all duration-150"} `}
|
||||
style={{
|
||||
left: displayPosition.x,
|
||||
top: displayPosition.y,
|
||||
|
|
@ -809,7 +853,7 @@ export function CanvasElement({
|
|||
)}
|
||||
{/* 제목 */}
|
||||
{!element.type || element.type !== "chart" ? (
|
||||
<span className="text-xs font-bold text-foreground">{element.customTitle || element.title}</span>
|
||||
<span className="text-foreground text-xs font-bold">{element.customTitle || element.title}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
|
|
@ -817,7 +861,7 @@ export function CanvasElement({
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="element-close hover:bg-destructive h-5 w-5 text-muted-foreground hover:text-white"
|
||||
className="element-close hover:bg-destructive text-muted-foreground h-5 w-5 hover:text-white"
|
||||
onClick={handleRemove}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
title="삭제"
|
||||
|
|
@ -831,9 +875,9 @@ export function CanvasElement({
|
|||
<div className="relative h-[calc(100%-32px)] px-2 pb-2">
|
||||
{element.type === "chart" ? (
|
||||
// 차트 렌더링
|
||||
<div className="h-full w-full bg-background">
|
||||
<div className="bg-background h-full w-full">
|
||||
{isLoadingData ? (
|
||||
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
|
||||
<div className="text-muted-foreground flex h-full w-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="border-primary mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
|
||||
<div className="text-sm">데이터 로딩 중...</div>
|
||||
|
|
@ -921,7 +965,12 @@ export function CanvasElement({
|
|||
) : element.type === "widget" && element.subtype === "status-summary" ? (
|
||||
// 커스텀 상태 카드 - 범용 위젯
|
||||
<div className="widget-interactive-area h-full w-full">
|
||||
<StatusSummaryWidget element={element} title="상태 요약" icon="📊" bgGradient="from-background to-primary/10" />
|
||||
<StatusSummaryWidget
|
||||
element={element}
|
||||
title="상태 요약"
|
||||
icon="📊"
|
||||
bgGradient="from-background to-primary/10"
|
||||
/>
|
||||
</div>
|
||||
) : /* element.type === "widget" && element.subtype === "list-summary" ? (
|
||||
// 커스텀 목록 카드 - 범용 위젯 (다른 분 작업 중 - 임시 주석)
|
||||
|
|
@ -1106,7 +1155,7 @@ function ResizeHandle({ position, onMouseDown }: ResizeHandleProps) {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`resize-handle absolute h-3 w-3 border border-white bg-success ${getPositionClass()} `}
|
||||
className={`resize-handle bg-success absolute h-3 w-3 border border-white ${getPositionClass()} `}
|
||||
onMouseDown={(e) => onMouseDown(e, position)}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
|
|||
const [uploading, setUploading] = useState(false);
|
||||
const [viewerFile, setViewerFile] = useState<FileInfo | null>(null);
|
||||
const [isViewerOpen, setIsViewerOpen] = useState(false);
|
||||
const [selectedFile, setSelectedFile] = useState<FileInfo | null>(null); // 선택된 파일 (좌측 미리보기용)
|
||||
const [previewImageUrl, setPreviewImageUrl] = useState<string | null>(null); // 이미지 미리보기 URL
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 파일 아이콘 가져오기
|
||||
|
|
@ -141,10 +143,49 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
|
|||
setViewerFile(null);
|
||||
};
|
||||
|
||||
// 파일 클릭 시 미리보기 로드
|
||||
const handleFileClick = async (file: FileInfo) => {
|
||||
setSelectedFile(file);
|
||||
|
||||
// 이미지 파일인 경우 미리보기 로드
|
||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
|
||||
if (imageExtensions.includes(file.fileExt.toLowerCase())) {
|
||||
try {
|
||||
// 이전 Blob URL 해제
|
||||
if (previewImageUrl) {
|
||||
URL.revokeObjectURL(previewImageUrl);
|
||||
}
|
||||
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
const response = await apiClient.get(`/files/preview/${file.objid}`, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
const blob = new Blob([response.data]);
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setPreviewImageUrl(blobUrl);
|
||||
} catch (error) {
|
||||
console.error("이미지 로드 실패:", error);
|
||||
setPreviewImageUrl(null);
|
||||
}
|
||||
} else {
|
||||
setPreviewImageUrl(null);
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 언마운트 시 Blob URL 해제
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (previewImageUrl) {
|
||||
URL.revokeObjectURL(previewImageUrl);
|
||||
}
|
||||
};
|
||||
}, [previewImageUrl]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={() => {}}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden [&>button]:hidden">
|
||||
<DialogContent className="max-w-6xl max-h-[90vh] overflow-hidden [&>button]:hidden">
|
||||
<DialogHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
파일 관리 ({uploadedFiles.length}개)
|
||||
|
|
@ -160,17 +201,21 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
|
|||
</Button>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col space-y-4 h-[70vh]">
|
||||
{/* 파일 업로드 영역 */}
|
||||
<div className="flex flex-col space-y-3 h-[75vh]">
|
||||
{/* 파일 업로드 영역 - 높이 축소 */}
|
||||
{!isDesignMode && (
|
||||
<div
|
||||
className={`
|
||||
border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors
|
||||
border-2 border-dashed rounded-lg p-4 text-center cursor-pointer transition-colors
|
||||
${dragOver ? 'border-blue-400 bg-blue-50' : 'border-gray-300'}
|
||||
${config.disabled ? 'opacity-50 cursor-not-allowed' : 'hover:border-gray-400'}
|
||||
${uploading ? 'opacity-75' : ''}
|
||||
`}
|
||||
onClick={handleFileSelect}
|
||||
onClick={() => {
|
||||
if (!config.disabled && !isDesignMode) {
|
||||
fileInputRef.current?.click();
|
||||
}
|
||||
}}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
|
|
@ -186,47 +231,71 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
|
|||
/>
|
||||
|
||||
{uploading ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-3"></div>
|
||||
<span className="text-blue-600 font-medium">업로드 중...</span>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div>
|
||||
<span className="text-sm text-blue-600 font-medium">업로드 중...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center">
|
||||
<Upload className="mx-auto h-12 w-12 text-gray-400 mb-4" />
|
||||
<p className="text-lg font-medium text-gray-900 mb-2">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Upload className="h-6 w-6 text-gray-400" />
|
||||
<p className="text-sm font-medium text-gray-700">
|
||||
파일을 드래그하거나 클릭하여 업로드하세요
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{config.accept && `지원 형식: ${config.accept}`}
|
||||
{config.maxSize && ` • 최대 ${formatFileSize(config.maxSize)}`}
|
||||
{config.multiple && ' • 여러 파일 선택 가능'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 파일 목록 */}
|
||||
<div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg">
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-gray-700">
|
||||
업로드된 파일
|
||||
</h3>
|
||||
{uploadedFiles.length > 0 && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
총 {formatFileSize(uploadedFiles.reduce((sum, file) => sum + file.fileSize, 0))}
|
||||
</Badge>
|
||||
)}
|
||||
{/* 좌우 분할 레이아웃 */}
|
||||
<div className="flex-1 flex gap-4 min-h-0">
|
||||
{/* 좌측: 이미지 미리보기 */}
|
||||
<div className="w-1/2 border border-gray-200 rounded-lg bg-gray-50 flex items-center justify-center overflow-hidden">
|
||||
{selectedFile && previewImageUrl ? (
|
||||
<img
|
||||
src={previewImageUrl}
|
||||
alt={selectedFile.realFileName}
|
||||
className="max-w-full max-h-full object-contain"
|
||||
/>
|
||||
) : selectedFile ? (
|
||||
<div className="flex flex-col items-center text-gray-400">
|
||||
{getFileIcon(selectedFile.fileExt)}
|
||||
<p className="mt-2 text-sm">미리보기 불가능</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center text-gray-400">
|
||||
<ImageIcon className="w-16 h-16 mb-2" />
|
||||
<p className="text-sm">파일을 선택하면 미리보기가 표시됩니다</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 우측: 파일 목록 */}
|
||||
<div className="w-1/2 border border-gray-200 rounded-lg overflow-hidden flex flex-col">
|
||||
<div className="p-3 border-b border-gray-200 bg-gray-50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-gray-700">
|
||||
업로드된 파일
|
||||
</h3>
|
||||
{uploadedFiles.length > 0 && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
총 {formatFileSize(uploadedFiles.reduce((sum, file) => sum + file.fileSize, 0))}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{uploadedFiles.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{uploadedFiles.map((file) => (
|
||||
<div
|
||||
key={file.objid}
|
||||
className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div className="flex-1 overflow-y-auto p-3">
|
||||
{uploadedFiles.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{uploadedFiles.map((file) => (
|
||||
<div
|
||||
key={file.objid}
|
||||
className={`
|
||||
flex items-center space-x-3 p-2 rounded-lg transition-colors cursor-pointer
|
||||
${selectedFile?.objid === file.objid ? 'bg-blue-50 border border-blue-200' : 'bg-gray-50 hover:bg-gray-100'}
|
||||
`}
|
||||
onClick={() => handleFileClick(file)}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
{getFileIcon(file.fileExt)}
|
||||
</div>
|
||||
|
|
@ -250,40 +319,52 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
|
|||
<Button
|
||||
variant={file.isRepresentative ? "default" : "ghost"}
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => onSetRepresentative(file)}
|
||||
className="h-7 w-7 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSetRepresentative(file);
|
||||
}}
|
||||
title={file.isRepresentative ? "현재 대표 파일" : "대표 파일로 설정"}
|
||||
>
|
||||
<Star className={`w-4 h-4 ${file.isRepresentative ? "fill-white" : ""}`} />
|
||||
<Star className={`w-3 h-3 ${file.isRepresentative ? "fill-white" : ""}`} />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => handleFileViewInternal(file)}
|
||||
className="h-7 w-7 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleFileViewInternal(file);
|
||||
}}
|
||||
title="미리보기"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
<Eye className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => onFileDownload(file)}
|
||||
className="h-7 w-7 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFileDownload(file);
|
||||
}}
|
||||
title="다운로드"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
<Download className="w-3 h-3" />
|
||||
</Button>
|
||||
{!isDesignMode && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 text-red-500 hover:text-red-700"
|
||||
onClick={() => onFileDelete(file)}
|
||||
className="h-7 w-7 p-0 text-red-500 hover:text-red-700"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFileDelete(file);
|
||||
}}
|
||||
title="삭제"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -291,17 +372,18 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-gray-500">
|
||||
<File className="w-16 h-16 mb-4 text-gray-300" />
|
||||
<p className="text-lg font-medium text-gray-600">업로드된 파일이 없습니다</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
{isDesignMode ? '디자인 모드에서는 파일을 업로드할 수 없습니다' : '위의 영역에 파일을 드래그하거나 클릭하여 업로드하세요'}
|
||||
<div className="flex flex-col items-center justify-center py-8 text-gray-500">
|
||||
<File className="w-12 h-12 mb-3 text-gray-300" />
|
||||
<p className="text-sm font-medium text-gray-600">업로드된 파일이 없습니다</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{isDesignMode ? '디자인 모드에서는 파일을 업로드할 수 없습니다' : '위의 영역에 파일을 업로드하세요'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue