init backend presensi
This commit is contained in:
270
app/Views/dashboard/discipline_settings.php
Normal file
270
app/Views/dashboard/discipline_settings.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user