fix(catalog): add fallback to local seed data for images and medicines
- Import `diseaseCatalogSeed` to use as a fallback data source in `catalog-page.tsx`. - Implement logic to merge backend API responses with local seed data, ensuring `imageUrl` and `medicineRecommendations` are populated. - Fix an issue where disease images and specific treatments were not rendering due to missing fields in the database payload. - Enhance UI resilience by displaying accurate visual and medical data without requiring immediate backend updates. - Convert static PNG image assets to WebP format and remove old PNG files to significantly reduce image payload size and improve page load performance. - Enhance overall UI resilience and frontend speed.
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 320 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
+229
-135
@@ -1,174 +1,268 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useMemo } from "react";
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import {
|
||||||
import { AlertCircle } from 'lucide-react';
|
ChevronDown,
|
||||||
import type { RiskLevel } from '@zeavis/shared';
|
ChevronUp,
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
AlertTriangle,
|
||||||
import { RiskBadge } from '@/components/risk-badge';
|
CheckCircle2,
|
||||||
import { apiClient } from '@/lib/api-client';
|
Pill,
|
||||||
|
ShieldCheck,
|
||||||
|
Search,
|
||||||
|
Leaf,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { apiClient } from "@/lib/api-client";
|
||||||
|
import { RiskBadge } from "@/components/risk-badge";
|
||||||
|
import { diseaseCatalogSeed } from "@zeavis/shared/diseases";
|
||||||
|
|
||||||
|
|
||||||
export function CatalogPage() {
|
export function CatalogPage() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||||
const initialRisk = searchParams.get('risk') as RiskLevel | null;
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [riskFilter, setRiskFilter] = useState("all");
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const { data: diseases, isLoading } = useQuery({
|
||||||
const [riskFilter, setRiskFilter] = useState<RiskLevel | 'all'>(initialRisk ?? 'all');
|
queryKey: ["diseases"],
|
||||||
|
|
||||||
// Sync URL params with state
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialRisk && initialRisk !== riskFilter) {
|
|
||||||
setRiskFilter(initialRisk);
|
|
||||||
}
|
|
||||||
}, [initialRisk]);
|
|
||||||
|
|
||||||
const { data: diseases, isLoading, error } = useQuery({
|
|
||||||
queryKey: ['diseases'],
|
|
||||||
queryFn: () => apiClient.getDiseases(),
|
queryFn: () => apiClient.getDiseases(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredDiseases = (diseases || [])
|
const filteredDiseases = useMemo(() => {
|
||||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
if (!diseases) return [];
|
||||||
.filter((disease) => {
|
|
||||||
|
return diseases.filter((disease) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
disease.commonName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
disease.commonName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
disease.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
disease.symptoms.some((symptom) =>
|
||||||
disease.summary.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
symptom.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||||
disease.symptoms.some((s) => s.toLowerCase().includes(searchQuery.toLowerCase()));
|
);
|
||||||
|
|
||||||
const matchesRisk = riskFilter === 'all' || disease.riskLevel === riskFilter;
|
const matchesRisk =
|
||||||
|
riskFilter === "all" || disease.riskLevel === riskFilter;
|
||||||
|
|
||||||
return matchesSearch && matchesRisk;
|
return matchesSearch && matchesRisk;
|
||||||
});
|
});
|
||||||
|
}, [diseases, searchQuery, riskFilter]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="p-8 text-center text-muted-foreground">
|
||||||
|
Memuat pustaka penyakit...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 max-w-5xl mx-auto pb-10">
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
{/* Page Title */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-emerald-800">Pustaka Penyakit</h1>
|
<h1 className="text-2xl md:text-3xl font-extrabold text-[#214B11]">
|
||||||
<p className="text-gray-500 mt-1 text-md">
|
Pustaka Penyakit
|
||||||
Referensi lengkap penyakit dan kondisi daun jagung yang dapat dideteksi oleh sistem AI ZeaVis Edu
|
</h1>
|
||||||
</p>
|
<p className="mt-1 text-muted-foreground text-sm md:text-base">
|
||||||
|
Referensi lengkap penyakit dan kondisi daun jagung yang dapat
|
||||||
|
dideteksi oleh sistem AI ZeaVis Edu.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search and Filter */}
|
||||||
|
<div className="flex flex-col md:flex-row gap-4 items-end bg-white p-4 rounded-2xl border border-slate-200 shadow-sm">
|
||||||
|
<div className="flex-1 w-full space-y-1.5">
|
||||||
|
<label className="text-sm font-semibold text-slate-700">
|
||||||
|
Cari penyakit
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Cari berdasarkan nama atau gejala..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="w-full pl-9 pr-4 py-2 border border-slate-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#214B11]/20 focus:border-[#214B11] transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full md:w-48 space-y-1.5 shrink-0">
|
||||||
|
<label className="text-sm font-semibold text-slate-700">
|
||||||
|
Filter risiko
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={riskFilter}
|
||||||
|
onChange={(e) => setRiskFilter(e.target.value)}
|
||||||
|
className="w-full py-2 px-3 border border-slate-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#214B11]/20 focus:border-[#214B11] transition-all bg-white"
|
||||||
|
>
|
||||||
|
<option value="all">Semua Risiko</option>
|
||||||
|
<option value="high">Risiko Tinggi</option>
|
||||||
|
<option value="medium">Risiko Sedang</option>
|
||||||
|
<option value="low">Risiko Rendah</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters */}
|
{/* Accordion List */}
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-end">
|
<div className="space-y-4">
|
||||||
<div className="flex-1">
|
{filteredDiseases.length === 0 ? (
|
||||||
<label htmlFor="search" className="block text-sm font-medium mb-2">
|
<div className="text-center py-10 bg-white rounded-2xl border border-slate-200">
|
||||||
Cari penyakit
|
<p className="text-slate-500">
|
||||||
</label>
|
Tidak ada penyakit yang sesuai dengan pencarianmu.
|
||||||
<div className="relative">
|
</p>
|
||||||
<AlertCircle className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
||||||
<input
|
|
||||||
id="search"
|
|
||||||
type="text"
|
|
||||||
placeholder="Cari berdasarkan nama atau gejala..."
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
className="w-full rounded-md border border-border bg-background pl-10 pr-3 py-2 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
) : (
|
||||||
<label htmlFor="risk-filter" className="block text-sm font-medium mb-2">
|
filteredDiseases.map((disease) => {
|
||||||
Filter risiko
|
const isExpanded = expandedId === disease.slug;
|
||||||
</label>
|
|
||||||
<select
|
const seedData = diseaseCatalogSeed.find(seed => seed.slug === disease.slug);
|
||||||
id="risk-filter"
|
const finalImageUrl = disease.imageUrl || seedData?.imageUrl || "https://placehold.co/100x100?text=Daun";
|
||||||
value={riskFilter}
|
const finalMedicines = disease.medicineRecommendations || seedData?.medicineRecommendations;
|
||||||
onChange={(e) => {
|
|
||||||
const v = e.target.value as RiskLevel | 'all';
|
|
||||||
setRiskFilter(v);
|
|
||||||
const next = new URLSearchParams(searchParams);
|
|
||||||
if (v === 'all') next.delete('risk');
|
|
||||||
else next.set('risk', v);
|
|
||||||
setSearchParams(next);
|
|
||||||
}}
|
|
||||||
className="rounded-md border border-border bg-background px-3 py-2 text-sm"
|
|
||||||
>
|
|
||||||
<option value="all">Semua Risiko</option>
|
|
||||||
<option value="low">Risiko Rendah</option>
|
|
||||||
<option value="medium">Risiko Sedang</option>
|
|
||||||
<option value="high">Risiko Tinggi</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isLoading && (
|
|
||||||
<Card className="p-8 text-center text-muted-foreground">
|
|
||||||
Memuat katalog...
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
return (
|
||||||
<Card className="p-8 text-center text-red-600">
|
<div
|
||||||
Katalog belum tersedia
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLoading && !error && filteredDiseases.length === 0 && (
|
|
||||||
<Card className="p-8 text-center text-muted-foreground">
|
|
||||||
Tidak ada penyakit yang cocok dengan filter ini.
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLoading && !error && filteredDiseases.length > 0 && (
|
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
|
||||||
{filteredDiseases.map((disease) => (
|
|
||||||
<Link
|
|
||||||
key={disease.slug}
|
key={disease.slug}
|
||||||
to={`/catalog/${disease.slug}`}
|
className={`bg-white rounded-2xl border transition-all duration-300 ${
|
||||||
className="block transition-transform hover:scale-[1.03]"
|
isExpanded
|
||||||
|
? "border-[#214B11]/30 shadow-md ring-1 ring-[#214B11]/5"
|
||||||
|
: "border-slate-200 hover:border-slate-300 hover:shadow-sm"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Card className="h-full rounded-2xl bg-white shadow-sm hover:shadow-md">
|
{/* Accordion Header */}
|
||||||
<CardContent className="p-5 space-y-4">
|
<div
|
||||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
onClick={() =>
|
||||||
<div className="min-w-0">
|
setExpandedId(isExpanded ? null : disease.slug)
|
||||||
|
}
|
||||||
|
className="flex items-center justify-between p-4 md:p-5 cursor-pointer select-none"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<img
|
||||||
|
src={finalImageUrl}
|
||||||
|
alt={disease.commonName}
|
||||||
|
className="w-12 h-12 md:w-16 md:h-16 rounded-xl object-cover shrink-0 bg-slate-100"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3">
|
||||||
<h3 className="text-lg font-bold text-[#214B11]">
|
<h3 className="text-lg font-bold text-[#214B11]">
|
||||||
{disease.commonName}
|
{disease.commonName}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-slate-400 italic">
|
<div className="w-fit">
|
||||||
{disease.label}
|
<RiskBadge level={disease.riskLevel} />
|
||||||
</p>
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-slate-500 italic mt-0.5">
|
||||||
|
{disease.label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-8 h-8 shrink-0 flex items-center justify-center rounded-full bg-slate-50 text-slate-400">
|
||||||
|
{isExpanded ? (
|
||||||
|
<ChevronUp className="w-5 h-5" />
|
||||||
|
) : (
|
||||||
|
<ChevronDown className="w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Accordion Content */}
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="p-4 md:p-6 border-t border-slate-100 animate-in slide-in-from-top-2">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
finalImageUrl
|
||||||
|
}
|
||||||
|
alt="Detail Penyakit"
|
||||||
|
className="w-full h-48 md:h-full object-cover rounded-xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-[#214B11] flex items-center gap-2 mb-2">
|
||||||
|
Deskripsi
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-slate-600 leading-relaxed">
|
||||||
|
{disease.description || disease.summary}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-[#214B11] flex items-center gap-2 mb-3">
|
||||||
|
<AlertTriangle className="w-4 h-4 text-amber-500" />
|
||||||
|
Gejala
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{disease.symptoms?.map((symptom, idx) => (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className="flex items-start gap-2 text-sm text-slate-600"
|
||||||
|
>
|
||||||
|
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 mt-1.5 shrink-0" />
|
||||||
|
<span>{symptom}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<RiskBadge level={disease.riskLevel} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground line-clamp-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
||||||
{disease.summary}
|
<div className="bg-[#F2F8F0] border border-[#D5E8CE] rounded-xl p-5">
|
||||||
</p>
|
<h4 className="font-bold text-[#214B11] flex items-center gap-2 mb-3">
|
||||||
|
<ShieldCheck className="w-4 h-4 text-emerald-600" />
|
||||||
{disease.symptoms.length > 0 && (
|
Panduan Pencegahan
|
||||||
<div>
|
|
||||||
<h4 className="text-xs font-semibold text-muted-foreground mb-1">
|
|
||||||
Gejala:
|
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-2">
|
||||||
{disease.symptoms.slice(0, 2).map((symptom, index) => (
|
{disease.recommendations?.map((rec, idx) => (
|
||||||
<li key={index} className="text-xs text-muted-foreground flex items-start gap-1.5">
|
<li
|
||||||
<span className="text-primary mt-0.5">•</span>
|
key={idx}
|
||||||
{symptom}
|
className="flex items-start gap-2 text-sm text-slate-700"
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-emerald-500 mt-0.5 shrink-0" />
|
||||||
|
<span>{rec}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-2 border-t">
|
<div className="bg-[#F8F4FD] border border-[#E9DDF5] rounded-xl p-5">
|
||||||
<div
|
<h4 className="font-bold text-purple-900 flex items-center gap-2 mb-3">
|
||||||
className="h-2.5 w-2.5 rounded-full"
|
<Pill className="w-4 h-4 text-purple-600" />
|
||||||
style={{ backgroundColor: disease.accentColor }}
|
Rekomendasi Obat
|
||||||
/>
|
</h4>
|
||||||
<span className="text-xs text-primary font-medium">
|
<ul className="space-y-2">
|
||||||
Lihat detail →
|
{finalMedicines ? (
|
||||||
</span>
|
finalMedicines.map((obat, idx) => (
|
||||||
|
<li key={idx} className="flex items-start gap-2 text-sm text-slate-700">
|
||||||
|
<Leaf className="w-4 h-4 text-purple-500 mt-0.5 shrink-0" />
|
||||||
|
<span>{obat}</span>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<li className="text-sm text-slate-500 italic">
|
||||||
|
Belum ada data rekomendasi obat spesifik.
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
)}
|
||||||
</Link>
|
</div>
|
||||||
))}
|
);
|
||||||
</div>
|
})
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Warning Box */}
|
||||||
|
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 flex gap-3 items-start mt-8">
|
||||||
|
<AlertTriangle className="w-5 h-5 text-amber-500 shrink-0 mt-0.5" />
|
||||||
|
<p className="text-sm text-amber-800 leading-relaxed">
|
||||||
|
<strong>Disclaimer:</strong> Informasi dalam pustaka ini bersifat
|
||||||
|
edukatif dan disarikan dari literatur ilmiah. ZeaVis Edu bukan
|
||||||
|
pengganti diagnosis lapangan oleh{" "}
|
||||||
|
<strong>Petugas Pengamat Organisme Pengganggu Tanaman (POPT)</strong>{" "}
|
||||||
|
atau ahli pertanian berlisensi. Selalu konsultasikan kondisi tanaman
|
||||||
|
Anda kepada dinas pertanian atau balai penelitian tanaman terkait.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { useUiStore } from "@/store/ui-store";
|
import { useUiStore } from "@/store/ui-store";
|
||||||
import { apiClient } from "@/lib/api-client";
|
import { apiClient } from "@/lib/api-client";
|
||||||
import bg from "@/assets/images/dashboard-bg.png";
|
import bg from "@/assets/images/dashboard-bg.webp";
|
||||||
|
|
||||||
export function DashboardPage() {
|
export function DashboardPage() {
|
||||||
const { dashboardCompact } = useUiStore();
|
const { dashboardCompact } = useUiStore();
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ export function ScanPage() {
|
|||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="rounded-lg overflow-hidden mt-4">
|
<div className="rounded-lg overflow-hidden mt-4">
|
||||||
<div className="relative h-36 overflow-hidden bg-[url('/src/assets/images/scan-guide-bg.png')] bg-cover bg-center text-white md:h-44">
|
<div className="relative h-36 overflow-hidden bg-[url('/src/assets/images/scan-guide-bg.webp')] bg-cover bg-center text-white md:h-44">
|
||||||
<div className="absolute inset-0 bg-black/25" />
|
<div className="absolute inset-0 bg-black/25" />
|
||||||
<div className="absolute inset-0 bg-linear-to-b from-black/5 via-transparent to-black/35" />
|
<div className="absolute inset-0 bg-linear-to-b from-black/5 via-transparent to-black/35" />
|
||||||
<div className="relative z-10 flex h-full flex-col items-center justify-center gap-5 text-center">
|
<div className="relative z-10 flex h-full flex-col items-center justify-center gap-5 text-center">
|
||||||
|
|||||||
+113
-68
@@ -1,10 +1,20 @@
|
|||||||
export const diseaseLabels = ['Bercak Daun', 'Hawar Daun', 'Karat Daun', 'Daun Sehat'] as const;
|
export const diseaseLabels = [
|
||||||
|
"Bercak Daun",
|
||||||
|
"Hawar Daun",
|
||||||
|
"Karat Daun",
|
||||||
|
"Daun Sehat",
|
||||||
|
] as const;
|
||||||
export type DiseaseLabel = (typeof diseaseLabels)[number];
|
export type DiseaseLabel = (typeof diseaseLabels)[number];
|
||||||
|
|
||||||
export const diseaseSlugs = ['bercak-daun', 'hawar-daun', 'karat-daun', 'daun-sehat'] as const;
|
export const diseaseSlugs = [
|
||||||
|
"bercak-daun",
|
||||||
|
"hawar-daun",
|
||||||
|
"karat-daun",
|
||||||
|
"daun-sehat",
|
||||||
|
] as const;
|
||||||
export type DiseaseSlug = (typeof diseaseSlugs)[number];
|
export type DiseaseSlug = (typeof diseaseSlugs)[number];
|
||||||
|
|
||||||
export const riskLevels = ['low', 'medium', 'high'] as const;
|
export const riskLevels = ["low", "medium", "high"] as const;
|
||||||
export type RiskLevel = (typeof riskLevels)[number];
|
export type RiskLevel = (typeof riskLevels)[number];
|
||||||
|
|
||||||
export type DiseaseCatalogItem = {
|
export type DiseaseCatalogItem = {
|
||||||
@@ -15,6 +25,8 @@ export type DiseaseCatalogItem = {
|
|||||||
description: string;
|
description: string;
|
||||||
symptoms: string[];
|
symptoms: string[];
|
||||||
recommendations: string[];
|
recommendations: string[];
|
||||||
|
medicineRecommendations: string[];
|
||||||
|
imageUrl: string;
|
||||||
riskLevel: RiskLevel;
|
riskLevel: RiskLevel;
|
||||||
accentColor: string;
|
accentColor: string;
|
||||||
displayOrder: number;
|
displayOrder: number;
|
||||||
@@ -22,100 +34,133 @@ export type DiseaseCatalogItem = {
|
|||||||
|
|
||||||
export const diseaseCatalogSeed: DiseaseCatalogItem[] = [
|
export const diseaseCatalogSeed: DiseaseCatalogItem[] = [
|
||||||
{
|
{
|
||||||
slug: 'bercak-daun',
|
slug: "bercak-daun",
|
||||||
label: 'Bercak Daun',
|
label: "Bercak Daun",
|
||||||
commonName: 'Gray Leaf Spot',
|
commonName: "Gray Leaf Spot",
|
||||||
summary: 'Penyakit jamur yang menyebabkan bercak abu-abu pada daun jagung.',
|
summary: "Penyakit jamur yang menyebabkan bercak abu-abu pada daun jagung.",
|
||||||
description:
|
description:
|
||||||
'Bercak Daun adalah penyakit jamur yang disebabkan oleh Cercospora zeae-maydis. Penyakit ini ditandai dengan munculnya bercak berbentuk persegi panjang berwarna abu-abu dengan tepi coklat pada daun jagung. Penyakit ini berkembang pesat dalam kondisi lembab dan dapat mengurangi hasil panen secara signifikan.',
|
"Bercak Daun adalah penyakit jamur yang disebabkan oleh Cercospora zeae-maydis. Penyakit ini ditandai dengan munculnya bercak berbentuk persegi panjang berwarna abu-abu dengan tepi coklat pada daun jagung. Penyakit ini berkembang pesat dalam kondisi lembab dan dapat mengurangi hasil panen secara signifikan.",
|
||||||
symptoms: [
|
symptoms: [
|
||||||
'Bercak berbentuk persegi panjang berwarna abu-abu',
|
"Bercak berbentuk persegi panjang berwarna abu-abu",
|
||||||
'Tepi bercak berwarna coklat atau merah',
|
"Tepi bercak berwarna coklat atau merah",
|
||||||
'Bercak biasanya muncul pada daun bagian bawah terlebih dahulu',
|
"Bercak biasanya muncul pada daun bagian bawah terlebih dahulu",
|
||||||
'Dalam kondisi lembab, bercak dapat berkembang dengan cepat',
|
"Dalam kondisi lembab, bercak dapat berkembang dengan cepat",
|
||||||
],
|
],
|
||||||
recommendations: [
|
recommendations: [
|
||||||
'Gunakan varietas jagung yang tahan terhadap penyakit ini',
|
"Gunakan varietas jagung yang tahan terhadap penyakit ini",
|
||||||
'Terapkan rotasi tanaman dengan tanaman non-inang',
|
"Terapkan rotasi tanaman dengan tanaman non-inang",
|
||||||
'Kurangi kelembaban dengan meningkatkan jarak tanam',
|
"Kurangi kelembaban dengan meningkatkan jarak tanam",
|
||||||
'Aplikasikan fungisida jika diperlukan',
|
"Aplikasikan fungisida jika diperlukan",
|
||||||
'Buang sisa tanaman yang terinfeksi setelah panen',
|
"Buang sisa tanaman yang terinfeksi setelah panen",
|
||||||
],
|
],
|
||||||
riskLevel: 'high',
|
medicineRecommendations: [
|
||||||
accentColor: '#9CA3AF',
|
"Azoksistrobin 250 SC: fungisida strobilurin sistemik",
|
||||||
|
"Piraklostrobin (Headline 250 EC): 1 mL/L air",
|
||||||
|
"Kombinasi strobilurin + triazol untuk efikasi maksimal",
|
||||||
|
"Aplikasi preventif lebih efektif daripada kuratif",
|
||||||
|
],
|
||||||
|
imageUrl:
|
||||||
|
"https://images.unsplash.com/photo-1651029826601-9d3fae8926d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=600",
|
||||||
|
riskLevel: "high",
|
||||||
|
accentColor: "#DC2626",
|
||||||
displayOrder: 1,
|
displayOrder: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'hawar-daun',
|
slug: "hawar-daun",
|
||||||
label: 'Hawar Daun',
|
label: "Hawar Daun",
|
||||||
commonName: 'Northern/Southern Leaf Blight',
|
commonName: "Northern/Southern Leaf Blight",
|
||||||
summary: 'Penyakit jamur yang menyebabkan hawar pada daun jagung dengan gejala bercak memanjang.',
|
summary:
|
||||||
|
"Penyakit jamur yang menyebabkan hawar pada daun jagung dengan gejala bercak memanjang.",
|
||||||
description:
|
description:
|
||||||
'Hawar Daun adalah penyakit jamur yang disebabkan oleh Exserohilum turcicum (Northern Leaf Blight) atau Bipolaris maydis (Southern Leaf Blight). Penyakit ini ditandai dengan munculnya bercak memanjang berwarna coklat atau abu-abu pada daun jagung. Penyakit ini dapat menyebabkan kerusakan daun yang parah dan mengurangi hasil panen.',
|
"Hawar Daun adalah penyakit jamur yang disebabkan oleh Exserohilum turcicum (Northern Leaf Blight) atau Bipolaris maydis (Southern Leaf Blight). Penyakit ini ditandai dengan munculnya bercak memanjang berwarna coklat atau abu-abu pada daun jagung. Penyakit ini dapat menyebabkan kerusakan daun yang parah dan mengurangi hasil panen.",
|
||||||
symptoms: [
|
symptoms: [
|
||||||
'Bercak memanjang berwarna coklat atau abu-abu',
|
"Bercak memanjang berwarna coklat atau abu-abu",
|
||||||
'Bercak dapat mencapai panjang 10-15 cm',
|
"Bercak dapat mencapai panjang 10-15 cm",
|
||||||
'Tepi bercak sering berwarna lebih gelap',
|
"Tepi bercak sering berwarna lebih gelap",
|
||||||
'Dalam kondisi lembab, bercak dapat berkembang dengan cepat',
|
"Dalam kondisi lembab, bercak dapat berkembang dengan cepat",
|
||||||
'Daun dapat mati jika terinfeksi parah',
|
"Daun dapat mati jika terinfeksi parah",
|
||||||
],
|
],
|
||||||
recommendations: [
|
recommendations: [
|
||||||
'Gunakan varietas jagung yang tahan terhadap penyakit ini',
|
"Gunakan varietas jagung yang tahan terhadap penyakit ini",
|
||||||
'Terapkan rotasi tanaman dengan tanaman non-inang',
|
"Terapkan rotasi tanaman dengan tanaman non-inang",
|
||||||
'Kurangi kelembaban dengan meningkatkan jarak tanam',
|
"Kurangi kelembaban dengan meningkatkan jarak tanam",
|
||||||
'Aplikasikan fungisida jika diperlukan',
|
"Aplikasikan fungisida jika diperlukan",
|
||||||
'Buang sisa tanaman yang terinfeksi setelah panen',
|
"Buang sisa tanaman yang terinfeksi setelah panen",
|
||||||
],
|
],
|
||||||
riskLevel: 'high',
|
medicineRecommendations: [
|
||||||
accentColor: '#8B4513',
|
"Mancozeb 80 WP: 2 g/L air, semprotkan 7–10 hari sekali",
|
||||||
|
"Propineb 70 WP: 2 g/L air, dimulai saat gejala awal",
|
||||||
|
"Azoksistrobin + Difenokonazol (Amistar Top 325 SC)",
|
||||||
|
"Klorotalonil 75 WP sebagai fungisida protektif",
|
||||||
|
],
|
||||||
|
riskLevel: "high",
|
||||||
|
accentColor: "#DC2626",
|
||||||
displayOrder: 2,
|
displayOrder: 2,
|
||||||
|
imageUrl:
|
||||||
|
"https://images.unsplash.com/photo-1671559457949-5ac52323e19d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=600",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'karat-daun',
|
slug: "karat-daun",
|
||||||
label: 'Karat Daun',
|
label: "Karat Daun",
|
||||||
commonName: 'Common Rust',
|
commonName: "Common Rust",
|
||||||
summary: 'Penyakit jamur yang menyebabkan pustula berwarna coklat kemerahan pada daun jagung.',
|
summary:
|
||||||
|
"Penyakit jamur yang menyebabkan pustula berwarna coklat kemerahan pada daun jagung.",
|
||||||
description:
|
description:
|
||||||
'Karat Daun adalah penyakit jamur yang disebabkan oleh Puccinia sorghi. Penyakit ini ditandai dengan munculnya pustula kecil berwarna coklat kemerahan pada permukaan daun jagung. Penyakit ini berkembang dalam kondisi lembab dan dapat mengurangi hasil panen jika tidak dikendalikan.',
|
"Karat Daun adalah penyakit jamur yang disebabkan oleh Puccinia sorghi. Penyakit ini ditandai dengan munculnya pustula kecil berwarna coklat kemerahan pada permukaan daun jagung. Penyakit ini berkembang dalam kondisi lembab dan dapat mengurangi hasil panen jika tidak dikendalikan.",
|
||||||
symptoms: [
|
symptoms: [
|
||||||
'Pustula kecil berwarna coklat kemerahan pada permukaan daun',
|
"Pustula kecil berwarna coklat kemerahan pada permukaan daun",
|
||||||
'Pustula biasanya muncul pada daun bagian bawah terlebih dahulu',
|
"Pustula biasanya muncul pada daun bagian bawah terlebih dahulu",
|
||||||
'Dalam kondisi lembab, pustula dapat berkembang dengan cepat',
|
"Dalam kondisi lembab, pustula dapat berkembang dengan cepat",
|
||||||
'Daun dapat menjadi kuning dan mati jika terinfeksi parah',
|
"Daun dapat menjadi kuning dan mati jika terinfeksi parah",
|
||||||
],
|
],
|
||||||
recommendations: [
|
recommendations: [
|
||||||
'Gunakan varietas jagung yang tahan terhadap penyakit ini',
|
"Gunakan varietas jagung yang tahan terhadap penyakit ini",
|
||||||
'Terapkan rotasi tanaman dengan tanaman non-inang',
|
"Terapkan rotasi tanaman dengan tanaman non-inang",
|
||||||
'Kurangi kelembaban dengan meningkatkan jarak tanam',
|
"Kurangi kelembaban dengan meningkatkan jarak tanam",
|
||||||
'Aplikasikan fungisida jika diperlukan',
|
"Aplikasikan fungisida jika diperlukan",
|
||||||
'Buang sisa tanaman yang terinfeksi setelah panen',
|
"Buang sisa tanaman yang terinfeksi setelah panen",
|
||||||
],
|
],
|
||||||
riskLevel: 'medium',
|
medicineRecommendations: [
|
||||||
accentColor: '#DC2626',
|
"Propikonazol 250 EC: 0.5 mL/L air, 2 minggu sekali",
|
||||||
|
"Tebukonazol 250 EC: fungisida sistemik triazol",
|
||||||
|
"Trifloksistrobin + Tebukonazol (Nativo 75 WG)",
|
||||||
|
"Aplikasikan saat ≥ 5% permukaan daun terinfeksi",
|
||||||
|
],
|
||||||
|
imageUrl:
|
||||||
|
"https://images.unsplash.com/photo-1660904900487-3c6fbcb3b387?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=600",
|
||||||
|
riskLevel: "medium",
|
||||||
|
accentColor: "#8B4513",
|
||||||
displayOrder: 3,
|
displayOrder: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'daun-sehat',
|
slug: "daun-sehat",
|
||||||
label: 'Daun Sehat',
|
label: "Daun Sehat",
|
||||||
commonName: 'Healthy Leaf',
|
commonName: "Healthy Leaf",
|
||||||
summary: 'Daun jagung yang sehat tanpa tanda-tanda penyakit atau kerusakan.',
|
summary:
|
||||||
|
"Daun jagung yang sehat tanpa tanda-tanda penyakit atau kerusakan.",
|
||||||
description:
|
description:
|
||||||
'Daun Sehat menunjukkan kondisi daun jagung yang optimal tanpa adanya gejala penyakit jamur atau kerusakan lainnya. Daun yang sehat memiliki warna hijau cerah, tekstur normal, dan tidak menunjukkan bercak, pustula, atau tanda-tanda kerusakan lainnya.',
|
"Daun Sehat menunjukkan kondisi daun jagung yang optimal tanpa adanya gejala penyakit jamur atau kerusakan lainnya. Daun yang sehat memiliki warna hijau cerah, tekstur normal, dan tidak menunjukkan bercak, pustula, atau tanda-tanda kerusakan lainnya.",
|
||||||
|
imageUrl: "https://cdn.iespa.or.id/image/manfaat-daun-jagung/public",
|
||||||
symptoms: [
|
symptoms: [
|
||||||
'Warna daun hijau cerah dan seragam',
|
"Warna daun hijau cerah dan seragam",
|
||||||
'Tekstur daun normal dan tidak ada bercak',
|
"Tekstur daun normal dan tidak ada bercak",
|
||||||
'Tidak ada pustula atau tanda-tanda penyakit lainnya',
|
"Tidak ada pustula atau tanda-tanda penyakit lainnya",
|
||||||
'Daun terlihat kuat dan tidak layu',
|
"Daun terlihat kuat dan tidak layu",
|
||||||
],
|
],
|
||||||
recommendations: [
|
recommendations: [
|
||||||
'Pertahankan praktik budidaya yang baik',
|
"Pertahankan praktik budidaya yang baik",
|
||||||
'Lakukan monitoring rutin untuk mendeteksi penyakit sejak dini',
|
"Lakukan monitoring rutin untuk mendeteksi penyakit sejak dini",
|
||||||
'Terapkan rotasi tanaman untuk menjaga kesehatan tanah',
|
"Terapkan rotasi tanaman untuk menjaga kesehatan tanah",
|
||||||
'Berikan nutrisi yang cukup untuk pertumbuhan optimal',
|
"Berikan nutrisi yang cukup untuk pertumbuhan optimal",
|
||||||
'Jaga kelembaban tanah yang sesuai',
|
"Jaga kelembaban tanah yang sesuai",
|
||||||
],
|
],
|
||||||
riskLevel: 'low',
|
medicineRecommendations: [
|
||||||
accentColor: '#22C55E',
|
"Tidak diperlukan aplikasi fungisida saat ini",
|
||||||
|
"Pupuk foliar mikro (Fe, Mn, Zn) dapat memperkuat ketahanan",
|
||||||
|
"Pupuk organik/biostimulant untuk memperbaiki kondisi tanah",
|
||||||
|
],
|
||||||
|
riskLevel: "low",
|
||||||
|
accentColor: "#22C55E",
|
||||||
displayOrder: 4,
|
displayOrder: 4,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user