From 6ad6bb79f40eeb18741bd495093068d73f756e12 Mon Sep 17 00:00:00 2001 From: WahyuTegarP <158023677+WahyuTegarP@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:19:37 +0700 Subject: [PATCH] penambahan fitur --- app/Http/Controllers/AdminController.php | 267 +++++++++- app/Http/Controllers/DiagnosisController.php | 49 +- resources/views/admin/dashboard.blade.php | 483 +++++++++--------- .../views/admin/disease-settings.blade.php | 106 ++++ resources/views/admin/faq-settings.blade.php | 71 +++ resources/views/faq.blade.php | 61 +-- resources/views/hasil-diagnosis.blade.php | 117 ++++- resources/views/landing.blade.php | 116 ++++- routes/web.php | 19 +- 9 files changed, 993 insertions(+), 296 deletions(-) create mode 100644 resources/views/admin/disease-settings.blade.php create mode 100644 resources/views/admin/faq-settings.blade.php 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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + foreach ($rows as $row) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo '
TanggalNama PemilikNama KucingUmur KucingJenis KelaminBerat BadanRas KucingAlamatNo TeleponHasil DiagnosisJenis
' . e(optional($row->created_at)->format('d-m-Y H:i')) . '' . e($row->nama_pemilik ?? '-') . '' . e($row->nama_kucing ?? '-') . '' . e($row->umur_kucing ?? '-') . '' . e($row->jenis_kelamin ?? '-') . '' . e($row->berat_badan ?? '-') . '' . e($row->ras_kucing ?? '-') . '' . e($row->alamat ?? '-') . '' . e($row->no_telepon ?? '-') . '' . e($row->hasil_diagnosis ?? '-') . '' . e($row->jenis ?? '-') . '
'; + }, $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

