From cf44a584867ff23664dda1ef54a9960775fb7518 Mon Sep 17 00:00:00 2001 From: Asep Haryana Saputra <90584806+MythEclipse@users.noreply.github.com> Date: Fri, 22 May 2026 16:58:23 +0000 Subject: [PATCH] feat: add diagnosis detail UI --- apps/web/src/app.tsx | 9 ++ apps/web/src/components/diagnosis-card.tsx | 28 +++++ .../src/components/diagnosis-status-badge.tsx | 22 ++++ apps/web/src/pages/diagnosis-detail-page.tsx | 104 ++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 apps/web/src/components/diagnosis-card.tsx create mode 100644 apps/web/src/components/diagnosis-status-badge.tsx create mode 100644 apps/web/src/pages/diagnosis-detail-page.tsx diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index 9458d1d..1b77ccc 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -5,6 +5,7 @@ import { DashboardPage } from '@/pages/dashboard-page'; import { LandingPage } from '@/pages/landing-page'; import { CatalogPage } from '@/pages/catalog-page'; import { DiseaseDetailPage } from '@/pages/disease-detail-page'; +import { DiagnosisDetailPage } from '@/pages/diagnosis-detail-page'; import { LoginPage } from '@/pages/login-page'; import { RegisterPage } from '@/pages/register-page'; @@ -22,6 +23,14 @@ const router = createBrowserRouter([ ), }, + { + path: '/diagnoses/:id', + element: ( + + + + ), + }, { path: '/catalog', element: }, { path: '/catalog/:slug', element: }, ]); diff --git a/apps/web/src/components/diagnosis-card.tsx b/apps/web/src/components/diagnosis-card.tsx new file mode 100644 index 0000000..4d23af7 --- /dev/null +++ b/apps/web/src/components/diagnosis-card.tsx @@ -0,0 +1,28 @@ +import { Link } from 'react-router-dom'; +import type { DiagnosisRecord } from '@zeavis/shared'; +import { Card, CardContent } from '@/components/ui/card'; +import { DiagnosisStatusBadge } from '@/components/diagnosis-status-badge'; + +export function DiagnosisCard({ diagnosis, expertLink = false }: { diagnosis: DiagnosisRecord; expertLink?: boolean }) { + const href = expertLink ? `/expert/reviews?diagnosis=${diagnosis.id}` : `/diagnoses/${diagnosis.id}`; + + return ( + + + + Daun jagung +
+
+

{diagnosis.disease?.commonName ?? 'Diagnosis gagal'}

+ +
+

+ {diagnosis.confidence === null ? 'Tidak ada confidence' : `Confidence ${(diagnosis.confidence * 100).toFixed(1)}%`} +

+

{new Date(diagnosis.createdAt).toLocaleString('id-ID')}

+
+
+
+ + ); +} diff --git a/apps/web/src/components/diagnosis-status-badge.tsx b/apps/web/src/components/diagnosis-status-badge.tsx new file mode 100644 index 0000000..385f0e0 --- /dev/null +++ b/apps/web/src/components/diagnosis-status-badge.tsx @@ -0,0 +1,22 @@ +import type { DiagnosisStatus } from '@zeavis/shared'; +import { cn } from '@/lib/utils'; + +const labels: Record = { + ai_verified: 'Terverifikasi AI', + needs_review: 'Menunggu review pakar', + expert_verified: 'Diverifikasi pakar', + expert_corrected: 'Dikoreksi pakar', + failed: 'Gagal diproses', +}; + +const styles: Record = { + ai_verified: 'bg-emerald-100 text-emerald-800', + needs_review: 'bg-amber-100 text-amber-800', + expert_verified: 'bg-blue-100 text-blue-800', + expert_corrected: 'bg-purple-100 text-purple-800', + failed: 'bg-red-100 text-red-800', +}; + +export function DiagnosisStatusBadge({ status, className }: { status: DiagnosisStatus; className?: string }) { + return {labels[status]}; +} diff --git a/apps/web/src/pages/diagnosis-detail-page.tsx b/apps/web/src/pages/diagnosis-detail-page.tsx new file mode 100644 index 0000000..c9f45e6 --- /dev/null +++ b/apps/web/src/pages/diagnosis-detail-page.tsx @@ -0,0 +1,104 @@ +import { Link, useParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { DiagnosisStatusBadge } from '@/components/diagnosis-status-badge'; +import { RiskBadge } from '@/components/risk-badge'; +import { apiClient } from '@/lib/api-client'; + +export function DiagnosisDetailPage() { + const { id } = useParams(); + const query = useQuery({ + queryKey: ['diagnosis', id], + queryFn: () => apiClient.getDiagnosis(id!), + enabled: Boolean(id), + }); + + if (query.isLoading) return
Memuat diagnosis...
; + if (query.error || !query.data) return
Diagnosis tidak ditemukan
; + + const diagnosis = query.data; + + return ( +
+
+
+
+

Detail Diagnosis

+

{diagnosis.disease?.commonName ?? 'Diagnosis gagal'}

+
+ +
+ +
+ + + Daun jagung + + + + + +
+ Hasil AI + +
+ + {diagnosis.confidence === null ? 'Model gagal memproses gambar.' : `Confidence ${(diagnosis.confidence * 100).toFixed(1)}%`} + +
+ + {diagnosis.status === 'needs_review' && ( +
+ Hasil ini masih sementara karena confidence di bawah 70% dan sedang menunggu review pakar. +
+ )} + {diagnosis.failureReason &&

{diagnosis.failureReason}

} + {diagnosis.disease && ( +
+
+

{diagnosis.disease.label}

+ +
+

{diagnosis.disease.summary}

+
+

Rekomendasi

+
    + {diagnosis.disease.recommendations.map((item) =>
  • {item}
  • )} +
+
+
+ )} +
+
+
+ + + + Top Predictions + + + {diagnosis.predictions.map((prediction) => ( +
+ {prediction.rank}. {prediction.modelLabel} + {(prediction.confidence * 100).toFixed(1)}% +
+ ))} +
+
+ + {diagnosis.latestReview && ( + + + Catatan Pakar + {diagnosis.latestReview.expert.name} + + +

{diagnosis.latestReview.notes}

+
+
+ )} +
+
+ ); +}