355 lines
12 KiB
JavaScript
355 lines
12 KiB
JavaScript
/**
|
|
* © 2025-2026 Skoci Admin
|
|
* Designed and Developed by Abimanyu
|
|
* Contact : abimanyuag12@gmail.com
|
|
* All Rights Reserved
|
|
*/
|
|
|
|
window.addEventListener('load', () => {
|
|
// 1. Logika Preloader
|
|
const preloader = document.getElementById('bz-preloader');
|
|
if (preloader) {
|
|
preloader.classList.add('hide');
|
|
setTimeout(() => {
|
|
preloader.remove();
|
|
}, 300);
|
|
}
|
|
|
|
// 2. Logika penyimpanan tab perhalaman
|
|
// Memilih semua elemen toggle
|
|
const tabTriggers = document.querySelectorAll('[data-bs-toggle="pill"], [data-bs-toggle="tab"]');
|
|
|
|
tabTriggers.forEach(trigger => {
|
|
// Membuat key unik berdasarkan path DAN tipe toggle (pill atau tab)
|
|
const toggleType = trigger.getAttribute('data-bs-toggle');
|
|
const storageKey = `activeTab_${location.pathname}_${toggleType}`;
|
|
|
|
// Event saat tab ditampilkan
|
|
trigger.addEventListener('shown.bs.tab', event => {
|
|
const targetIdentifier = event.target.getAttribute('data-bs-target');
|
|
localStorage.setItem(storageKey, targetIdentifier);
|
|
});
|
|
|
|
// Cek storage saat halaman dimuat (hanya jalankan sekali untuk masing-masing tipe)
|
|
const savedTarget = localStorage.getItem(storageKey);
|
|
if (savedTarget) {
|
|
const targetElement = document.querySelector(`[data-bs-target="${savedTarget}"]`);
|
|
if (targetElement) {
|
|
const tabInstance = new bootstrap.Tab(targetElement);
|
|
tabInstance.show();
|
|
}
|
|
}
|
|
});
|
|
|
|
// 3. Logika sidebar Toggle
|
|
|
|
const sidebar = document.querySelector('.sidebar-right');
|
|
const topbar = document.querySelector('.topbar-right');
|
|
const toggleBtn = document.getElementById('toggleSidebarRight');
|
|
const mobileToggle = document.getElementById('mobileToogle');
|
|
const hoverTargets = [sidebar, topbar];
|
|
|
|
// Ambil status awal dari localStorage
|
|
const isCollapsedSaved = localStorage.getItem('sidebar-collapsed') === 'true';
|
|
|
|
// Terapkan hanya jika di desktop (window width > 992)
|
|
if (isCollapsedSaved && window.innerWidth >= 992) {
|
|
sidebar.classList.add('collapsed');
|
|
}
|
|
|
|
hoverTargets.forEach(target => {
|
|
if (!target) return;
|
|
|
|
target.addEventListener('mouseenter', () => {
|
|
// Hanya aktifkan temp-expanded jika sidebar memang sedang collapsed
|
|
if (sidebar.classList.contains('collapsed')) {
|
|
sidebar.classList.remove('collapsed');
|
|
sidebar.classList.add('temp-expanded');
|
|
}
|
|
});
|
|
|
|
target.addEventListener('mouseleave', () => {
|
|
// Kembalikan ke collapsed hanya jika statusnya temp-expanded
|
|
if (sidebar.classList.contains('temp-expanded')) {
|
|
sidebar.classList.add('collapsed');
|
|
sidebar.classList.remove('temp-expanded');
|
|
}
|
|
});
|
|
});
|
|
|
|
if (toggleBtn && sidebar) {
|
|
toggleBtn.addEventListener('click', (e) => {
|
|
// Mencegah bubbling agar tidak terpicu dua kali
|
|
e.stopPropagation();
|
|
|
|
if (sidebar.classList.contains('temp-expanded')) {
|
|
// Jika sedang di-hover lalu diklik, hapus status 'temp'
|
|
// agar sidebar tetap terbuka saat kursor keluar
|
|
sidebar.classList.remove('temp-expanded');
|
|
} else {
|
|
// Kondisi klik normal (buka/tutup)
|
|
sidebar.classList.toggle('collapsed');
|
|
}
|
|
|
|
// --- SIMPAN KE LOCALSTORAGE ---
|
|
// Kita simpan true jika class 'collapsed' ada, false jika tidak ada
|
|
const isCurrentlyCollapsed = sidebar.classList.contains('collapsed');
|
|
localStorage.setItem('sidebar-collapsed', isCurrentlyCollapsed);
|
|
});
|
|
}
|
|
|
|
if (mobileToggle && sidebar) {
|
|
mobileToggle.addEventListener('click', function(e) {
|
|
e.stopPropagation(); // Mencegah klik menyebar
|
|
sidebar.classList.toggle('mobile-show');
|
|
});
|
|
|
|
// Opsional: Klik di mana saja di luar sidebar untuk menutupnya
|
|
document.addEventListener('click', function(e) {
|
|
if (!sidebar.contains(e.target) && sidebar.classList.contains('mobile-show')) {
|
|
sidebar.classList.remove('mobile-show');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Fungsi untuk bersihkan class collapsed di mobile
|
|
const cleanMobileSidebar = () => {
|
|
if (!sidebar) return;
|
|
|
|
if (window.innerWidth < 992) {
|
|
sidebar.classList.remove('collapsed');
|
|
}
|
|
};
|
|
|
|
// Jalankan saat pertama kali load
|
|
cleanMobileSidebar();
|
|
|
|
// Jalankan juga saat layar di-resize (penting!)
|
|
window.addEventListener('resize', cleanMobileSidebar);
|
|
|
|
// Hide Alert
|
|
const alerts = document.querySelectorAll('.bz-alert');
|
|
|
|
alerts.forEach(alert => {
|
|
setTimeout(() => {
|
|
alert.classList.remove('show'); // fade out
|
|
|
|
alert.addEventListener('transitionend', () => {
|
|
alert.remove(); // hapus dari DOM setelah animasi selesai
|
|
});
|
|
}, 5000);
|
|
});
|
|
|
|
// Tab pada mobile mode
|
|
document.querySelectorAll('.js-tab-trigger').forEach(el => {
|
|
el.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
|
|
const targetId = this.dataset.tabTarget;
|
|
|
|
const desktopTrigger = document.querySelector(
|
|
`[data-bs-toggle="tab"][data-bs-target="#${targetId}"]`
|
|
);
|
|
|
|
if (desktopTrigger) {
|
|
bootstrap.Tab.getOrCreateInstance(desktopTrigger).show();
|
|
}
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
function adjustQuantity(button, change) {
|
|
const container = button.closest('.quantity-control'); // cari parent terdekat
|
|
if (!container) return;
|
|
|
|
const input = container.querySelector('.qty-input'); // ambil input di dalam container
|
|
if (!input) return;
|
|
|
|
// ambil value, min, max dari HTML langsung
|
|
let value = +input.value; // unary + → convert string ke number
|
|
const min = +input.min; // ambil min dari HTML
|
|
const max = +input.max; // ambil max dari HTML
|
|
|
|
value += change;
|
|
|
|
// jaga value tetap dalam batas min & max
|
|
if (value < min) value = min;
|
|
if (value > max) value = max;
|
|
|
|
input.value = value;
|
|
}
|
|
|
|
// File upload handling
|
|
function handleFileUpload(input, previewId, inputId) {
|
|
const previewContainer = document.getElementById(previewId);
|
|
previewContainer.innerHTML = '';
|
|
|
|
if (input.files.length > 0) {
|
|
previewContainer.style.display = 'block';
|
|
|
|
for (let i = 0; i < input.files.length; i++) {
|
|
const file = input.files[i];
|
|
const fileSize = formatFileSize(file.size);
|
|
const fileExtension = getFileExtension(file.name);
|
|
const fileIcon = getFileIcon(fileExtension);
|
|
|
|
const fileItem = document.createElement('div');
|
|
fileItem.className = 'file-item';
|
|
|
|
fileItem.innerHTML = `
|
|
<div class="file-info d-flex align-items-center p-2">
|
|
<div class="icon-wrapper rounded-circle bg-dark bg-opacity-10
|
|
d-flex justify-content-center align-items-center">
|
|
<i class="${fileIcon} text-dark"></i>
|
|
</div>
|
|
|
|
<div class="file-details">
|
|
<div class="file-name fw-bold ">${file.name}</div>
|
|
|
|
<div class="file-size text-muted small">${fileSize}</div>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="file-remove" onclick="showDeleteConfirmation('${inputId}', '${previewId}', ${i})">
|
|
<i class="ri-close-line"></i>
|
|
</button>
|
|
`;
|
|
|
|
previewContainer.appendChild(fileItem);
|
|
}
|
|
} else {
|
|
previewContainer.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function removeFile(inputId, previewId, index) {
|
|
const input = document.getElementById(inputId);
|
|
const dt = new DataTransfer();
|
|
|
|
for (let i = 0; i < input.files.length; i++) {
|
|
if (i !== index) {
|
|
dt.items.add(input.files[i]);
|
|
}
|
|
}
|
|
|
|
input.files = dt.files;
|
|
|
|
handleFileUpload(input, previewId, inputId);
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function getFileExtension(filename) {
|
|
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
|
|
}
|
|
|
|
function getFileIcon(extension) {
|
|
const icons = {
|
|
'pdf': 'bi bi-file-earmark-pdf',
|
|
'doc': 'bi bi-file-earmark-word',
|
|
'docx': 'bi bi-file-earmark-word',
|
|
'xls': 'bi bi-file-earmark-excel',
|
|
'xlsx': 'bi bi-file-earmark-excel',
|
|
'jpg': 'bi bi-file-earmark-image',
|
|
'jpeg': 'bi bi-file-earmark-image',
|
|
'png': 'bi bi-file-earmark-image',
|
|
'gif': 'bi bi-file-earmark-image',
|
|
'zip': 'bi bi-file-earmark-zip',
|
|
'rar': 'bi bi-file-earmark-zip',
|
|
'txt': 'bi bi-file-earmark-text',
|
|
'csv': 'bi bi-filetype-csv'
|
|
};
|
|
|
|
return icons[extension] || 'bi bi-file-earmark';
|
|
}
|
|
|
|
// generate icon untuk file lama
|
|
document.querySelectorAll('.icon-wrapper[data-extension]').forEach(wrapper => {
|
|
const ext = wrapper.dataset.extension.toLowerCase();
|
|
const iconClass = getFileIcon(ext);
|
|
wrapper.innerHTML = `<i class="${iconClass} text-dark"></i>`;
|
|
});
|
|
|
|
function showDeleteConfirmation(
|
|
inputId,
|
|
previewId,
|
|
index,
|
|
) {
|
|
Swal.fire({
|
|
html: '<p class="mb-0">Delete this file?</p>',
|
|
showCancelButton: true,
|
|
cancelButtonText: 'Cancel',
|
|
confirmButtonText: 'Delete',
|
|
reverseButtons: true,
|
|
customClass: { popup: 'swal-minimal' }
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
/* 1. Ubah nilai hidden input agar server tahu harus hapus database
|
|
const deleteInput = document.getElementById('delete_existing_file');
|
|
if (deleteInput) {
|
|
deleteInput.value = "true";
|
|
} */
|
|
|
|
// 2. Jalankan fungsi visual yang sudah Anda buat
|
|
removeFile(inputId, previewId, index);
|
|
|
|
/* 3. Hapus preview secara visual (front-end)
|
|
const previewContainer = document.getElementById(previewId);
|
|
if (previewContainer) {
|
|
previewContainer.style.display = 'none';
|
|
} */
|
|
// hapus sessionStorage
|
|
sessionStorage.removeItem('file_draft');
|
|
|
|
// BERSIHKAN URL (Mencegah "Resend" saat Reload)
|
|
if (window.history.replaceState) {
|
|
window.history.replaceState(null, null, window.location.href);
|
|
}
|
|
|
|
Swal.fire({
|
|
html: '<p class="mb-0">File has been removed.</p>',
|
|
showConfirmButton: true,
|
|
customClass: {
|
|
popup: 'swal-minimal'
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof flatpickr !== 'undefined' && $('.date').length) {
|
|
flatpickr(".date", {
|
|
// dateFormat: "d-m-Y",
|
|
dateFormat: "d/m/Y H:i", // Ditambah H:i untuk jam dan menit
|
|
enableTime: true,
|
|
allowInput: true,
|
|
// locale: "id",
|
|
|
|
onDayCreate: function(_, __, ___, dayElem) {
|
|
const d = dayElem.dateObj;
|
|
|
|
if (
|
|
dayElem.classList.contains("prevMonthDay") ||
|
|
dayElem.classList.contains("nextMonthDay")
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Sunday (0)
|
|
if (d.getDay() === 0) {
|
|
dayElem.classList.add("sunday");
|
|
}
|
|
|
|
// Friday (5)
|
|
if (d.getDay() === 5) {
|
|
dayElem.classList.add("friday");
|
|
}
|
|
}
|
|
});
|
|
} |