penambahan fitur
This commit is contained in:
parent
3564fc3fb8
commit
6ad6bb79f4
|
|
@ -10,6 +10,9 @@
|
||||||
use Carbon\CarbonPeriod;
|
use Carbon\CarbonPeriod;
|
||||||
use App\Models\Ulasan;
|
use App\Models\Ulasan;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -53,17 +56,28 @@ public function dashboard()
|
||||||
$yesterday = Biodata::whereDate('created_at', Carbon::yesterday())->count();
|
$yesterday = Biodata::whereDate('created_at', Carbon::yesterday())->count();
|
||||||
$diff = $todayDiagnosis - $yesterday;
|
$diff = $todayDiagnosis - $yesterday;
|
||||||
|
|
||||||
// total user
|
// user list + diagnosis list
|
||||||
$totalUsers = Biodata::count();
|
$sort = request('sort');
|
||||||
|
if ($sort == 'oldest') {
|
||||||
|
$data = Biodata::orderBy('created_at', 'asc')->get();
|
||||||
|
} else {
|
||||||
|
$data = Biodata::orderBy('created_at', 'desc')->get();
|
||||||
|
}
|
||||||
|
|
||||||
// user list
|
// Data pengguna unik (hindari duplikasi input yang sama)
|
||||||
$sort = request('sort');
|
$userData = $data->unique(function ($item) {
|
||||||
|
$phone = trim((string)($item->no_telepon ?? ''));
|
||||||
|
if ($phone !== '') {
|
||||||
|
return 'phone:' . $phone;
|
||||||
|
}
|
||||||
|
|
||||||
if ($sort == 'oldest') {
|
return 'fallback:' . Str::lower(trim((string)($item->nama_pemilik ?? ''))) . '|' .
|
||||||
$data = Biodata::orderBy('created_at', 'asc')->get();
|
Str::lower(trim((string)($item->nama_kucing ?? ''))) . '|' .
|
||||||
} else {
|
Str::lower(trim((string)($item->alamat ?? '')));
|
||||||
$data = Biodata::orderBy('created_at', 'desc')->get();
|
})->values();
|
||||||
}
|
|
||||||
|
// total user = jumlah data unik
|
||||||
|
$totalUsers = $userData->count();
|
||||||
|
|
||||||
// penyakit paling umum
|
// penyakit paling umum
|
||||||
$mostCommon = Biodata::select('hasil_diagnosis')
|
$mostCommon = Biodata::select('hasil_diagnosis')
|
||||||
|
|
@ -142,7 +156,7 @@ public function dashboard()
|
||||||
$stats['rating_labels'] = $ratingChart->pluck('rating');
|
$stats['rating_labels'] = $ratingChart->pluck('rating');
|
||||||
$stats['rating_data'] = $ratingChart->pluck('total');
|
$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)
|
public function logout(Request $request)
|
||||||
|
|
@ -196,4 +210,237 @@ public function sortDiagnosis(Request $request)
|
||||||
|
|
||||||
return response()->json($data);
|
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 '<html><head><meta charset="UTF-8"></head><body>';
|
||||||
|
echo '<table border="1" cellpadding="6" cellspacing="0">';
|
||||||
|
echo '<tr style="background:#e8f7ef;font-weight:bold;">';
|
||||||
|
echo '<th>Tanggal</th>';
|
||||||
|
echo '<th>Nama Pemilik</th>';
|
||||||
|
echo '<th>Nama Kucing</th>';
|
||||||
|
echo '<th>Umur Kucing</th>';
|
||||||
|
echo '<th>Jenis Kelamin</th>';
|
||||||
|
echo '<th>Berat Badan</th>';
|
||||||
|
echo '<th>Ras Kucing</th>';
|
||||||
|
echo '<th>Alamat</th>';
|
||||||
|
echo '<th>No Telepon</th>';
|
||||||
|
echo '<th>Hasil Diagnosis</th>';
|
||||||
|
echo '<th>Jenis</th>';
|
||||||
|
echo '</tr>';
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<td>' . e(optional($row->created_at)->format('d-m-Y H:i')) . '</td>';
|
||||||
|
echo '<td>' . e($row->nama_pemilik ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->nama_kucing ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->umur_kucing ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->jenis_kelamin ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->berat_badan ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->ras_kucing ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->alamat ?? '-') . '</td>';
|
||||||
|
echo '<td style="mso-number-format:\'\\@\';">' . e($row->no_telepon ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->hasil_diagnosis ?? '-') . '</td>';
|
||||||
|
echo '<td>' . e($row->jenis ?? '-') . '</td>';
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</table></body></html>';
|
||||||
|
}, $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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,29 @@ public function prosesDiagnosis(Request $request)
|
||||||
// 🔥 halaman hasil
|
// 🔥 halaman hasil
|
||||||
public function 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)
|
public function simpanBiodata(Request $request)
|
||||||
|
|
@ -99,4 +121,29 @@ public function simpanBiodata(Request $request)
|
||||||
|
|
||||||
return redirect()->route('gejala');
|
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 '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +140,24 @@
|
||||||
letter-spacing:-0.02em;
|
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 ===== */
|
||||||
.stats-grid {
|
.stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
@ -238,6 +256,41 @@
|
||||||
gap:12px;
|
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{
|
.table{
|
||||||
width:100%;
|
width:100%;
|
||||||
border-collapse:collapse;
|
border-collapse:collapse;
|
||||||
|
|
@ -342,6 +395,12 @@
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="page-title">Dashboard Admin</h1>
|
<h1 class="page-title">Dashboard Admin</h1>
|
||||||
|
<div class="admin-shortcuts">
|
||||||
|
<a href="#" class="admin-shortcut" onclick="toggleDiagnosis(); return false;">📋 Data Diagnosis</a>
|
||||||
|
<a href="#" class="admin-shortcut" onclick="toggleUsers(); return false;">👥 Data Pengguna</a>
|
||||||
|
<a href="#" class="admin-shortcut" onclick="toggleChart(); return false;">📊 Statistik</a>
|
||||||
|
<a href="{{ route('admin.disease.settings') }}" class="admin-shortcut">🧾 Pengaturan Penyakit</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Statistics -->
|
<!-- Statistics -->
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
|
|
@ -443,45 +502,44 @@
|
||||||
<div class="data-section">
|
<div class="data-section">
|
||||||
<div class="section-title">📋 Data Diagnosis</div>
|
<div class="section-title">📋 Data Diagnosis</div>
|
||||||
|
|
||||||
<div style="display:flex; gap:10px; margin-bottom:10px; flex-wrap:wrap;">
|
<div class="table-controls">
|
||||||
|
<input type="text" id="searchDiagnosis" class="form-control" placeholder="🔍 Cari data diagnosis...">
|
||||||
<input type="text" id="searchDiagnosis"
|
<select id="filterDiagnosis" class="form-control">
|
||||||
class="form-control"
|
|
||||||
placeholder="🔍 Cari..." style="max-width:200px;">
|
|
||||||
|
|
||||||
<select id="filterDiagnosis" class="form-control" style="max-width:200px;">
|
|
||||||
<option value="">Semua Penyakit</option>
|
<option value="">Semua Penyakit</option>
|
||||||
@foreach($data->pluck('hasil_diagnosis')->unique() as $penyakit)
|
@foreach($data->pluck('hasil_diagnosis')->unique() as $penyakit)
|
||||||
<option value="{{ strtolower($penyakit) }}">{{ $penyakit }}</option>
|
<option value="{{ strtolower($penyakit) }}">{{ $penyakit }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
|
<select id="sortDiagnosis" class="form-control">
|
||||||
|
<option value="latest">Terbaru</option>
|
||||||
|
<option value="oldest">Terlama</option>
|
||||||
|
<option value="name_asc">Nama Pemilik A-Z</option>
|
||||||
|
<option value="name_desc">Nama Pemilik Z-A</option>
|
||||||
|
</select>
|
||||||
|
<a href="{{ route('admin.export.diagnosis') }}" class="btn-export">⬇ Export Excel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="GET">
|
<div class="table-wrap">
|
||||||
<select id="sortDiagnosis">
|
|
||||||
<option value="latest">Terbaru</option>
|
|
||||||
<option value="oldest">Terlama</option>
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="max-height:400px; overflow-y:auto;">
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Nama Pemilik</th>
|
<th>Nama Pemilik</th>
|
||||||
<th>Nama Kucing</th>
|
<th>Nama Kucing</th>
|
||||||
|
<th>Umur Kucing</th>
|
||||||
|
<th>Jenis Kelamin</th>
|
||||||
<th>Penyakit</th>
|
<th>Penyakit</th>
|
||||||
<th>Tanggal</th>
|
<th>Tanggal</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="diagnosisTable">
|
<tbody id="diagnosisTable">
|
||||||
@foreach($data as $user)
|
@foreach($data as $item)
|
||||||
<tr data-date="{{ $user->created_at }}">
|
<tr data-date="{{ $item->created_at }}" data-name="{{ strtolower($item->nama_pemilik ?? '') }}" data-disease="{{ strtolower($item->hasil_diagnosis ?? '') }}">
|
||||||
<td>{{ $user->nama_pemilik }}</td>
|
<td>{{ $item->nama_pemilik }}</td>
|
||||||
<td>{{ $user->nama_kucing }}</td>
|
<td>{{ $item->nama_kucing }}</td>
|
||||||
<td>{{ $user->hasil_diagnosis ?? '-' }}</td>
|
<td>{{ $item->umur_kucing ?? '-' }}</td>
|
||||||
<td>{{ \Carbon\Carbon::parse($user->created_at)->format('d M Y') }}</td>
|
<td>{{ $item->jenis_kelamin ?? '-' }}</td>
|
||||||
|
<td>{{ $item->hasil_diagnosis ?? '-' }}</td>
|
||||||
|
<td>{{ \Carbon\Carbon::parse($item->created_at)->format('d M Y') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -493,26 +551,39 @@ class="form-control"
|
||||||
<div id="userBox" style="display:none; margin-top:20px;">
|
<div id="userBox" style="display:none; margin-top:20px;">
|
||||||
<div class="data-section">
|
<div class="data-section">
|
||||||
<div class="section-title">👥 Data Pengguna</div>
|
<div class="section-title">👥 Data Pengguna</div>
|
||||||
|
<div class="table-controls">
|
||||||
|
<input type="text" id="searchUser" class="form-control" placeholder="🔍 Cari pengguna...">
|
||||||
|
<select id="sortUser" class="form-control">
|
||||||
|
<option value="latest">Terbaru</option>
|
||||||
|
<option value="oldest">Terlama</option>
|
||||||
|
<option value="name_asc">Nama Pemilik A-Z</option>
|
||||||
|
<option value="name_desc">Nama Pemilik Z-A</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrap">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Tanggal</th>
|
||||||
<th>Nama Pemilik</th>
|
<th>Nama Pemilik</th>
|
||||||
<th>No Telepon</th>
|
<th>No Telepon</th>
|
||||||
<th>Alamat</th>
|
<th>Alamat</th>
|
||||||
<th>Nama Kucing</th>
|
<th>Nama Kucing</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="userTable">
|
||||||
@foreach($data ?? [] as $user)
|
@foreach(($userData ?? collect()) as $item)
|
||||||
<tr>
|
<tr data-date="{{ $item->created_at }}" data-name="{{ strtolower($item->nama_pemilik ?? '') }}">
|
||||||
<td>{{ $user->nama_pemilik }}</td>
|
<td>{{ \Carbon\Carbon::parse($item->created_at)->format('d M Y') }}</td>
|
||||||
<td>{{ $user->no_telepon }}</td>
|
<td>{{ $item->nama_pemilik }}</td>
|
||||||
<td>{{ $user->alamat ?? 'Tidak tersedia'}}</td>
|
<td>{{ $item->no_telepon }}</td>
|
||||||
<td>{{ $user->nama_kucing }}</td>
|
<td>{{ $item->alamat ?? 'Tidak tersedia'}}</td>
|
||||||
|
<td>{{ $item->nama_kucing }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -560,6 +631,12 @@ class="form-control"
|
||||||
<a href="{{ route('faq') }}" style="padding:12px 24px; background:white; border:2px solid var(--primary); color:var(--primary-dark); text-decoration:none; border-radius:12px; font-weight:600; transition:all 0.3s ease;">
|
<a href="{{ route('faq') }}" style="padding:12px 24px; background:white; border:2px solid var(--primary); color:var(--primary-dark); text-decoration:none; border-radius:12px; font-weight:600; transition:all 0.3s ease;">
|
||||||
❓ Lihat FAQ
|
❓ Lihat FAQ
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ route('admin.faq.settings') }}" style="padding:12px 24px; background:white; border:2px solid var(--primary); color:var(--primary-dark); text-decoration:none; border-radius:12px; font-weight:600; transition:all 0.3s ease;">
|
||||||
|
🛠️ Kelola FAQ
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.disease.settings') }}" style="padding:12px 24px; background:white; border:2px solid var(--primary); color:var(--primary-dark); text-decoration:none; border-radius:12px; font-weight:600; transition:all 0.3s ease;">
|
||||||
|
🧾 Atur Penjelasan Penyakit
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -567,236 +644,186 @@ class="form-control"
|
||||||
@include('components.scroll-top')
|
@include('components.scroll-top')
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let currentPage = 1;
|
const diagnosisBox = document.getElementById('diagnosisBox');
|
||||||
const rowsPerPage = 20;
|
const userBox = document.getElementById('userBox');
|
||||||
let allRows = [];
|
const chartBox = document.getElementById('chartBox');
|
||||||
|
|
||||||
|
const diagnosisTableBody = document.getElementById('diagnosisTable');
|
||||||
|
const diagnosisRows = Array.from(diagnosisTableBody.querySelectorAll('tr'));
|
||||||
|
|
||||||
// SEARCH + FILTER + SORT
|
const userTableBody = document.getElementById('userTable');
|
||||||
function applyFilters() {
|
const userRows = Array.from(userTableBody.querySelectorAll('tr'));
|
||||||
let search = document.getElementById("searchDiagnosis").value.toLowerCase();
|
|
||||||
let filter = document.getElementById("filterDiagnosis").value;
|
|
||||||
|
|
||||||
let rows = Array.from(document.querySelectorAll("#diagnosisTable tr"));
|
function sortRows(rows, sortType, nameAttr = 'data-name') {
|
||||||
|
const cloned = [...rows];
|
||||||
|
cloned.sort((a, b) => {
|
||||||
|
if (sortType === 'oldest') {
|
||||||
|
return new Date(a.getAttribute('data-date')) - new Date(b.getAttribute('data-date'));
|
||||||
|
}
|
||||||
|
if (sortType === 'name_asc') {
|
||||||
|
return (a.getAttribute(nameAttr) || '').localeCompare((b.getAttribute(nameAttr) || ''));
|
||||||
|
}
|
||||||
|
if (sortType === 'name_desc') {
|
||||||
|
return (b.getAttribute(nameAttr) || '').localeCompare((a.getAttribute(nameAttr) || ''));
|
||||||
|
}
|
||||||
|
return new Date(b.getAttribute('data-date')) - new Date(a.getAttribute('data-date'));
|
||||||
|
});
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
let filtered = rows.filter(row => {
|
function applyDiagnosisFilters() {
|
||||||
let text = row.innerText.toLowerCase();
|
const search = (document.getElementById('searchDiagnosis').value || '').toLowerCase();
|
||||||
let penyakit = row.children[2].innerText.toLowerCase();
|
const diseaseFilter = document.getElementById('filterDiagnosis').value;
|
||||||
|
const sort = document.getElementById('sortDiagnosis').value;
|
||||||
|
|
||||||
return text.includes(search) &&
|
const filtered = diagnosisRows.filter((row) => {
|
||||||
(filter === "" || penyakit === filter);
|
const text = row.innerText.toLowerCase();
|
||||||
|
const disease = row.getAttribute('data-disease') || '';
|
||||||
|
return text.includes(search) && (diseaseFilter === '' || disease === diseaseFilter);
|
||||||
});
|
});
|
||||||
|
|
||||||
// SEMUA DIHIDE DULU
|
const sorted = sortRows(filtered, sort);
|
||||||
rows.forEach(row => row.style.display = "none");
|
diagnosisTableBody.innerHTML = '';
|
||||||
|
sorted.forEach((row) => diagnosisTableBody.appendChild(row));
|
||||||
// TAMPILKAN HASIL
|
|
||||||
filtered.forEach(row => row.style.display = "");
|
|
||||||
}
|
|
||||||
// TAMPILKAN DATA
|
|
||||||
function displayRows(rows) {
|
|
||||||
let start = (currentPage - 1) * rowsPerPage;
|
|
||||||
let end = start + rowsPerPage;
|
|
||||||
|
|
||||||
let visible = rows.slice(start, end);
|
|
||||||
|
|
||||||
document.getElementById("tableBody").innerHTML = "";
|
|
||||||
|
|
||||||
visible.forEach(row => {
|
|
||||||
document.getElementById("tableBody").appendChild(row);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("pageInfo").innerText =
|
|
||||||
`Page ${currentPage}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PAGINATION
|
function applyUserFilters() {
|
||||||
function nextPage() {
|
const search = (document.getElementById('searchUser').value || '').toLowerCase();
|
||||||
currentPage++;
|
const sort = document.getElementById('sortUser').value;
|
||||||
applyFilters();
|
|
||||||
|
const filtered = userRows.filter((row) => row.innerText.toLowerCase().includes(search));
|
||||||
|
const sorted = sortRows(filtered, sort);
|
||||||
|
|
||||||
|
userTableBody.innerHTML = '';
|
||||||
|
sorted.forEach((row) => userTableBody.appendChild(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevPage() {
|
function toggleChart() {
|
||||||
if (currentPage > 1) {
|
const isHidden = window.getComputedStyle(chartBox).display === 'none';
|
||||||
currentPage--;
|
if (isHidden) {
|
||||||
applyFilters();
|
chartBox.style.display = 'block';
|
||||||
|
chartBox.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
loadMainChart();
|
||||||
|
loadRatingChart();
|
||||||
|
} else {
|
||||||
|
chartBox.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
|
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' });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.onload = function () {
|
let diseaseChart = null;
|
||||||
allRows = Array.from(document.querySelectorAll("#tableBody tr"));
|
let ratingChart = null;
|
||||||
applyFilters();
|
|
||||||
|
|
||||||
document.getElementById("searchDiagnosis").addEventListener("keyup", () => {
|
function loadMainChart() {
|
||||||
currentPage = 1;
|
if (diseaseChart) return;
|
||||||
applyFilters();
|
const ctx = document.getElementById('chartPenyakit');
|
||||||
|
diseaseChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($stats['chart_labels']) !!},
|
||||||
|
datasets: [{
|
||||||
|
label: 'Jumlah Kasus',
|
||||||
|
data: {!! json_encode($stats['chart_data']) !!},
|
||||||
|
backgroundColor: '#6fcf97',
|
||||||
|
borderColor: '#4bb66f',
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderRadius: 8
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: { mode: 'index', intersect: false }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: { beginAtZero: true, ticks: { precision: 0 }, grid: { color: '#e5e7eb' } },
|
||||||
|
x: { grid: { display: false } }
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("filterDiagnosis").addEventListener("change", () => {
|
new Chart(document.getElementById('chartHarian'), {
|
||||||
currentPage = 1;
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("sortDiagnosis").addEventListener("change", () => {
|
|
||||||
currentPage = 1;
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const ctx2 = document.getElementById('chartHarian');
|
|
||||||
|
|
||||||
new Chart(ctx2, {
|
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: {!! json_encode($stats['daily_labels']) !!},
|
labels: {!! json_encode($stats['daily_labels']) !!},
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Jumlah Diagnosis',
|
label: 'Jumlah Diagnosis',
|
||||||
data: {!! json_encode($stats['daily_data']) !!},
|
data: {!! json_encode($stats['daily_data']) !!},
|
||||||
tension: 0.4,
|
tension: 0.35,
|
||||||
fill: false,
|
fill: true,
|
||||||
|
backgroundColor: 'rgba(111, 207, 151, 0.2)',
|
||||||
|
borderColor: '#4bb66f',
|
||||||
borderWidth: 3,
|
borderWidth: 3,
|
||||||
pointRadius: 5
|
pointRadius: 4,
|
||||||
}]
|
pointBackgroundColor: '#4bb66f'
|
||||||
}
|
|
||||||
});
|
|
||||||
let chart = null;
|
|
||||||
|
|
||||||
function toggleChart() {
|
|
||||||
const chartBox = document.getElementById('chartBox');
|
|
||||||
|
|
||||||
const isHidden = window.getComputedStyle(chartBox).display === "none";
|
|
||||||
|
|
||||||
if (isHidden) {
|
|
||||||
chartBox.style.display = "block";
|
|
||||||
|
|
||||||
chartBox.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
|
|
||||||
if (!chart) {
|
|
||||||
const ctx = document.getElementById('chartPenyakit');
|
|
||||||
|
|
||||||
chart = new Chart(ctx, {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: {!! json_encode($stats['chart_labels']) !!},
|
|
||||||
datasets: [{
|
|
||||||
label: 'Jumlah Kasus',
|
|
||||||
data: {!! json_encode($stats['chart_data']) !!}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
plugins: {
|
|
||||||
legend: { display: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRatingChart();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
chartBox.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function toggleUsers() {
|
|
||||||
const userBox = document.getElementById('userBox');
|
|
||||||
const chartBox = document.getElementById('chartBox');
|
|
||||||
|
|
||||||
chartBox.style.display = "none"; // tutup chart
|
|
||||||
|
|
||||||
if (userBox.style.display === "none") {
|
|
||||||
userBox.style.display = "block";
|
|
||||||
userBox.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
} else {
|
|
||||||
userBox.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function toggleDiagnosis() {
|
|
||||||
const diagnosisBox = document.getElementById('diagnosisBox');
|
|
||||||
const chartBox = document.getElementById('chartBox');
|
|
||||||
const userBox = document.getElementById('userBox');
|
|
||||||
|
|
||||||
// tutup yang lain biar rapi
|
|
||||||
chartBox.style.display = "none";
|
|
||||||
userBox.style.display = "none";
|
|
||||||
|
|
||||||
if (diagnosisBox.style.display === "none") {
|
|
||||||
diagnosisBox.style.display = "block";
|
|
||||||
diagnosisBox.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
} else {
|
|
||||||
diagnosisBox.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let ratingChart = null;
|
|
||||||
|
|
||||||
function loadRatingChart() {
|
|
||||||
if (ratingChart) return;
|
|
||||||
|
|
||||||
const ctx = document.getElementById('chartRating');
|
|
||||||
|
|
||||||
ratingChart = new Chart(ctx, {
|
|
||||||
type: 'pie',
|
|
||||||
data: {
|
|
||||||
labels: {!! json_encode($stats['rating_labels']) !!}.map(r => 'Bintang ' + r),
|
|
||||||
datasets: [{
|
|
||||||
data: {!! json_encode($stats['rating_data']) !!}
|
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
plugins: {
|
responsive: true,
|
||||||
datalabels: {
|
plugins: { legend: { display: true } },
|
||||||
color: '#fff',
|
scales: {
|
||||||
formatter: (value, context) => {
|
y: { beginAtZero: true, ticks: { precision: 0 }, grid: { color: '#e5e7eb' } },
|
||||||
let total = context.dataset.data.reduce((a, b) => a + b, 0);
|
x: { grid: { display: false } }
|
||||||
let percent = (value / total * 100).toFixed(1);
|
|
||||||
return percent + '%';
|
|
||||||
},
|
|
||||||
font: {
|
|
||||||
weight: 'bold'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: 'bottom'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
plugins: [ChartDataLabels]
|
|
||||||
});}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
document.getElementById('sortDiagnosis').addEventListener('change', function() {
|
|
||||||
let sort = this.value;
|
|
||||||
let table = document.getElementById('diagnosisTable');
|
|
||||||
|
|
||||||
// loading dulu
|
|
||||||
table.innerHTML = "<tr><td colspan='4'>Loading...</td></tr>";
|
|
||||||
|
|
||||||
fetch(`/admin/sort-diagnosis?sort=${sort}`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
|
|
||||||
table.innerHTML = '';
|
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
table.innerHTML += `
|
|
||||||
<tr>
|
|
||||||
<td>${item.nama_pemilik}</td>
|
|
||||||
<td>${item.nama_kucing}</td>
|
|
||||||
<td>${item.hasil_diagnosis ?? '-'}</td>
|
|
||||||
<td>${new Date(item.created_at).toLocaleDateString()}</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
table.innerHTML = "<tr><td colspan='4'>Error load data</td></tr>";
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadRatingChart() {
|
||||||
|
if (ratingChart) return;
|
||||||
|
ratingChart = new Chart(document.getElementById('chartRating'), {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($stats['rating_labels']) !!}.map(r => 'Bintang ' + r),
|
||||||
|
datasets: [{
|
||||||
|
data: {!! json_encode($stats['rating_data']) !!},
|
||||||
|
backgroundColor: ['#22c55e', '#84cc16', '#f59e0b', '#f97316', '#ef4444']
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
color: '#fff',
|
||||||
|
formatter: (value, context) => {
|
||||||
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||||
|
if (!total) return '0%';
|
||||||
|
return ((value / total) * 100).toFixed(1) + '%';
|
||||||
|
},
|
||||||
|
font: { weight: 'bold' }
|
||||||
|
},
|
||||||
|
legend: { position: 'bottom' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [ChartDataLabels]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('searchDiagnosis').addEventListener('input', applyDiagnosisFilters);
|
||||||
|
document.getElementById('filterDiagnosis').addEventListener('change', applyDiagnosisFilters);
|
||||||
|
document.getElementById('sortDiagnosis').addEventListener('change', applyDiagnosisFilters);
|
||||||
|
document.getElementById('searchUser').addEventListener('input', applyUserFilters);
|
||||||
|
document.getElementById('sortUser').addEventListener('change', applyUserFilters);
|
||||||
|
|
||||||
|
applyDiagnosisFilters();
|
||||||
|
applyUserFilters();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pengaturan Penjelasan Penyakit - PawMedic</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@600;700;800&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--ff-heading:'Poppins',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial;
|
||||||
|
--ff-body:'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial;
|
||||||
|
--primary:#6fcf97;
|
||||||
|
--primary-dark:#4bb66f;
|
||||||
|
--primary-light:#e8f7ef;
|
||||||
|
--text-dark:#114d3a;
|
||||||
|
--text-muted:#64748b;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;}
|
||||||
|
body{
|
||||||
|
margin:0;
|
||||||
|
font-family:var(--ff-body);
|
||||||
|
background:linear-gradient(135deg,#f0fdf4 0%,#eaf7f0 50%,#f0f9ff 100%);
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
.container{max-width:1200px;margin:0 auto;padding:32px 20px;}
|
||||||
|
.topbar{
|
||||||
|
display:flex;justify-content:space-between;align-items:center;gap:12px;
|
||||||
|
margin-bottom:18px;
|
||||||
|
}
|
||||||
|
.title{font-family:var(--ff-heading);color:var(--text-dark);font-size:30px;font-weight:800;margin:0;}
|
||||||
|
.muted{color:var(--text-muted);margin:6px 0 0;}
|
||||||
|
.back{
|
||||||
|
text-decoration:none;padding:10px 14px;border-radius:10px;border:1px solid var(--primary);
|
||||||
|
background:#fff;color:var(--text-dark);font-weight:600;
|
||||||
|
}
|
||||||
|
.card{
|
||||||
|
background:rgba(255,255,255,.95);border:1px solid rgba(111,207,151,.2);border-radius:18px;
|
||||||
|
box-shadow:0 8px 24px rgba(17,77,58,.1);padding:20px;
|
||||||
|
}
|
||||||
|
.notice{
|
||||||
|
padding:10px 14px;border-radius:10px;background:var(--primary-light);color:var(--text-dark);
|
||||||
|
border:1px solid rgba(111,207,151,.3);margin-bottom:14px;font-weight:600;
|
||||||
|
}
|
||||||
|
.table-wrap{max-height:70vh;overflow:auto;border:1px solid #e2e8f0;border-radius:12px;}
|
||||||
|
table{width:100%;border-collapse:collapse;background:#fff;}
|
||||||
|
th,td{padding:12px 10px;border-bottom:1px solid #e2e8f0;vertical-align:top;}
|
||||||
|
th{background:var(--primary-light);text-align:left;color:var(--text-dark);}
|
||||||
|
textarea{
|
||||||
|
width:100%;min-height:90px;resize:vertical;padding:10px;border:1px solid #cbd5e1;border-radius:10px;
|
||||||
|
font-family:var(--ff-body);font-size:14px;line-height:1.5;
|
||||||
|
}
|
||||||
|
.actions{margin-top:16px;display:flex;justify-content:flex-end;}
|
||||||
|
.btn{
|
||||||
|
border:none;border-radius:12px;padding:12px 18px;font-weight:700;cursor:pointer;
|
||||||
|
background:linear-gradient(135deg,var(--primary),var(--primary-dark));color:#fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="topbar">
|
||||||
|
<div>
|
||||||
|
<h1 class="title">Pengaturan Penjelasan Penyakit</h1>
|
||||||
|
<p class="muted">Atur deskripsi penyakit yang tampil di halaman hasil diagnosis.</p>
|
||||||
|
</div>
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="back">← Kembali ke Dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="notice">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.disease.settings.save') }}">
|
||||||
|
@csrf
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:32%;">Nama Penyakit</th>
|
||||||
|
<th>Penjelasan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($diseases as $disease)
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ $disease }}</strong></td>
|
||||||
|
<td>
|
||||||
|
<textarea name="descriptions[{{ $disease }}]" placeholder="Tulis penjelasan singkat penyakit...">{{ $descriptions[$disease] ?? '' }}</textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr><td colspan="2">Belum ada data penyakit.</td></tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button type="submit" class="btn">Simpan Penjelasan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Kelola FAQ - PawMedic Admin</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@600;700;800&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body{margin:0;font-family:'Inter',sans-serif;background:#f4faf7;color:#1f2937}
|
||||||
|
.container{max-width:1100px;margin:0 auto;padding:28px 16px}
|
||||||
|
.head{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:18px}
|
||||||
|
.title{font-family:'Poppins',sans-serif;font-size:30px;margin:0;color:#114d3a}
|
||||||
|
.card{background:#fff;border:1px solid #d1fae5;border-radius:16px;padding:16px;box-shadow:0 8px 20px rgba(17,77,58,.08)}
|
||||||
|
.row{display:grid;grid-template-columns:1fr 2fr;gap:10px;margin-bottom:10px}
|
||||||
|
input,textarea{width:100%;padding:10px;border:1px solid #cbd5e1;border-radius:10px;font:inherit}
|
||||||
|
textarea{min-height:92px;resize:vertical}
|
||||||
|
.btn{padding:10px 14px;border-radius:10px;border:1px solid #6fcf97;background:#6fcf97;color:#fff;font-weight:700;cursor:pointer}
|
||||||
|
.btn.secondary{background:#fff;color:#114d3a}
|
||||||
|
.actions{display:flex;justify-content:space-between;align-items:center;margin-top:12px;gap:10px}
|
||||||
|
.notice{background:#e8f7ef;border:1px solid #b7ebcf;padding:10px;border-radius:10px;color:#114d3a;margin-bottom:10px}
|
||||||
|
@media(max-width:768px){.row{grid-template-columns:1fr}.title{font-size:24px}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="head">
|
||||||
|
<h1 class="title">Kelola FAQ</h1>
|
||||||
|
<a class="btn secondary" href="{{ route('admin.dashboard') }}">← Dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="notice">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
<form method="POST" action="{{ route('admin.faq.settings.save') }}">
|
||||||
|
@csrf
|
||||||
|
<div id="faqRows">
|
||||||
|
@forelse($faqs as $faq)
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" name="questions[]" value="{{ $faq['question'] }}" placeholder="Pertanyaan">
|
||||||
|
<textarea name="answers[]" placeholder="Jawaban">{{ $faq['answer'] }}</textarea>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" name="questions[]" placeholder="Pertanyaan">
|
||||||
|
<textarea name="answers[]" placeholder="Jawaban"></textarea>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button type="button" class="btn secondary" onclick="addFaqRow()">+ Tambah FAQ</button>
|
||||||
|
<button type="submit" class="btn">Simpan FAQ</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function addFaqRow() {
|
||||||
|
const wrap = document.getElementById('faqRows');
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'row';
|
||||||
|
row.innerHTML = `
|
||||||
|
<input type="text" name="questions[]" placeholder="Pertanyaan">
|
||||||
|
<textarea name="answers[]" placeholder="Jawaban"></textarea>
|
||||||
|
`;
|
||||||
|
wrap.appendChild(row);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -226,60 +226,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="faq-list">
|
<div class="faq-list">
|
||||||
<div class="faq-card">
|
@forelse(($faqs ?? []) as $faq)
|
||||||
<div class="faq-question">Apa itu PawMedic?</div>
|
<div class="faq-card">
|
||||||
<div class="faq-answer">
|
<div class="faq-question">{{ $faq['question'] ?? '-' }}</div>
|
||||||
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.
|
<div class="faq-answer" style="white-space: pre-line;">{{ $faq['answer'] ?? '-' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@empty
|
||||||
|
<div class="faq-card">
|
||||||
<div class="faq-card">
|
<div class="faq-question">Belum ada FAQ</div>
|
||||||
<div class="faq-question">Bagaimana cara menggunakan PawMedic?</div>
|
<div class="faq-answer">Konten FAQ belum tersedia.</div>
|
||||||
<div class="faq-answer">
|
|
||||||
Cara menggunakan PawMedic sangat mudah:
|
|
||||||
<ol style="margin:12px 0; padding-left:20px;">
|
|
||||||
<li>Isi biodata kucing Anda</li>
|
|
||||||
<li>Pilih gejala yang Anda amati pada kucing</li>
|
|
||||||
<li>Sistem akan menganalisis dan memberikan hasil diagnosis</li>
|
|
||||||
<li>Baca rekomendasi perawatan yang diberikan</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endforelse
|
||||||
|
|
||||||
<div class="faq-card">
|
|
||||||
<div class="faq-question">Apakah hasil diagnosis akurat?</div>
|
|
||||||
<div class="faq-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.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-card">
|
|
||||||
<div class="faq-question">Apakah data saya aman?</div>
|
|
||||||
<div class="faq-answer">
|
|
||||||
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.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-card">
|
|
||||||
<div class="faq-question">Berapa banyak gejala yang harus dipilih?</div>
|
|
||||||
<div class="faq-answer">
|
|
||||||
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.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-card">
|
|
||||||
<div class="faq-question">Apakah aplikasi ini gratis?</div>
|
|
||||||
<div class="faq-answer">
|
|
||||||
Ya, PawMedic sepenuhnya gratis untuk digunakan. Anda dapat melakukan diagnosis tanpa batas dan mengakses semua fitur yang tersedia tanpa biaya apapun.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-card">
|
|
||||||
<div class="faq-question">Bagaimana jika kucing saya dalam kondisi darurat?</div>
|
|
||||||
<div class="faq-answer">
|
|
||||||
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.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,17 @@
|
||||||
font-weight:600;
|
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 ===== */
|
||||||
.gejala-list-section{
|
.gejala-list-section{
|
||||||
margin-bottom:32px;
|
margin-bottom:32px;
|
||||||
|
|
@ -327,6 +338,32 @@
|
||||||
line-height:1.7;
|
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 ===== */
|
/* ===== BUTTONS ===== */
|
||||||
.action-buttons{
|
.action-buttons{
|
||||||
display:flex;
|
display:flex;
|
||||||
|
|
@ -450,6 +487,12 @@
|
||||||
<div class="diagnosis-category">
|
<div class="diagnosis-category">
|
||||||
Jenis: {{ $diagnosis['kategori'] ?? '-' }}
|
Jenis: {{ $diagnosis['kategori'] ?? '-' }}
|
||||||
</div>
|
</div>
|
||||||
|
@if(!empty($diseaseDescription))
|
||||||
|
<div class="disease-explanation">
|
||||||
|
<strong>Penjelasan penyakit:</strong><br>
|
||||||
|
{{ $diseaseDescription }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
<!-- Gejala yang Dipilih -->
|
<!-- Gejala yang Dipilih -->
|
||||||
|
|
@ -516,13 +559,85 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if(isset($diagnosisHistory) && $diagnosisHistory->count() > 0)
|
||||||
|
<div class="result-card history-section">
|
||||||
|
<div class="section-title">
|
||||||
|
<span>🕘 Riwayat Diagnosis (Nomor yang sama)</span>
|
||||||
|
</div>
|
||||||
|
<table class="history-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
<th>Nama Kucing</th>
|
||||||
|
<th>Hasil Diagnosis</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($diagnosisHistory as $row)
|
||||||
|
<tr>
|
||||||
|
<td>{{ \Carbon\Carbon::parse($row->created_at)->format('d M Y H:i') }}</td>
|
||||||
|
<td>{{ $row->nama_kucing ?? '-' }}</td>
|
||||||
|
<td>{{ $row->hasil_diagnosis ?? '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@include('components.scroll-top')
|
@include('components.scroll-top')
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
// Print function
|
// Print function
|
||||||
function printDiagnosis() {
|
function printDiagnosis() {
|
||||||
window.print();
|
const diagnosis = @json($diagnosis);
|
||||||
|
const gejala = @json(session('gejala', []));
|
||||||
|
const penjelasan = @json($diseaseDescription ?? '');
|
||||||
|
const html = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Cetak Hasil Diagnosis</title>
|
||||||
|
<style>
|
||||||
|
body{font-family:Arial,sans-serif;padding:24px;color:#111;line-height:1.5}
|
||||||
|
h1{font-size:22px;margin-bottom:6px}
|
||||||
|
.muted{color:#666;font-size:13px;margin-bottom:18px}
|
||||||
|
.box{border:1px solid #ddd;border-radius:8px;padding:12px;margin-bottom:12px}
|
||||||
|
ul{margin:8px 0 0 18px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hasil Diagnosis PawMedic</h1>
|
||||||
|
<div class="muted">Dicetak pada: ${new Date().toLocaleString('id-ID')}</div>
|
||||||
|
<div class="box">
|
||||||
|
<strong>Penyakit:</strong> ${diagnosis.nama || '-'}<br>
|
||||||
|
<strong>Jenis:</strong> ${diagnosis.kategori || '-'}
|
||||||
|
${penjelasan ? `<br><strong>Penjelasan:</strong> ${penjelasan}` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<strong>Gejala Dipilih:</strong>
|
||||||
|
<ul>${(gejala || []).map(g => `<li>${g}</li>`).join('') || '<li>-</li>'}</ul>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<strong>Pertolongan:</strong>
|
||||||
|
<ul>${(diagnosis.pertolongan || []).map(p => `<li>${p}</li>`).join('') || '<li>-</li>'}</ul>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<strong>Pencegahan:</strong>
|
||||||
|
<ul>${(diagnosis.pencegahan || []).map(p => `<li>${p}</li>`).join('') || '<li>-</li>'}</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
const w = window.open('', '_blank');
|
||||||
|
if (!w) return;
|
||||||
|
w.document.open();
|
||||||
|
w.document.write(html);
|
||||||
|
w.document.close();
|
||||||
|
w.focus();
|
||||||
|
w.print();
|
||||||
|
w.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share function
|
// Share function
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,16 @@
|
||||||
gap:18px;
|
gap:18px;
|
||||||
align-items:center;
|
align-items:center;
|
||||||
}
|
}
|
||||||
|
.menu-toggle{
|
||||||
|
display:none;
|
||||||
|
border:1px solid #d1d5db;
|
||||||
|
background:#fff;
|
||||||
|
border-radius:10px;
|
||||||
|
padding:8px 10px;
|
||||||
|
font-size:20px;
|
||||||
|
cursor:pointer;
|
||||||
|
color:#114d3a;
|
||||||
|
}
|
||||||
.nav-menu a{
|
.nav-menu a{
|
||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
color:#555;
|
color:#555;
|
||||||
|
|
@ -471,6 +481,26 @@
|
||||||
|
|
||||||
/* ===== RESPONSIVE ===== */
|
/* ===== RESPONSIVE ===== */
|
||||||
@media(max-width:900px){
|
@media(max-width:900px){
|
||||||
|
.container{
|
||||||
|
padding:24px;
|
||||||
|
}
|
||||||
|
.navbar{
|
||||||
|
flex-direction:column;
|
||||||
|
align-items:flex-start;
|
||||||
|
gap:14px;
|
||||||
|
margin-bottom:28px;
|
||||||
|
}
|
||||||
|
.nav-menu{
|
||||||
|
width:100%;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
gap:10px;
|
||||||
|
}
|
||||||
|
.nav-menu a{
|
||||||
|
font-size:14px;
|
||||||
|
}
|
||||||
|
.nav-menu .btn{
|
||||||
|
margin-left:auto;
|
||||||
|
}
|
||||||
.hero{
|
.hero{
|
||||||
flex-direction:column;
|
flex-direction:column;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
|
@ -503,9 +533,56 @@
|
||||||
.features{
|
.features{
|
||||||
grid-template-columns:repeat(2,1fr);
|
grid-template-columns:repeat(2,1fr);
|
||||||
}
|
}
|
||||||
|
section{
|
||||||
|
margin-top:72px;
|
||||||
|
}
|
||||||
|
#diagnosa{
|
||||||
|
padding:24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width:500px){
|
@media(max-width:500px){
|
||||||
|
.container{
|
||||||
|
padding:16px;
|
||||||
|
}
|
||||||
|
.logo-text{
|
||||||
|
font-size:18px;
|
||||||
|
}
|
||||||
|
.navbar{
|
||||||
|
padding:12px 0;
|
||||||
|
align-items:stretch;
|
||||||
|
}
|
||||||
|
.menu-toggle{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
align-self:flex-end;
|
||||||
|
}
|
||||||
|
.nav-menu{
|
||||||
|
display:none;
|
||||||
|
grid-template-columns:1fr 1fr;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
.nav-menu.open{
|
||||||
|
display:grid;
|
||||||
|
}
|
||||||
|
.nav-menu a{
|
||||||
|
text-align:center;
|
||||||
|
padding:8px 6px;
|
||||||
|
border-radius:8px;
|
||||||
|
background:#fff;
|
||||||
|
border:1px solid #eef5f3;
|
||||||
|
}
|
||||||
|
.nav-menu .btn{
|
||||||
|
grid-column:1 / -1;
|
||||||
|
width:100%;
|
||||||
|
margin-left:0;
|
||||||
|
}
|
||||||
|
.hero{
|
||||||
|
padding:28px 18px 32px;
|
||||||
|
gap:20px;
|
||||||
|
border-radius:18px;
|
||||||
|
}
|
||||||
.features{
|
.features{
|
||||||
grid-template-columns:1fr;
|
grid-template-columns:1fr;
|
||||||
}
|
}
|
||||||
|
|
@ -516,6 +593,33 @@
|
||||||
max-width:320px;
|
max-width:320px;
|
||||||
width:100%;
|
width:100%;
|
||||||
}
|
}
|
||||||
|
.hero-actions{
|
||||||
|
width:100%;
|
||||||
|
flex-direction:column;
|
||||||
|
}
|
||||||
|
.hero-actions .btn{
|
||||||
|
width:100%;
|
||||||
|
min-width:unset;
|
||||||
|
}
|
||||||
|
section{
|
||||||
|
margin-top:56px;
|
||||||
|
}
|
||||||
|
section > p{
|
||||||
|
font-size:15px;
|
||||||
|
margin-bottom:24px;
|
||||||
|
}
|
||||||
|
.card.feature{
|
||||||
|
padding:22px 18px;
|
||||||
|
min-height:unset;
|
||||||
|
}
|
||||||
|
footer{
|
||||||
|
margin-top:44px;
|
||||||
|
padding-bottom:42px;
|
||||||
|
}
|
||||||
|
.admin-login-link{
|
||||||
|
bottom:2px;
|
||||||
|
right:2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -529,7 +633,8 @@
|
||||||
<div class="logo-icon">🐾</div>
|
<div class="logo-icon">🐾</div>
|
||||||
<div class="logo-text">PawMedic</div>
|
<div class="logo-text">PawMedic</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-menu">
|
<button class="menu-toggle" id="menuToggle" aria-label="Buka menu">☰</button>
|
||||||
|
<div class="nav-menu" id="navMenu">
|
||||||
<a href="#fitur">Fitur</a>
|
<a href="#fitur">Fitur</a>
|
||||||
<a href="#cara">Cara Kerja</a>
|
<a href="#cara">Cara Kerja</a>
|
||||||
<a href="{{ route('ulasan') }}">Ulasan</a>
|
<a href="{{ route('ulasan') }}">Ulasan</a>
|
||||||
|
|
@ -649,6 +754,15 @@ function scrollToSection(id){
|
||||||
behavior:'smooth'
|
behavior:'smooth'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const menuToggle = document.getElementById('menuToggle');
|
||||||
|
const navMenu = document.getElementById('navMenu');
|
||||||
|
if (menuToggle && navMenu) {
|
||||||
|
menuToggle.addEventListener('click', () => {
|
||||||
|
navMenu.classList.toggle('open');
|
||||||
|
menuToggle.textContent = navMenu.classList.contains('open') ? '✕' : '☰';
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,21 @@
|
||||||
use App\Models\Ulasan;
|
use App\Models\Ulasan;
|
||||||
|
|
||||||
Route::get('/admin/sort-diagnosis', [AdminController::class, 'sortDiagnosis']);
|
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');
|
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('/hasil-diagnosis', [DiagnosisController::class, 'hasil'])->name('hasil-diagnosis');
|
||||||
|
|
||||||
Route::get('/faq', function () {
|
Route::get('/faq', [AdminController::class, 'faqPage'])->name('faq');
|
||||||
return view('faq');
|
|
||||||
})->name('faq');
|
|
||||||
|
|
||||||
// Admin Routes
|
// Admin Routes
|
||||||
Route::get('/admin/login', [AdminController::class, 'login'])->name('admin.login');
|
Route::get('/admin/login', [AdminController::class, 'login'])->name('admin.login');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue