responsive mobile
This commit is contained in:
parent
cc5936051e
commit
cd3a6052d5
|
|
@ -100,114 +100,133 @@
|
|||
@section('scripts')
|
||||
<script>
|
||||
|
||||
/* =========================
|
||||
TREND LINE
|
||||
========================= */
|
||||
const trendLabels = @json(
|
||||
$trend->pluck('date')->map(fn($d) =>
|
||||
\Carbon\Carbon::parse($d)->translatedFormat('D')
|
||||
)
|
||||
);
|
||||
// Tunggu sampai Chart.js library loaded
|
||||
function initCharts() {
|
||||
if (typeof Chart === 'undefined') {
|
||||
console.warn('Chart.js belum loaded, retry dalam 100ms');
|
||||
setTimeout(initCharts, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
const trendData = @json($trend->pluck('total'));
|
||||
/* =========================
|
||||
TREND LINE
|
||||
========================= */
|
||||
const trendLabels = @json(
|
||||
$trend->pluck('date')->map(fn($d) =>
|
||||
\Carbon\Carbon::parse($d)->translatedFormat('D')
|
||||
)
|
||||
);
|
||||
|
||||
// Optimize for mobile
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const trendData = @json($trend->pluck('total'));
|
||||
|
||||
new Chart(document.getElementById('trendChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: trendLabels,
|
||||
datasets: [{
|
||||
data: trendData,
|
||||
borderColor: '#ef4444',
|
||||
backgroundColor: 'rgba(239,68,68,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: isMobile ? 3 : 5,
|
||||
pointBorderWidth: 0,
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
filler: { propagate: true }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { drawBorder: false, color: 'rgba(0,0,0,0.05)' },
|
||||
ticks: { font: { size: isMobile ? 10 : 12 } }
|
||||
// Optimize for mobile
|
||||
const isMobile = window.innerWidth < 768;
|
||||
|
||||
try {
|
||||
new Chart(document.getElementById('trendChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: trendLabels,
|
||||
datasets: [{
|
||||
data: trendData,
|
||||
borderColor: '#ef4444',
|
||||
backgroundColor: 'rgba(239,68,68,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: isMobile ? 3 : 5,
|
||||
pointBorderWidth: 0,
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: isMobile ? 10 : 12 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* =========================
|
||||
PIE CHART
|
||||
========================= */
|
||||
const distLabels = @json(
|
||||
$distribution->pluck('category')->map(
|
||||
fn($c) => ucfirst(str_replace('_',' ',$c))
|
||||
)
|
||||
);
|
||||
|
||||
const distData = @json($distribution->pluck('total'));
|
||||
|
||||
const dynamicColors = distLabels.map(label => {
|
||||
if (label.toLowerCase() === 'matang') {
|
||||
return '#ef4444'; // merah
|
||||
}
|
||||
else if (label.toLowerCase() === 'mentah') {
|
||||
return '#22c55e'; // hijau
|
||||
}
|
||||
else {
|
||||
return '#f59e0b'; // kuning/orange
|
||||
}
|
||||
});
|
||||
|
||||
new Chart(document.getElementById('distributionChart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: distLabels,
|
||||
datasets: [{
|
||||
data: distData,
|
||||
backgroundColor: dynamicColors,
|
||||
borderWidth: 1,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: isMobile ? 'bottom' : 'bottom',
|
||||
labels: {
|
||||
font: { size: isMobile ? 10 : 12 },
|
||||
padding: isMobile ? 10 : 15,
|
||||
usePointStyle: true
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
filler: { propagate: true }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { drawBorder: false, color: 'rgba(0,0,0,0.05)' },
|
||||
ticks: { font: { size: isMobile ? 10 : 12 } }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: isMobile ? 10 : 12 } }
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
titleFont: { size: isMobile ? 11 : 13 },
|
||||
bodyFont: { size: isMobile ? 10 : 12 }
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing trend chart:', error);
|
||||
}
|
||||
});
|
||||
|
||||
/* =========================
|
||||
PIE CHART
|
||||
========================= */
|
||||
const distLabels = @json(
|
||||
$distribution->pluck('category')->map(
|
||||
fn($c) => ucfirst(str_replace('_',' ',$c))
|
||||
)
|
||||
);
|
||||
|
||||
const distData = @json($distribution->pluck('total'));
|
||||
|
||||
const dynamicColors = distLabels.map(label => {
|
||||
if (label.toLowerCase() === 'matang') {
|
||||
return '#ef4444'; // merah
|
||||
}
|
||||
else if (label.toLowerCase() === 'mentah') {
|
||||
return '#22c55e'; // hijau
|
||||
}
|
||||
else {
|
||||
return '#f59e0b'; // kuning/orange
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
new Chart(document.getElementById('distributionChart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: distLabels,
|
||||
datasets: [{
|
||||
data: distData,
|
||||
backgroundColor: dynamicColors,
|
||||
borderWidth: 1,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: isMobile ? 'bottom' : 'bottom',
|
||||
labels: {
|
||||
font: { size: isMobile ? 10 : 12 },
|
||||
padding: isMobile ? 10 : 15,
|
||||
usePointStyle: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
titleFont: { size: isMobile ? 11 : 13 },
|
||||
bodyFont: { size: isMobile ? 10 : 12 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing distribution chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize charts saat DOM ready
|
||||
document.addEventListener('DOMContentLoaded', initCharts);
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -17,9 +17,6 @@
|
|||
<!-- DNS Prefetch untuk domain eksternal -->
|
||||
<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) -->
|
||||
<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>
|
||||
|
|
@ -30,11 +27,18 @@
|
|||
<!-- 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'))
|
||||
<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
|
||||
|
||||
<!-- Anti-flash dark mode script -->
|
||||
<script>
|
||||
if (localStorage.getItem('theme') === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Performance optimization: Use system fonts for faster render */
|
||||
body {
|
||||
|
|
@ -300,27 +304,88 @@
|
|||
transform: translateY(-2px);
|
||||
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>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- 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')
|
||||
</div>
|
||||
|
||||
<!-- 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 -->
|
||||
<div class="flex-1 overflow-auto">
|
||||
<div class="main-content-area">
|
||||
<!-- Top Navigation -->
|
||||
<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 flex-1">
|
||||
<!-- Mobile Menu Toggle -->
|
||||
<button id="menuToggle" class="md:hidden p-2 hover:bg-gray-100 rounded-lg transition-colors mr-2">
|
||||
<i class="fas fa-bars text-xl text-gray-600"></i>
|
||||
<button id="menuToggle"
|
||||
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>
|
||||
<!-- Page Title (Mobile) -->
|
||||
<h1 class="text-base md:text-lg font-semibold text-gray-900">@yield('page-title')</h1>
|
||||
|
|
@ -406,62 +471,52 @@
|
|||
const menuToggle = document.getElementById('menuToggle');
|
||||
const sidebarWrapper = document.getElementById('sidebar-wrapper');
|
||||
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) {
|
||||
menuToggle.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const isHidden = sidebarWrapper.classList.contains('-translate-x-full');
|
||||
|
||||
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');
|
||||
}
|
||||
sidebarWrapper.classList.contains('is-open') ? closeSidebar() : openSidebar();
|
||||
});
|
||||
}
|
||||
|
||||
// Tutup sidebar saat overlay diklik
|
||||
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', function() {
|
||||
sidebarWrapper.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
});
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// Tutup sidebar saat menu link diklik (mobile only)
|
||||
const navLinks = document.querySelectorAll('.sidebar-link');
|
||||
navLinks.forEach(link => {
|
||||
|
||||
document.querySelectorAll('#sidebar-wrapper .sidebar-link, #sidebar-wrapper a').forEach(function(link) {
|
||||
link.addEventListener('click', function() {
|
||||
// Hanya tutup di mobile (< 768px)
|
||||
if (window.innerWidth < 768) {
|
||||
sidebarWrapper.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
}
|
||||
if (window.innerWidth < 768) closeSidebar();
|
||||
});
|
||||
});
|
||||
|
||||
// Handle resize untuk responsive behavior
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') closeSidebar();
|
||||
});
|
||||
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function() {
|
||||
// Jika window > md (768px), reset sidebar position
|
||||
if (window.innerWidth >= 768) {
|
||||
sidebarWrapper.classList.remove('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
} else {
|
||||
// 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');
|
||||
sidebarWrapper.classList.remove('is-open');
|
||||
sidebarOverlay.classList.remove('is-active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
}, 250);
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,25 +32,25 @@
|
|||
<!-- Navigation Menu -->
|
||||
<nav class="flex-1 p-2 md:p-4 space-y-1 md:space-y-2 overflow-y-auto">
|
||||
<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>
|
||||
<span class="truncate">Dashboard</span>
|
||||
</a>
|
||||
|
||||
<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>
|
||||
<span class="truncate">Kelola Akun Admin</span>
|
||||
</a>
|
||||
|
||||
<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>
|
||||
<span class="truncate">Riwayat Klasifikasi</span>
|
||||
</a>
|
||||
|
||||
<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>
|
||||
<span class="truncate">Statistik Sistem</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -217,135 +217,157 @@
|
|||
@section('scripts')
|
||||
<script>
|
||||
|
||||
// Detect mobile
|
||||
const isMobileStats = window.innerWidth < 768;
|
||||
|
||||
/* ==================================================
|
||||
BAR CHART
|
||||
================================================== */
|
||||
new Chart(document.getElementById('barChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: @json($hariLabels),
|
||||
datasets: [{
|
||||
label: 'Jumlah',
|
||||
data: @json($hariData),
|
||||
backgroundColor: '#ef4444',
|
||||
borderRadius: 8,
|
||||
barThickness: isMobileStats ? 20 : 34,
|
||||
borderSkipped: false
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display:false },
|
||||
tooltip: {
|
||||
titleFont: { size: isMobileStats ? 11 : 13 },
|
||||
bodyFont: { size: isMobileStats ? 10 : 12 }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { precision: 0, font: { size: isMobileStats ? 10 : 12 } },
|
||||
grid: { drawBorder: false, color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: isMobileStats ? 10 : 12 } }
|
||||
}
|
||||
}
|
||||
// Tunggu sampai Chart.js library loaded
|
||||
function initStatisticsCharts() {
|
||||
if (typeof Chart === 'undefined') {
|
||||
console.warn('Chart.js belum loaded, retry dalam 100ms');
|
||||
setTimeout(initStatisticsCharts, 100);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Detect mobile
|
||||
const isMobileStats = window.innerWidth < 768;
|
||||
|
||||
/* ==================================================
|
||||
LINE CHART
|
||||
================================================== */
|
||||
new Chart(document.getElementById('lineChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: @json($bulanLabels),
|
||||
datasets: [{
|
||||
label:'Jumlah',
|
||||
data: @json($bulanData),
|
||||
borderColor: '#ef4444',
|
||||
backgroundColor: 'rgba(239,68,68,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: isMobileStats ? 3 : 4,
|
||||
pointBorderWidth: 0,
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
titleFont: { size: isMobileStats ? 11 : 13 },
|
||||
bodyFont: { size: isMobileStats ? 10 : 12 }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { precision: 0, font: { size: isMobileStats ? 10 : 12 } },
|
||||
grid: { drawBorder: false, color: 'rgba(0,0,0,0.05)' }
|
||||
/* ==================================================
|
||||
BAR CHART
|
||||
================================================== */
|
||||
try {
|
||||
new Chart(document.getElementById('barChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: @json($hariLabels),
|
||||
datasets: [{
|
||||
label: 'Jumlah',
|
||||
data: @json($hariData),
|
||||
backgroundColor: '#ef4444',
|
||||
borderRadius: 8,
|
||||
barThickness: isMobileStats ? 20 : 34,
|
||||
borderSkipped: false
|
||||
}]
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: isMobileStats ? 10 : 12 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* ==================================================
|
||||
PIE CHART
|
||||
================================================== */
|
||||
new Chart(document.getElementById('pieChart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: @json(array_column($distribusiData, 'label')),
|
||||
datasets: [{
|
||||
data: @json(array_column($distribusiData, 'jumlah')),
|
||||
backgroundColor: [
|
||||
'#22c55e',
|
||||
'#eab308',
|
||||
'#ef4444'
|
||||
],
|
||||
borderWidth: 1,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
font: { size: isMobileStats ? 10 : 12 },
|
||||
padding: isMobileStats ? 10 : 15,
|
||||
usePointStyle: true
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display:false },
|
||||
tooltip: {
|
||||
titleFont: { size: isMobileStats ? 11 : 13 },
|
||||
bodyFont: { size: isMobileStats ? 10 : 12 }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { precision: 0, font: { size: isMobileStats ? 10 : 12 } },
|
||||
grid: { drawBorder: false, color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: isMobileStats ? 10 : 12 } }
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
titleFont: { size: isMobileStats ? 11 : 13 },
|
||||
bodyFont: { size: isMobileStats ? 10 : 12 }
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing bar chart:', error);
|
||||
}
|
||||
});
|
||||
|
||||
/* ==================================================
|
||||
LINE CHART
|
||||
================================================== */
|
||||
try {
|
||||
new Chart(document.getElementById('lineChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: @json($bulanLabels),
|
||||
datasets: [{
|
||||
label:'Jumlah',
|
||||
data: @json($bulanData),
|
||||
borderColor: '#ef4444',
|
||||
backgroundColor: 'rgba(239,68,68,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: isMobileStats ? 3 : 4,
|
||||
pointBorderWidth: 0,
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
titleFont: { size: isMobileStats ? 11 : 13 },
|
||||
bodyFont: { size: isMobileStats ? 10 : 12 }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { precision: 0, font: { size: isMobileStats ? 10 : 12 } },
|
||||
grid: { drawBorder: false, color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: isMobileStats ? 10 : 12 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing line chart:', error);
|
||||
}
|
||||
|
||||
/* ==================================================
|
||||
PIE CHART
|
||||
================================================== */
|
||||
try {
|
||||
new Chart(document.getElementById('pieChart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: @json(array_column($distribusiData, 'label')),
|
||||
datasets: [{
|
||||
data: @json(array_column($distribusiData, 'jumlah')),
|
||||
backgroundColor: [
|
||||
'#22c55e',
|
||||
'#eab308',
|
||||
'#ef4444'
|
||||
],
|
||||
borderWidth: 1,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
font: { size: isMobileStats ? 10 : 12 },
|
||||
padding: isMobileStats ? 10 : 15,
|
||||
usePointStyle: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
titleFont: { size: isMobileStats ? 11 : 13 },
|
||||
bodyFont: { size: isMobileStats ? 10 : 12 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing pie chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize charts saat DOM ready
|
||||
document.addEventListener('DOMContentLoaded', initStatisticsCharts);
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -136,6 +136,13 @@ class="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-300 rounded"
|
|||
Lupa kata sandi?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Lupa Kata Sandi (Disabled) -->
|
||||
<div>
|
||||
<span class="text-sm text-gray-400 cursor-not-allowed">
|
||||
Lupa kata sandi?
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Login Button -->
|
||||
<div>
|
||||
|
|
@ -154,16 +161,6 @@ class="btn-login w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-
|
|||
</button>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<!-- 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 class="fixed bottom-4 left-4 text-xs text-gray-300 opacity-60">
|
||||
Made with ♥ by MaturityScan
|
||||
Made with ♥ by TomatScan
|
||||
</footer>
|
||||
|
||||
<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');
|
||||
});
|
||||
|
||||
// Auto-focus username field on load
|
||||
// Auto-focus email field on load
|
||||
window.addEventListener('load', function() {
|
||||
document.getElementById('username').focus();
|
||||
document.getElementById('email').focus();
|
||||
});
|
||||
|
||||
// Add keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
const activeElement = document.activeElement;
|
||||
if (activeElement.id === 'username') {
|
||||
if (activeElement.id === 'email') {
|
||||
e.preventDefault();
|
||||
document.getElementById('password').focus();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,47 @@
|
|||
|
||||
<!-- Font Awesome -->
|
||||
<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>
|
||||
<body class="bg-gray-50">
|
||||
<!-- Navbar -->
|
||||
|
|
@ -50,14 +91,38 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="md:hidden">
|
||||
<button class="text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<button id="mobileMenuBtn"
|
||||
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" />
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<!-- 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">
|
||||
<img src="{{ asset('assets/images/tomatt.png') }}"
|
||||
alt="Fresh Tomatoes"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="rounded-lg shadow-md w-full h-auto">
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -102,7 +169,11 @@ class="rounded-lg shadow-md w-full h-auto">
|
|||
<!-- 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="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>
|
||||
<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>
|
||||
|
|
@ -114,7 +185,11 @@ class="rounded-lg shadow-md w-full h-auto">
|
|||
<!-- 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="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>
|
||||
<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>
|
||||
|
|
@ -126,7 +201,11 @@ class="rounded-lg shadow-md w-full h-auto">
|
|||
<!-- 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="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>
|
||||
<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>
|
||||
|
|
@ -239,5 +318,80 @@ class="rounded-lg shadow-md w-full h-auto">
|
|||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue