271 lines
13 KiB
PHP
271 lines
13 KiB
PHP
<div class="space-y-6">
|
|
<div>
|
|
<h1 class="text-xl font-semibold">Aturan Poin Pelanggaran</h1>
|
|
<p class="text-gray-600 dark:text-gray-400 mt-1 text-sm">
|
|
Kelola daftar jenis pelanggaran dan skor poinnya. Perubahan akan otomatis mempengaruhi perhitungan total poin siswa.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Form -->
|
|
<div class="space-y-6 lg:col-span-1">
|
|
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 shadow-sm space-y-3">
|
|
<h2 class="font-semibold text-gray-900 dark:text-gray-100 text-sm">Form Pelanggaran</h2>
|
|
<input type="hidden" id="form-id" value="">
|
|
<div class="space-y-3 text-sm">
|
|
<div>
|
|
<label class="block mb-1 text-gray-600 dark:text-gray-300">Kategori</label>
|
|
<select id="form-category" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
|
|
<option value="">Pilih kategori</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block mb-1 text-gray-600 dark:text-gray-300">Judul Pelanggaran</label>
|
|
<input type="text" id="form-title" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block mb-1 text-gray-600 dark:text-gray-300">Skor Poin</label>
|
|
<input type="number" id="form-score" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm" min="0">
|
|
</div>
|
|
<div>
|
|
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
|
<input type="checkbox" id="form-active" class="rounded border-gray-300 text-primary focus:ring-primary" checked>
|
|
<span>Aktif</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="block mb-1 text-gray-600 dark:text-gray-300">Deskripsi (opsional)</label>
|
|
<textarea id="form-description" rows="3" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm"></textarea>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button type="button" id="btn-save-violation-master" class="flex-1 inline-flex items-center justify-center gap-2 px-3 py-2.5 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover">
|
|
Simpan
|
|
</button>
|
|
<button type="button" id="btn-reset-violation-master" class="px-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 text-sm hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
Reset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List -->
|
|
<div class="lg:col-span-2 space-y-4">
|
|
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 shadow-sm">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h2 class="font-semibold text-gray-900 dark:text-gray-100 text-sm">Daftar Pelanggaran</h2>
|
|
</div>
|
|
<div id="master-loading" class="py-10 text-center text-gray-500 dark:text-gray-400">Memuat…</div>
|
|
<div id="master-error" class="hidden py-3 text-sm text-red-600 dark:text-red-400"></div>
|
|
<div id="master-content" class="hidden overflow-x-auto">
|
|
<table class="w-full text-left text-xs sm:text-sm">
|
|
<thead class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-400">
|
|
<tr>
|
|
<th class="px-3 py-2 font-medium">Kategori</th>
|
|
<th class="px-3 py-2 font-medium">Judul</th>
|
|
<th class="px-3 py-2 font-medium text-right">Skor</th>
|
|
<th class="px-3 py-2 font-medium">Status</th>
|
|
<th class="px-3 py-2 font-medium text-right">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="master-tbody" class="divide-y divide-gray-200 dark:divide-gray-700"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="discipline-settings-toast" class="fixed bottom-6 right-6 z-[60] flex flex-col gap-2 pointer-events-none"></div>
|
|
|
|
<script>
|
|
(function() {
|
|
var baseUrl = '<?= base_url() ?>'.replace(/\/$/, '');
|
|
var apiViolationsMaster = baseUrl + '/api/discipline/violations';
|
|
var apiViolationsAdmin = baseUrl + '/api/discipline/violations-admin';
|
|
|
|
var formId = document.getElementById('form-id');
|
|
var formCategory = document.getElementById('form-category');
|
|
var formTitle = document.getElementById('form-title');
|
|
var formScore = document.getElementById('form-score');
|
|
var formDescription = document.getElementById('form-description');
|
|
var formActive = document.getElementById('form-active');
|
|
var btnSave = document.getElementById('btn-save-violation-master');
|
|
var btnReset = document.getElementById('btn-reset-violation-master');
|
|
|
|
var masterLoading = document.getElementById('master-loading');
|
|
var masterError = document.getElementById('master-error');
|
|
var masterContent = document.getElementById('master-content');
|
|
var masterTbody = document.getElementById('master-tbody');
|
|
var toastContainer = document.getElementById('discipline-settings-toast');
|
|
|
|
var masterData = [];
|
|
|
|
function showToast(message, type) {
|
|
type = type || 'success';
|
|
var el = document.createElement('div');
|
|
el.className = 'pointer-events-auto px-4 py-3 rounded-lg shadow-lg text-sm font-medium ' + (type === 'success' ? 'bg-green-600 text-white' : 'bg-red-600 text-white');
|
|
el.textContent = message;
|
|
toastContainer.appendChild(el);
|
|
setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 4000);
|
|
}
|
|
|
|
function fetchJson(url) {
|
|
return fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } })
|
|
.then(function(r) { return r.json().then(function(j) { return { ok: r.ok, data: j }; }); });
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
if (str == null) return '';
|
|
var d = document.createElement('div'); d.textContent = str; return d.innerHTML;
|
|
}
|
|
|
|
function resetForm() {
|
|
formId.value = '';
|
|
formCategory.value = '';
|
|
formTitle.value = '';
|
|
formScore.value = '';
|
|
formDescription.value = '';
|
|
formActive.checked = true;
|
|
}
|
|
|
|
btnReset.addEventListener('click', function() {
|
|
resetForm();
|
|
});
|
|
|
|
function loadMaster() {
|
|
masterLoading.classList.remove('hidden');
|
|
masterError.classList.add('hidden');
|
|
masterContent.classList.add('hidden');
|
|
|
|
fetchJson(apiViolationsMaster).then(function(r) {
|
|
masterLoading.classList.add('hidden');
|
|
if (!r.ok) {
|
|
masterError.textContent = (r.data && r.data.message) ? r.data.message : 'Gagal memuat data';
|
|
masterError.classList.remove('hidden');
|
|
return;
|
|
}
|
|
var cats = (r.data && r.data.data) ? r.data.data : [];
|
|
masterData = [];
|
|
// isi kategori dropdown
|
|
var first = formCategory.options[0];
|
|
formCategory.innerHTML = '';
|
|
if (first) formCategory.appendChild(first);
|
|
|
|
cats.forEach(function(cat) {
|
|
var opt = document.createElement('option');
|
|
opt.value = cat.id;
|
|
opt.textContent = '[' + (cat.code || '') + '] ' + (cat.name || '');
|
|
formCategory.appendChild(opt);
|
|
|
|
(cat.items || []).forEach(function(v) {
|
|
masterData.push({
|
|
id: v.id,
|
|
category_id: cat.id,
|
|
category_code: cat.code,
|
|
category_name: cat.name,
|
|
title: v.title,
|
|
description: v.description || '',
|
|
score: v.score,
|
|
is_active: 1 // dari endpoint master kita asumsikan aktif saja
|
|
});
|
|
});
|
|
});
|
|
|
|
renderTable();
|
|
masterContent.classList.remove('hidden');
|
|
}).catch(function() {
|
|
masterLoading.classList.add('hidden');
|
|
masterError.textContent = 'Gagal memuat data';
|
|
masterError.classList.remove('hidden');
|
|
});
|
|
}
|
|
|
|
function renderTable() {
|
|
masterTbody.innerHTML = '';
|
|
if (!masterData.length) return;
|
|
masterData.forEach(function(v) {
|
|
var tr = document.createElement('tr');
|
|
var statusLabel = v.is_active ? 'Aktif' : 'Nonaktif';
|
|
var statusClass = v.is_active ? 'text-green-600 dark:text-green-400' : 'text-gray-500 dark:text-gray-400';
|
|
tr.innerHTML =
|
|
'<td class="px-3 py-2">' + escapeHtml('[' + (v.category_code || '') + '] ' + (v.category_name || '')) + '</td>' +
|
|
'<td class="px-3 py-2">' + escapeHtml(v.title) + '</td>' +
|
|
'<td class="px-3 py-2 text-right">' + (v.score || 0) + '</td>' +
|
|
'<td class="px-3 py-2 ' + statusClass + '">' + statusLabel + '</td>' +
|
|
'<td class="px-3 py-2 text-right">' +
|
|
'<button type="button" class="btn-edit text-primary text-xs sm:text-sm px-2 py-1 rounded hover:bg-primary/10" data-id="' + v.id + '">Edit</button>' +
|
|
'</td>';
|
|
masterTbody.appendChild(tr);
|
|
});
|
|
|
|
masterTbody.querySelectorAll('.btn-edit').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var id = parseInt(btn.getAttribute('data-id'), 10);
|
|
var row = masterData.find(function(x) { return x.id === id; });
|
|
if (!row) return;
|
|
formId.value = row.id;
|
|
formCategory.value = row.category_id;
|
|
formTitle.value = row.title || '';
|
|
formScore.value = row.score || 0;
|
|
formDescription.value = row.description || '';
|
|
formActive.checked = !!row.is_active;
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
});
|
|
});
|
|
}
|
|
|
|
btnSave.addEventListener('click', function() {
|
|
var id = formId.value ? parseInt(formId.value, 10) : null;
|
|
var categoryId = formCategory.value ? parseInt(formCategory.value, 10) : 0;
|
|
var title = formTitle.value.trim();
|
|
var score = parseInt(formScore.value, 10);
|
|
var description = formDescription.value.trim();
|
|
var isActive = formActive.checked ? 1 : 0;
|
|
|
|
if (!categoryId) { showToast('Pilih kategori', 'error'); return; }
|
|
if (!title) { showToast('Judul pelanggaran wajib diisi', 'error'); return; }
|
|
if (isNaN(score)) { showToast('Skor wajib diisi', 'error'); return; }
|
|
|
|
var payload = {
|
|
category_id: categoryId,
|
|
title: title,
|
|
score: score,
|
|
is_active: isActive
|
|
};
|
|
if (description) payload.description = description;
|
|
|
|
btnSave.disabled = true;
|
|
var url = apiViolationsAdmin;
|
|
var method = 'POST';
|
|
if (id) {
|
|
url = apiViolationsAdmin + '/' + id;
|
|
method = 'PUT';
|
|
}
|
|
|
|
fetch(url, {
|
|
method: method,
|
|
credentials: 'same-origin',
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
}).then(function(r) { return r.json().then(function(j) { return { ok: r.ok, data: j }; }); })
|
|
.then(function(r) {
|
|
btnSave.disabled = false;
|
|
if (!r.ok) {
|
|
showToast(r.data && r.data.message ? r.data.message : 'Gagal menyimpan', 'error');
|
|
return;
|
|
}
|
|
showToast('Berhasil disimpan');
|
|
resetForm();
|
|
loadMaster();
|
|
}).catch(function() {
|
|
btnSave.disabled = false;
|
|
showToast('Gagal menyimpan', 'error');
|
|
});
|
|
});
|
|
|
|
loadMaster();
|
|
})();
|
|
</script>
|
|
|