diff --git a/src/App.jsx b/src/App.jsx
index 6314408..9b585d4 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -52,6 +52,9 @@ import SvgAirDryerC from './pages/home/SvgAirDryerC';
import IndexHistoryAlarm from './pages/history/alarm/IndexHistoryAlarm';
import IndexHistoryEvent from './pages/history/event/IndexHistoryEvent';
+// Image Viewer
+import ImageViewer from './Utils/ImageViewer';
+
const App = () => {
return (
@@ -76,6 +79,8 @@ const App = () => {
} />
+ } />
+
}>
} />
} />
@@ -148,7 +153,6 @@ const App = () => {
} />
- {/* Catch-all */}
} />
diff --git a/src/Utils/ImageViewer.jsx b/src/Utils/ImageViewer.jsx
new file mode 100644
index 0000000..a463f32
--- /dev/null
+++ b/src/Utils/ImageViewer.jsx
@@ -0,0 +1,248 @@
+import React, { useState, useEffect } from 'react';
+import { useParams } from 'react-router-dom';
+import { getFileUrl, getFolderFromFileType } from '../api/file-uploads';
+
+const ImageViewer = () => {
+ const { fileName } = useParams();
+ const [fileUrl, setFileUrl] = useState('');
+ const [error, setError] = useState('');
+ const [zoom, setZoom] = useState(1);
+ const [isImage, setIsImage] = useState(false);
+
+ useEffect(() => {
+ if (!fileName) {
+ setError('No file specified');
+ return;
+ }
+
+ try {
+ const decodedFileName = decodeURIComponent(fileName);
+ const fileExtension = decodedFileName.split('.').pop()?.toLowerCase();
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
+
+ setIsImage(imageExtensions.includes(fileExtension));
+
+ const folder = getFolderFromFileType(fileExtension);
+
+ const url = getFileUrl(folder, decodedFileName);
+ setFileUrl(url);
+
+ document.title = `File Viewer - ${decodedFileName}`;
+ } catch (error) {
+
+ setError('Failed to load file');
+ }
+ }, [fileName]);
+
+ useEffect(() => {
+ const handleKeyDown = (e) => {
+ if (!isImage) return;
+
+ if (e.key === '+' || e.key === '=') {
+ setZoom(prev => Math.min(prev + 0.1, 3));
+ } else if (e.key === '-' || e.key === '_') {
+ setZoom(prev => Math.max(prev - 0.1, 0.1));
+ } else if (e.key === '0') {
+ setZoom(1);
+ } else if (e.key === 'Escape') {
+ window.close();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [isImage]);
+
+
+ const handleWheel = (e) => {
+ if (!isImage || !e.ctrlKey) return;
+
+ e.preventDefault();
+ const delta = e.deltaY > 0 ? -0.1 : 0.1;
+ setZoom(prev => Math.min(Math.max(prev + delta, 0.1), 3));
+ };
+
+ const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.1, 3));
+ const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.1, 0.1));
+ const handleResetZoom = () => setZoom(1);
+
+ if (error) {
+ return (
+
+ );
+ }
+
+
+ if (!isImage) {
+ return (
+
+
+
File Type Not Supported
+
Image viewer only supports image files.
+
Please use direct file preview for PDFs and other documents.
+
+
+ );
+ }
+
+ return (
+
+
+ {isImage && (
+
+
+
+ {Math.round(zoom * 100)}%
+
+
+
+
+ )}
+
+
+ {isImage && fileUrl ? (
+
+

1 ? 'move' : 'default'
+ }}
+ onError={() => setError('Failed to load image')}
+ draggable={false}
+ />
+
+ ) : isImage ? (
+
+ ) : null}
+
+
+ {isImage && (
+
+
Mouse wheel + Ctrl: Zoom
+
Keyboard: +/− Zoom, 0: Reset, ESC: Close
+
+ )}
+
+ );
+};
+
+export default ImageViewer;
\ No newline at end of file