fix(frontend): wrap catalog, disease detail, and expert reviews with MainLayout

- Add MainLayout wrapper to /catalog, /catalog/:slug, and /expert/reviews
  routes in app.tsx so they share the same navbar and footer as dashboard
- Remove duplicate header/layout/spacing from CatalogPage,
  DiseaseDetailPage, and ExpertReviewsPage
- Update ExpertReviewsPage to use consistent space-y layout pattern
  matching other pages
- Update CatalogPage to remove outer main wrapper since MainLayout
  provides the container

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Asep Haryana Saputra
2026-06-02 07:47:59 +00:00
co-authored by Claude Opus 4.7
parent 4d6ca4912c
commit 81d083a2b8
4 changed files with 344 additions and 357 deletions
+19 -3
View File
@@ -78,12 +78,28 @@ const router = createBrowserRouter([
path: "/expert/reviews",
element: (
<AuthGuard requireExpert>
<ExpertReviewsPage />
<MainLayout>
<ExpertReviewsPage />
</MainLayout>
</AuthGuard>
),
},
{ path: "/catalog", element: <CatalogPage /> },
{ path: "/catalog/:slug", element: <DiseaseDetailPage /> },
{
path: "/catalog",
element: (
<MainLayout>
<CatalogPage />
</MainLayout>
),
},
{
path: "/catalog/:slug",
element: (
<MainLayout>
<DiseaseDetailPage />
</MainLayout>
),
},
]);
export function App() {
+10 -21
View File
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link, useSearchParams } from 'react-router-dom';
import { ArrowLeft, Shield, AlertCircle } from 'lucide-react';
import { AlertCircle } from 'lucide-react';
import type { RiskLevel } from '@zeavis/shared';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
@@ -42,25 +42,15 @@ export function CatalogPage() {
});
return (
<main className="min-h-screen px-6 py-8 bg-[#ECF4E8]">
<div className="mx-auto max-w-6xl space-y-8">
<header className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button asChild variant="ghost" className="p-2">
<Link to="/dashboard">
<ArrowLeft className="h-5 w-5" />
</Link>
</Button>
<div>
<p className="text-sm font-medium text-primary">Edukasi Penyakit</p>
<h1 className="text-3xl font-bold tracking-tight">Katalog Penyakit</h1>
</div>
</div>
</div>
</header>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-primary">Edukasi Penyakit</p>
<h1 className="text-3xl font-bold tracking-tight">Katalog Penyakit</h1>
</div>
</div>
{/* Filters */}
{/* Filters */}
<div className="flex flex-col gap-4 md:flex-row md:items-end">
<div className="flex-1">
<label htmlFor="search" className="block text-sm font-medium mb-2">
@@ -178,7 +168,6 @@ export function CatalogPage() {
))}
</div>
)}
</div>
</main>
</div>
);
}
+95 -97
View File
@@ -1,7 +1,7 @@
import { useParams, Link } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { isDiseaseSlug } from '@zeavis/shared';
import { ArrowLeft, BookOpen, AlertCircle } from 'lucide-react';
import { ArrowLeft, AlertCircle, BookOpen } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { RiskBadge } from '@/components/risk-badge';
@@ -20,37 +20,37 @@ export function DiseaseDetailPage() {
if (!validatedSlug || error || (!isLoading && !disease)) {
return (
<main className="min-h-screen px-6 py-8">
<div className="mx-auto max-w-4xl">
<Button asChild variant="ghost" className="mb-8">
<div className="space-y-6">
<div className="flex items-center gap-4">
<Button asChild variant="ghost">
<Link to="/catalog">
<ArrowLeft className="mr-2 h-4 w-4" />
Kembali ke Katalog
</Link>
</Button>
<Card className="p-8 text-center">
<p className="text-muted-foreground">Materi tidak ditemukan</p>
</Card>
</div>
</main>
<Card className="p-8 text-center">
<p className="text-muted-foreground">Materi tidak ditemukan</p>
</Card>
</div>
);
}
if (isLoading) {
return (
<main className="min-h-screen px-6 py-8">
<div className="mx-auto max-w-4xl">
<Button asChild variant="ghost" className="mb-8">
<div className="space-y-6">
<div className="flex items-center gap-4">
<Button asChild variant="ghost">
<Link to="/catalog">
<ArrowLeft className="mr-2 h-4 w-4" />
Kembali ke Katalog
</Link>
</Button>
<Card className="p-8 text-center">
<p className="text-muted-foreground">Memuat materi...</p>
</Card>
</div>
</main>
<Card className="p-8 text-center">
<p className="text-muted-foreground">Memuat materi...</p>
</Card>
</div>
);
}
@@ -59,88 +59,86 @@ export function DiseaseDetailPage() {
}
return (
<main className="min-h-screen px-6 py-8">
<div className="mx-auto max-w-4xl space-y-8">
<div className="flex items-center justify-between">
<Button asChild variant="ghost">
<Link to="/catalog">
<ArrowLeft className="mr-2 h-4 w-4" />
Kembali ke Katalog
</Link>
</Button>
<Button asChild variant="outline">
<Link to="/dashboard">
<BookOpen className="mr-2 h-4 w-4" />
Dashboard
</Link>
</Button>
</div>
<div className="space-y-4">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-4xl font-bold tracking-tight">{disease.commonName}</h1>
<p className="mt-2 text-lg text-muted-foreground">{disease.label}</p>
</div>
<RiskBadge level={disease.riskLevel} />
</div>
<p className="text-lg leading-relaxed">{disease.summary}</p>
</div>
<Card>
<CardHeader>
<CardTitle>Deskripsi</CardTitle>
</CardHeader>
<CardContent>
<p className="leading-relaxed text-muted-foreground">{disease.description}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Gejala</CardTitle>
<CardDescription>Tanda-tanda yang perlu diperhatikan</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-3">
{disease.symptoms.map((symptom, index) => (
<li key={index} className="flex gap-3">
<AlertCircle className="h-5 w-5 flex-shrink-0 text-primary mt-0.5" />
<span className="text-muted-foreground">{symptom}</span>
</li>
))}
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Rekomendasi Penanganan</CardTitle>
<CardDescription>Langkah-langkah untuk mengendalikan penyakit</CardDescription>
</CardHeader>
<CardContent>
<ol className="space-y-3">
{disease.recommendations.map((recommendation, index) => (
<li key={index} className="flex gap-3">
<span className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground">
{index + 1}
</span>
<span className="text-muted-foreground pt-0.5">{recommendation}</span>
</li>
))}
</ol>
</CardContent>
</Card>
<div className="flex gap-3 pt-4">
<Button asChild className="flex-1">
<Link to="/catalog">Lihat Katalog Lengkap</Link>
</Button>
<Button asChild variant="outline" className="flex-1">
<Link to="/dashboard">Kembali ke Dashboard</Link>
</Button>
</div>
<div className="space-y-6">
<div className="flex items-center justify-between">
<Button asChild variant="ghost">
<Link to="/catalog">
<ArrowLeft className="mr-2 h-4 w-4" />
Kembali ke Katalog
</Link>
</Button>
<Button asChild variant="outline">
<Link to="/dashboard">
<BookOpen className="mr-2 h-4 w-4" />
Dashboard
</Link>
</Button>
</div>
</main>
<div className="space-y-4">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-4xl font-bold tracking-tight">{disease.commonName}</h1>
<p className="mt-2 text-lg text-muted-foreground">{disease.label}</p>
</div>
<RiskBadge level={disease.riskLevel} />
</div>
<p className="text-lg leading-relaxed">{disease.summary}</p>
</div>
<Card>
<CardHeader>
<CardTitle>Deskripsi</CardTitle>
</CardHeader>
<CardContent>
<p className="leading-relaxed text-muted-foreground">{disease.description}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Gejala</CardTitle>
<CardDescription>Tanda-tanda yang perlu diperhatikan</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-3">
{disease.symptoms.map((symptom, index) => (
<li key={index} className="flex gap-3">
<AlertCircle className="h-5 w-5 flex-shrink-0 text-primary mt-0.5" />
<span className="text-muted-foreground">{symptom}</span>
</li>
))}
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Rekomendasi Penanganan</CardTitle>
<CardDescription>Langkah-langkah untuk mengendalikan penyakit</CardDescription>
</CardHeader>
<CardContent>
<ol className="space-y-3">
{disease.recommendations.map((recommendation, index) => (
<li key={index} className="flex gap-3">
<span className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground">
{index + 1}
</span>
<span className="text-muted-foreground pt-0.5">{recommendation}</span>
</li>
))}
</ol>
</CardContent>
</Card>
<div className="flex gap-3 pt-4">
<Button asChild className="flex-1">
<Link to="/catalog">Lihat Katalog Lengkap</Link>
</Button>
<Button asChild variant="outline" className="flex-1">
<Link to="/dashboard">Kembali ke Dashboard</Link>
</Button>
</div>
</div>
);
}
+220 -236
View File
@@ -1,7 +1,7 @@
import { FormEvent, useMemo, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom';
import { useMutation, useQueries, useQueryClient } from '@tanstack/react-query';
import type { DiseaseSlug, DiagnosisStatus } from '@zeavis/shared';
import type { DiseaseSlug } from '@zeavis/shared';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { DiagnosisStatusBadge } from '@/components/diagnosis-status-badge';
@@ -24,14 +24,18 @@ export function ExpertReviewsPage() {
const reviews = reviewsQuery.data || [];
const diseases = diseasesQuery.data || [];
const selected = useMemo(() => reviews.find((item) => item.id === selectedId) ?? reviews[0] ?? null, [reviews, selectedId]);
const selected = useMemo(
() => reviews.find((item) => item.id === selectedId) ?? reviews[0] ?? null,
[reviews, selectedId],
);
const mutation = useMutation({
mutationFn: () => apiClient.reviewDiagnosis(selected!.id, {
verdict,
correctedDiseaseSlug: verdict === 'corrected' ? correctedDiseaseSlug || undefined : undefined,
notes,
}),
mutationFn: () =>
apiClient.reviewDiagnosis(selected!.id, {
verdict,
correctedDiseaseSlug: verdict === 'corrected' ? correctedDiseaseSlug || undefined : undefined,
notes,
}),
onSuccess: () => {
setNotes('');
setVerdict('verified');
@@ -47,244 +51,224 @@ export function ExpertReviewsPage() {
await mutation.mutateAsync();
}
const statusLabel = (status: DiagnosisStatus) => {
switch (status) {
case 'ai_verified': return 'Terverifikasi AI';
case 'needs_review': return 'Menunggu Review';
case 'expert_verified': return 'Diverifikasi Pakar';
case 'expert_corrected': return 'Dikoreksi Pakar';
case 'failed': return 'Gagal';
}
};
const isLoading = reviewsQuery.isPending || diseasesQuery.isPending;
return (
<main className="min-h-screen bg-background">
<header className="border-b bg-card">
<div className="mx-auto flex max-w-7xl items-center justify-between p-6">
<div>
<p className="text-sm font-medium text-primary">Review Pakar</p>
<h1 className="text-2xl font-bold">Diagnosis Menunggu Review</h1>
</div>
<Link to="/dashboard">
<Button variant="outline">Dashboard</Button>
</Link>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-primary">Review Pakar</p>
<h1 className="text-3xl font-bold">Diagnosis Menunggu Review</h1>
</div>
</header>
</div>
<div className="mx-auto max-w-7xl p-6">
<div className="grid gap-6 lg:grid-cols-3">
{/* Left: List of reviews */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="font-semibold">Daftar Diagnosis</h2>
<span className="rounded-full bg-muted px-2.5 py-0.5 text-xs font-medium">{reviews.length}</span>
</div>
<div className="space-y-2">
{reviewsQuery.isPending || diseasesQuery.isPending ? (
<p className="text-sm text-muted-foreground">Memuat diagnosis...</p>
) : reviews.length === 0 ? (
<p className="text-sm text-muted-foreground">Tidak ada diagnosis yang menunggu review</p>
) : (
reviews.map((review) => (
<button
key={review.id}
type="button"
onClick={() => setSearchParams({ diagnosis: review.id })}
className={`w-full text-left transition ${
selected?.id === review.id
? 'ring-2 ring-primary'
: 'hover:border-primary'
}`}
>
<Card className="transition hover:border-primary">
<CardContent className="flex gap-3 p-3">
<img
src={review.imageUrl}
alt="Daun jagung"
className="h-20 w-20 rounded-lg object-cover"
/>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex flex-wrap items-center justify-between gap-1">
<h3 className="text-sm font-semibold">{review.disease?.commonName ?? 'Diagnosis gagal'}</h3>
<DiagnosisStatusBadge status={review.status} className="text-xs" />
</div>
<p className="text-xs text-muted-foreground">
{review.confidence === null ? 'Tidak ada confidence' : `${(review.confidence * 100).toFixed(1)}%`}
</p>
<p className="text-xs text-muted-foreground">{new Date(review.createdAt).toLocaleString('id-ID')}</p>
</div>
</CardContent>
</Card>
</button>
))
)}
</div>
<div className="grid gap-6 lg:grid-cols-3">
{/* Left: List of reviews */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="font-semibold">Daftar Diagnosis</h2>
<span className="rounded-full bg-muted px-2.5 py-0.5 text-xs font-medium">{reviews.length}</span>
</div>
{/* Right: Detail and form */}
<div className="lg:col-span-2 space-y-6">
{reviewsQuery.isPending || diseasesQuery.isPending ? (
<Card>
<CardContent className="p-6 text-center text-muted-foreground">
Memuat diagnosis...
</CardContent>
</Card>
) : selected ? (
<>
{/* Detail card */}
<Card>
<CardHeader>
<CardTitle>{selected.disease?.commonName ?? 'Diagnosis gagal'}</CardTitle>
<CardDescription>
{new Date(selected.createdAt).toLocaleString('id-ID')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<img
src={selected.imageUrl}
alt={`Gambar daun jagung untuk ${selected.disease?.commonName ?? 'diagnosis'}`}
className="h-64 w-full rounded-lg object-cover"
/>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Status</span>
<DiagnosisStatusBadge status={selected.status} />
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Confidence</span>
<span className="text-sm">
{selected.confidence === null ? 'N/A' : `${(selected.confidence * 100).toFixed(1)}%`}
</span>
</div>
</div>
{/* Top predictions */}
{selected.predictions.length > 0 && (
<div className="space-y-2">
<h4 className="text-sm font-medium">Prediksi Teratas</h4>
<div className="space-y-1">
{selected.predictions.map((pred) => (
<div key={pred.id} className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">{pred.modelLabel}</span>
<span className="font-medium">{(pred.confidence * 100).toFixed(1)}%</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
{/* Review form */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Review Pakar</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Verdict selection */}
<div className="space-y-2">
<label className="text-sm font-medium">Keputusan</label>
<div className="flex gap-3">
<label htmlFor="verdict-verified" className="flex items-center gap-2">
<input
id="verdict-verified"
type="radio"
name="verdict"
value="verified"
checked={verdict === 'verified'}
onChange={(e) => {
setVerdict(e.target.value as 'verified' | 'corrected');
setCorrectedDiseaseSlug('');
}}
className="h-4 w-4"
/>
<span className="text-sm">Terverifikasi</span>
</label>
<label htmlFor="verdict-corrected" className="flex items-center gap-2">
<input
id="verdict-corrected"
type="radio"
name="verdict"
value="corrected"
checked={verdict === 'corrected'}
onChange={(e) => setVerdict(e.target.value as 'verified' | 'corrected')}
className="h-4 w-4"
/>
<span className="text-sm">Dikoreksi</span>
</label>
</div>
</div>
{/* Disease selection (only if corrected) */}
{verdict === 'corrected' && (
<div className="space-y-2">
<label htmlFor="disease" className="text-sm font-medium">
Penyakit yang Benar
</label>
<select
id="disease"
value={correctedDiseaseSlug}
onChange={(e) => setCorrectedDiseaseSlug(e.target.value as DiseaseSlug | '')}
required={verdict === 'corrected'}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
>
<option value="">Pilih penyakit...</option>
{diseases.map((disease) => (
<option key={disease.slug} value={disease.slug}>
{disease.commonName}
</option>
))}
</select>
</div>
)}
{/* Notes */}
<div className="space-y-2">
<label htmlFor="notes" className="text-sm font-medium">
Catatan <span className="text-red-500">*</span>
</label>
<textarea
id="notes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Tulis catatan review Anda..."
required
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
rows={4}
/>
</div>
{/* Submit button */}
<Button
type="submit"
disabled={mutation.isPending || (verdict === 'corrected' && !correctedDiseaseSlug) || !notes.trim()}
className="w-full"
>
{mutation.isPending ? 'Mengirim...' : 'Kirim Review'}
</Button>
{mutation.isError && (
<p className="text-sm text-red-500">
Error: {mutation.error instanceof Error ? mutation.error.message : 'Terjadi kesalahan'}
</p>
)}
</form>
</CardContent>
</Card>
</>
<div className="space-y-2">
{isLoading ? (
<p className="text-sm text-muted-foreground">Memuat diagnosis...</p>
) : reviews.length === 0 ? (
<p className="text-sm text-muted-foreground">Tidak ada diagnosis yang menunggu review</p>
) : (
<Card>
<CardContent className="p-6 text-center text-muted-foreground">
Tidak ada diagnosis yang tersedia untuk review
</CardContent>
</Card>
reviews.map((review) => (
<button
key={review.id}
type="button"
onClick={() => setSearchParams({ diagnosis: review.id })}
className={`w-full text-left transition ${
selected?.id === review.id
? 'ring-2 ring-primary'
: 'hover:border-primary'
}`}
>
<Card className="transition hover:border-primary">
<CardContent className="flex gap-3 p-3">
<img
src={review.imageUrl}
alt="Daun jagung"
className="h-20 w-20 rounded-lg object-cover"
/>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex flex-wrap items-center justify-between gap-1">
<h3 className="text-sm font-semibold">{review.disease?.commonName ?? 'Diagnosis gagal'}</h3>
<DiagnosisStatusBadge status={review.status} className="text-xs" />
</div>
<p className="text-xs text-muted-foreground">
{review.confidence === null ? 'Tidak ada confidence' : `${(review.confidence * 100).toFixed(1)}%`}
</p>
<p className="text-xs text-muted-foreground">{new Date(review.createdAt).toLocaleString('id-ID')}</p>
</div>
</CardContent>
</Card>
</button>
))
)}
</div>
</div>
{/* Right: Detail and form */}
<div className="lg:col-span-2 space-y-6">
{isLoading ? (
<Card>
<CardContent className="p-6 text-center text-muted-foreground">
Memuat diagnosis...
</CardContent>
</Card>
) : selected ? (
<>
{/* Detail card */}
<Card>
<CardHeader>
<CardTitle>{selected.disease?.commonName ?? 'Diagnosis gagal'}</CardTitle>
<CardDescription>
{new Date(selected.createdAt).toLocaleString('id-ID')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<img
src={selected.imageUrl}
alt={`Gambar daun jagung untuk ${selected.disease?.commonName ?? 'diagnosis'}`}
className="h-64 w-full rounded-lg object-cover"
/>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Status</span>
<DiagnosisStatusBadge status={selected.status} />
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Confidence</span>
<span className="text-sm">
{selected.confidence === null ? 'N/A' : `${(selected.confidence * 100).toFixed(1)}%`}
</span>
</div>
</div>
{selected.predictions.length > 0 && (
<div className="space-y-2">
<h4 className="text-sm font-medium">Prediksi Teratas</h4>
<div className="space-y-1">
{selected.predictions.map((pred) => (
<div key={pred.id} className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">{pred.modelLabel}</span>
<span className="font-medium">{(pred.confidence * 100).toFixed(1)}%</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
{/* Review form */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Review Pakar</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">Keputusan</label>
<div className="flex gap-3">
<label htmlFor="verdict-verified" className="flex items-center gap-2">
<input
id="verdict-verified"
type="radio"
name="verdict"
value="verified"
checked={verdict === 'verified'}
onChange={(e) => {
setVerdict(e.target.value as 'verified' | 'corrected');
setCorrectedDiseaseSlug('');
}}
className="h-4 w-4"
/>
<span className="text-sm">Terverifikasi</span>
</label>
<label htmlFor="verdict-corrected" className="flex items-center gap-2">
<input
id="verdict-corrected"
type="radio"
name="verdict"
value="corrected"
checked={verdict === 'corrected'}
onChange={(e) => setVerdict(e.target.value as 'verified' | 'corrected')}
className="h-4 w-4"
/>
<span className="text-sm">Dikoreksi</span>
</label>
</div>
</div>
{verdict === 'corrected' && (
<div className="space-y-2">
<label htmlFor="disease" className="text-sm font-medium">
Penyakit yang Benar
</label>
<select
id="disease"
value={correctedDiseaseSlug}
onChange={(e) => setCorrectedDiseaseSlug(e.target.value as DiseaseSlug | '')}
required={verdict === 'corrected'}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
>
<option value="">Pilih penyakit...</option>
{diseases.map((disease) => (
<option key={disease.slug} value={disease.slug}>
{disease.commonName}
</option>
))}
</select>
</div>
)}
<div className="space-y-2">
<label htmlFor="notes" className="text-sm font-medium">
Catatan <span className="text-red-500">*</span>
</label>
<textarea
id="notes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Tulis catatan review Anda..."
required
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
rows={4}
/>
</div>
<Button
type="submit"
disabled={mutation.isPending || (verdict === 'corrected' && !correctedDiseaseSlug) || !notes.trim()}
className="w-full"
>
{mutation.isPending ? 'Mengirim...' : 'Kirim Review'}
</Button>
{mutation.isError && (
<p className="text-sm text-red-500">
Error: {mutation.error instanceof Error ? mutation.error.message : 'Terjadi kesalahan'}
</p>
)}
</form>
</CardContent>
</Card>
</>
) : (
<Card>
<CardContent className="p-6 text-center text-muted-foreground">
Tidak ada diagnosis yang tersedia untuk review
</CardContent>
</Card>
)}
</div>
</div>
</main>
</div>
);
}