diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php
index 6415873..f81cedd 100644
--- a/app/Http/Controllers/AdminController.php
+++ b/app/Http/Controllers/AdminController.php
@@ -10,6 +10,9 @@
use Carbon\CarbonPeriod;
use App\Models\Ulasan;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Str;
+use Symfony\Component\HttpFoundation\StreamedResponse;
class AdminController extends Controller
{
@@ -53,17 +56,28 @@ public function dashboard()
$yesterday = Biodata::whereDate('created_at', Carbon::yesterday())->count();
$diff = $todayDiagnosis - $yesterday;
- // total user
- $totalUsers = Biodata::count();
+ // user list + diagnosis list
+ $sort = request('sort');
+ if ($sort == 'oldest') {
+ $data = Biodata::orderBy('created_at', 'asc')->get();
+ } else {
+ $data = Biodata::orderBy('created_at', 'desc')->get();
+ }
- // user list
- $sort = request('sort');
+ // Data pengguna unik (hindari duplikasi input yang sama)
+ $userData = $data->unique(function ($item) {
+ $phone = trim((string)($item->no_telepon ?? ''));
+ if ($phone !== '') {
+ return 'phone:' . $phone;
+ }
-if ($sort == 'oldest') {
- $data = Biodata::orderBy('created_at', 'asc')->get();
-} else {
- $data = Biodata::orderBy('created_at', 'desc')->get();
-}
+ return 'fallback:' . Str::lower(trim((string)($item->nama_pemilik ?? ''))) . '|' .
+ Str::lower(trim((string)($item->nama_kucing ?? ''))) . '|' .
+ Str::lower(trim((string)($item->alamat ?? '')));
+ })->values();
+
+ // total user = jumlah data unik
+ $totalUsers = $userData->count();
// penyakit paling umum
$mostCommon = Biodata::select('hasil_diagnosis')
@@ -142,7 +156,7 @@ public function dashboard()
$stats['rating_labels'] = $ratingChart->pluck('rating');
$stats['rating_data'] = $ratingChart->pluck('total');
- return view('admin.dashboard', compact('stats', 'data'));
+ return view('admin.dashboard', compact('stats', 'data', 'userData'));
}
public function logout(Request $request)
@@ -196,4 +210,237 @@ public function sortDiagnosis(Request $request)
return response()->json($data);
}
+
+public function exportDiagnosisExcel(): StreamedResponse
+{
+ $rows = Biodata::orderBy('created_at', 'desc')->get();
+ $filename = 'diagnosis-pawmedic-' . now()->format('Ymd-His') . '.xls';
+
+ $headers = [
+ 'Content-Type' => 'application/vnd.ms-excel; charset=UTF-8',
+ 'Content-Disposition' => 'attachment; filename="' . $filename . '"',
+ 'Cache-Control' => 'max-age=0',
+ ];
+
+ return response()->streamDownload(function () use ($rows) {
+ echo '
';
+ echo '';
+ echo '';
+ echo '| Tanggal | ';
+ echo 'Nama Pemilik | ';
+ echo 'Nama Kucing | ';
+ echo 'Umur Kucing | ';
+ echo 'Jenis Kelamin | ';
+ echo 'Berat Badan | ';
+ echo 'Ras Kucing | ';
+ echo 'Alamat | ';
+ echo 'No Telepon | ';
+ echo 'Hasil Diagnosis | ';
+ echo 'Jenis | ';
+ echo '
';
+
+ foreach ($rows as $row) {
+ echo '';
+ echo '| ' . e(optional($row->created_at)->format('d-m-Y H:i')) . ' | ';
+ echo '' . e($row->nama_pemilik ?? '-') . ' | ';
+ echo '' . e($row->nama_kucing ?? '-') . ' | ';
+ echo '' . e($row->umur_kucing ?? '-') . ' | ';
+ echo '' . e($row->jenis_kelamin ?? '-') . ' | ';
+ echo '' . e($row->berat_badan ?? '-') . ' | ';
+ echo '' . e($row->ras_kucing ?? '-') . ' | ';
+ echo '' . e($row->alamat ?? '-') . ' | ';
+ echo '' . e($row->no_telepon ?? '-') . ' | ';
+ echo '' . e($row->hasil_diagnosis ?? '-') . ' | ';
+ echo '' . e($row->jenis ?? '-') . ' | ';
+ echo '
';
+ }
+
+ echo '
';
+ }, $filename, $headers);
+}
+
+public function diseaseSettings()
+{
+ $existing = $this->loadDiseaseExplanations();
+ $diseasesFromData = Biodata::query()
+ ->whereNotNull('hasil_diagnosis')
+ ->where('hasil_diagnosis', '!=', '')
+ ->pluck('hasil_diagnosis')
+ ->map(fn ($d) => trim((string) $d))
+ ->filter()
+ ->unique()
+ ->values()
+ ->all();
+
+ $diseases = collect(array_merge($diseasesFromData, array_keys($existing)))
+ ->map(fn ($d) => trim((string) $d))
+ ->filter()
+ ->unique()
+ ->sort()
+ ->values()
+ ->all();
+
+ return view('admin.disease-settings', [
+ 'diseases' => $diseases,
+ 'descriptions' => $existing,
+ ]);
+}
+
+public function saveDiseaseSettings(Request $request)
+{
+ $items = $request->input('descriptions', []);
+ $normalized = [];
+
+ if (is_array($items)) {
+ foreach ($items as $name => $description) {
+ $diseaseName = trim((string) $name);
+ if ($diseaseName === '') {
+ continue;
+ }
+
+ $desc = trim((string) $description);
+ if ($desc === '') {
+ continue;
+ }
+
+ $normalized[$diseaseName] = $desc;
+ }
+ }
+
+ $path = $this->diseaseExplanationPath();
+ $dir = dirname($path);
+ if (!is_dir($dir)) {
+ File::makeDirectory($dir, 0755, true);
+ }
+
+ file_put_contents(
+ $path,
+ json_encode($normalized, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+ );
+
+ return redirect()
+ ->route('admin.disease.settings')
+ ->with('success', 'Penjelasan penyakit berhasil disimpan.');
+}
+
+private function diseaseExplanationPath(): string
+{
+ return storage_path('app/disease_explanations.json');
+}
+
+private function loadDiseaseExplanations(): array
+{
+ $path = $this->diseaseExplanationPath();
+ if (!file_exists($path)) {
+ return [];
+ }
+
+ $decoded = json_decode((string) file_get_contents($path), true);
+ return is_array($decoded) ? $decoded : [];
+}
+
+public function faqSettings()
+{
+ return view('admin.faq-settings', [
+ 'faqs' => $this->loadFaqItems(),
+ ]);
+}
+
+public function saveFaqSettings(Request $request)
+{
+ $questions = $request->input('questions', []);
+ $answers = $request->input('answers', []);
+
+ $faqs = [];
+ if (is_array($questions) && is_array($answers)) {
+ $count = max(count($questions), count($answers));
+ for ($i = 0; $i < $count; $i++) {
+ $q = trim((string)($questions[$i] ?? ''));
+ $a = trim((string)($answers[$i] ?? ''));
+ if ($q === '' || $a === '') {
+ continue;
+ }
+ $faqs[] = [
+ 'question' => $q,
+ 'answer' => $a,
+ ];
+ }
+ }
+
+ $path = $this->faqPath();
+ $dir = dirname($path);
+ if (!is_dir($dir)) {
+ File::makeDirectory($dir, 0755, true);
+ }
+
+ file_put_contents(
+ $path,
+ json_encode($faqs, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+ );
+
+ return redirect()->route('admin.faq.settings')->with('success', 'FAQ berhasil disimpan.');
+}
+
+public function faqPage()
+{
+ return view('faq', [
+ 'faqs' => $this->loadFaqItems(),
+ ]);
+}
+
+private function faqPath(): string
+{
+ return storage_path('app/faqs.json');
+}
+
+private function defaultFaqItems(): array
+{
+ return [
+ [
+ 'question' => 'Apa itu PawMedic?',
+ 'answer' => 'PawMedic adalah aplikasi sistem pakar yang membantu pemilik kucing memahami gejala dan mendapatkan rekomendasi perawatan awal. Aplikasi ini menggunakan metode sistem pakar untuk menganalisis gejala yang dipilih dan memberikan diagnosis kemungkinan penyakit.',
+ ],
+ [
+ 'question' => 'Bagaimana cara menggunakan PawMedic?',
+ 'answer' => "Cara menggunakan PawMedic sangat mudah:\n1. Isi biodata kucing Anda\n2. Pilih gejala yang Anda amati pada kucing\n3. Sistem akan menganalisis dan memberikan hasil diagnosis\n4. Baca rekomendasi perawatan yang diberikan",
+ ],
+ [
+ 'question' => 'Apakah hasil diagnosis akurat?',
+ 'answer' => 'Hasil diagnosis dari PawMedic adalah sebagai panduan awal berdasarkan gejala yang Anda pilih. Untuk diagnosis yang akurat dan penanganan yang tepat, sangat disarankan untuk berkonsultasi langsung dengan dokter hewan profesional. PawMedic tidak menggantikan konsultasi medis profesional.',
+ ],
+ [
+ 'question' => 'Apakah data saya aman?',
+ 'answer' => 'Ya, data yang Anda masukkan hanya digunakan untuk keperluan diagnosis dan tidak dibagikan kepada pihak ketiga.',
+ ],
+ [
+ 'question' => 'Berapa banyak gejala yang harus dipilih?',
+ 'answer' => 'Pilih gejala yang benar-benar Anda amati. Semakin relevan gejala yang dipilih, semakin baik hasil analisis.',
+ ],
+ ];
+}
+
+private function loadFaqItems(): array
+{
+ $path = $this->faqPath();
+ if (!file_exists($path)) {
+ return $this->defaultFaqItems();
+ }
+
+ $decoded = json_decode((string) file_get_contents($path), true);
+ if (!is_array($decoded) || empty($decoded)) {
+ return $this->defaultFaqItems();
+ }
+
+ $faqs = [];
+ foreach ($decoded as $item) {
+ $q = trim((string)($item['question'] ?? ''));
+ $a = trim((string)($item['answer'] ?? ''));
+ if ($q === '' || $a === '') {
+ continue;
+ }
+ $faqs[] = ['question' => $q, 'answer' => $a];
+ }
+
+ return !empty($faqs) ? $faqs : $this->defaultFaqItems();
+}
}
diff --git a/app/Http/Controllers/DiagnosisController.php b/app/Http/Controllers/DiagnosisController.php
index cc8d44f..333f150 100644
--- a/app/Http/Controllers/DiagnosisController.php
+++ b/app/Http/Controllers/DiagnosisController.php
@@ -71,7 +71,29 @@ public function prosesDiagnosis(Request $request)
// ๐ฅ halaman hasil
public function hasil()
{
- return view('hasil-diagnosis');
+ $diagnosis = session('diagnosis', []);
+ $diseaseName = trim((string)($diagnosis['nama'] ?? ''));
+ $description = $this->getDiseaseDescription($diseaseName);
+ $history = collect();
+
+ $biodataId = session('biodata_id');
+ if ($biodataId) {
+ $current = Biodata::find($biodataId);
+ $phone = trim((string)($current->no_telepon ?? ''));
+ if ($phone !== '') {
+ $history = Biodata::query()
+ ->where('no_telepon', $phone)
+ ->whereNotNull('hasil_diagnosis')
+ ->orderByDesc('created_at')
+ ->take(10)
+ ->get(['nama_kucing', 'hasil_diagnosis', 'created_at']);
+ }
+ }
+
+ return view('hasil-diagnosis', [
+ 'diseaseDescription' => $description,
+ 'diagnosisHistory' => $history,
+ ]);
}
public function simpanBiodata(Request $request)
@@ -99,4 +121,29 @@ public function simpanBiodata(Request $request)
return redirect()->route('gejala');
}
+
+ private function getDiseaseDescription(string $diseaseName): string
+ {
+ if ($diseaseName === '') {
+ return '';
+ }
+
+ $path = storage_path('app/disease_explanations.json');
+ if (!file_exists($path)) {
+ return '';
+ }
+
+ $decoded = json_decode((string)file_get_contents($path), true);
+ if (!is_array($decoded)) {
+ return '';
+ }
+
+ foreach ($decoded as $name => $description) {
+ if (trim((string)$name) === $diseaseName) {
+ return trim((string)$description);
+ }
+ }
+
+ return '';
+ }
}
\ No newline at end of file
diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php
index 4231ee7..81aa888 100644
--- a/resources/views/admin/dashboard.blade.php
+++ b/resources/views/admin/dashboard.blade.php
@@ -140,6 +140,24 @@
letter-spacing:-0.02em;
}
+.admin-shortcuts{
+ display:flex;
+ gap:10px;
+ flex-wrap:wrap;
+ margin-bottom:22px;
+}
+
+.admin-shortcut{
+ text-decoration:none;
+ padding:9px 14px;
+ border-radius:999px;
+ border:1px solid rgba(111,207,151,0.35);
+ background:#fff;
+ color:var(--text-dark);
+ font-weight:600;
+ font-size:13px;
+}
+
/* ===== STATS GRID ===== */
.stats-grid {
display: grid;
@@ -238,6 +256,41 @@
gap:12px;
}
+.table-wrap{
+ width:100%;
+ max-height:420px;
+ overflow:auto;
+ border:1px solid #e2e8f0;
+ border-radius:14px;
+}
+
+.table-controls{
+ display:flex;
+ gap:10px;
+ margin-bottom:12px;
+ flex-wrap:wrap;
+}
+
+.form-control{
+ padding:10px 12px;
+ border:1px solid #cbd5e1;
+ border-radius:10px;
+ background:#fff;
+ font-size:14px;
+ min-width:180px;
+}
+
+.btn-export{
+ padding:10px 14px;
+ border-radius:10px;
+ border:1px solid var(--primary);
+ background:var(--primary-light);
+ color:var(--text-dark);
+ text-decoration:none;
+ font-weight:600;
+ font-size:14px;
+}
+
.table{
width:100%;
border-collapse:collapse;
@@ -342,6 +395,12 @@
Dashboard Admin
+
@@ -443,45 +502,44 @@
๐ Data Diagnosis
-
-
-
-
-
-
-
+
| Nama Pemilik |
Nama Kucing |
+ Umur Kucing |
+ Jenis Kelamin |
Penyakit |
Tanggal |
- @foreach($data as $user)
-
- | {{ $user->nama_pemilik }} |
- {{ $user->nama_kucing }} |
- {{ $user->hasil_diagnosis ?? '-' }} |
- {{ \Carbon\Carbon::parse($user->created_at)->format('d M Y') }} |
+ @foreach($data as $item)
+
+ | {{ $item->nama_pemilik }} |
+ {{ $item->nama_kucing }} |
+ {{ $item->umur_kucing ?? '-' }} |
+ {{ $item->jenis_kelamin ?? '-' }} |
+ {{ $item->hasil_diagnosis ?? '-' }} |
+ {{ \Carbon\Carbon::parse($item->created_at)->format('d M Y') }} |
@endforeach
@@ -493,26 +551,39 @@ class="form-control"
๐ฅ Data Pengguna
+
+
+
+
+
+
+
+
+
+
+ | Tanggal |
Nama Pemilik |
No Telepon |
Alamat |
Nama Kucing |
-
- @foreach($data ?? [] as $user)
-
- | {{ $user->nama_pemilik }} |
- {{ $user->no_telepon }} |
- {{ $user->alamat ?? 'Tidak tersedia'}} |
- {{ $user->nama_kucing }} |
+
+ @foreach(($userData ?? collect()) as $item)
+
+ | {{ \Carbon\Carbon::parse($item->created_at)->format('d M Y') }} |
+ {{ $item->nama_pemilik }} |
+ {{ $item->no_telepon }} |
+ {{ $item->alamat ?? 'Tidak tersedia'}} |
+ {{ $item->nama_kucing }} |
@endforeach
+
@@ -560,6 +631,12 @@ class="form-control"
โ Lihat FAQ
+
+ ๐ ๏ธ Kelola FAQ
+
+
+ ๐งพ Atur Penjelasan Penyakit
+
@@ -567,236 +644,186 @@ class="form-control"
@include('components.scroll-top')
+function toggleUsers() {
+ chartBox.style.display = 'none';
+ const hidden = window.getComputedStyle(userBox).display === 'none';
+ userBox.style.display = hidden ? 'block' : 'none';
+ if (hidden) userBox.scrollIntoView({ behavior: 'smooth' });
+}
+
+function toggleDiagnosis() {
+ chartBox.style.display = 'none';
+ userBox.style.display = 'none';
+ const hidden = window.getComputedStyle(diagnosisBox).display === 'none';
+ diagnosisBox.style.display = hidden ? 'block' : 'none';
+ if (hidden) diagnosisBox.scrollIntoView({ behavior: 'smooth' });
+}
+
-
-