Files
presensi/app/Views/dashboard/discipline_settings.php
2026-03-05 14:37:36 +07:00

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>