diff --git a/apps/web/src/components/diagnose-result-view.tsx b/apps/web/src/components/diagnose-result-view.tsx index 091e8ab..cd1fbe3 100644 --- a/apps/web/src/components/diagnose-result-view.tsx +++ b/apps/web/src/components/diagnose-result-view.tsx @@ -1,7 +1,16 @@ import { useState } from "react"; -import { ChevronDown, ChevronUp, AlertTriangle, BookOpen, ShieldCheck, Pill, RefreshCcw } from "lucide-react"; +import { + ChevronDown, + ChevronUp, + AlertTriangle, + BookOpen, + ShieldCheck, + Pill, + RefreshCcw, + FlaskConical, +} from "lucide-react"; +import { RiskBadge } from "./risk-badge"; -// Spesifikasi data yang dibutuhkan oleh cetakan ini type Props = { imageUrl: string; confidence: number; // contoh: 0.95 @@ -12,7 +21,7 @@ type Props = { symptoms: string[]; preventions: string[]; medicines: string[]; - onRescan?: () => void; // Tombol opsional untuk Scan Ulang + onRescan?: () => void; }; export function DiagnosisResultView({ @@ -27,9 +36,15 @@ export function DiagnosisResultView({ medicines, onRescan, }: Props) { - // Ingatan untuk laci (accordion) sebelah kanan const [openSection, setOpenSection] = useState("detail"); + const getRiskLevelKey = (level: string): "low" | "medium" | "high" => { + const normalized = level.toLowerCase(); + if (normalized.includes("rendah")) return "low"; + if (normalized.includes("tinggi")) return "high"; + return "medium"; + }; + const toggleSection = (section: string) => { setOpenSection(openSection === section ? "" : section); }; @@ -38,65 +53,67 @@ export function DiagnosisResultView({ return (
- - {/* KIRI: Area Gambar & Hasil Deteksi ML */} + {/* Image Area & Result */}
- {/* Gambar Daun */}
- Daun yang dianalisis
- 📷 + + 📷 + Gambar yang dianalisis
- {/* Kartu Hasil Deteksi ML */} + {/* Result Card */}
HASIL DETEKSI ML
- -

{diseaseName}

+ +

+ {diseaseName} +

{scientificName}

- {/* Bar Tingkat Keyakinan AI */} + {/* Confidence Bar */}
Tingkat Keyakinan AI {confidencePercent}%
-

- Di atas ambang batas minimum (75%) + Di atas ambang batas minimum + (75%)

