initialize project documentation and setup instructions

This commit is contained in:
itsmeabimanyu
2026-03-01 07:03:16 +07:00
parent df7071f519
commit 6fdf9d5b62
146 changed files with 89855 additions and 1 deletions

355
static/js/main.js Normal file
View File

@@ -0,0 +1,355 @@
/**
* © 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");
}
}
});
}