Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/laciasmara.com/public_html/shop/application/views/admin_new/layouts/ |
Upload File : |
</div> <div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-[96] hidden md:hidden"> </div> <!-- Tour Guide JS --> <script src="<?= base_url('assets/admin/js/tour-guide.js') ?>"></script> </body> <!-- jsPDF --> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.31/jspdf.plugin.autotable.min.js"></script> <!-- Feather Icon --> <script src="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.29.0/feather.min.js"></script> <!-- Tom Select --> <link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script> <!-- Text Editor --> <script src="https://cdn.ckeditor.com/ckeditor5/36.0.1/classic/ckeditor.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script> <!-- Tambahkan SweetAlert2 --> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <!-- Flatpickr CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"> <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> <script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/id.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script> <script> const notyf = new Notyf({ duration: 2000, position: { x: 'center', y: 'top' }, ripple: false, types: [{ type: 'success', background: '#2e3137', className: 'custom-notification', icon: false }, { type: 'error', background: '#2e3137', className: 'custom-notification', icon: false } ] }); document.addEventListener('DOMContentLoaded', function() { // Initialize Feather Icons feather.replace(); // Handle collapsible menus document.querySelectorAll(".menu-item button").forEach(button => { button.addEventListener("click", function() { const submenu = this.nextElementSibling; const iconContainer = this.querySelector(".transform"); const menuItem = this.closest(".menu-item"); if (submenu) { const isOpen = submenu.classList.contains("open"); // Close all other submenus document.querySelectorAll(".submenu").forEach(menu => { if (menu !== submenu) { menu.classList.remove("open"); const menuButton = menu.previousElementSibling; menuButton.querySelector(".transform")?.classList.remove("rotate-180"); menuButton.classList.remove("text-[#7A4397]"); } }); // Toggle current submenu submenu.classList.toggle("open", !isOpen); // Toggle chevron rotation if (iconContainer) { iconContainer.classList.toggle("rotate-180", !isOpen); } // Toggle active color on button button.classList.toggle("text-[#7A4397]", !isOpen); } }); }); // Make sure active menu stays open when page loads document.querySelectorAll(".menu-item.active").forEach(menuItem => { const submenu = menuItem.querySelector(".submenu"); const button = menuItem.querySelector("button"); const iconContainer = button?.querySelector(".transform"); if (submenu && button) { submenu.classList.add("open"); if (iconContainer) { iconContainer.classList.add("rotate-180"); } button.classList.add("text-[#7A4397]"); } }); const notificationBell = document.getElementById('notification-bell'); const notificationCount = document.getElementById('notification-count'); const notificationModal = document.getElementById('notification-modal'); const notificationList = document.getElementById('notification-list'); const markAllReadBtn = document.getElementById('mark-all-read'); function loadNotificationCount() { fetch('<?= base_url('admin/dashboard/count_notification') ?>') .then(response => response.json()) .then(data => { if (data.count > 0) { notificationCount.textContent = data.count > 9 ? '9+' : data.count; notificationCount.style.display = 'block'; } else { notificationCount.style.display = 'none'; } }) .catch(error => console.error('Error loading notification count:', error)); } // Fungsi untuk memuat daftar notifikasi function loadNotifications() { fetch('<?= base_url('admin/dashboard/list_notification') ?>') .then(response => response.json()) .then(data => { if (data.notifications && data.notifications.length > 0) { let notificationHTML = ''; data.notifications.forEach(notification => { const isUnread = notification.is_read == 0 ? 'unread' : ''; const timeAgo = getTimeAgo(new Date(notification.created_at)); notificationHTML += ` <div class="notification-item ${isUnread}" data-id="${notification.notification_id}" data-url="${notification.reference_url}"> <div class="flex justify-between items-start"> <div class="flex-1 pr-4"> <p class="text-sm mb-1">${notification.message}</p> <span class="text-xs text-gray-500">${timeAgo}</span> </div> ${notification.is_read == 0 ? '<div class="w-2 h-2 bg-blue-500 rounded-full"></div>' : ''} </div> </div> `; }); notificationList.innerHTML = notificationHTML; // Tambahkan event listener untuk setiap notifikasi document.querySelectorAll('.notification-item').forEach(item => { item.addEventListener('click', function() { const notifId = this.getAttribute('data-id'); markAsRead(notifId, this); }); }); } else { notificationList.innerHTML = '<div class="notification-empty">Tidak ada notifikasi baru</div>'; } }) .catch(error => console.error('Error loading notifications:', error)); } // Fungsi untuk menandai notifikasi sudah dibaca function markAsRead(notificationId, element) { let csrfToken = '<?= $this->security->get_csrf_hash(); ?>'; const csrfName = '<?= $this->security->get_csrf_token_name(); ?>'; const formData = new FormData(); formData.append('notification_id', notificationId); formData.append(csrfName, csrfToken); // Tambahkan CSRF token fetch('<?= base_url('admin/dashboard/mark_read') ?>', { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' // Pastikan ini agar dikenali sebagai AJAX request } }) .then(response => response.json()) .then(data => { if (data.success) { let refUrl = element.getAttribute('data-url'); // Update UI element.classList.remove('unread'); const blueIndicator = element.querySelector('.bg-blue-500'); if (blueIndicator) { blueIndicator.remove(); } loadNotificationCount(); // Redirect jika ada URL referensi if (refUrl) { window.location.href = refUrl; } // Update CSRF token setelah request berhasil csrfToken = data.csrf_token; } }) .catch(error => console.error('Error marking notification as read:', error)); } // console.log(<?= $this->session->userdata('user_id') ?>) // Fungsi untuk menandai semua notifikasi sudah dibaca function markAllAsRead() { let csrfToken = '<?= $this->security->get_csrf_hash(); ?>'; const csrfName = '<?= $this->security->get_csrf_token_name(); ?>'; const formData = new FormData(); formData.append(csrfName, csrfToken); fetch('<?= base_url('admin/dashboard/mark_all_read') ?>', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { // Perbarui UI document.querySelectorAll('.notification-item.unread').forEach(item => { item.classList.remove('unread'); const blueIndicator = item.querySelector('.bg-blue-500'); if (blueIndicator) { blueIndicator.remove(); } }); // Perbarui jumlah notifikasi const notificationCount = document.getElementById('notification-count'); if (notificationCount) { notificationCount.style.display = 'none'; } } }) .catch(error => console.error('Error marking all notifications as read:', error)); } // Fungsi helper untuk format waktu relatif (time ago) function getTimeAgo(date) { const seconds = Math.floor((new Date() - date) / 1000); let interval = Math.floor(seconds / 31536000); if (interval > 1) return interval + ' tahun yang lalu'; interval = Math.floor(seconds / 2592000); if (interval > 1) return interval + ' bulan yang lalu'; interval = Math.floor(seconds / 86400); if (interval > 1) return interval + ' hari yang lalu'; interval = Math.floor(seconds / 3600); if (interval > 1) return interval + ' jam yang lalu'; interval = Math.floor(seconds / 60); if (interval > 1) return interval + ' menit yang lalu'; return 'Baru saja'; } // Event listener untuk toggle modal notifikasi notificationBell.addEventListener('click', function(e) { e.stopPropagation(); const isVisible = notificationModal.style.display === 'block'; if (!isVisible) { notificationModal.style.display = 'block'; loadNotifications(); } else { notificationModal.style.display = 'none'; } }); // Event listener untuk tombol "tandai semua dibaca" markAllReadBtn.addEventListener('click', function(e) { e.stopPropagation(); markAllAsRead(); }); // Tutup modal saat klik di luar document.addEventListener('click', function(e) { if (notificationModal.style.display === 'block' && !notificationModal.contains(e.target) && !notificationBell.contains(e.target)) { notificationModal.style.display = 'none'; } }); // Muat jumlah notifikasi saat halaman dimuat loadNotificationCount(); // Polling untuk memperbarui jumlah notifikasi setiap 30 detik setInterval(loadNotificationCount, 30000); initializeSearchContainer(); const sidebar = document.getElementById('sidebar'); const sidebarToggle = document.getElementById('sidebar-toggle'); const sidebarOverlay = document.getElementById('sidebar-overlay'); const originalSidebarClasses = sidebar.className; // Track sidebar state without localStorage let sidebarCollapsed = false; // Function to toggle mobile sidebar function toggleMobileSidebar() { if (isMobile()) { sidebar.classList.toggle('-translate-x-full'); sidebarOverlay.classList.toggle('hidden'); } } // Definisi yang jelas untuk deteksi mobile function isMobile() { return window.innerWidth < 768; // 768px adalah breakpoint md di Tailwind } // Apply initial sidebar state function initSidebar() { // Cek apakah di mobile atau desktop if (isMobile()) { // Hanya atur sidebar di mobile sidebar.classList.add('-translate-x-full'); // Sembunyikan sidebar di mobile secara default // Pastikan overlay tersembunyi if (sidebarOverlay) { sidebarOverlay.classList.add('hidden'); } } else { sidebar.classList.remove('-translate-x-full'); sidebar.classList.add('md:translate-x-0'); sidebarCollapsed = false; } } // Toggle sidebar on click if (sidebarToggle) { sidebarToggle.addEventListener('click', (e) => { e.preventDefault(); if (isMobile()) { toggleMobileSidebar(); } }); } // Hide sidebar when clicking overlay if (sidebarOverlay) { sidebarOverlay.addEventListener('click', toggleMobileSidebar); } // Toggle mobile search if (mobileSearchToggle && mobileSearch) { mobileSearchToggle.addEventListener('click', () => { mobileSearch.classList.toggle('hidden'); }); } // Toggle notification modal if (notificationBell && notificationModal) { notificationBell.addEventListener('click', (e) => { e.stopPropagation(); notificationModal.classList.toggle('hidden'); }); // Close notification modal when clicking outside document.addEventListener('click', (event) => { if (notificationModal && notificationBell && !notificationBell.contains(event.target) && !notificationModal.contains(event.target)) { notificationModal.classList.add('hidden'); } }); } document.querySelectorAll('.menu-item > button').forEach(btn => { btn.addEventListener('click', () => { if (isMobile()) { const submenu = btn.nextElementSibling; // Safely get arrow element const arrowEl = btn.querySelector('.sidebar-arrow i'); if (!submenu || !arrowEl) return; const arrow = arrowEl.parentElement; // Toggle submenu visibility submenu.classList.toggle('hidden'); arrow.classList.toggle('rotate-180'); // Close other submenus document.querySelectorAll('.menu-item > button').forEach(otherBtn => { if (otherBtn !== btn) { const otherSubmenu = otherBtn.nextElementSibling; const otherArrowEl = otherBtn.querySelector('.sidebar-arrow i'); if (!otherSubmenu || !otherArrowEl) return; const otherArrow = otherArrowEl.parentElement; const otherMenuItem = otherBtn.parentElement; if (!otherSubmenu.classList.contains('hidden') && !otherMenuItem.classList.contains('active')) { otherSubmenu.classList.add('hidden'); otherArrow.classList.remove('rotate-180'); } } }); } }); }); // Handle window resize window.addEventListener('resize', () => { initSidebar(); }); }); // Search const barSearchInput = document.getElementById('globalSearch'); const mobileSearchInput = document.querySelector('#mobile-search input'); const searchOverlay = document.getElementById('search-overlay'); const searchBody = document.body; const mobileSearchToggle = document.getElementById('mobile-search-toggle'); const mobileSearch = document.getElementById('mobile-search'); const isMobile = () => window.innerWidth <= 768; function getActiveSearchInput() { return isMobile() ? mobileSearchInput : barSearchInput; } // Initialize search container and ensure it's responsive function initializeSearchContainer() { let suggestionsContainer = document.getElementById('search-suggestions'); if (!suggestionsContainer) { suggestionsContainer = document.createElement('div'); suggestionsContainer.id = 'search-suggestions'; // Styling dasar suggestionsContainer.className = ` bg-white border border-gray-200 rounded-md shadow-lg max-h-64 overflow-y-auto hidden z-50 `; // Styling berbeda untuk mobile vs desktop if (isMobile()) { suggestionsContainer.classList.add('fixed', 'left-0', 'right-0', 'top-[120px]', 'max-w-full', 'mx-4', 'my-2'); // Tambahkan ke mobile search container document.getElementById('mobile-search').appendChild(suggestionsContainer); } else { suggestionsContainer.classList.add('absolute', 'left-0', 'right-0', 'top-full', 'mt-2', 'w-full'); // Tambahkan ke desktop search container barSearchInput.parentElement.classList.add('relative'); barSearchInput.parentElement.appendChild(suggestionsContainer); } } return suggestionsContainer; } // Responsive search focus handling barSearchInput.addEventListener('focus', () => { searchOverlay.classList.remove('opacity-0', 'pointer-events-none'); searchOverlay.classList.add('opacity-100', 'pointer-events-auto'); searchBody.classList.add('overflow-hidden'); // Special handling for mobile if (isMobile()) { // Expand search input on mobile when focused barSearchInput.classList.add('w-full'); barSearchInput.parentElement.classList.add('w-full', 'absolute', 'left-0', 'px-4'); } }); barSearchInput.addEventListener('blur', () => { searchOverlay.classList.remove('opacity-100', 'pointer-events-auto'); searchOverlay.classList.add('opacity-0', 'pointer-events-none'); searchBody.classList.remove('overflow-hidden'); // Reset mobile-specific classes with slight delay to allow for click actions if (isMobile()) { setTimeout(() => { if (!barSearchInput.matches(':focus')) { barSearchInput.classList.remove('w-full'); barSearchInput.parentElement.classList.remove('w-full', 'absolute', 'left-0', 'px-4'); } }, 150); } }); // Keyboard shortcut for search document.addEventListener('keydown', function(event) { if ((event.ctrlKey || event.metaKey) && event.key === '/') { event.preventDefault(); barSearchInput.focus(); } }); // Overlay click handling searchOverlay.addEventListener('click', () => { barSearchInput.blur(); const suggestionsContainer = document.getElementById('search-suggestions'); if (suggestionsContainer) { suggestionsContainer.classList.add('hidden'); } }); // Debounce function function debounce(func, delay) { let timeoutId; return function() { const context = this; const args = arguments; clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(context, args), delay); }; } // Fetch suggestions async function fetchSuggestions(query) { const BASE_URL = '<?= base_url() ?>'; try { const response = await fetch(`${BASE_URL}admin/search/get_suggestions?query=${encodeURIComponent(query)}`); return await response.json(); } catch (error) { console.error('Error fetching suggestions:', error); return []; } } // Render suggestions with responsive design function renderSuggestions(suggestions) { const suggestionsContainer = initializeSearchContainer(); // Clear previous suggestions suggestionsContainer.innerHTML = ''; if (suggestions.length === 0) { suggestionsContainer.classList.add('hidden'); return; } // Add a header for mobile if (isMobile()) { const header = document.createElement('div'); header.className = 'px-4 py-2 bg-gray-50 border-b font-medium text-[#333]'; header.textContent = 'Hasil Pencarian'; suggestionsContainer.appendChild(header); } suggestions.forEach((suggestion, index) => { const suggestionItem = document.createElement('div'); // Enhanced responsive styling suggestionItem.className = ` px-4 py-3 md:py-2 cursor-pointer hover:bg-gray-100 flex items-center gap-2 ${index === 0 ? 'bg-gray-50' : ''} transition duration-200 ease-in-out border-b border-gray-100 last:border-0 `; // Create icon container (optional) const iconContainer = document.createElement('div'); iconContainer.className = 'text-gray-400'; iconContainer.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>'; // Text container const textContainer = document.createElement('div'); textContainer.className = 'flex-1'; // Highlight matching text const query = barSearchInput.value.toLowerCase(); const title = suggestion.title; const matchIndex = title.toLowerCase().indexOf(query); if (matchIndex !== -1) { const beforeMatch = title.slice(0, matchIndex); const match = title.slice(matchIndex, matchIndex + query.length); const afterMatch = title.slice(matchIndex + query.length); textContainer.innerHTML = ` ${beforeMatch}<span class="font-bold text-purple-600">${match}</span>${afterMatch} `; } else { textContainer.textContent = title; } // For mobile, add a subtitle/category if available if (isMobile() && suggestion.category) { const subtitle = document.createElement('div'); subtitle.className = 'text-xs text-gray-500 mt-1'; subtitle.textContent = suggestion.category; textContainer.appendChild(subtitle); } suggestionItem.appendChild(iconContainer); suggestionItem.appendChild(textContainer); suggestionItem.addEventListener('click', () => { window.location.href = suggestion.link; }); suggestionsContainer.appendChild(suggestionItem); }); suggestionsContainer.classList.remove('hidden'); } // Search handler with debounce const handleSearch = debounce(async function() { const activeInput = getActiveSearchInput(); const query = activeInput.value.trim(); if (query.length < 2) { const suggestionsContainer = document.getElementById('search-suggestions'); if (suggestionsContainer) { suggestionsContainer.classList.add('hidden'); } return; } const suggestions = await fetchSuggestions(query); renderSuggestions(suggestions); }, 300); // Event listeners barSearchInput.addEventListener('input', handleSearch); // Close suggestions when clicking outside document.addEventListener('click', (event) => { const suggestionsContainer = document.getElementById('search-suggestions'); if (suggestionsContainer && !barSearchInput.contains(event.target) && !suggestionsContainer.contains(event.target)) { suggestionsContainer.classList.add('hidden'); } }); // Handle window resize for responsive adjustments window.addEventListener('resize', debounce(() => { // Reposisi suggestion container saat ukuran layar berubah const suggestionsContainer = document.getElementById('search-suggestions'); if (suggestionsContainer) { // Hapus container lama dan buat ulang dengan posisi yang tepat suggestionsContainer.remove(); initializeSearchContainer(); } }, 250)); mobileSearchInput.addEventListener('input', handleSearch); mobileSearchInput.addEventListener('focus', () => { // Logika focus untuk mobile searchOverlay.classList.remove('opacity-0', 'pointer-events-none'); searchOverlay.classList.add('opacity-100', 'pointer-events-auto'); searchBody.classList.add('overflow-hidden'); }); mobileSearchInput.addEventListener('blur', () => { // Logika blur untuk mobile searchOverlay.classList.remove('opacity-100', 'pointer-events-auto'); searchOverlay.classList.add('opacity-0', 'pointer-events-none'); searchBody.classList.remove('overflow-hidden'); }); </script> </html>