Update graduation workflow, promotions, and admin module improvements
This commit is contained in:
@@ -445,6 +445,243 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
renderBulkUploadFiles();
|
||||
}
|
||||
|
||||
const bulkUploadOverlay = document.querySelector('[data-bulk-upload-overlay]');
|
||||
|
||||
document.querySelectorAll('[data-bulk-upload-form]').forEach((form) => {
|
||||
const submitButton = form.querySelector('[data-bulk-upload-submit-button]');
|
||||
const submitLabel = form.querySelector('[data-bulk-upload-submit-label]');
|
||||
const submitIcon = form.querySelector('[data-bulk-upload-submit-icon]');
|
||||
const mode = form.dataset.bulkUploadMode || 'preview';
|
||||
const useAjaxPreview = form.dataset.bulkUploadAjaxPreview === 'true';
|
||||
const previewUrl = form.dataset.bulkUploadPreviewUrl || form.getAttribute('action') || '';
|
||||
|
||||
const showOverlay = (config = {}) => {
|
||||
if (!bulkUploadOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = bulkUploadOverlay.querySelector('[data-bulk-upload-overlay-title]');
|
||||
const message = bulkUploadOverlay.querySelector('[data-bulk-upload-overlay-message]');
|
||||
const fileCountLabel = bulkUploadOverlay.querySelector('[data-bulk-upload-overlay-file-count]');
|
||||
const stageUpload = bulkUploadOverlay.querySelector('[data-bulk-upload-stage-upload]');
|
||||
const stageMatch = bulkUploadOverlay.querySelector('[data-bulk-upload-stage-match]');
|
||||
const stageSave = bulkUploadOverlay.querySelector('[data-bulk-upload-stage-save]');
|
||||
const progressLabel = bulkUploadOverlay.querySelector('[data-bulk-upload-overlay-progress-label]');
|
||||
const progressPercent = bulkUploadOverlay.querySelector('[data-bulk-upload-overlay-progress-percent]');
|
||||
const progressBar = bulkUploadOverlay.querySelector('[data-bulk-upload-overlay-progress-bar]');
|
||||
const fileCountSource = form.dataset.bulkUploadFileCountSource || 'input';
|
||||
const inputFileCount = bulkUploadInput?.files?.length || 0;
|
||||
const previewFileCount = Number(form.dataset.bulkUploadPreviewCount || 0);
|
||||
const activeFileCount = fileCountSource === 'preview' ? previewFileCount : inputFileCount;
|
||||
const percent = Math.max(0, Math.min(100, Number(config.percent ?? 0)));
|
||||
|
||||
if (title) {
|
||||
title.textContent = config.title || (mode === 'process'
|
||||
? 'Sedang memproses upload permanen...'
|
||||
: 'Sedang menyiapkan preview dokumen...');
|
||||
}
|
||||
|
||||
if (message) {
|
||||
message.textContent = config.message || (mode === 'process'
|
||||
? 'Mohon tunggu. Sistem sedang menyimpan dokumen ke arsip kelulusan dan mencocokkannya ke data siswa. Jangan tutup tab ini agar proses tidak terputus.'
|
||||
: 'Mohon tunggu. File sedang diunggah dan dipetakan oleh sistem untuk menyiapkan preview. Jangan tutup tab ini agar proses tidak terputus.');
|
||||
}
|
||||
|
||||
if (fileCountLabel) {
|
||||
fileCountLabel.textContent = activeFileCount > 0
|
||||
? `${activeFileCount} file sedang diproses`
|
||||
: 'Sedang menghitung jumlah file...';
|
||||
}
|
||||
|
||||
if (stageUpload) {
|
||||
stageUpload.textContent = config.stageUpload || (mode === 'process' ? 'Validasi file upload' : 'Upload file');
|
||||
stageUpload.parentElement?.classList.add('ring-1', 'ring-indigo-300/50', 'bg-indigo-500/10');
|
||||
}
|
||||
|
||||
if (stageMatch) {
|
||||
stageMatch.textContent = config.stageMatch || (mode === 'process' ? 'Cocokkan ke siswa lulus' : 'Susun preview & matching');
|
||||
stageMatch.parentElement?.classList.add('ring-1', 'ring-sky-300/40', 'bg-sky-500/10');
|
||||
}
|
||||
|
||||
if (stageSave) {
|
||||
stageSave.textContent = config.stageSave || (mode === 'process' ? 'Simpan ke arsip kelulusan' : 'Tunggu konfirmasi operator');
|
||||
stageSave.parentElement?.classList.add('ring-1', 'ring-emerald-300/40', 'bg-emerald-500/10');
|
||||
}
|
||||
|
||||
if (progressLabel) {
|
||||
progressLabel.textContent = config.progressLabel || 'Menunggu proses dimulai...';
|
||||
}
|
||||
|
||||
if (progressPercent) {
|
||||
progressPercent.textContent = `${Math.round(percent)}%`;
|
||||
}
|
||||
|
||||
if (progressBar) {
|
||||
progressBar.style.width = `${percent}%`;
|
||||
progressBar.classList.toggle('animate-pulse', !!config.indeterminate);
|
||||
}
|
||||
|
||||
bulkUploadOverlay.classList.remove('hidden');
|
||||
bulkUploadOverlay.classList.add('flex');
|
||||
document.body.classList.add('overflow-hidden');
|
||||
};
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
if (submitButton) {
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
|
||||
if (submitIcon) {
|
||||
submitIcon.classList.remove('fa-magnifying-glass', 'fa-upload', 'fa-cloud-arrow-up');
|
||||
submitIcon.classList.add('fa-spinner', 'fa-spin');
|
||||
}
|
||||
|
||||
if (submitLabel) {
|
||||
submitLabel.textContent = mode === 'process'
|
||||
? 'Sedang memproses upload...'
|
||||
: 'Sedang menyiapkan preview...';
|
||||
}
|
||||
|
||||
if (mode === 'preview' && useAjaxPreview) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(form);
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
||||
|
||||
showOverlay({
|
||||
title: 'Sedang mengunggah file preview...',
|
||||
message: 'Mohon tunggu. Browser sedang mengirim file ke server untuk disiapkan preview dan pencocokan data.',
|
||||
progressLabel: 'Mengunggah file ke server...',
|
||||
percent: 0,
|
||||
stageUpload: 'Upload file',
|
||||
stageMatch: 'Menunggu upload selesai',
|
||||
stageSave: 'Siapkan halaman preview',
|
||||
indeterminate: false,
|
||||
});
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', previewUrl, true);
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
if (csrfToken) {
|
||||
xhr.setRequestHeader('X-CSRF-TOKEN', csrfToken);
|
||||
}
|
||||
|
||||
xhr.upload.addEventListener('progress', (progressEvent) => {
|
||||
if (!progressEvent.lengthComputable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const percent = (progressEvent.loaded / progressEvent.total) * 100;
|
||||
showOverlay({
|
||||
title: 'Sedang mengunggah file preview...',
|
||||
message: 'Mohon tunggu. Browser sedang mengirim file ke server untuk disiapkan preview dan pencocokan data.',
|
||||
progressLabel: `Upload file ${Math.round(percent)}%`,
|
||||
percent,
|
||||
stageUpload: 'Upload file',
|
||||
stageMatch: 'Menunggu upload selesai',
|
||||
stageSave: 'Siapkan halaman preview',
|
||||
indeterminate: false,
|
||||
});
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState !== XMLHttpRequest.DONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
let response = {};
|
||||
|
||||
try {
|
||||
response = JSON.parse(xhr.responseText || '{}');
|
||||
} catch (error) {
|
||||
response = {};
|
||||
}
|
||||
|
||||
showOverlay({
|
||||
title: 'Upload selesai, sistem sedang menyusun preview...',
|
||||
message: 'File sudah terkirim. Sekarang sistem sedang mencocokkan dokumen dan menyiapkan halaman preview.',
|
||||
progressLabel: 'Upload selesai, menyiapkan preview...',
|
||||
percent: 100,
|
||||
stageUpload: 'Upload selesai',
|
||||
stageMatch: 'Mencocokkan ke siswa lulus',
|
||||
stageSave: 'Membuka halaman preview',
|
||||
indeterminate: true,
|
||||
});
|
||||
|
||||
window.location.href = response.redirect_url || window.location.href;
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.classList.remove('overflow-hidden');
|
||||
bulkUploadOverlay?.classList.add('hidden');
|
||||
bulkUploadOverlay?.classList.remove('flex');
|
||||
|
||||
if (submitButton) {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
|
||||
if (submitIcon) {
|
||||
submitIcon.classList.remove('fa-spinner', 'fa-spin');
|
||||
submitIcon.classList.add('fa-magnifying-glass');
|
||||
}
|
||||
|
||||
if (submitLabel) {
|
||||
submitLabel.textContent = 'Preview Mapping Dokumen';
|
||||
}
|
||||
|
||||
let errorMessage = 'Bulk upload gagal diproses. Silakan cek file lalu coba lagi.';
|
||||
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText || '{}');
|
||||
if (response.message) {
|
||||
errorMessage = response.message;
|
||||
} else if (response.errors) {
|
||||
const firstError = Object.values(response.errors).flat()[0];
|
||||
if (firstError) {
|
||||
errorMessage = firstError;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// noop
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Bulk upload gagal',
|
||||
text: errorMessage,
|
||||
confirmButtonColor: '#dc2626',
|
||||
});
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
return;
|
||||
}
|
||||
|
||||
showOverlay({
|
||||
progressLabel: mode === 'process' ? 'Menyimpan dokumen ke arsip...' : 'Menyiapkan proses...',
|
||||
percent: mode === 'process' ? 100 : 15,
|
||||
indeterminate: mode === 'process',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const graduationFilterForm = document.querySelector('[data-graduation-filter-form]');
|
||||
const graduationAcademicYearFilter = document.querySelector('[data-graduation-academic-year-filter]');
|
||||
|
||||
if (graduationFilterForm && graduationAcademicYearFilter) {
|
||||
graduationAcademicYearFilter.addEventListener('change', () => {
|
||||
const classFilter = graduationFilterForm.querySelector('[name="class_id"]');
|
||||
|
||||
if (classFilter) {
|
||||
classFilter.value = '';
|
||||
}
|
||||
|
||||
graduationFilterForm.submit();
|
||||
});
|
||||
}
|
||||
|
||||
const promotionForm = document.querySelector('[data-student-promotion-form]');
|
||||
|
||||
if (promotionForm) {
|
||||
|
||||
Reference in New Issue
Block a user