diff --git a/apps/web/src/pages/scan-page.tsx b/apps/web/src/pages/scan-page.tsx index 9765a18..f55e837 100644 --- a/apps/web/src/pages/scan-page.tsx +++ b/apps/web/src/pages/scan-page.tsx @@ -1,25 +1,30 @@ import { useRef, useState } from "react"; import { useNavigate, Link } from "react-router-dom"; import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query"; +import { + Camera, + Upload, + X, + ImageOff, + CircleAlert, + ZoomIn, + Sun, + AlignCenter, + Check, +} from "lucide-react"; import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; import { Modal } from "@/components/ui/modal"; import { DiagnosisStatusBadge } from "@/components/diagnosis-status-badge"; import type { DiagnosisRecord } from "@zeavis/shared"; import { apiClient } from "@/lib/api-client"; -import { - AlignCenter, - Camera, - Check, - CircleAlert, - Sun, - Upload, - ZoomIn, -} from "lucide-react"; export function ScanPage() { const [fileName, setFileName] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); + const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null); const inputRef = useRef(null); + const imageRef = useRef(null); const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -42,7 +47,15 @@ export function ScanPage() { return; } setFileName(f.name); - setPreviewUrl(URL.createObjectURL(f)); + const url = URL.createObjectURL(f); + setPreviewUrl(url); + + // Detect image dimensions + const img = new Image(); + img.onload = () => { + setImageDimensions({ width: img.width, height: img.height }); + }; + img.src = url; }; const handleUpload = () => { @@ -61,250 +74,291 @@ export function ScanPage() { enabled: previewOpen, }); - const getUploadErrorMessage = (error: unknown): string => { - let raw = ""; - let source: string | undefined; - - if (error instanceof Error) { - raw = error.message; - // Check if error has source property from our ApiError - source = (error as any).source; - } else if (typeof error === "string") { - raw = error; - } - - // Distinguish between uploader service down vs model service down - if (raw.includes("HTTP 502")) { - if (source === "uploader") { - return "Layanan penyimpanan gambar sedang down. Hubungi administrator."; - } - if (source === "model-service") { - return "Layanan AI model sedang down. Hubungi administrator."; - } - return "Layanan backend sedang bermasalah (502). Silakan coba lagi."; - } - - if (raw.includes("Failed to fetch")) { - return "Koneksi ke server gagal. Pastikan API dan koneksi jaringan aktif."; - } - - if (raw.includes("Upload service error")) { - return "Layanan penyimpanan gambar sedang bermasalah. Coba beberapa saat lagi."; - } - - if (raw.includes("Model service error")) { - return "Layanan AI model sedang bermasalah. Coba beberapa saat lagi."; - } - - if (raw.includes("File type is not allowed")) { - return "Format file tidak didukung. Gunakan PNG, JPG, atau JPEG."; - } - - if (raw.includes("File must be smaller")) { - return "Ukuran file terlalu besar. Maksimal 5 MB."; - } - - if (raw.includes("File is empty")) { - return "File kosong. Silakan pilih file gambar yang valid."; - } - - return raw || "Upload gagal. Silakan coba lagi."; - }; - return ( -
- {/* Header */} -
-

Scan Tanaman

-

- Unggah foto daun jagung untuk dianalisis oleh sistem AI kami secara - real-time. -

+
+
+
+

Scan Tanaman

+

+ Unggah foto daun jagung untuk dianalisis oleh sistem AI kami secara + real-time. +

+
+
- {/* Main Content */} -
- {/* Left Sidebar */} -
-
- - Area Unggah Gambar -
+
+
+ + + {/* Left Sidebar */} +
+ + Area Unggah Gambar +
- {/* Upload Area */} -
-
- - Maks. 5MB + ) : ( +
+
+ Preview { + const img = e.currentTarget; + setImageDimensions({ + width: img.naturalWidth, + height: img.naturalHeight, + }); + }} + /> + {/* Scanner area detection overlay */} + {imageDimensions && ( +
+ {/* Outer dark overlay */} +
+ + {/* Scan area frame - responsive to image */} +
+ {/* Corner markers */} +
+
+
+
+ + {/* Center text */} +
+ AREA SCAN + + {imageDimensions.width} × {imageDimensions.height} + +
+
+
+ )} + + +
+
+

+ File: {fileName} +

+
+ + +
+
-
-
- {fileName && ( -
- Dipilih: {fileName} -
- )} - + )} - handleFile(e.target.files?.[0])} - /> + handleFile(e.target.files?.[0])} + /> -
- {!previewUrl ? ( - - ) : ( - - )} - {mutation.isError && ( -
- {getUploadErrorMessage(mutation.error)} -
- )} -
+ {/* Error state */} + {mutation.isError && ( +
+

Upload gagal

+

+ {mutation.error instanceof Error + ? mutation.error.message + : "Terjadi kesalahan saat memproses gambar"} +

+
+ )} + +
{/* Right Sidebar */} -
+
- {/* Modal Section */} + {/* Result Modal */} { @@ -429,6 +483,6 @@ export function ScanPage() {
) : null} -
+ ); }