이미지 미리보기 기능
This commit is contained in:
@@ -32,6 +32,9 @@ import {
|
||||
Download,
|
||||
Eye,
|
||||
X,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
RotateCw,
|
||||
} from "lucide-react";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { getCurrentUser, UserInfo } from "@/lib/api/client";
|
||||
@@ -84,6 +87,49 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||
const [editFormData, setEditFormData] = useState<Record<string, any>>({});
|
||||
const [editingRowData, setEditingRowData] = useState<Record<string, any> | null>(null);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// 이미지 미리보기 상태
|
||||
const [previewImage, setPreviewImage] = useState<FileInfo | null>(null);
|
||||
const [showPreviewModal, setShowPreviewModal] = useState(false);
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [rotation, setRotation] = useState(0);
|
||||
|
||||
// 이미지 미리보기 핸들러들
|
||||
const handlePreviewImage = useCallback((fileInfo: FileInfo) => {
|
||||
setPreviewImage(fileInfo);
|
||||
setShowPreviewModal(true);
|
||||
setZoom(1);
|
||||
setRotation(0);
|
||||
}, []);
|
||||
|
||||
const closePreviewModal = useCallback(() => {
|
||||
setShowPreviewModal(false);
|
||||
setPreviewImage(null);
|
||||
setZoom(1);
|
||||
setRotation(0);
|
||||
}, []);
|
||||
|
||||
const handleZoom = useCallback((direction: "in" | "out") => {
|
||||
setZoom((prev) => {
|
||||
if (direction === "in") {
|
||||
return Math.min(prev + 0.25, 3);
|
||||
} else {
|
||||
return Math.max(prev - 0.25, 0.25);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleRotate = useCallback(() => {
|
||||
setRotation((prev) => (prev + 90) % 360);
|
||||
}, []);
|
||||
|
||||
const formatFileSize = useCallback((bytes: number): string => {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||
}, []);
|
||||
const [showFileModal, setShowFileModal] = useState(false);
|
||||
const [currentFileData, setCurrentFileData] = useState<FileColumnData | null>(null);
|
||||
const [currentFileColumn, setCurrentFileColumn] = useState<DataTableColumn | null>(null);
|
||||
@@ -1812,10 +1858,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
// TODO: 이미지 미리보기 모달 구현
|
||||
alert("이미지 미리보기 기능은 준비 중입니다.");
|
||||
}}
|
||||
onClick={() => handlePreviewImage(fileInfo)}
|
||||
>
|
||||
<Eye className="mr-1 h-4 w-4" />
|
||||
미리보기
|
||||
@@ -1904,6 +1947,65 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 이미지 미리보기 다이얼로그 */}
|
||||
<Dialog open={showPreviewModal} onOpenChange={closePreviewModal}>
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center justify-between">
|
||||
<span className="truncate">{previewImage?.name}</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button size="sm" variant="outline" onClick={() => handleZoom("out")} disabled={zoom <= 0.25}>
|
||||
<ZoomOut className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="min-w-[60px] text-center text-sm text-gray-500">{Math.round(zoom * 100)}%</span>
|
||||
<Button size="sm" variant="outline" onClick={() => handleZoom("in")} disabled={zoom >= 3}>
|
||||
<ZoomIn className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={handleRotate}>
|
||||
<RotateCw className="h-4 w-4" />
|
||||
</Button>
|
||||
{previewImage && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
handleDownloadFile(previewImage);
|
||||
}}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-1 items-center justify-center overflow-auto rounded-lg bg-gray-50 p-4">
|
||||
{previewImage && (
|
||||
<img
|
||||
src={`${process.env.NEXT_PUBLIC_API_URL}/files/preview/${previewImage.id}?serverFilename=${previewImage.serverFilename}`}
|
||||
alt={previewImage.name}
|
||||
className="max-h-full max-w-full object-contain transition-transform duration-200"
|
||||
style={{
|
||||
transform: `scale(${zoom}) rotate(${rotation}deg)`,
|
||||
}}
|
||||
onError={() => {
|
||||
console.error("이미지 로딩 실패:", previewImage);
|
||||
toast.error("이미지를 불러올 수 없습니다.");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{previewImage && (
|
||||
<div className="flex items-center justify-between border-t pt-3 text-sm text-gray-500">
|
||||
<div>크기: {formatFileSize(previewImage.size)}</div>
|
||||
<div>타입: {previewImage.type}</div>
|
||||
<div>업로드: {new Date(previewImage.uploadedAt).toLocaleDateString("ko-KR")}</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user