Update Dashboard penambahan fitur

This commit is contained in:
WahyuTegarP 2026-04-12 00:49:48 +07:00
parent ccdd44927b
commit 3564fc3fb8
10 changed files with 674 additions and 259 deletions

View File

@ -7,6 +7,9 @@
use Illuminate\Support\Facades\Hash;
use App\Models\Biodata;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use App\Models\Ulasan;
use Illuminate\Support\Facades\DB;
class AdminController extends Controller
{
@ -39,15 +42,29 @@ public function authenticate(Request $request)
public function dashboard()
{
// total diagnosis
$totalDiagnosis = Biodata::count();
// hari ini
$todayDiagnosis = Biodata::whereDate('created_at', Carbon::today())->count();
// kemarin
$yesterday = Biodata::whereDate('created_at', Carbon::yesterday())->count();
$diff = $todayDiagnosis - $yesterday;
// total user
$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
$mostCommon = Biodata::select('hasil_diagnosis')
->whereNotNull('hasil_diagnosis')
@ -55,13 +72,20 @@ public function dashboard()
->orderByRaw('COUNT(*) DESC')
->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
$recent = Biodata::select('hasil_diagnosis', 'created_at')
->latest()
->take(5)
->get();
// format tabel
$recentFormatted = $recent->map(function ($item) {
return [
'date' => $item->created_at,
@ -70,7 +94,7 @@ public function dashboard()
];
});
// 🔥 CHART (HARUS DI LUAR MAP)
// chart penyakit
$diseaseStats = Biodata::select('hasil_diagnosis')
->whereNotNull('hasil_diagnosis')
->get()
@ -82,6 +106,19 @@ public function dashboard()
$chartLabels = $diseaseStats->keys()->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
$stats = [
'total_diagnosis' => $totalDiagnosis,
@ -90,10 +127,22 @@ public function dashboard()
'most_common_disease' => $mostCommon,
'recent_diagnosis' => $recentFormatted,
'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)
@ -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);
}
}

View File

@ -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');
}
}

17
app/Models/Ulasan.php Normal file
View File

@ -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'
];
}

View File

@ -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');
}
};

View File

