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 = `
`;
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
? `
`
: `
${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 bulkUploadInput = document.querySelector('[data-bulk-upload-input]');
const bulkUploadDropzone = document.querySelector('[data-bulk-upload-dropzone]');
const bulkUploadDropzoneInner = document.querySelector('[data-bulk-upload-dropzone-inner]');
const bulkUploadSummary = document.querySelector('[data-bulk-upload-summary]');
const bulkUploadFileList = document.querySelector('[data-bulk-upload-file-list]');
if (bulkUploadInput && bulkUploadDropzone && bulkUploadDropzoneInner) {
const renderBulkUploadFiles = () => {
const files = Array.from(bulkUploadInput.files || []);
if (!bulkUploadSummary || !bulkUploadFileList) {
return;
}
if (files.length === 0) {
bulkUploadSummary.classList.add('hidden');
bulkUploadFileList.classList.add('hidden');
bulkUploadFileList.innerHTML = '';
return;
}
const totalBytes = files.reduce((sum, file) => sum + (file.size || 0), 0);
const totalMb = (totalBytes / (1024 * 1024)).toFixed(2);
bulkUploadSummary.classList.remove('hidden');
bulkUploadSummary.innerHTML = `${files.length} file siap dipreview
Total ukuran sementara ${totalMb} MB. Batas upload bulk saat ini maksimal 4 MB per file.
`;
bulkUploadFileList.classList.remove('hidden');
bulkUploadFileList.innerHTML = `
File terpilih
Menampilkan ${Math.min(files.length, 8)} dari ${files.length} file
${files.slice(0, 8).map((file) => `
${file.name}
${(file.size / 1024 / 1024).toFixed(2)} MB
${(file.name.split('.').pop() || 'FILE').toUpperCase()}
`).join('')}
${files.length > 8 ? `
+${files.length - 8} file lainnya
` : ''}
`;
};
['dragenter', 'dragover'].forEach((eventName) => {
bulkUploadDropzone.addEventListener(eventName, (event) => {
event.preventDefault();
bulkUploadDropzone.classList.add('border-indigo-400', 'bg-indigo-50/40');
bulkUploadDropzoneInner.classList.add('border-indigo-300', 'bg-indigo-50/50');
});
});
['dragleave', 'drop'].forEach((eventName) => {
bulkUploadDropzone.addEventListener(eventName, (event) => {
event.preventDefault();
bulkUploadDropzone.classList.remove('border-indigo-400', 'bg-indigo-50/40');
bulkUploadDropzoneInner.classList.remove('border-indigo-300', 'bg-indigo-50/50');
});
});
bulkUploadDropzone.addEventListener('drop', (event) => {
const files = event.dataTransfer?.files;
if (files && files.length > 0) {
bulkUploadInput.files = files;
renderBulkUploadFiles();
}
});
bulkUploadInput.addEventListener('change', renderBulkUploadFiles);
renderBulkUploadFiles();
}
const promotionForm = document.querySelector('[data-student-promotion-form]');
if (promotionForm) {
const rows = promotionForm.querySelectorAll('tbody tr');
const counterElements = {
promote: document.querySelector('[data-promotion-counter="promote"]'),
repeat: document.querySelector('[data-promotion-counter="repeat"]'),
graduate: document.querySelector('[data-promotion-counter="graduate"]'),
skip: document.querySelector('[data-promotion-counter="skip"]'),
};
const liveSummary = document.querySelector('[data-promotion-live-summary]');
const capacitySummary = document.querySelector('[data-promotion-capacity-summary]');
const finalPreview = document.querySelector('[data-promotion-final-preview]');
const submitButton = document.querySelector('[data-promotion-submit-button]');
const toolbar = document.querySelector('[data-promotion-toolbar]');
const getOptionsForAction = (targetSelect, actionValue) => {
const optionKey = actionValue === 'repeat' ? 'optionsRepeat' : 'optionsPromote';
return JSON.parse(targetSelect?.dataset?.[optionKey] || '[]');
};
const calculateCounts = () => {
const counts = { promote: 0, repeat: 0, graduate: 0, skip: 0 };
rows.forEach((row) => {
const actionSelect = row.querySelector('.promotion-action-select');
const actionValue = actionSelect?.value || 'skip';
if (counts[actionValue] !== undefined) {
counts[actionValue] += 1;
}
});
return counts;
};
const updateCounters = () => {
const counts = calculateCounts();
Object.entries(counterElements).forEach(([key, element]) => {
if (element) {
element.textContent = counts[key];
}
});
if (liveSummary) {
liveSummary.innerHTML = `Ringkasan live: ${counts.promote} naik, ${counts.repeat} tinggal kelas, ${counts.graduate} lulus, dan ${counts.skip} dilewati.`;
}
};
const updateFinalPreview = () => {
if (!finalPreview) {
return;
}
const counts = calculateCounts();
const totalProcessed = counts.promote + counts.repeat + counts.graduate;
finalPreview.classList.remove('hidden');
finalPreview.innerHTML = `
Preview final sebelum submit
Batch ini akan memproses ${totalProcessed} siswa.
- ${counts.promote} siswa naik kelas
- ${counts.repeat} siswa tinggal kelas
- ${counts.graduate} siswa lulus
- ${counts.skip} siswa dilewati
`;
};
const updateCapacitySummary = () => {
if (!capacitySummary) {
return;
}
const plannedByClass = new Map();
rows.forEach((row) => {
const actionSelect = row.querySelector('.promotion-action-select');
const targetSelect = row.querySelector('.promotion-target-select');
const actionValue = actionSelect?.value || 'skip';
if (!targetSelect || !['promote', 'repeat'].includes(actionValue) || !targetSelect.value) {
return;
}
const options = getOptionsForAction(targetSelect, actionValue);
const selectedOption = options.find((option) => String(option.id) === String(targetSelect.value));
if (!selectedOption) {
return;
}
const entry = plannedByClass.get(String(selectedOption.id)) || {
name: selectedOption.name,
planned: 0,
remaining: selectedOption.remaining_capacity,
capacityLabel: selectedOption.capacity_label,
};
entry.planned += 1;
plannedByClass.set(String(selectedOption.id), entry);
});
const conflicts = Array.from(plannedByClass.values()).filter((entry) => entry.remaining !== null && entry.planned > entry.remaining);
const warnings = Array.from(plannedByClass.values()).filter((entry) => entry.remaining !== null && entry.planned <= entry.remaining && entry.remaining - entry.planned <= 1);
capacitySummary.classList.remove('hidden', 'border-rose-200', 'bg-rose-50', 'text-rose-700', 'border-amber-200', 'bg-amber-50', 'text-amber-700', 'border-emerald-200', 'bg-emerald-50', 'text-emerald-700');
if (conflicts.length > 0) {
capacitySummary.classList.add('border-rose-200', 'bg-rose-50', 'text-rose-700');
capacitySummary.innerHTML = `Konflik kapasitas terdeteksi sebelum submit.
${conflicts.map((entry) => `- ${entry.name}: alokasi ${entry.planned} siswa, sisa kuota hanya ${entry.remaining}. (${entry.capacityLabel})
`).join('')}
`;
if (submitButton) {
submitButton.disabled = true;
submitButton.classList.add('opacity-50', 'cursor-not-allowed');
}
return;
}
if (warnings.length > 0) {
capacitySummary.classList.add('border-amber-200', 'bg-amber-50', 'text-amber-700');
capacitySummary.innerHTML = `Perhatian: alokasi mendekati batas kuota.
${warnings.map((entry) => `- ${entry.name}: alokasi ${entry.planned} siswa. ${entry.capacityLabel}.
`).join('')}
`;
if (submitButton) {
submitButton.disabled = false;
submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
}
return;
}
capacitySummary.classList.add('border-emerald-200', 'bg-emerald-50', 'text-emerald-700');
capacitySummary.innerHTML = 'Cek kapasitas aman.
Belum ada konflik kuota pada alokasi kelas tujuan saat ini.
';
if (submitButton) {
submitButton.disabled = false;
submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
}
};
const updateCapacityWarning = (row, actionValue) => {
const targetSelect = row.querySelector('.promotion-target-select');
const warningBox = row.querySelector('[data-promotion-capacity-warning]');
if (!targetSelect || !warningBox || (actionValue !== 'promote' && actionValue !== 'repeat')) {
warningBox?.classList.add('hidden');
warningBox?.classList.remove('border-rose-200', 'bg-rose-50', 'text-rose-700', 'border-amber-200', 'bg-amber-50', 'text-amber-700', 'border-emerald-200', 'bg-emerald-50', 'text-emerald-700');
return;
}
const options = getOptionsForAction(targetSelect, actionValue);
const selectedOption = options.find((option) => String(option.id) === String(targetSelect.value));
if (!selectedOption) {
warningBox.classList.add('hidden');
warningBox.classList.remove('border-rose-200', 'bg-rose-50', 'text-rose-700', 'border-amber-200', 'bg-amber-50', 'text-amber-700', 'border-emerald-200', 'bg-emerald-50', 'text-emerald-700');
return;
}
warningBox.classList.remove('hidden', 'border-rose-200', 'bg-rose-50', 'text-rose-700', 'border-amber-200', 'bg-amber-50', 'text-amber-700', 'border-emerald-200', 'bg-emerald-50', 'text-emerald-700');
if (selectedOption.warning_level === 'full') {
warningBox.classList.add('border-rose-200', 'bg-rose-50', 'text-rose-700');
warningBox.textContent = `Peringatan: ${selectedOption.name} sudah penuh. ${selectedOption.capacity_label}.`;
} else if (selectedOption.warning_level === 'low') {
warningBox.classList.add('border-amber-200', 'bg-amber-50', 'text-amber-700');
warningBox.textContent = `Perhatian: kuota ${selectedOption.name} menipis. ${selectedOption.capacity_label}.`;
} else {
warningBox.classList.add('border-emerald-200', 'bg-emerald-50', 'text-emerald-700');
warningBox.textContent = `Kelas tujuan tersedia. ${selectedOption.capacity_label}.`;
}
};
const renderTargetOptions = (targetSelect, actionValue) => {
if (!targetSelect) {
return 0;
}
const selectedKey = actionValue === 'repeat' ? 'selectedRepeat' : 'selectedPromote';
const options = getOptionsForAction(targetSelect, actionValue);
const selectedValue = targetSelect.dataset[selectedKey] || '';
targetSelect.innerHTML = '';
options.forEach((option) => {
const optionElement = document.createElement('option');
optionElement.value = option.id;
optionElement.textContent = option.option_label || 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');
updateCapacityWarning(row, actionValue);
updateCounters();
updateCapacitySummary();
updateFinalPreview();
};
rows.forEach((row) => {
const actionSelect = row.querySelector('.promotion-action-select');
const targetSelect = row.querySelector('.promotion-target-select');
if (!actionSelect) {
return;
}
applyRowActionState(row, actionSelect.value);
actionSelect.addEventListener('change', () => applyRowActionState(row, actionSelect.value));
targetSelect?.addEventListener('change', () => {
updateCapacityWarning(row, actionSelect.value);
updateCapacitySummary();
});
});
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 === 'graduate' && actionSelect.querySelector('option[value="graduate"]')) {
actionSelect.value = 'graduate';
} else if (actionValue === 'repeat') {
actionSelect.value = 'repeat';
} else if (actionValue === 'skip') {
actionSelect.value = 'skip';
}
applyRowActionState(row, actionSelect.value);
});
});
});
if (toolbar?.dataset?.classLevel === 'XII') {
const graduateButton = toolbar.querySelector('[data-promotion-bulk-action="graduate"]');
graduateButton?.classList.add('ring-2', 'ring-sky-200');
}
updateCounters();
updateCapacitySummary();
updateFinalPreview();
}
});