- Tingkat Keparahan: - - {riskLevel.toUpperCase()} + + Tingkat Keparahan: +
- {/* KANAN: Area Edukasi (Accordion) & Referensi */} + {/* Education & References */}
- - {/* Tombol Header (Jika ada fungsi onRescan) */} + {/* Rescan Button */} {onRescan && (
-
)} - {/* Laci 1: Detail Penyakit */} + {/* Detail Section */}
-
toggleSection("detail")} className="flex items-center justify-between p-4 cursor-pointer hover:bg-slate-50" > @@ -117,22 +134,28 @@ export function DiagnosisResultView({
Detail Penyakit
- {openSection === "detail" ? : } + {openSection === "detail" ? ( + + ) : ( + + )}
{openSection === "detail" && (

{description}

Gejala Umum:
    - {symptoms.map((sym, i) =>
  • {sym}
  • )} + {symptoms.map((sym, i) => ( +
  • {sym}
  • + ))}
)}
- {/* Laci 2: Panduan Pencegahan */} + {/* Prevention Section */}
-
toggleSection("cegah")} className="flex items-center justify-between p-4 cursor-pointer hover:bg-slate-50" > @@ -142,14 +165,19 @@ export function DiagnosisResultView({
Panduan Pencegahan
- {openSection === "cegah" ? : } + {openSection === "cegah" ? ( + + ) : ( + + )} {openSection === "cegah" && (
    {preventions.map((prev, i) => (
  • - {prev} + {" "} + {prev}
  • ))}
@@ -157,9 +185,9 @@ export function DiagnosisResultView({ )}
- {/* Laci 3: Rekomendasi Obat */} + {/* Medicine Section */}
-
toggleSection("obat")} className="flex items-center justify-between p-4 cursor-pointer hover:bg-slate-50" > @@ -169,14 +197,19 @@ export function DiagnosisResultView({
Rekomendasi Obat
- {openSection === "obat" ? : } + {openSection === "obat" ? ( + + ) : ( + + )} {openSection === "obat" && (
-
    +
      {medicines.map((med, i) => (
    • - ⚕️ {med} + {" "} + {med}
    • ))}
    @@ -184,22 +217,43 @@ export function DiagnosisResultView({ )}
- {/* Kotak Disclaimer Penting */} + {/* Disclaimer */}
Disclaimer Penting

- ZeaVis Edu adalah alat bantu edukasi awal, bukan pengganti diagnosis profesional. Hasil ini tidak dapat dijadikan dasar keputusan agronomis tanpa konfirmasi dari ahli pertanian atau Petugas Pengamat Organisme Pengganggu Tanaman (POPT) yang berwenang. + ZeaVis Edu adalah alat bantu edukasi awal, bukan + pengganti diagnosis profesional. Hasil ini tidak dapat dijadikan + dasar keputusan agronomis tanpa konfirmasi dari{" "} + + ahli pertanian atau Petugas Pengamat Organisme Pengganggu Tanaman + (POPT) + {" "} + yang berwenang.

- ); } -// Ikon bantuan untuk persentase function CheckCircle2(props: any) { - return -} \ No newline at end of file + return ( + + + + + ); +} diff --git a/apps/web/src/components/layout/main-layout.tsx b/apps/web/src/components/layout/main-layout.tsx index 5179f45..c8e87fc 100644 --- a/apps/web/src/components/layout/main-layout.tsx +++ b/apps/web/src/components/layout/main-layout.tsx @@ -19,13 +19,7 @@ export function MainLayout({ children }: Props) { {/* Main Content */} -
{ - if (isSidebarOpen) { - setIsSidebarOpen(false); - } - }} - > +
{/* Header Mobile */}
diff --git a/apps/web/src/components/layout/sidebar.tsx b/apps/web/src/components/layout/sidebar.tsx index 0505098..f1ecd7f 100644 --- a/apps/web/src/components/layout/sidebar.tsx +++ b/apps/web/src/components/layout/sidebar.tsx @@ -3,14 +3,14 @@ import { Link, useLocation } from "react-router-dom"; import { Leaf, LogOut, - Menu, - ChevronLeft, + ChevronsLeft, LayoutDashboard, Scan, Activity, BookOpen, UserCheck, ChartBar, + ChevronsRight, } from "lucide-react"; import { MobileNav } from "./mobile-nav"; import { useAuthStore } from "@/store/auth-store"; @@ -81,6 +81,7 @@ export function Sidebar() {
+ {/* Toggle Button */}
@@ -103,7 +104,6 @@ export function Sidebar() { className={`flex-1 overflow-y-auto py-6 flex flex-col gap-3 ${isSidebarOpen ? "px-5" : "px-0 items-center"}`} > {filteredNavItems.map((item) => { - // Skip expert-only items for non-expert users if (item.expertOnly && !isExpert) { return null; } @@ -116,7 +116,6 @@ export function Sidebar() { key={item.path} to={item.path} title={item.label} - onClick={() => setIsSidebarOpen(!isSidebarOpen)} className={`flex items-center rounded-full font-medium transition-all ${ isSidebarOpen ? "px-4 py-3 gap-3 text-[16px] w-full" @@ -160,4 +159,4 @@ export function Sidebar() { /> ); -} +} \ No newline at end of file diff --git a/apps/web/src/pages/catalog-page.tsx b/apps/web/src/pages/catalog-page.tsx index 46606ca..775a638 100644 --- a/apps/web/src/pages/catalog-page.tsx +++ b/apps/web/src/pages/catalog-page.tsx @@ -9,6 +9,7 @@ import { ShieldCheck, Search, Leaf, + FlaskConical, } from "lucide-react"; import { apiClient } from "@/lib/api-client"; import { RiskBadge } from "@/components/risk-badge"; @@ -224,14 +225,14 @@ export function CatalogPage() {

- + Rekomendasi Obat

    {finalMedicines ? ( finalMedicines.map((obat, idx) => (
  • - + {obat}
  • )) diff --git a/apps/web/src/pages/diagnosis-detail-page.tsx b/apps/web/src/pages/diagnosis-detail-page.tsx index fe23342..8303407 100644 --- a/apps/web/src/pages/diagnosis-detail-page.tsx +++ b/apps/web/src/pages/diagnosis-detail-page.tsx @@ -1,5 +1,6 @@ import { Link, useParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, @@ -10,9 +11,19 @@ import { } from "@/components/ui/card"; import { apiClient } from "@/lib/api-client"; import { DiagnosisResultView } from "../components/diagnose-result-view"; +import { diseaseCatalogSeed } from "@zeavis/shared"; // Pastikan jalur import ini benar export function DiagnosisDetailPage() { const { id } = useParams(); + + // Scroll to top when diagnosis ID changes + useEffect(() => { + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + }, [id]); + const query = useQuery({ queryKey: ["diagnosis", id], queryFn: () => apiClient.getDiagnosis(id!), @@ -33,44 +44,55 @@ export function DiagnosisDetailPage() { ); const diagnosis = query.data; + + // Fallback logic for image and medicine recommendations const finalImageUrl = diagnosis.imageUrl || "/src/assets/images/placeholder-image.png"; + const seedData = diagnosis.disease + ? diseaseCatalogSeed.find( + (seed) => seed.commonName === diagnosis.disease?.commonName, + ) + : null; + + const finalMedicines = + (diagnosis.disease as any)?.medicineRecommendations || + seedData?.medicineRecommendations || + []; + return (
    - {/* Header Page */}
    -

    +

    Analisis & Modul Edukasi

    - Hasil deteksi AI dan panduan edukasi berdasarkan gambar yang - diunggah + Hasil deteksi AI dan panduan edukasi

    -
    - {/* Warning if expert review is needed */} {diagnosis.status === "needs_review" && (
    Catatan: Hasil ini masih sementara karena tingkat - keyakinan (confidence) di bawah standar minimum dan sedang menunggu - ulasan pakar. + keyakinan (confidence) di bawah standar minimum.
    )} - {/* Message if AI failed to process */} {diagnosis.failureReason && (
    Gagal Memproses: {diagnosis.failureReason}
    )} - {/* Diagnosis Result View */} - {/* List of Alternative Predictions */} {diagnosis.predictions && diagnosis.predictions.length > 0 && (

    @@ -116,7 +137,7 @@ export function DiagnosisDetailPage() {

    )} - {/* Expert Notes (Only shown if already reviewed by human) */} + {/* Expert Review Section */} {diagnosis.latestReview && ( diff --git a/apps/web/src/pages/scan-page.tsx b/apps/web/src/pages/scan-page.tsx index 7ffabe4..28938ac 100644 --- a/apps/web/src/pages/scan-page.tsx +++ b/apps/web/src/pages/scan-page.tsx @@ -18,12 +18,15 @@ import { Modal } from "@/components/ui/modal"; import type { DiagnosisRecord } from "@zeavis/shared"; import { apiClient } from "@/lib/api-client"; import { trackScan, trackDiagnosisResult } from "@/lib/telemetry"; -import { DiagnosisResultView } from "../components/diagnose-result-view"; +import { DiagnosisResultView } from "../components/diagnose-result-view"; export function ScanPage() { const [fileName, setFileName] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); - const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null); + const [imageDimensions, setImageDimensions] = useState<{ + width: number; + height: number; + } | null>(null); const inputRef = useRef(null); const imageRef = useRef(null); const navigate = useNavigate(); @@ -36,6 +39,8 @@ export function ScanPage() { queryClient.invalidateQueries({ queryKey: ["diagnoses"] }); setDiagnosisPreview(diagnosis); setPreviewOpen(true); + setFileName(null); + setPreviewUrl(null); if (inputRef.current) inputRef.current.value = ""; }, onError: () => { @@ -68,7 +73,8 @@ export function ScanPage() { }; const [previewOpen, setPreviewOpen] = useState(false); - const [diagnosisPreview, setDiagnosisPreview] = useState(null); + const [diagnosisPreview, setDiagnosisPreview] = + useState(null); const diagnosesQuery = useQuery({ queryKey: ["diagnoses"], @@ -83,7 +89,8 @@ export function ScanPage() {

    Scan Tanaman

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