@ -145,6 +145,7 @@
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card{
@ -306,6 +307,9 @@
padding:12px 8px;
}
}
#chartBox {
transition: all 0.3s ease;
}
</style>
</head>
@ -341,13 +345,6 @@
<!-- Statistics -->
<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-header">
@ -358,6 +355,10 @@
<div class="stat-icon">📊</div>
</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 class="stat-card">
@ -369,6 +370,19 @@
<div class="stat-icon">📈</div>
</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 class="stat-card">
@ -380,9 +394,13 @@
<div class="stat-icon">👥</div>
</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 class="stat-card" onclick="toggleChart()" style="cursor:pointer;">
<div class="stat-card">
<div class="stat-header">
<div>
<div class="stat-value" style="font-size:24px;">{{ $stats['most_common_disease'] }}</div>
@ -391,6 +409,110 @@
<div class="stat-icon">🩺</div>
</div>
<div class="stat-change">Paling sering didiagnosis</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 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>
@ -443,23 +565,120 @@
</div>
@include('components.scroll-top')
<script>
function toggleChart() {
const chartBox = document.getElementById('chartBox');
if (chartBox.style.display === "none") {
chartBox.style.display = "block";
} else {
chartBox.style.display = "none";
<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() {
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');
new Chart(ctx, {
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: {!! json_encode($stats['chart_labels']) !!},
@ -467,7 +686,116 @@ function toggleChart() {
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: {
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>
</body>

View File

@ -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>

View File

@ -876,7 +876,7 @@ function updateSelectedCount() {
form.addEventListener('submit', function(e) {
const checked = document.querySelectorAll('.gejala-checkbox:checked').length;
if (checked < 5) {
if (checked < 4) {
e.preventDefault();
alert("Minimal pilih 4 gejala!");
return;

View File

@ -613,54 +613,24 @@
</a>
</p>
<div class="testimonials">
@foreach($ulasan as $item)
<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 class="avatar">
{{ strtoupper(substr($item->nama, 0, 1)) }}
</div>
<div class="meta">
<div class="rating" aria-label="5 dari 5 bintang">★★★★★</div>
<div class="author">Siti pemilik dari <em>Kiki</em></div>
<div class="rating">
{{ str_repeat('★', $item->rating) }}
</div>
<div class="author">
{{ $item->nama }} pemilik dari <em>{{ $item->nama_kucing }}</em>
</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>
<p class="quote">{{ $item->komentar }}</p>
</div>
@endforeach
</div>
</section>

View File

@ -490,15 +490,15 @@
<!-- Stats -->
<div class="stats-card">
<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>
<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>
<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>
</div>
@ -509,29 +509,31 @@
<span>✍️</span>
<span>Tulis Ulasan Anda</span>
</div>
<form id="reviewForm">
<form method="POST" action="{{ route('ulasan.store') }}">
@csrf
<div class="form-group">
<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 class="form-group">
<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 class="form-group">
<label>Rating</label>
<div class="rating-input">
<span class="star" data-rating="1"></span>
<span class="star" data-rating="2"></span>
<span class="star" data-rating="3"></span>
<span class="star" data-rating="4"></span>
<span class="star" data-rating="5"></span>
<input type="hidden" id="ratingValue" value="0" required>
<div id="rating-stars" style="font-size: 28px; cursor: pointer;">
<span data-value="1"></span>
<span data-value="2"></span>
<span data-value="3"></span>
<span data-value="4"></span>
<span data-value="5"></span>
</div>
<input type="hidden" name="rating" id="rating-value">
</div>
<div class="form-group">
<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>
<button type="submit" class="btn btn-primary">
<span>Kirim Ulasan</span>
@ -551,7 +553,49 @@
<button class="filter-btn" data-filter="3">3 Bintang</button>
</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 -->
</div>
</div>
@ -559,182 +603,63 @@
@include('components.toast')
@include('components.scroll-top')
<script>
// Sample reviews data
const reviews = [
{
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'
}
];
const buttons = document.querySelectorAll('.filter-btn');
const cards = document.querySelectorAll('.review-card');
let currentFilter = 'all';
// Rating stars
const stars = document.querySelectorAll('.star');
const ratingInput = document.getElementById('ratingValue');
stars.forEach((star, index) => {
star.addEventListener('click', () => {
const rating = index + 1;
ratingInput.value = rating;
updateStars(rating);
});
star.addEventListener('mouseenter', () => {
updateStars(index + 1);
});
});
document.querySelector('.rating-input').addEventListener('mouseleave', () => {
const currentRating = parseInt(ratingInput.value) || 0;
updateStars(currentRating);
});
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 => {
buttons.forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
// hapus active semua
buttons.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 filter = btn.getAttribute('data-filter');
const name = document.getElementById('reviewName').value;
const cat = document.getElementById('reviewCat').value;
const rating = parseInt(ratingInput.value);
const text = document.getElementById('reviewText').value;
cards.forEach(card => {
const rating = card.getAttribute('data-rating');
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');
if (filter === 'all' || rating === filter) {
card.style.display = 'block';
} else {
alert('Terima kasih atas ulasan Anda! 🐾');
card.style.display = 'none';
}
});
// 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>
const stars = document.querySelectorAll('#rating-stars span');
const ratingInput = document.getElementById('rating-value');
stars.forEach((star, index) => {
// ✅ CLICK (INI YANG KAMU KURANG)
star.addEventListener('click', () => {
const value = star.getAttribute('data-value');
ratingInput.value = value;
stars.forEach((s, i) => {
s.textContent = i < value ? '★' : '☆';
});
});
// hover (punya kamu)
star.addEventListener('mouseover', () => {
stars.forEach((s, i) => {
s.textContent = i <= index ? '★' : '☆';
});
});
// keluar hover
star.addEventListener('mouseout', () => {
let value = ratingInput.value;
stars.forEach((s, i) => {
s.textContent = i < value ? '★' : '☆';
});
});
});
</script>
</body>
</html>

View File

@ -4,11 +4,24 @@
use App\Http\Controllers\DiagnosisController;
use App\Http\Controllers\AdminController;
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::delete('/admin/ulasan/{id}', [UlasanController::class, 'destroy'])
->name('ulasan.delete');
Route::get('/', function () {
return view('landing');
$ulasan = Ulasan::latest()->take(3)->get();
return view('landing', compact('ulasan'));
});
Route::get('/biodata', function () {
@ -23,10 +36,6 @@
Route::get('/hasil-diagnosis', [DiagnosisController::class, 'hasil'])->name('hasil-diagnosis');
Route::get('/ulasan', function () {
return view('ulasan');
})->name('ulasan');
Route::get('/faq', function () {
return view('faq');
})->name('faq');
@ -36,5 +45,5 @@
Route::post('/admin/login', [AdminController::class, 'authenticate'])->name('admin.authenticate');
Route::post('/admin/logout', [AdminController::class, 'logout'])->name('admin.logout');
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])->name('admin.dashboard')->middleware('auth');
Route::post('/biodata/simpan', [DiagnosisController::class, 'simpanBiodata'])->name('biodata.simpan');
Route::get('/admin/statistik', [AdminController::class, 'statistik'])->name('admin.statistik');