import './bootstrap'; import Alpine from 'alpinejs'; import Swal from 'sweetalert2'; window.Alpine = Alpine; window.Swal = Swal; Alpine.start(); document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.delete-form').forEach((form) => { form.addEventListener('submit', (event) => { event.preventDefault(); const entity = form.dataset.entity || 'data ini'; Swal.fire({ title: 'Hapus data?', text: `Yakin ingin menghapus ${entity}? Tindakan ini tidak bisa dibatalkan.`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#e11d48', cancelButtonColor: '#475569', confirmButtonText: 'Ya, hapus', cancelButtonText: 'Batal', reverseButtons: true, }).then((result) => { if (result.isConfirmed) { form.submit(); } }); }); }); const passwordInput = document.querySelector('[data-password-input]'); const passwordToggle = document.querySelector('[data-password-toggle]'); document.querySelectorAll('[data-searchable-student-select]').forEach((wrapper) => { const input = wrapper.querySelector('[data-student-search-input]'); const hiddenInput = wrapper.querySelector('[data-student-search-value]'); const toggle = wrapper.querySelector('[data-student-search-toggle]'); const dropdown = wrapper.querySelector('[data-student-search-dropdown]'); const options = Array.from(wrapper.querySelectorAll('[data-student-option]')); const emptyState = wrapper.querySelector('[data-student-empty-state]'); if (!input || !hiddenInput || !toggle || !dropdown || options.length === 0) { return; } const closeDropdown = () => { dropdown.classList.add('hidden'); }; const openDropdown = () => { dropdown.classList.remove('hidden'); }; const filterOptions = () => { const keyword = input.value.trim().toLowerCase(); let visibleCount = 0; options.forEach((option) => { const haystack = option.dataset.search || ''; const match = keyword === '' || haystack.includes(keyword); option.classList.toggle('hidden', !match); if (match) visibleCount += 1; }); if (emptyState) { emptyState.classList.toggle('hidden', visibleCount > 0); } }; input.addEventListener('focus', () => { filterOptions(); openDropdown(); }); input.addEventListener('input', () => { hiddenInput.value = ''; filterOptions(); openDropdown(); }); toggle.addEventListener('click', () => { filterOptions(); dropdown.classList.toggle('hidden'); }); options.forEach((option) => { option.addEventListener('click', () => { hiddenInput.value = option.dataset.value || ''; input.value = option.dataset.label || ''; closeDropdown(); }); }); document.addEventListener('click', (event) => { if (!wrapper.contains(event.target)) { closeDropdown(); } }); }); const lightboxTriggers = document.querySelectorAll('[data-lightbox-trigger]'); if (lightboxTriggers.length > 0) { const lightbox = document.createElement('div'); lightbox.className = 'fixed inset-0 z-[10000] hidden items-center justify-center bg-slate-950/85 px-4 py-6'; lightbox.innerHTML = `
Preview Gambar
Preview
`; document.body.appendChild(lightbox); const lightboxImage = lightbox.querySelector('[data-lightbox-image]'); const lightboxTitle = lightbox.querySelector('[data-lightbox-title]'); const closeLightbox = () => { lightbox.classList.add('hidden'); lightbox.classList.remove('flex'); document.body.classList.remove('overflow-hidden'); if (lightboxImage) { lightboxImage.setAttribute('src', ''); } }; lightbox.querySelectorAll('[data-lightbox-close]').forEach((element) => { element.addEventListener('click', closeLightbox); }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeLightbox(); } }); lightboxTriggers.forEach((trigger) => { trigger.addEventListener('click', () => { const src = trigger.dataset.lightboxSrc || ''; const title = trigger.dataset.lightboxTitle || 'Preview Gambar'; if (!src || !lightboxImage || !lightboxTitle) { return; } lightboxImage.setAttribute('src', src); lightboxTitle.textContent = title; lightbox.classList.remove('hidden'); lightbox.classList.add('flex'); document.body.classList.add('overflow-hidden'); }); }); } if (passwordInput && passwordToggle) { passwordToggle.addEventListener('click', () => { const isPassword = passwordInput.getAttribute('type') === 'password'; passwordInput.setAttribute('type', isPassword ? 'text' : 'password'); passwordToggle.setAttribute('aria-label', isPassword ? 'Sembunyikan password' : 'Tampilkan password'); }); } const documentTypeSelect = document.querySelector('[data-document-type-select]'); const maxFileSizeInfo = document.querySelector('[data-max-file-size-info]'); const allowedExtensionsInfo = document.querySelector('[data-allowed-extensions-info]'); if (documentTypeSelect && maxFileSizeInfo && allowedExtensionsInfo) { const updateDocumentTypeInfo = () => { const selectedOption = documentTypeSelect.options[documentTypeSelect.selectedIndex]; const maxSizeKb = Number(selectedOption?.dataset?.maxSizeKb || 5120); const maxSizeMb = (maxSizeKb / 1024).toFixed(2); const extensions = (selectedOption?.dataset?.extensions || 'jpg,jpeg,png,pdf,webp') .split(',') .map((item) => item.trim().toUpperCase()) .join(', '); maxFileSizeInfo.textContent = `Maksimal ukuran file untuk jenis dokumen ini: ${maxSizeMb} MB`; allowedExtensionsInfo.textContent = `Format diizinkan: ${extensions}.`; }; documentTypeSelect.addEventListener('change', updateDocumentTypeInfo); updateDocumentTypeInfo(); } const studentHoverTriggers = document.querySelectorAll('.student-hover-trigger'); if (studentHoverTriggers.length > 0) { const hoverCard = document.createElement('div'); hoverCard.className = 'pointer-events-none fixed z-[9999] hidden w-64 overflow-hidden rounded-[20px] border border-slate-200 bg-white p-2.5 shadow-[0_20px_45px_-20px_rgba(15,23,42,0.35)] transition-opacity duration-150'; document.body.appendChild(hoverCard); const renderHoverCard = (trigger) => { const photoUrl = trigger.dataset.photoUrl; const studentName = trigger.dataset.studentName || '-'; const studentNis = trigger.dataset.studentNis || '-'; const studentClass = trigger.dataset.studentClass || '-'; const studentDepartment = trigger.dataset.studentDepartment || '-'; const studentInitial = studentName.charAt(0).toUpperCase(); hoverCard.innerHTML = `
${photoUrl ? `
${studentName}
` : `
${studentInitial}
No Photo
`}
${studentName}
NIS ยท ${studentNis}
${studentClass}
${studentDepartment}
`; }; const positionHoverCard = (trigger) => { const rect = trigger.getBoundingClientRect(); const cardWidth = 256; const cardHeight = 120; let left = rect.left; let top = rect.bottom + 12; if (left + cardWidth > window.innerWidth - 16) { left = window.innerWidth - cardWidth - 16; } if (top + cardHeight > window.innerHeight - 16) { top = rect.top - cardHeight - 12; } if (top < 16) { top = 16; } hoverCard.style.left = `${Math.max(left, 16)}px`; hoverCard.style.top = `${top}px`; }; const showHoverCard = (trigger) => { renderHoverCard(trigger); positionHoverCard(trigger); hoverCard.classList.remove('hidden'); hoverCard.style.opacity = '1'; }; const hideHoverCard = () => { hoverCard.style.opacity = '0'; hoverCard.classList.add('hidden'); }; studentHoverTriggers.forEach((trigger) => { trigger.addEventListener('mouseenter', () => showHoverCard(trigger)); trigger.addEventListener('mouseleave', hideHoverCard); trigger.addEventListener('focus', () => showHoverCard(trigger)); trigger.addEventListener('blur', hideHoverCard); }); window.addEventListener('scroll', hideHoverCard, true); window.addEventListener('resize', hideHoverCard); } const setupBulkDelete = ({ formSelector, selectAllSelector, rowCheckboxSelector, buttonSelector, title, textBuilder, confirmText }) => { const form = document.querySelector(formSelector); if (!form) { return; } const selectAllCheckbox = document.querySelector(selectAllSelector); const rowCheckboxes = form.querySelectorAll(rowCheckboxSelector); const bulkDeleteButton = document.querySelector(buttonSelector); const syncBulkDeleteState = () => { const checkedCount = Array.from(rowCheckboxes).filter((checkbox) => checkbox.checked).length; const enabledCheckboxes = Array.from(rowCheckboxes).filter((checkbox) => !checkbox.disabled); if (bulkDeleteButton) { bulkDeleteButton.disabled = checkedCount === 0; bulkDeleteButton.classList.toggle('opacity-50', checkedCount === 0); bulkDeleteButton.classList.toggle('cursor-not-allowed', checkedCount === 0); } if (selectAllCheckbox) { selectAllCheckbox.checked = checkedCount > 0 && enabledCheckboxes.length > 0 && checkedCount === enabledCheckboxes.length; selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < enabledCheckboxes.length; } }; selectAllCheckbox?.addEventListener('change', () => { rowCheckboxes.forEach((checkbox) => { if (!checkbox.disabled) { checkbox.checked = selectAllCheckbox.checked; } }); syncBulkDeleteState(); }); rowCheckboxes.forEach((checkbox) => { checkbox.addEventListener('change', syncBulkDeleteState); }); form.addEventListener('submit', (event) => { const checkedCount = Array.from(rowCheckboxes).filter((checkbox) => checkbox.checked).length; if (checkedCount === 0) { event.preventDefault(); return; } event.preventDefault(); Swal.fire({ title, text: textBuilder(checkedCount), icon: 'warning', showCancelButton: true, confirmButtonColor: '#e11d48', cancelButtonColor: '#475569', confirmButtonText: confirmText, cancelButtonText: 'Batal', reverseButtons: true, }).then((result) => { if (result.isConfirmed) { form.submit(); } }); }); syncBulkDeleteState(); }; setupBulkDelete({ formSelector: '#bulk-delete-students-form', selectAllSelector: '#select-all-students', rowCheckboxSelector: '.student-row-checkbox', buttonSelector: '#bulk-delete-students-button', title: 'Hapus banyak data siswa?', textBuilder: (checkedCount) => `${checkedCount} data siswa yang dipilih akan dihapus. Tindakan ini tidak bisa dibatalkan.`, confirmText: 'Ya, hapus semua', }); setupBulkDelete({ formSelector: '#bulk-delete-classes-form', selectAllSelector: '#select-all-classes', rowCheckboxSelector: '.class-row-checkbox', buttonSelector: '#bulk-delete-classes-button', title: 'Hapus banyak kelas?', textBuilder: (checkedCount) => `${checkedCount} kelas yang dipilih akan dihapus. Tindakan ini tidak bisa dibatalkan.`, confirmText: 'Ya, hapus semua', }); const promotionForm = document.querySelector('[data-student-promotion-form]'); if (promotionForm) { const rows = promotionForm.querySelectorAll('tbody tr'); const renderTargetOptions = (targetSelect, actionValue) => { if (!targetSelect) { return 0; } const optionKey = actionValue === 'repeat' ? 'optionsRepeat' : 'optionsPromote'; const selectedKey = actionValue === 'repeat' ? 'selectedRepeat' : 'selectedPromote'; const options = JSON.parse(targetSelect.dataset[optionKey] || '[]'); const selectedValue = targetSelect.dataset[selectedKey] || ''; targetSelect.innerHTML = ''; options.forEach((option) => { const optionElement = document.createElement('option'); optionElement.value = option.id; optionElement.textContent = option.name; if (String(option.id) === String(selectedValue)) { optionElement.selected = true; } targetSelect.appendChild(optionElement); }); return options.length; }; const applyRowActionState = (row, actionValue) => { const targetSelect = row.querySelector('.promotion-target-select'); const targetHelp = row.querySelector('.promotion-target-help'); const graduatePlaceholder = row.querySelector('.promotion-graduate-placeholder'); const hiddenTarget = row.querySelector('[data-promotion-target-hidden]'); if (targetSelect) { const requiresTarget = actionValue === 'promote' || actionValue === 'repeat'; const optionCount = requiresTarget ? renderTargetOptions(targetSelect, actionValue) : 0; targetSelect.disabled = !requiresTarget; targetSelect.classList.toggle('bg-slate-100', !requiresTarget); targetSelect.classList.toggle('text-slate-400', !requiresTarget); targetSelect.classList.toggle('hidden', !requiresTarget); if (!requiresTarget) { targetSelect.value = ''; } if (targetHelp) { if (actionValue === 'skip') { targetHelp.textContent = 'Siswa ini akan dilewati saat proses promosi.'; } else if (actionValue === 'graduate') { targetHelp.textContent = 'Aksi lulus tidak memerlukan kelas tujuan.'; } else if (optionCount === 0) { targetHelp.textContent = actionValue === 'repeat' ? 'Belum ada kelas aktif dengan level yang sama pada tahun ajaran target.' : 'Belum ada kelas aktif level berikutnya pada tahun ajaran target.'; } else { targetHelp.textContent = actionValue === 'repeat' ? 'Pilih kelas tujuan dengan level yang sama untuk skenario tinggal kelas.' : 'Pilih kelas tujuan level berikutnya untuk skenario naik kelas.'; } } } if (graduatePlaceholder) { graduatePlaceholder.classList.toggle('hidden', actionValue !== 'graduate'); graduatePlaceholder.classList.toggle('opacity-60', actionValue === 'skip'); } if (hiddenTarget && (actionValue === 'graduate' || actionValue === 'skip')) { hiddenTarget.value = ''; } row.classList.toggle('opacity-60', actionValue === 'skip'); }; rows.forEach((row) => { const actionSelect = row.querySelector('.promotion-action-select'); if (!actionSelect) { return; } applyRowActionState(row, actionSelect.value); actionSelect.addEventListener('change', () => applyRowActionState(row, actionSelect.value)); }); promotionForm.querySelectorAll('[data-promotion-bulk-action]').forEach((button) => { button.addEventListener('click', () => { const actionValue = button.dataset.promotionBulkAction; rows.forEach((row) => { const actionSelect = row.querySelector('.promotion-action-select'); if (!actionSelect) { return; } if (actionValue === 'promote' && actionSelect.querySelector('option[value="promote"]')) { actionSelect.value = 'promote'; } else if (actionValue === 'repeat') { actionSelect.value = 'repeat'; } else if (actionValue === 'skip') { actionSelect.value = 'skip'; } applyRowActionState(row, actionSelect.value); }); }); }); } });