responsive mobile
This commit is contained in:
parent
cc5936051e
commit
cd3a6052d5
|
|
@ -100,21 +100,30 @@
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
/* =========================
|
// Tunggu sampai Chart.js library loaded
|
||||||
TREND LINE
|
function initCharts() {
|
||||||
========================= */
|
if (typeof Chart === 'undefined') {
|
||||||
const trendLabels = @json(
|
console.warn('Chart.js belum loaded, retry dalam 100ms');
|
||||||
|
setTimeout(initCharts, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
TREND LINE
|
||||||
|
========================= */
|
||||||
|
const trendLabels = @json(
|
||||||
$trend->pluck('date')->map(fn($d) =>
|
$trend->pluck('date')->map(fn($d) =>
|
||||||
\Carbon\Carbon::parse($d)->translatedFormat('D')
|
\Carbon\Carbon::parse($d)->translatedFormat('D')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const trendData = @json($trend->pluck('total'));
|
const trendData = @json($trend->pluck('total'));
|
||||||
|
|
||||||
// Optimize for mobile
|
// Optimize for mobile
|
||||||
const isMobile = window.innerWidth < 768;
|
const isMobile = window.innerWidth < 768;
|
||||||
|
|
||||||
new Chart(document.getElementById('trendChart'), {
|
try {
|
||||||
|
new Chart(document.getElementById('trendChart'), {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: trendLabels,
|
labels: trendLabels,
|
||||||
|
|
@ -152,21 +161,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing trend chart:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
/* =========================
|
PIE CHART
|
||||||
PIE CHART
|
========================= */
|
||||||
========================= */
|
const distLabels = @json(
|
||||||
const distLabels = @json(
|
|
||||||
$distribution->pluck('category')->map(
|
$distribution->pluck('category')->map(
|
||||||
fn($c) => ucfirst(str_replace('_',' ',$c))
|
fn($c) => ucfirst(str_replace('_',' ',$c))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const distData = @json($distribution->pluck('total'));
|
const distData = @json($distribution->pluck('total'));
|
||||||
|
|
||||||
const dynamicColors = distLabels.map(label => {
|
const dynamicColors = distLabels.map(label => {
|
||||||
if (label.toLowerCase() === 'matang') {
|
if (label.toLowerCase() === 'matang') {
|
||||||
return '#ef4444'; // merah
|
return '#ef4444'; // merah
|
||||||
}
|
}
|
||||||
|
|
@ -176,9 +187,10 @@
|
||||||
else {
|
else {
|
||||||
return '#f59e0b'; // kuning/orange
|
return '#f59e0b'; // kuning/orange
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
new Chart(document.getElementById('distributionChart'), {
|
try {
|
||||||
|
new Chart(document.getElementById('distributionChart'), {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
data: {
|
data: {
|
||||||
labels: distLabels,
|
labels: distLabels,
|
||||||
|
|
@ -207,7 +219,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing distribution chart:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts saat DOM ready
|
||||||
|
document.addEventListener('DOMContentLoaded', initCharts);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -17,9 +17,6 @@
|
||||||
<!-- DNS Prefetch untuk domain eksternal -->
|
<!-- DNS Prefetch untuk domain eksternal -->
|
||||||
<link rel="dns-prefetch" href="https://cdn.tailwindcss.com">
|
<link rel="dns-prefetch" href="https://cdn.tailwindcss.com">
|
||||||
|
|
||||||
<!-- Tailwind CSS dengan preload -->
|
|
||||||
<script src="https://cdn.tailwindcss.com" defer></script>
|
|
||||||
|
|
||||||
<!-- Google Fonts - Inter dengan font-display: swap (non-blocking) -->
|
<!-- Google Fonts - Inter dengan font-display: swap (non-blocking) -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
|
||||||
<noscript><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"></noscript>
|
<noscript><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"></noscript>
|
||||||
|
|
@ -30,11 +27,18 @@
|
||||||
<!-- CSRF Token -->
|
<!-- CSRF Token -->
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
<!-- Chart.js (defer loading untuk non-blocking) -->
|
<!-- Chart.js (tanpa defer agar library siap sebelum scripts lain) -->
|
||||||
@if(request()->routeIs('admin.dashboard') || request()->routeIs('admin.system-statistics'))
|
@if(request()->routeIs('admin.dashboard') || request()->routeIs('admin.system-statistics'))
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Anti-flash dark mode script -->
|
||||||
|
<script>
|
||||||
|
if (localStorage.getItem('theme') === 'dark') {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Performance optimization: Use system fonts for faster render */
|
/* Performance optimization: Use system fonts for faster render */
|
||||||
body {
|
body {
|
||||||
|
|
@ -300,27 +304,88 @@
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== SIDEBAR RESPONSIVE ===== */
|
||||||
|
#sidebar-wrapper {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 260px;
|
||||||
|
height: 100% !important;
|
||||||
|
z-index: 50;
|
||||||
|
transform: translateX(-100%) !important;
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
will-change: transform;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
#sidebar-wrapper.is-open {
|
||||||
|
transform: translateX(0) !important;
|
||||||
|
box-shadow: 4px 0 24px rgba(0,0,0,0.18);
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#sidebar-wrapper {
|
||||||
|
position: relative !important;
|
||||||
|
width: 256px !important;
|
||||||
|
height: auto !important;
|
||||||
|
transform: translateX(0) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
z-index: auto !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#sidebar-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 45;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
#sidebar-overlay.is-active {
|
||||||
|
display: block;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#sidebar-overlay { display: none !important; }
|
||||||
|
}
|
||||||
|
.main-content-area {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50">
|
<body class="bg-gray-50">
|
||||||
<div class="flex h-screen overflow-hidden">
|
<div class="flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar - Fixed on Desktop, Toggleable on Mobile -->
|
<!-- Sidebar - Fixed on Desktop, Toggleable on Mobile -->
|
||||||
<div id="sidebar-wrapper" class="fixed md:relative md:w-64 w-64 h-screen bg-white border-r border-gray-200 transform -translate-x-full md:translate-x-0 transition-transform duration-300 z-30">
|
<div id="sidebar-wrapper" class="bg-white border-r border-gray-200">
|
||||||
@include('Admin.partials.sidebar')
|
@include('Admin.partials.sidebar')
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Overlay (Hidden by default) -->
|
<!-- Mobile Overlay (Hidden by default) -->
|
||||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 hidden z-20"></div>
|
<div id="sidebar-overlay"></div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="flex-1 overflow-auto">
|
<div class="main-content-area">
|
||||||
<!-- Top Navigation -->
|
<!-- Top Navigation -->
|
||||||
<header class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
|
<header class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
|
||||||
<div class="flex items-center justify-between px-4 md:px-6 py-3 md:py-4">
|
<div class="flex items-center justify-between px-4 md:px-6 py-3 md:py-4">
|
||||||
<div class="flex items-center flex-1">
|
<div class="flex items-center flex-1">
|
||||||
<!-- Mobile Menu Toggle -->
|
<!-- Mobile Menu Toggle -->
|
||||||
<button id="menuToggle" class="md:hidden p-2 hover:bg-gray-100 rounded-lg transition-colors mr-2">
|
<button id="menuToggle"
|
||||||
<i class="fas fa-bars text-xl text-gray-600"></i>
|
class="md:hidden flex items-center justify-center"
|
||||||
|
style="min-width:44px; min-height:44px; background:transparent; border:none; cursor:pointer; margin-right:0.5rem;"
|
||||||
|
aria-label="Buka menu"
|
||||||
|
aria-expanded="false">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<rect y="4" width="24" height="2.5" rx="1.25" fill="#374151"/>
|
||||||
|
<rect y="11" width="24" height="2.5" rx="1.25" fill="#374151"/>
|
||||||
|
<rect y="18" width="24" height="2.5" rx="1.25" fill="#374151"/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- Page Title (Mobile) -->
|
<!-- Page Title (Mobile) -->
|
||||||
<h1 class="text-base md:text-lg font-semibold text-gray-900">@yield('page-title')</h1>
|
<h1 class="text-base md:text-lg font-semibold text-gray-900">@yield('page-title')</h1>
|
||||||
|
|
@ -407,61 +472,51 @@
|
||||||
const sidebarWrapper = document.getElementById('sidebar-wrapper');
|
const sidebarWrapper = document.getElementById('sidebar-wrapper');
|
||||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||||
|
|
||||||
// Toggle sidebar saat hamburger diklik
|
function openSidebar() {
|
||||||
|
sidebarWrapper.classList.add('is-open');
|
||||||
|
sidebarOverlay.classList.add('is-active');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
if (menuToggle) menuToggle.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSidebar() {
|
||||||
|
sidebarWrapper.classList.remove('is-open');
|
||||||
|
sidebarOverlay.classList.remove('is-active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
if (menuToggle) menuToggle.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
if (menuToggle) {
|
if (menuToggle) {
|
||||||
menuToggle.addEventListener('click', function(e) {
|
menuToggle.addEventListener('click', function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const isHidden = sidebarWrapper.classList.contains('-translate-x-full');
|
sidebarWrapper.classList.contains('is-open') ? closeSidebar() : openSidebar();
|
||||||
|
|
||||||
if (isHidden) {
|
|
||||||
// Buka sidebar
|
|
||||||
sidebarWrapper.classList.remove('-translate-x-full');
|
|
||||||
sidebarOverlay.classList.remove('hidden');
|
|
||||||
} else {
|
|
||||||
// Tutup sidebar
|
|
||||||
sidebarWrapper.classList.add('-translate-x-full');
|
|
||||||
sidebarOverlay.classList.add('hidden');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tutup sidebar saat overlay diklik
|
|
||||||
if (sidebarOverlay) {
|
if (sidebarOverlay) {
|
||||||
sidebarOverlay.addEventListener('click', function() {
|
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||||
sidebarWrapper.classList.add('-translate-x-full');
|
|
||||||
sidebarOverlay.classList.add('hidden');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tutup sidebar saat menu link diklik (mobile only)
|
document.querySelectorAll('#sidebar-wrapper .sidebar-link, #sidebar-wrapper a').forEach(function(link) {
|
||||||
const navLinks = document.querySelectorAll('.sidebar-link');
|
|
||||||
navLinks.forEach(link => {
|
|
||||||
link.addEventListener('click', function() {
|
link.addEventListener('click', function() {
|
||||||
// Hanya tutup di mobile (< 768px)
|
if (window.innerWidth < 768) closeSidebar();
|
||||||
if (window.innerWidth < 768) {
|
|
||||||
sidebarWrapper.classList.add('-translate-x-full');
|
|
||||||
sidebarOverlay.classList.add('hidden');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle resize untuk responsive behavior
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape') closeSidebar();
|
||||||
|
});
|
||||||
|
|
||||||
let resizeTimer;
|
let resizeTimer;
|
||||||
window.addEventListener('resize', function() {
|
window.addEventListener('resize', function() {
|
||||||
clearTimeout(resizeTimer);
|
clearTimeout(resizeTimer);
|
||||||
resizeTimer = setTimeout(function() {
|
resizeTimer = setTimeout(function() {
|
||||||
// Jika window > md (768px), reset sidebar position
|
|
||||||
if (window.innerWidth >= 768) {
|
if (window.innerWidth >= 768) {
|
||||||
sidebarWrapper.classList.remove('-translate-x-full');
|
sidebarWrapper.classList.remove('is-open');
|
||||||
sidebarOverlay.classList.add('hidden');
|
sidebarOverlay.classList.remove('is-active');
|
||||||
} else {
|
document.body.style.overflow = '';
|
||||||
// Jika window < md (768px), hide sidebar dan overlay
|
|
||||||
if (!sidebarWrapper.classList.contains('-translate-x-full')) {
|
|
||||||
sidebarWrapper.classList.add('-translate-x-full');
|
|
||||||
}
|
}
|
||||||
sidebarOverlay.classList.add('hidden');
|
}, 200);
|
||||||
}
|
|
||||||
}, 250);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,25 +32,25 @@
|
||||||
<!-- Navigation Menu -->
|
<!-- Navigation Menu -->
|
||||||
<nav class="flex-1 p-2 md:p-4 space-y-1 md:space-y-2 overflow-y-auto">
|
<nav class="flex-1 p-2 md:p-4 space-y-1 md:space-y-2 overflow-y-auto">
|
||||||
<a href="{{ route('admin.dashboard') }}"
|
<a href="{{ route('admin.dashboard') }}"
|
||||||
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');">
|
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||||
<i class="fas fa-home w-5 flex-shrink-0"></i>
|
<i class="fas fa-home w-5 flex-shrink-0"></i>
|
||||||
<span class="truncate">Dashboard</span>
|
<span class="truncate">Dashboard</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('admin.manage-admin') }}"
|
<a href="{{ route('admin.manage-admin') }}"
|
||||||
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.manage-admin') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');">
|
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.manage-admin') ? 'active' : '' }}">
|
||||||
<i class="fas fa-users w-5 flex-shrink-0"></i>
|
<i class="fas fa-users w-5 flex-shrink-0"></i>
|
||||||
<span class="truncate">Kelola Akun Admin</span>
|
<span class="truncate">Kelola Akun Admin</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('admin.classification-history') }}"
|
<a href="{{ route('admin.classification-history') }}"
|
||||||
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.classification-history') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');">
|
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.classification-history') ? 'active' : '' }}">
|
||||||
<i class="fas fa-history w-5 flex-shrink-0"></i>
|
<i class="fas fa-history w-5 flex-shrink-0"></i>
|
||||||
<span class="truncate">Riwayat Klasifikasi</span>
|
<span class="truncate">Riwayat Klasifikasi</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('admin.system-statistics') }}"
|
<a href="{{ route('admin.system-statistics') }}"
|
||||||
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.system-statistics') ? 'active' : '' }}" onclick="document.getElementById('sidebar-wrapper').classList.add('-translate-x-full'); document.getElementById('sidebar-overlay').classList.add('hidden');">
|
class="sidebar-link flex items-center space-x-3 px-3 md:px-4 py-2 md:py-3 rounded-lg text-sm md:text-base text-gray-700 hover:bg-gray-50 transition-all {{ request()->routeIs('admin.system-statistics') ? 'active' : '' }}">
|
||||||
<i class="fas fa-chart-bar w-5 flex-shrink-0"></i>
|
<i class="fas fa-chart-bar w-5 flex-shrink-0"></i>
|
||||||
<span class="truncate">Statistik Sistem</span>
|
<span class="truncate">Statistik Sistem</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -217,13 +217,22 @@
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
// Detect mobile
|
// Tunggu sampai Chart.js library loaded
|
||||||
const isMobileStats = window.innerWidth < 768;
|
function initStatisticsCharts() {
|
||||||
|
if (typeof Chart === 'undefined') {
|
||||||
|
console.warn('Chart.js belum loaded, retry dalam 100ms');
|
||||||
|
setTimeout(initStatisticsCharts, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==================================================
|
// Detect mobile
|
||||||
BAR CHART
|
const isMobileStats = window.innerWidth < 768;
|
||||||
================================================== */
|
|
||||||
new Chart(document.getElementById('barChart'), {
|
/* ==================================================
|
||||||
|
BAR CHART
|
||||||
|
================================================== */
|
||||||
|
try {
|
||||||
|
new Chart(document.getElementById('barChart'), {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: @json($hariLabels),
|
labels: @json($hariLabels),
|
||||||
|
|
@ -258,13 +267,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing bar chart:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================================================
|
||||||
/* ==================================================
|
LINE CHART
|
||||||
LINE CHART
|
================================================== */
|
||||||
================================================== */
|
try {
|
||||||
new Chart(document.getElementById('lineChart'), {
|
new Chart(document.getElementById('lineChart'), {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: @json($bulanLabels),
|
labels: @json($bulanLabels),
|
||||||
|
|
@ -306,13 +318,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing line chart:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================================================
|
||||||
/* ==================================================
|
PIE CHART
|
||||||
PIE CHART
|
================================================== */
|
||||||
================================================== */
|
try {
|
||||||
new Chart(document.getElementById('pieChart'), {
|
new Chart(document.getElementById('pieChart'), {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
data: {
|
data: {
|
||||||
labels: @json(array_column($distribusiData, 'label')),
|
labels: @json(array_column($distribusiData, 'label')),
|
||||||
|
|
@ -345,7 +360,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing pie chart:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts saat DOM ready
|
||||||
|
document.addEventListener('DOMContentLoaded', initStatisticsCharts);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -137,6 +137,13 @@ class="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-300 rounded"
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Lupa Kata Sandi (Disabled) -->
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-400 cursor-not-allowed">
|
||||||
|
Lupa kata sandi?
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Login Button -->
|
<!-- Login Button -->
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
|
@ -154,16 +161,6 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Additional Links -->
|
|
||||||
<div class="mt-6 text-center">
|
|
||||||
<p class="text-sm text-gray-600">
|
|
||||||
Belum punya akun admin?
|
|
||||||
<a href="{{ route('admin.manage-admin') }}" class="text-red-600 hover:text-red-700 font-medium transition-colors">
|
|
||||||
Hubungi administrator
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Security Notice -->
|
<!-- Security Notice -->
|
||||||
|
|
@ -176,7 +173,7 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="fixed bottom-4 left-4 text-xs text-gray-300 opacity-60">
|
<footer class="fixed bottom-4 left-4 text-xs text-gray-300 opacity-60">
|
||||||
Made with ♥ by MaturityScan
|
Made with ♥ by TomatScan
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -209,16 +206,16 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
|
||||||
btnLoading.classList.remove('hidden');
|
btnLoading.classList.remove('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-focus username field on load
|
// Auto-focus email field on load
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
document.getElementById('username').focus();
|
document.getElementById('email').focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add keyboard navigation
|
// Add keyboard navigation
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const activeElement = document.activeElement;
|
const activeElement = document.activeElement;
|
||||||
if (activeElement.id === 'username') {
|
if (activeElement.id === 'email') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById('password').focus();
|
document.getElementById('password').focus();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,47 @@
|
||||||
|
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Cegah layout shift saat gambar loading */
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder abu-abu saat gambar belum load */
|
||||||
|
img:not([src]), img[src=""] {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animasi skeleton untuk gambar loading */
|
||||||
|
@keyframes skeleton-pulse {
|
||||||
|
0% { background-color: #e5e7eb; }
|
||||||
|
100% { background-color: #f9fafb; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth scroll */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce motion untuk device hemat baterai */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPU acceleration untuk animasi hover */
|
||||||
|
.hover\:scale-105,
|
||||||
|
.hover\:-translate-y-2,
|
||||||
|
.hover\:rotate-0 {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50">
|
<body class="bg-gray-50">
|
||||||
<!-- Navbar -->
|
<!-- Navbar -->
|
||||||
|
|
@ -50,14 +91,38 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:hidden">
|
<div class="md:hidden">
|
||||||
<button class="text-gray-500 hover:text-gray-700 focus:outline-none">
|
<button id="mobileMenuBtn"
|
||||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
class="text-gray-500 hover:text-gray-700 focus:outline-none p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
|
aria-label="Buka menu"
|
||||||
|
aria-expanded="false">
|
||||||
|
<svg id="iconHamburger" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
</svg>
|
</svg>
|
||||||
|
<svg id="iconClose" class="h-6 w-6 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Mobile Menu Dropdown -->
|
||||||
|
<div id="mobileMenu"
|
||||||
|
class="hidden md:hidden border-t border-gray-100 bg-white">
|
||||||
|
<div class="px-4 py-3 space-y-1">
|
||||||
|
<a href="#"
|
||||||
|
class="block px-3 py-2 rounded-lg text-gray-900 hover:bg-red-50 hover:text-red-600 font-medium transition-colors">
|
||||||
|
Beranda
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('about') }}"
|
||||||
|
class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-red-50 hover:text-red-600 font-medium transition-colors">
|
||||||
|
Tentang Kami
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('login') }}"
|
||||||
|
class="block w-full text-center bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-4 rounded-lg transition-colors mt-2">
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
|
|
@ -84,6 +149,8 @@
|
||||||
<div class="bg-white rounded-2xl shadow-2xl p-8 transform rotate-3 hover:rotate-0 transition-transform duration-300">
|
<div class="bg-white rounded-2xl shadow-2xl p-8 transform rotate-3 hover:rotate-0 transition-transform duration-300">
|
||||||
<img src="{{ asset('assets/images/tomatt.png') }}"
|
<img src="{{ asset('assets/images/tomatt.png') }}"
|
||||||
alt="Fresh Tomatoes"
|
alt="Fresh Tomatoes"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
class="rounded-lg shadow-md w-full h-auto">
|
class="rounded-lg shadow-md w-full h-auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -102,7 +169,11 @@ class="rounded-lg shadow-md w-full h-auto">
|
||||||
<!-- Mentah Card -->
|
<!-- Mentah Card -->
|
||||||
<div class="bg-green-50 border-2 border-green-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
<div class="bg-green-50 border-2 border-green-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
<div class="w-32 h-32 bg-green-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
|
<div class="w-32 h-32 bg-green-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
|
||||||
<img src="{{ asset('assets/images/mentah.jpg') }}" alt="Tomat Mentah" class="w-full h-full object-contain rounded-full">
|
<img src="{{ asset('assets/images/mentah.jpg') }}"
|
||||||
|
alt="Tomat Mentah"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
class="w-full h-full object-contain rounded-full">
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-2xl font-bold text-green-800 mb-4">Mentah</h3>
|
<h3 class="text-2xl font-bold text-green-800 mb-4">Mentah</h3>
|
||||||
<p class="text-gray-600 mb-4">Tomat yang belum matang sempurna, berwarna hijau dan tekstur masih keras.</p>
|
<p class="text-gray-600 mb-4">Tomat yang belum matang sempurna, berwarna hijau dan tekstur masih keras.</p>
|
||||||
|
|
@ -114,7 +185,11 @@ class="rounded-lg shadow-md w-full h-auto">
|
||||||
<!-- Setengah Matang Card -->
|
<!-- Setengah Matang Card -->
|
||||||
<div class="bg-yellow-50 border-2 border-yellow-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
<div class="bg-yellow-50 border-2 border-yellow-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
<div class="w-32 h-32 bg-yellow-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
|
<div class="w-32 h-32 bg-yellow-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
|
||||||
<img src="{{ asset('assets/images/setengahmateng.jpg') }}" alt="Tomat Setengah Matang" class="w-full h-full object-contain rounded-full">
|
<img src="{{ asset('assets/images/setengahmateng.jpg') }}"
|
||||||
|
alt="Tomat Setengah Matang"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
class="w-full h-full object-contain rounded-full">
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-2xl font-bold text-yellow-800 mb-4">Setengah Matang</h3>
|
<h3 class="text-2xl font-bold text-yellow-800 mb-4">Setengah Matang</h3>
|
||||||
<p class="text-gray-600 mb-4">Tomat dalam proses pematangan, perpaduan warna hijau dan merah.</p>
|
<p class="text-gray-600 mb-4">Tomat dalam proses pematangan, perpaduan warna hijau dan merah.</p>
|
||||||
|
|
@ -126,7 +201,11 @@ class="rounded-lg shadow-md w-full h-auto">
|
||||||
<!-- Matang Card -->
|
<!-- Matang Card -->
|
||||||
<div class="bg-red-50 border-2 border-red-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
<div class="bg-red-50 border-2 border-red-200 rounded-xl p-8 text-center hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
<div class="w-32 h-32 bg-red-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
|
<div class="w-32 h-32 bg-red-500 rounded-full mx-auto mb-6 flex items-center justify-center p-2">
|
||||||
<img src="{{ asset('assets/images/matang.jpg') }}" alt="Tomat Matang" class="w-full h-full object-contain rounded-full">
|
<img src="{{ asset('assets/images/matang.jpg') }}"
|
||||||
|
alt="Tomat Matang"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
class="w-full h-full object-contain rounded-full">
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-2xl font-bold text-red-800 mb-4">Matang</h3>
|
<h3 class="text-2xl font-bold text-red-800 mb-4">Matang</h3>
|
||||||
<p class="text-gray-600 mb-4">Tomat matang sempurna, berwarna merah cerah dan tekstur lembut.</p>
|
<p class="text-gray-600 mb-4">Tomat matang sempurna, berwarna merah cerah dan tekstur lembut.</p>
|
||||||
|
|
@ -239,5 +318,80 @@ class="rounded-lg shadow-md w-full h-auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// =====================
|
||||||
|
// MOBILE MENU TOGGLE
|
||||||
|
// =====================
|
||||||
|
(function() {
|
||||||
|
var btn = document.getElementById('mobileMenuBtn');
|
||||||
|
var menu = document.getElementById('mobileMenu');
|
||||||
|
var iconOpen = document.getElementById('iconHamburger');
|
||||||
|
var iconClose = document.getElementById('iconClose');
|
||||||
|
|
||||||
|
if (!btn || !menu) return;
|
||||||
|
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var isOpen = !menu.classList.contains('hidden');
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
iconOpen.classList.remove('hidden');
|
||||||
|
iconClose.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
} else {
|
||||||
|
menu.classList.remove('hidden');
|
||||||
|
iconOpen.classList.add('hidden');
|
||||||
|
iconClose.classList.remove('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tutup menu saat link diklik
|
||||||
|
menu.querySelectorAll('a').forEach(function(link) {
|
||||||
|
link.addEventListener('click', function() {
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
iconOpen.classList.remove('hidden');
|
||||||
|
iconClose.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tutup menu saat resize ke desktop
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
if (window.innerWidth >= 768) {
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
iconOpen.classList.remove('hidden');
|
||||||
|
iconClose.classList.add('hidden');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// LAZY LOAD GAMBAR FALLBACK
|
||||||
|
// untuk browser lama yang tidak support loading="lazy"
|
||||||
|
// =====================
|
||||||
|
(function() {
|
||||||
|
if ('loading' in HTMLImageElement.prototype) return;
|
||||||
|
if (!('IntersectionObserver' in window)) return;
|
||||||
|
|
||||||
|
var obs = new IntersectionObserver(function(entries, observer) {
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
if (!entry.isIntersecting) return;
|
||||||
|
var img = entry.target;
|
||||||
|
if (img.dataset.src) {
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
img.removeAttribute('data-src');
|
||||||
|
}
|
||||||
|
observer.unobserve(img);
|
||||||
|
});
|
||||||
|
}, { rootMargin: '200px' });
|
||||||
|
|
||||||
|
document.querySelectorAll('img[loading="lazy"]').forEach(function(img) {
|
||||||
|
obs.observe(img);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue