Update Dashboard penambahan fitur
This commit is contained in:
parent
ccdd44927b
commit
3564fc3fb8
|
|
@ -7,6 +7,9 @@
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use App\Models\Biodata;
|
use App\Models\Biodata;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use App\Models\Ulasan;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -39,15 +42,29 @@ public function authenticate(Request $request)
|
||||||
|
|
||||||
public function dashboard()
|
public function dashboard()
|
||||||
{
|
{
|
||||||
|
|
||||||
// total diagnosis
|
// total diagnosis
|
||||||
$totalDiagnosis = Biodata::count();
|
$totalDiagnosis = Biodata::count();
|
||||||
|
|
||||||
// hari ini
|
// hari ini
|
||||||
$todayDiagnosis = Biodata::whereDate('created_at', Carbon::today())->count();
|
$todayDiagnosis = Biodata::whereDate('created_at', Carbon::today())->count();
|
||||||
|
|
||||||
|
// kemarin
|
||||||
|
$yesterday = Biodata::whereDate('created_at', Carbon::yesterday())->count();
|
||||||
|
$diff = $todayDiagnosis - $yesterday;
|
||||||
|
|
||||||
// total user
|
// total user
|
||||||
$totalUsers = Biodata::count();
|
$totalUsers = Biodata::count();
|
||||||
|
|
||||||
|
// user list
|
||||||
|
$sort = request('sort');
|
||||||
|
|
||||||
|
if ($sort == 'oldest') {
|
||||||
|
$data = Biodata::orderBy('created_at', 'asc')->get();
|
||||||
|
} else {
|
||||||
|
$data = Biodata::orderBy('created_at', 'desc')->get();
|
||||||
|
}
|
||||||
|
|
||||||
// penyakit paling umum
|
// penyakit paling umum
|
||||||
$mostCommon = Biodata::select('hasil_diagnosis')
|
$mostCommon = Biodata::select('hasil_diagnosis')
|
||||||
->whereNotNull('hasil_diagnosis')
|
->whereNotNull('hasil_diagnosis')
|
||||||
|
|
@ -55,13 +72,20 @@ public function dashboard()
|
||||||
->orderByRaw('COUNT(*) DESC')
|
->orderByRaw('COUNT(*) DESC')
|
||||||
->value('hasil_diagnosis');
|
->value('hasil_diagnosis');
|
||||||
|
|
||||||
|
// penyakit terbanyak hari ini
|
||||||
|
$todayDisease = Biodata::whereDate('created_at', Carbon::today())
|
||||||
|
->select('hasil_diagnosis')
|
||||||
|
->whereNotNull('hasil_diagnosis')
|
||||||
|
->groupBy('hasil_diagnosis')
|
||||||
|
->orderByRaw('COUNT(*) DESC')
|
||||||
|
->value('hasil_diagnosis');
|
||||||
|
|
||||||
// diagnosis terbaru
|
// diagnosis terbaru
|
||||||
$recent = Biodata::select('hasil_diagnosis', 'created_at')
|
$recent = Biodata::select('hasil_diagnosis', 'created_at')
|
||||||
->latest()
|
->latest()
|
||||||
->take(5)
|
->take(5)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// format tabel
|
|
||||||
$recentFormatted = $recent->map(function ($item) {
|
$recentFormatted = $recent->map(function ($item) {
|
||||||
return [
|
return [
|
||||||
'date' => $item->created_at,
|
'date' => $item->created_at,
|
||||||
|
|
@ -70,7 +94,7 @@ public function dashboard()
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🔥 CHART (HARUS DI LUAR MAP)
|
// chart penyakit
|
||||||
$diseaseStats = Biodata::select('hasil_diagnosis')
|
$diseaseStats = Biodata::select('hasil_diagnosis')
|
||||||
->whereNotNull('hasil_diagnosis')
|
->whereNotNull('hasil_diagnosis')
|
||||||
->get()
|
->get()
|
||||||
|
|
@ -82,6 +106,19 @@ public function dashboard()
|
||||||
$chartLabels = $diseaseStats->keys()->values();
|
$chartLabels = $diseaseStats->keys()->values();
|
||||||
$chartData = $diseaseStats->values();
|
$chartData = $diseaseStats->values();
|
||||||
|
|
||||||
|
// 🔥 7 hari terakhir
|
||||||
|
$period = CarbonPeriod::create(Carbon::now()->subDays(6), Carbon::now());
|
||||||
|
|
||||||
|
$dailyLabels = [];
|
||||||
|
$dailyData = [];
|
||||||
|
|
||||||
|
foreach ($period as $date) {
|
||||||
|
$count = Biodata::whereDate('created_at', $date)->count();
|
||||||
|
|
||||||
|
$dailyLabels[] = $date->format('d M');
|
||||||
|
$dailyData[] = $count;
|
||||||
|
}
|
||||||
|
|
||||||
// kirim ke blade
|
// kirim ke blade
|
||||||
$stats = [
|
$stats = [
|
||||||
'total_diagnosis' => $totalDiagnosis,
|
'total_diagnosis' => $totalDiagnosis,
|
||||||
|
|
@ -90,10 +127,22 @@ public function dashboard()
|
||||||
'most_common_disease' => $mostCommon,
|
'most_common_disease' => $mostCommon,
|
||||||
'recent_diagnosis' => $recentFormatted,
|
'recent_diagnosis' => $recentFormatted,
|
||||||
'chart_labels' => $chartLabels,
|
'chart_labels' => $chartLabels,
|
||||||
'chart_data' => $chartData
|
'chart_data' => $chartData,
|
||||||
|
'diagnosis_diff' => $diff,
|
||||||
|
'today_top_disease' => $todayDisease,
|
||||||
|
'daily_labels' => $dailyLabels,
|
||||||
|
'daily_data' => $dailyData
|
||||||
];
|
];
|
||||||
|
// 🔥 STAT
|
||||||
|
$ratingChart = Ulasan::select('rating', DB::raw('count(*) as total'))
|
||||||
|
->groupBy('rating')
|
||||||
|
->orderBy('rating')
|
||||||
|
->get();
|
||||||
|
|
||||||
return view('admin.dashboard', compact('stats'));
|
$stats['rating_labels'] = $ratingChart->pluck('rating');
|
||||||
|
$stats['rating_data'] = $ratingChart->pluck('total');
|
||||||
|
|
||||||
|
return view('admin.dashboard', compact('stats', 'data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logout(Request $request)
|
public function logout(Request $request)
|
||||||
|
|
@ -120,4 +169,31 @@ private function getStatistics()
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
public function statistik()
|
||||||
|
{
|
||||||
|
$diseaseStats = Biodata::select('hasil_diagnosis')
|
||||||
|
->whereNotNull('hasil_diagnosis')
|
||||||
|
->get()
|
||||||
|
->groupBy('hasil_diagnosis')
|
||||||
|
->map(function ($item) {
|
||||||
|
return count($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$chartLabels = $diseaseStats->keys()->values();
|
||||||
|
$chartData = $diseaseStats->values();
|
||||||
|
|
||||||
|
return view('admin.statistik', compact('chartLabels', 'chartData'));
|
||||||
|
}
|
||||||
|
public function sortDiagnosis(Request $request)
|
||||||
|
{
|
||||||
|
$sort = $request->sort;
|
||||||
|
|
||||||
|
if ($sort == 'oldest') {
|
||||||
|
$data = Biodata::orderBy('created_at', 'asc')->get();
|
||||||
|
} else {
|
||||||
|
$data = Biodata::orderBy('created_at', 'desc')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Ulasan;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class UlasanController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$ulasan = Ulasan::latest()->get();
|
||||||
|
// 🔥 TOTAL ULASAN
|
||||||
|
$total = $ulasan->count();
|
||||||
|
// 🔥 RATING RATA-RATA
|
||||||
|
$avg = $ulasan->avg('rating') ?? 0;
|
||||||
|
// 🔥 5 BINTANG %
|
||||||
|
$fiveStar = $ulasan->where('rating', 5)->count();
|
||||||
|
$fivePercent = $total > 0 ? round(($fiveStar / $total) * 100) : 0;
|
||||||
|
|
||||||
|
return view('ulasan', compact('ulasan', 'total', 'avg', 'fivePercent'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
Ulasan::create($request->all());
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Ulasan berhasil dikirim!');
|
||||||
|
}
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
if (!Auth::check() || Auth::user()->email !== 'admin@pawmedic.app') {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ulasan = Ulasan::findOrFail($id);
|
||||||
|
$ulasan->delete();
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Ulasan berhasil dihapus');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Ulasan extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'ulasans';
|
||||||
|
protected $fillable = [
|
||||||
|
'nama',
|
||||||
|
'nama_kucing',
|
||||||
|
'hasil_diagnosis',
|
||||||
|
'rating',
|
||||||
|
'komentar'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('ulasans', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ulasans');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -145,6 +145,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card{
|
.stat-card{
|
||||||
|
|
@ -306,6 +307,9 @@
|
||||||
padding:12px 8px;
|
padding:12px 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#chartBox {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
@ -341,15 +345,8 @@
|
||||||
|
|
||||||
<!-- Statistics -->
|
<!-- Statistics -->
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
</div>
|
|
||||||
<div id="chartBox" style="display:none; margin-top:20px;">
|
|
||||||
<div class="data-section">
|
|
||||||
<div class="section-title">📊 Statistik Penyakit</div>
|
|
||||||
<canvas id="chartPenyakit"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-header">
|
<div class="stat-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="stat-value">{{ $stats['total_diagnosis'] }}</div>
|
<div class="stat-value">{{ $stats['total_diagnosis'] }}</div>
|
||||||
|
|
@ -358,6 +355,10 @@
|
||||||
<div class="stat-icon">📊</div>
|
<div class="stat-icon">📊</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-change">+{{ $stats['today_diagnosis'] }} hari ini</div>
|
<div class="stat-change">+{{ $stats['today_diagnosis'] }} hari ini</div>
|
||||||
|
<a href="#" onclick="toggleDiagnosis(); return false;"
|
||||||
|
style="margin-top:10px; display:inline-block; font-size:13px; color:#4bb66f; font-weight:600;">
|
||||||
|
Lihat Data →
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
|
|
@ -369,6 +370,19 @@
|
||||||
<div class="stat-icon">📈</div>
|
<div class="stat-icon">📈</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-change">Aktif hari ini</div>
|
<div class="stat-change">Aktif hari ini</div>
|
||||||
|
<div class="stat-change">
|
||||||
|
@if($stats['diagnosis_diff'] > 0)
|
||||||
|
↑ +{{ $stats['diagnosis_diff'] }} dari kemarin
|
||||||
|
@elseif($stats['diagnosis_diff'] < 0)
|
||||||
|
↓ {{ $stats['diagnosis_diff'] }} dari kemarin
|
||||||
|
@else
|
||||||
|
Sama dengan kemarin
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size:12px; color:#64748b; margin-top:4px;">
|
||||||
|
Terbanyak: {{ $stats['today_top_disease'] ?? '-' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
|
|
@ -380,9 +394,13 @@
|
||||||
<div class="stat-icon">👥</div>
|
<div class="stat-icon">👥</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-change">Pengguna aktif</div>
|
<div class="stat-change">Pengguna aktif</div>
|
||||||
|
<a href="#" onclick="toggleUsers(); return false;"
|
||||||
|
style="margin-top:10px; display:inline-block; font-size:13px; color:#4bb66f; font-weight:600;">
|
||||||
|
Lihat Data →
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card" onclick="toggleChart()" style="cursor:pointer;">
|
<div class="stat-card">
|
||||||
<div class="stat-header">
|
<div class="stat-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="stat-value" style="font-size:24px;">{{ $stats['most_common_disease'] }}</div>
|
<div class="stat-value" style="font-size:24px;">{{ $stats['most_common_disease'] }}</div>
|
||||||
|
|
@ -391,8 +409,112 @@
|
||||||
<div class="stat-icon">🩺</div>
|
<div class="stat-icon">🩺</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-change">Paling sering didiagnosis</div>
|
<div class="stat-change">Paling sering didiagnosis</div>
|
||||||
</div>
|
|
||||||
|
<a href="#" onclick="toggleChart(); return false;"
|
||||||
|
style="margin-top:10px; display:inline-block; font-size:13px; color:#4bb66f; font-weight:600;">
|
||||||
|
Lihat Data →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="chartBox" style="display:none; margin-top:20px;">
|
||||||
|
|
||||||
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap: 20px;">
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="section-title">📊 Statistik Penyakit</div>
|
||||||
|
<div style="max-width:600px; margin:auto;">
|
||||||
|
<canvas id="chartPenyakit"></canvas></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="section-title">⭐ Distribusi Rating Ulasan</div>
|
||||||
|
<div style="max-width:400px; margin:auto;">
|
||||||
|
<canvas id="chartRating" height="250"></canvas></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="section-title">📈 Tren Diagnosis (7 Hari Terakhir)</div>
|
||||||
|
<canvas id="chartHarian"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="diagnosisBox" style="display:none; margin-top:20px;">
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="section-title">📋 Data Diagnosis</div>
|
||||||
|
|
||||||
|
<div style="display:flex; gap:10px; margin-bottom:10px; flex-wrap:wrap;">
|
||||||
|
|
||||||
|
<input type="text" id="searchDiagnosis"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="🔍 Cari..." style="max-width:200px;">
|
||||||
|
|
||||||
|
<select id="filterDiagnosis" class="form-control" style="max-width:200px;">
|
||||||
|
<option value="">Semua Penyakit</option>
|
||||||
|
@foreach($data->pluck('hasil_diagnosis')->unique() as $penyakit)
|
||||||
|
<option value="{{ strtolower($penyakit) }}">{{ $penyakit }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<form method="GET">
|
||||||
|
<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">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nama Pemilik</th>
|
||||||
|
<th>Nama Kucing</th>
|
||||||
|
<th>Penyakit</th>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="diagnosisTable">
|
||||||
|
@foreach($data as $user)
|
||||||
|
<tr data-date="{{ $user->created_at }}">
|
||||||
|
<td>{{ $user->nama_pemilik }}</td>
|
||||||
|
<td>{{ $user->nama_kucing }}</td>
|
||||||
|
<td>{{ $user->hasil_diagnosis ?? '-' }}</td>
|
||||||
|
<td>{{ \Carbon\Carbon::parse($user->created_at)->format('d M Y') }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="userBox" style="display:none; margin-top:20px;">
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="section-title">👥 Data Pengguna</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nama Pemilik</th>
|
||||||
|
<th>No Telepon</th>
|
||||||
|
<th>Alamat</th>
|
||||||
|
<th>Nama Kucing</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($data ?? [] as $user)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $user->nama_pemilik }}</td>
|
||||||
|
<td>{{ $user->no_telepon }}</td>
|
||||||
|
<td>{{ $user->alamat ?? 'Tidak tersedia'}}</td>
|
||||||
|
<td>{{ $user->nama_kucing }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Recent Diagnosis -->
|
<!-- Recent Diagnosis -->
|
||||||
<div class="data-section">
|
<div class="data-section">
|
||||||
|
|
@ -443,31 +565,237 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include('components.scroll-top')
|
@include('components.scroll-top')
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
let currentPage = 1;
|
||||||
|
const rowsPerPage = 20;
|
||||||
|
let allRows = [];
|
||||||
|
|
||||||
|
|
||||||
|
// SEARCH + FILTER + SORT
|
||||||
|
function applyFilters() {
|
||||||
|
let search = document.getElementById("searchDiagnosis").value.toLowerCase();
|
||||||
|
let filter = document.getElementById("filterDiagnosis").value;
|
||||||
|
|
||||||
|
let rows = Array.from(document.querySelectorAll("#diagnosisTable tr"));
|
||||||
|
|
||||||
|
let filtered = rows.filter(row => {
|
||||||
|
let text = row.innerText.toLowerCase();
|
||||||
|
let penyakit = row.children[2].innerText.toLowerCase();
|
||||||
|
|
||||||
|
return text.includes(search) &&
|
||||||
|
(filter === "" || penyakit === filter);
|
||||||
|
});
|
||||||
|
|
||||||
|
// SEMUA DIHIDE DULU
|
||||||
|
rows.forEach(row => row.style.display = "none");
|
||||||
|
|
||||||
|
// 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 nextPage() {
|
||||||
|
currentPage++;
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevPage() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
allRows = Array.from(document.querySelectorAll("#tableBody tr"));
|
||||||
|
applyFilters();
|
||||||
|
|
||||||
|
document.getElementById("searchDiagnosis").addEventListener("keyup", () => {
|
||||||
|
currentPage = 1;
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("filterDiagnosis").addEventListener("change", () => {
|
||||||
|
currentPage = 1;
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("sortDiagnosis").addEventListener("change", () => {
|
||||||
|
currentPage = 1;
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const ctx2 = document.getElementById('chartHarian');
|
||||||
|
|
||||||
|
new Chart(ctx2, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($stats['daily_labels']) !!},
|
||||||
|
datasets: [{
|
||||||
|
label: 'Jumlah Diagnosis',
|
||||||
|
data: {!! json_encode($stats['daily_data']) !!},
|
||||||
|
tension: 0.4,
|
||||||
|
fill: false,
|
||||||
|
borderWidth: 3,
|
||||||
|
pointRadius: 5
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let chart = null;
|
||||||
|
|
||||||
function toggleChart() {
|
function toggleChart() {
|
||||||
const chartBox = document.getElementById('chartBox');
|
const chartBox = document.getElementById('chartBox');
|
||||||
|
|
||||||
if (chartBox.style.display === "none") {
|
const isHidden = window.getComputedStyle(chartBox).display === "none";
|
||||||
|
|
||||||
|
if (isHidden) {
|
||||||
chartBox.style.display = "block";
|
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 {
|
} else {
|
||||||
chartBox.style.display = "none";
|
chartBox.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
function toggleUsers() {
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
const userBox = document.getElementById('userBox');
|
||||||
|
const chartBox = document.getElementById('chartBox');
|
||||||
|
|
||||||
<script>
|
chartBox.style.display = "none"; // tutup chart
|
||||||
const ctx = document.getElementById('chartPenyakit');
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
if (userBox.style.display === "none") {
|
||||||
type: 'bar',
|
userBox.style.display = "block";
|
||||||
data: {
|
userBox.scrollIntoView({ behavior: 'smooth' });
|
||||||
labels: {!! json_encode($stats['chart_labels']) !!},
|
} else {
|
||||||
datasets: [{
|
userBox.style.display = "none";
|
||||||
label: 'Jumlah Kasus',
|
|
||||||
data: {!! json_encode($stats['chart_data']) !!}
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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: {
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
color: '#fff',
|
||||||
|
formatter: (value, context) => {
|
||||||
|
let total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||||
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<h2>📊 Statistik Penyakit</h2>
|
||||||
|
|
||||||
|
<canvas id="chartPenyakit"></canvas>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const ctx = document.getElementById('chartPenyakit');
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: {!! json_encode($chartLabels) !!},
|
||||||
|
datasets: [{
|
||||||
|
label: 'Jumlah Kasus',
|
||||||
|
data: {!! json_encode($chartData) !!}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -876,7 +876,7 @@ function updateSelectedCount() {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
const checked = document.querySelectorAll('.gejala-checkbox:checked').length;
|
const checked = document.querySelectorAll('.gejala-checkbox:checked').length;
|
||||||
|
|
||||||
if (checked < 5) {
|
if (checked < 4) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert("Minimal pilih 4 gejala!");
|
alert("Minimal pilih 4 gejala!");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -613,55 +613,25 @@
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<div class="testimonials">
|
<div class="testimonials">
|
||||||
<div class="card testimonial">
|
@foreach($ulasan as $item)
|
||||||
<div class="testimonial-head">
|
<div class="card testimonial">
|
||||||
<div class="avatar" aria-hidden="true">
|
<div class="testimonial-head">
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img">
|
<div class="avatar">
|
||||||
<path d="M4 10c1-3 3-4 8-4s7 1 8 4c0 5-3 7-8 7s-8-2-8-7z" stroke="#fff" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
{{ strtoupper(substr($item->nama, 0, 1)) }}
|
||||||
<circle cx="9" cy="11" r="0.9" fill="#fff"/>
|
</div>
|
||||||
<circle cx="15" cy="11" r="0.9" fill="#fff"/>
|
<div class="meta">
|
||||||
</svg>
|
<div class="rating">
|
||||||
|
{{ str_repeat('★', $item->rating) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="author">
|
||||||
<div class="rating" aria-label="5 dari 5 bintang">★★★★★</div>
|
{{ $item->nama }} — pemilik dari <em>{{ $item->nama_kucing }}</em>
|
||||||
<div class="author">Siti — pemilik dari <em>Kiki</em></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="quote">PawMedic memberi panduan cepat yang membantu saya mengambil tindakan tepat pada kucing saya.</p>
|
|
||||||
</div>
|
|
||||||
<div class="card testimonial">
|
|
||||||
<div class="testimonial-head">
|
|
||||||
<div class="avatar" aria-hidden="true">
|
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img">
|
|
||||||
<path d="M4 10c1-3 3-4 8-4s7 1 8 4c0 5-3 7-8 7s-8-2-8-7z" stroke="#fff" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<circle cx="9" cy="11" r="0.9" fill="#fff"/>
|
|
||||||
<circle cx="15" cy="11" r="0.9" fill="#fff"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="meta">
|
|
||||||
<div class="rating" aria-label="5 dari 5 bintang">★★★★★</div>
|
|
||||||
<div class="author">Budi — pemilik dari <em>Cleo</em></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="quote">Aplikasinya mudah dipahami dan rekomendasinya sangat membantu sebelum pergi ke dokter hewan.</p>
|
|
||||||
</div>
|
|
||||||
<div class="card testimonial">
|
|
||||||
<div class="testimonial-head">
|
|
||||||
<div class="avatar" aria-hidden="true">
|
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img">
|
|
||||||
<path d="M4 10c1-3 3-4 8-4s7 1 8 4c0 5-3 7-8 7s-8-2-8-7z" stroke="#fff" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<circle cx="9" cy="11" r="0.9" fill="#fff"/>
|
|
||||||
<circle cx="15" cy="11" r="0.9" fill="#fff"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="meta">
|
|
||||||
<div class="rating" aria-label="5 dari 5 bintang">★★★★★</div>
|
|
||||||
<div class="author">Lina — pemilik dari <em>Oreo</em></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="quote">Sangat berguna! Saya merasa lebih tenang mengetahui langkah awal yang harus dilakukan.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="quote">{{ $item->komentar }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER -->
|
||||||
|
|
|
||||||
|
|
@ -490,15 +490,15 @@
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="stats-card">
|
<div class="stats-card">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-value" id="totalReviews">12</div>
|
<div class="stat-value">{{ $total }}</div>
|
||||||
<div class="stat-label">Total Ulasan</div>
|
<div class="stat-label">Total Ulasan</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-value" id="avgRating">4.8</div>
|
<div class="stat-value">{{ number_format($avg, 1) }}</div>
|
||||||
<div class="stat-label">Rating Rata-rata</div>
|
<div class="stat-label">Rating Rata-rata</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-value" id="fiveStars">95%</div>
|
<div class="stat-value">{{ $fivePercent }}%</div>
|
||||||
<div class="stat-label">5 Bintang</div>
|
<div class="stat-label">5 Bintang</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -509,29 +509,31 @@
|
||||||
<span>✍️</span>
|
<span>✍️</span>
|
||||||
<span>Tulis Ulasan Anda</span>
|
<span>Tulis Ulasan Anda</span>
|
||||||
</div>
|
</div>
|
||||||
<form id="reviewForm">
|
<form method="POST" action="{{ route('ulasan.store') }}">
|
||||||
|
@csrf
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Nama Anda</label>
|
<label>Nama Anda</label>
|
||||||
<input type="text" id="reviewName" placeholder="Masukkan nama Anda" required>
|
<input type="text" name="nama" placeholder="Masukkan nama Anda" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Nama Kucing</label>
|
<label>Nama Kucing</label>
|
||||||
<input type="text" id="reviewCat" placeholder="Nama kucing Anda (opsional)">
|
<input type="text" name="nama_kucing" placeholder="Nama kucing Anda">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Rating</label>
|
<label>Rating</label>
|
||||||
<div class="rating-input">
|
<div id="rating-stars" style="font-size: 28px; cursor: pointer;">
|
||||||
<span class="star" data-rating="1">★</span>
|
<span data-value="1">☆</span>
|
||||||
<span class="star" data-rating="2">★</span>
|
<span data-value="2">☆</span>
|
||||||
<span class="star" data-rating="3">★</span>
|
<span data-value="3">☆</span>
|
||||||
<span class="star" data-rating="4">★</span>
|
<span data-value="4">☆</span>
|
||||||
<span class="star" data-rating="5">★</span>
|
<span data-value="5">☆</span>
|
||||||
<input type="hidden" id="ratingValue" value="0" required>
|
</div>
|
||||||
</div>
|
|
||||||
|
<input type="hidden" name="rating" id="rating-value">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Ulasan</label>
|
<label>Ulasan</label>
|
||||||
<textarea id="reviewText" placeholder="Bagikan pengalaman Anda menggunakan PawMedic..." required></textarea>
|
<textarea name="komentar" placeholder="Bagikan pengalaman Anda menggunakan PawMedic..." required></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<span>Kirim Ulasan</span>
|
<span>Kirim Ulasan</span>
|
||||||
|
|
@ -551,7 +553,49 @@
|
||||||
<button class="filter-btn" data-filter="3">3 Bintang</button>
|
<button class="filter-btn" data-filter="3">3 Bintang</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="reviews-grid" id="reviewsGrid">
|
<div class="reviews-grid">
|
||||||
|
|
||||||
|
@foreach($ulasan as $review)
|
||||||
|
<div class="review-card" data-rating="{{ $review->rating }}">
|
||||||
|
<div class="review-header">
|
||||||
|
<div class="avatar">{{ substr($review->nama,0,1) }}</div>
|
||||||
|
<div class="review-info">
|
||||||
|
<div class="review-name">{{ $review->nama }}</div>
|
||||||
|
<div class="review-cat">
|
||||||
|
{{ $review->hasil_diagnosis ?? 'Pengguna' }}
|
||||||
|
</div>
|
||||||
|
<div class="review-rating">
|
||||||
|
{{ str_repeat('★', $review->rating) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ✅ KOMENTAR UNTUK SEMUA -->
|
||||||
|
<p class="review-text">{{ $review->komentar }}</p>
|
||||||
|
|
||||||
|
<!-- ✅ HANYA ADMIN -->
|
||||||
|
@auth
|
||||||
|
@if(Auth::user()->email === 'admin@pawmedic.app')
|
||||||
|
<form action="{{ route('ulasan.delete', $review->id) }}"
|
||||||
|
method="POST"
|
||||||
|
onsubmit="return confirm('Yakin hapus ulasan ini?')"
|
||||||
|
style="margin-top:10px;">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit"
|
||||||
|
style="background:#ef4444; color:white; border:none; padding:6px 12px; border-radius:6px;">
|
||||||
|
🗑 Hapus
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
|
|
||||||
|
<div class="review-date">
|
||||||
|
{{ $review->created_at->diffForHumans() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endforeach
|
||||||
<!-- Reviews will be populated by JavaScript -->
|
<!-- Reviews will be populated by JavaScript -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -559,182 +603,63 @@
|
||||||
|
|
||||||
@include('components.toast')
|
@include('components.toast')
|
||||||
@include('components.scroll-top')
|
@include('components.scroll-top')
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Sample reviews data
|
const buttons = document.querySelectorAll('.filter-btn');
|
||||||
const reviews = [
|
const cards = document.querySelectorAll('.review-card');
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'Siti',
|
|
||||||
cat: 'Kiki',
|
|
||||||
rating: 5,
|
|
||||||
text: 'PawMedic memberi panduan cepat yang membantu saya mengambil tindakan tepat pada kucing saya. Sangat membantu!',
|
|
||||||
date: '2 hari yang lalu'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Budi',
|
|
||||||
cat: 'Cleo',
|
|
||||||
rating: 5,
|
|
||||||
text: 'Aplikasinya mudah dipahami dan rekomendasinya sangat membantu sebelum pergi ke dokter hewan.',
|
|
||||||
date: '5 hari yang lalu'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'Lina',
|
|
||||||
cat: 'Oreo',
|
|
||||||
rating: 5,
|
|
||||||
text: 'Sangat berguna! Saya merasa lebih tenang mengetahui langkah awal yang harus dilakukan.',
|
|
||||||
date: '1 minggu yang lalu'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'Ahmad',
|
|
||||||
cat: 'Milo',
|
|
||||||
rating: 5,
|
|
||||||
text: 'Sistem diagnosisnya akurat dan mudah digunakan. Sangat membantu untuk pemilik kucing pemula seperti saya.',
|
|
||||||
date: '2 minggu yang lalu'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: 'Dewi',
|
|
||||||
cat: 'Luna',
|
|
||||||
rating: 4,
|
|
||||||
text: 'Bagus sekali aplikasinya. Interface-nya user-friendly dan informasinya lengkap.',
|
|
||||||
date: '3 minggu yang lalu'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
name: 'Rudi',
|
|
||||||
cat: 'Max',
|
|
||||||
rating: 5,
|
|
||||||
text: 'PawMedic membantu saya memahami kondisi kucing dengan lebih baik. Terima kasih!',
|
|
||||||
date: '1 bulan yang lalu'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentFilter = 'all';
|
buttons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
|
||||||
// Rating stars
|
// hapus active semua
|
||||||
const stars = document.querySelectorAll('.star');
|
buttons.forEach(b => b.classList.remove('active'));
|
||||||
const ratingInput = document.getElementById('ratingValue');
|
btn.classList.add('active');
|
||||||
|
|
||||||
|
const filter = btn.getAttribute('data-filter');
|
||||||
|
|
||||||
|
cards.forEach(card => {
|
||||||
|
const rating = card.getAttribute('data-rating');
|
||||||
|
|
||||||
|
if (filter === 'all' || rating === filter) {
|
||||||
|
card.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
card.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const stars = document.querySelectorAll('#rating-stars span');
|
||||||
|
const ratingInput = document.getElementById('rating-value');
|
||||||
|
|
||||||
stars.forEach((star, index) => {
|
stars.forEach((star, index) => {
|
||||||
|
|
||||||
|
// ✅ CLICK (INI YANG KAMU KURANG)
|
||||||
star.addEventListener('click', () => {
|
star.addEventListener('click', () => {
|
||||||
const rating = index + 1;
|
const value = star.getAttribute('data-value');
|
||||||
ratingInput.value = rating;
|
ratingInput.value = value;
|
||||||
updateStars(rating);
|
|
||||||
|
stars.forEach((s, i) => {
|
||||||
|
s.textContent = i < value ? '★' : '☆';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
star.addEventListener('mouseenter', () => {
|
// hover (punya kamu)
|
||||||
updateStars(index + 1);
|
star.addEventListener('mouseover', () => {
|
||||||
|
stars.forEach((s, i) => {
|
||||||
|
s.textContent = i <= index ? '★' : '☆';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('.rating-input').addEventListener('mouseleave', () => {
|
// keluar hover
|
||||||
const currentRating = parseInt(ratingInput.value) || 0;
|
star.addEventListener('mouseout', () => {
|
||||||
updateStars(currentRating);
|
let value = ratingInput.value;
|
||||||
});
|
stars.forEach((s, i) => {
|
||||||
|
s.textContent = i < value ? '★' : '☆';
|
||||||
function updateStars(rating) {
|
});
|
||||||
stars.forEach((star, index) => {
|
|
||||||
if (index < rating) {
|
|
||||||
star.classList.add('active');
|
|
||||||
} else {
|
|
||||||
star.classList.remove('active');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Display reviews
|
|
||||||
function displayReviews(filter = 'all') {
|
|
||||||
const grid = document.getElementById('reviewsGrid');
|
|
||||||
const filteredReviews = filter === 'all'
|
|
||||||
? reviews
|
|
||||||
: reviews.filter(r => r.rating === parseInt(filter));
|
|
||||||
|
|
||||||
grid.innerHTML = filteredReviews.map(review => `
|
|
||||||
<div class="review-card">
|
|
||||||
<div class="review-header">
|
|
||||||
<div class="avatar">${review.name.charAt(0)}</div>
|
|
||||||
<div class="review-info">
|
|
||||||
<div class="review-name">${review.name}</div>
|
|
||||||
<div class="review-cat">pemilik dari <em>${review.cat}</em></div>
|
|
||||||
<div class="review-rating">${'★'.repeat(review.rating)}${'☆'.repeat(5 - review.rating)}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="review-text">${review.text}</p>
|
|
||||||
<div class="review-date">${review.date}</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter buttons
|
|
||||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
||||||
btn.classList.add('active');
|
|
||||||
currentFilter = btn.dataset.filter;
|
|
||||||
displayReviews(currentFilter);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Form submission
|
|
||||||
document.getElementById('reviewForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const name = document.getElementById('reviewName').value;
|
|
||||||
const cat = document.getElementById('reviewCat').value;
|
|
||||||
const rating = parseInt(ratingInput.value);
|
|
||||||
const text = document.getElementById('reviewText').value;
|
|
||||||
|
|
||||||
if (!rating || rating === 0) {
|
|
||||||
alert('Mohon berikan rating!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new review
|
|
||||||
const newReview = {
|
|
||||||
id: reviews.length + 1,
|
|
||||||
name: name,
|
|
||||||
cat: cat || 'Kucing',
|
|
||||||
rating: rating,
|
|
||||||
text: text,
|
|
||||||
date: 'Baru saja'
|
|
||||||
};
|
|
||||||
|
|
||||||
reviews.unshift(newReview);
|
|
||||||
displayReviews(currentFilter);
|
|
||||||
updateStats();
|
|
||||||
|
|
||||||
// Reset form
|
|
||||||
this.reset();
|
|
||||||
ratingInput.value = 0;
|
|
||||||
updateStars(0);
|
|
||||||
|
|
||||||
// Show toast
|
|
||||||
if (window.showToast) {
|
|
||||||
showToast('Terima kasih atas ulasan Anda!', 'success', 'Ulasan Terkirim');
|
|
||||||
} else {
|
|
||||||
alert('Terima kasih atas ulasan Anda! 🐾');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update stats
|
|
||||||
function updateStats() {
|
|
||||||
const total = reviews.length;
|
|
||||||
const avg = (reviews.reduce((sum, r) => sum + r.rating, 0) / total).toFixed(1);
|
|
||||||
const fiveStars = Math.round((reviews.filter(r => r.rating === 5).length / total) * 100);
|
|
||||||
|
|
||||||
document.getElementById('totalReviews').textContent = total;
|
|
||||||
document.getElementById('avgRating').textContent = avg;
|
|
||||||
document.getElementById('fiveStars').textContent = fiveStars + '%';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
displayReviews();
|
|
||||||
updateStats();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,24 @@
|
||||||
use App\Http\Controllers\DiagnosisController;
|
use App\Http\Controllers\DiagnosisController;
|
||||||
use App\Http\Controllers\AdminController;
|
use App\Http\Controllers\AdminController;
|
||||||
use App\Http\Controllers\GejalaController;
|
use App\Http\Controllers\GejalaController;
|
||||||
|
use App\Http\Controllers\UlasanController;
|
||||||
|
use App\Models\Ulasan;
|
||||||
|
|
||||||
|
Route::get('/admin/sort-diagnosis', [AdminController::class, 'sortDiagnosis']);
|
||||||
|
|
||||||
|
Route::delete('/ulasan/{id}', [UlasanController::class, 'destroy'])->name('ulasan.delete');
|
||||||
|
|
||||||
|
Route::get('/ulasan', [UlasanController::class, 'index'])->name('ulasan');
|
||||||
|
Route::post('/ulasan', [UlasanController::class, 'store'])->name('ulasan.store');
|
||||||
|
|
||||||
Route::get('/gejala', [GejalaController::class, 'index'])->name('gejala');
|
Route::get('/gejala', [GejalaController::class, 'index'])->name('gejala');
|
||||||
|
|
||||||
|
Route::delete('/admin/ulasan/{id}', [UlasanController::class, 'destroy'])
|
||||||
|
->name('ulasan.delete');
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return view('landing');
|
$ulasan = Ulasan::latest()->take(3)->get();
|
||||||
|
return view('landing', compact('ulasan'));
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/biodata', function () {
|
Route::get('/biodata', function () {
|
||||||
|
|
@ -23,10 +36,6 @@
|
||||||
|
|
||||||
Route::get('/hasil-diagnosis', [DiagnosisController::class, 'hasil'])->name('hasil-diagnosis');
|
Route::get('/hasil-diagnosis', [DiagnosisController::class, 'hasil'])->name('hasil-diagnosis');
|
||||||
|
|
||||||
Route::get('/ulasan', function () {
|
|
||||||
return view('ulasan');
|
|
||||||
})->name('ulasan');
|
|
||||||
|
|
||||||
Route::get('/faq', function () {
|
Route::get('/faq', function () {
|
||||||
return view('faq');
|
return view('faq');
|
||||||
})->name('faq');
|
})->name('faq');
|
||||||
|
|
@ -36,5 +45,5 @@
|
||||||
Route::post('/admin/login', [AdminController::class, 'authenticate'])->name('admin.authenticate');
|
Route::post('/admin/login', [AdminController::class, 'authenticate'])->name('admin.authenticate');
|
||||||
Route::post('/admin/logout', [AdminController::class, 'logout'])->name('admin.logout');
|
Route::post('/admin/logout', [AdminController::class, 'logout'])->name('admin.logout');
|
||||||
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])->name('admin.dashboard')->middleware('auth');
|
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])->name('admin.dashboard')->middleware('auth');
|
||||||
|
Route::post('/biodata/simpan', [DiagnosisController::class, 'simpanBiodata'])->name('biodata.simpan');
|
||||||
Route::post('/biodata/simpan', [DiagnosisController::class, 'simpanBiodata'])->name('biodata.simpan');
|
Route::get('/admin/statistik', [AdminController::class, 'statistik'])->name('admin.statistik');
|
||||||
Loading…
Reference in New Issue