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
+18 -2
View File
@@ -78,12 +78,28 @@ const router = createBrowserRouter([
path: "/expert/reviews",
element: (
<AuthGuard requireExpert>
<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() {
+2 -13
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,23 +42,13 @@ 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="space-y-6">
<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>
{/* Filters */}
<div className="flex flex-col gap-4 md:flex-row md:items-end">
@@ -179,6 +169,5 @@ export function CatalogPage() {
</div>
)}
</div>
</main>
);
}
+10 -12
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>
</div>
<Card className="p-8 text-center">
<p className="text-muted-foreground">Materi tidak ditemukan</p>
</Card>
</div>
</main>
);
}
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>
</div>
<Card className="p-8 text-center">
<p className="text-muted-foreground">Memuat materi...</p>
</Card>
</div>
</main>
);
}
@@ -59,8 +59,7 @@ 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="space-y-6">
<div className="flex items-center justify-between">
<Button asChild variant="ghost">
<Link to="/catalog">
@@ -141,6 +140,5 @@ export function DiseaseDetailPage() {
</Button>
</div>
</div>
</main>
);
}
+14 -30
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,10 +24,14 @@ 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, {
mutationFn: () =>
apiClient.reviewDiagnosis(selected!.id, {
verdict,
correctedDiseaseSlug: verdict === 'corrected' ? correctedDiseaseSlug || undefined : undefined,
notes,
@@ -47,31 +51,17 @@ 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 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-2xl font-bold">Diagnosis Menunggu Review</h1>
<h1 className="text-3xl font-bold">Diagnosis Menunggu Review</h1>
</div>
<Link to="/dashboard">
<Button variant="outline">Dashboard</Button>
</Link>
</div>
</header>
<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">
@@ -81,7 +71,7 @@ export function ExpertReviewsPage() {
</div>
<div className="space-y-2">
{reviewsQuery.isPending || diseasesQuery.isPending ? (
{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>
@@ -124,7 +114,7 @@ export function ExpertReviewsPage() {
{/* Right: Detail and form */}
<div className="lg:col-span-2 space-y-6">
{reviewsQuery.isPending || diseasesQuery.isPending ? (
{isLoading ? (
<Card>
<CardContent className="p-6 text-center text-muted-foreground">
Memuat diagnosis...
@@ -160,7 +150,6 @@ export function ExpertReviewsPage() {
</div>
</div>
{/* Top predictions */}
{selected.predictions.length > 0 && (
<div className="space-y-2">
<h4 className="text-sm font-medium">Prediksi Teratas</h4>
@@ -184,7 +173,6 @@ export function ExpertReviewsPage() {
</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">
@@ -218,7 +206,6 @@ export function ExpertReviewsPage() {
</div>
</div>
{/* Disease selection (only if corrected) */}
{verdict === 'corrected' && (
<div className="space-y-2">
<label htmlFor="disease" className="text-sm font-medium">
@@ -241,7 +228,6 @@ export function ExpertReviewsPage() {
</div>
)}
{/* Notes */}
<div className="space-y-2">
<label htmlFor="notes" className="text-sm font-medium">
Catatan <span className="text-red-500">*</span>
@@ -257,7 +243,6 @@ export function ExpertReviewsPage() {
/>
</div>
{/* Submit button */}
<Button
type="submit"
disabled={mutation.isPending || (verdict === 'corrected' && !correctedDiseaseSlug) || !notes.trim()}
@@ -285,6 +270,5 @@ export function ExpertReviewsPage() {
</div>
</div>
</div>
</main>
);
}