+
+ ๐Ÿ“‹ Data Diagnosis + ๐Ÿ‘ฅ Data Pengguna + ๐Ÿ“Š Statistik + ๐Ÿงพ Pengaturan Penyakit +
@@ -443,45 +502,44 @@
๐Ÿ“‹ Data Diagnosis
-
- - - - + + + โฌ‡ Export Excel +
-
- -
- -
- -
+
+ + - @foreach($data as $user) - - - - - + @foreach($data as $item) + + + + + + + @endforeach @@ -493,26 +551,39 @@ class="form-control"
Nama Pemilik Nama KucingUmur KucingJenis Kelamin Penyakit Tanggal
{{ $user->nama_pemilik }}{{ $user->nama_kucing }}{{ $user->hasil_diagnosis ?? '-' }}{{ \Carbon\Carbon::parse($user->created_at)->format('d M Y') }}
{{ $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') }}
+ - - @foreach($data ?? [] as $user) - - - - - + + @foreach(($userData ?? collect()) as $item) + + + + + + @endforeach
Tanggal Nama Pemilik No Telepon Alamat Nama Kucing
{{ $user->nama_pemilik }}{{ $user->no_telepon }}{{ $user->alamat ?? 'Tidak tersedia'}}{{ $user->nama_kucing }}
{{ \Carbon\Carbon::parse($item->created_at)->format('d M Y') }}{{ $item->nama_pemilik }}{{ $item->no_telepon }}{{ $item->alamat ?? 'Tidak tersedia'}}{{ $item->nama_kucing }}
+
@@ -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' }); +} + - - diff --git a/resources/views/admin/disease-settings.blade.php b/resources/views/admin/disease-settings.blade.php new file mode 100644 index 0000000..6362a59 --- /dev/null +++ b/resources/views/admin/disease-settings.blade.php @@ -0,0 +1,106 @@ + + + + + +Pengaturan Penjelasan Penyakit - PawMedic + + + + +
+
+
+

Pengaturan Penjelasan Penyakit

+

Atur deskripsi penyakit yang tampil di halaman hasil diagnosis.

+
+ โ† Kembali ke Dashboard +
+ +
+ @if(session('success')) +
{{ session('success') }}
+ @endif + +
+ @csrf +
+ + + + + + + + + @forelse($diseases as $disease) + + + + + @empty + + @endforelse + +
Nama PenyakitPenjelasan
{{ $disease }} + +
Belum ada data penyakit.
+
+
+ +
+
+
+
+ + + diff --git a/resources/views/admin/faq-settings.blade.php b/resources/views/admin/faq-settings.blade.php new file mode 100644 index 0000000..5374a0b --- /dev/null +++ b/resources/views/admin/faq-settings.blade.php @@ -0,0 +1,71 @@ + + + + + +Kelola FAQ - PawMedic Admin + + + + +
+
+

Kelola FAQ

+ โ† Dashboard +
+ +
+ @if(session('success')) +
{{ session('success') }}
+ @endif +
+ @csrf +
+ @forelse($faqs as $faq) +
+ + +
+ @empty +
+ + +
+ @endforelse +
+
+ + +
+
+
+
+ + + + diff --git a/resources/views/faq.blade.php b/resources/views/faq.blade.php index 423e0bb..ada86d5 100644 --- a/resources/views/faq.blade.php +++ b/resources/views/faq.blade.php @@ -226,60 +226,17 @@
-
-
Apa itu PawMedic?
-
- 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. + @forelse(($faqs ?? []) as $faq) +
+
{{ $faq['question'] ?? '-' }}
+
{{ $faq['answer'] ?? '-' }}
-
- -
-
Bagaimana cara menggunakan PawMedic?
-
- Cara menggunakan PawMedic sangat mudah: -
    -
  1. Isi biodata kucing Anda
  2. -
  3. Pilih gejala yang Anda amati pada kucing
  4. -
  5. Sistem akan menganalisis dan memberikan hasil diagnosis
  6. -
  7. Baca rekomendasi perawatan yang diberikan
  8. -
+ @empty +
+
Belum ada FAQ
+
Konten FAQ belum tersedia.
-
- -
-
Apakah hasil diagnosis akurat?
-
- 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. -
-
- -
-
Apakah data saya aman?
-
- Ya, data yang Anda masukkan hanya digunakan untuk keperluan diagnosis dan tidak dibagikan kepada pihak ketiga. Semua data disimpan secara lokal di browser Anda (sessionStorage) dan tidak dikirim ke server kecuali untuk keperluan analisis diagnosis. -
-
- -
-
Berapa banyak gejala yang harus dipilih?
-
- Anda dapat memilih sebanyak mungkin gejala yang sesuai dengan kondisi kucing Anda. Semakin banyak gejala yang dipilih, semakin akurat diagnosis yang akan diberikan. Namun, pastikan gejala yang dipilih benar-benar Anda amati pada kucing. -
-
- -
-
Apakah aplikasi ini gratis?
-
- Ya, PawMedic sepenuhnya gratis untuk digunakan. Anda dapat melakukan diagnosis tanpa batas dan mengakses semua fitur yang tersedia tanpa biaya apapun. -
-
- -
-
Bagaimana jika kucing saya dalam kondisi darurat?
-
- Jika kucing Anda menunjukkan tanda-tanda darurat seperti kesulitan bernapas, kejang, tidak sadar, atau luka parah, segera bawa ke dokter hewan terdekat atau klinik hewan darurat. Jangan menunggu diagnosis dari aplikasi ini. -
-
+ @endforelse
diff --git a/resources/views/hasil-diagnosis.blade.php b/resources/views/hasil-diagnosis.blade.php index cedc0b4..332bd87 100644 --- a/resources/views/hasil-diagnosis.blade.php +++ b/resources/views/hasil-diagnosis.blade.php @@ -203,6 +203,17 @@ font-weight:600; } +.disease-explanation{ + margin-top:14px; + background:#fff; + border:1px solid #d1fae5; + border-radius:12px; + padding:14px 16px; + color:#0f5132; + font-size:14px; + line-height:1.7; +} + /* ===== GEJALA LIST ===== */ .gejala-list-section{ margin-bottom:32px; @@ -327,6 +338,32 @@ line-height:1.7; } +.history-section{ + margin-bottom:32px; +} + +.history-table{ + width:100%; + border-collapse:collapse; + background:#fff; + border:1px solid #d1fae5; + border-radius:12px; + overflow:hidden; +} + +.history-table th, +.history-table td{ + padding:10px 12px; + border-bottom:1px solid #e2e8f0; + text-align:left; + font-size:14px; +} + +.history-table th{ + background:#f0fdf4; + color:#0f5132; +} + /* ===== BUTTONS ===== */ .action-buttons{ display:flex; @@ -450,6 +487,12 @@
Jenis: {{ $diagnosis['kategori'] ?? '-' }}
+@if(!empty($diseaseDescription)) +
+ Penjelasan penyakit:
+ {{ $diseaseDescription }} +
+@endif @@ -516,13 +559,85 @@
+@if(isset($diagnosisHistory) && $diagnosisHistory->count() > 0) +
+
+ ๐Ÿ•˜ Riwayat Diagnosis (Nomor yang sama) +
+ + + + + + + + + + @foreach($diagnosisHistory as $row) + + + + + + @endforeach + +
TanggalNama KucingHasil Diagnosis
{{ \Carbon\Carbon::parse($row->created_at)->format('d M Y H:i') }}{{ $row->nama_kucing ?? '-' }}{{ $row->hasil_diagnosis ?? '-' }}
+
+@endif + @include('components.scroll-top') diff --git a/routes/web.php b/routes/web.php index 2feafca..b65e56f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,6 +8,21 @@ use App\Models\Ulasan; Route::get('/admin/sort-diagnosis', [AdminController::class, 'sortDiagnosis']); +Route::get('/admin/export-diagnosis', [AdminController::class, 'exportDiagnosisExcel']) + ->name('admin.export.diagnosis') + ->middleware('auth'); +Route::get('/admin/disease-settings', [AdminController::class, 'diseaseSettings']) + ->name('admin.disease.settings') + ->middleware('auth'); +Route::post('/admin/disease-settings', [AdminController::class, 'saveDiseaseSettings']) + ->name('admin.disease.settings.save') + ->middleware('auth'); +Route::get('/admin/faq-settings', [AdminController::class, 'faqSettings']) + ->name('admin.faq.settings') + ->middleware('auth'); +Route::post('/admin/faq-settings', [AdminController::class, 'saveFaqSettings']) + ->name('admin.faq.settings.save') + ->middleware('auth'); Route::delete('/ulasan/{id}', [UlasanController::class, 'destroy'])->name('ulasan.delete'); @@ -36,9 +51,7 @@ Route::get('/hasil-diagnosis', [DiagnosisController::class, 'hasil'])->name('hasil-diagnosis'); -Route::get('/faq', function () { - return view('faq'); -})->name('faq'); +Route::get('/faq', [AdminController::class, 'faqPage'])->name('faq'); // Admin Routes Route::get('/admin/login', [AdminController::class, 'login'])->name('admin.login');