Gabungkan aplikasi RPS & Portofolio OBE (dashboard jadi bagian aplikasi)
Dashboard pemetaan statis kini dilayani aplikasi di /dashboard (app/static/dashboard.html). Fitur lengkap Tahap 1-4 + dasbor monitoring, menu global, filter semester, input nilai langsung, penggabungan nilai team teaching, tabel bisa urut. Dockerfile berubah dari nginx statis ke uvicorn/FastAPI: healthcheck, proxy-headers, volume /data (SQLite). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
{% extends "base.html" %}
|
||||
{% block isi %}
|
||||
<div class="kpi-baris" title="Persentase dihitung dari MK pada semester terpilih — hijau ≥ 75 %, kuning 40–74 %, merah < 40 %">
|
||||
<div><b id="kpi-mk">{{ daftar|length }}</b> <span>mata kuliah</span></div>
|
||||
<div><b id="kpi-rps">{{ n_selesai }}</b> <span>RPS selesai</span> <span class="kpi-pct" id="pct-rps"></span></div>
|
||||
<div><b id="kpi-draft">{{ n_draft }}</b> <span>RPS draft</span></div>
|
||||
<div><b id="kpi-nilai">{{ st_nilai|length }}</b> <span>MK setor nilai</span> <span class="kpi-pct" id="pct-nilai"></span></div>
|
||||
<div><b id="kpi-porto">{{ st_porto.values()|selectattr('status', 'equalto', 'selesai')|list|length }}</b> <span>portofolio selesai</span> <span class="kpi-pct" id="pct-porto"></span></div>
|
||||
</div>
|
||||
|
||||
{% if user.role == 'dosen' %}
|
||||
{% if hanya_saya %}
|
||||
<div class="info" style="margin-top:0">Menampilkan <b>{{ daftar|length }} MK yang Anda ampu</b>.
|
||||
RPS mata kuliah lain tetap terbuka baca — <a href="/?semua=1">tampilkan seluruh {{ n_total }} MK</a>.</div>
|
||||
{% elif mk_saya %}
|
||||
<div class="info" style="margin-top:0">Menampilkan seluruh {{ n_total }} MK —
|
||||
<a href="/">kembali ke MK yang Anda ampu saja</a>.</div>
|
||||
{% else %}
|
||||
<div class="peringatan" style="margin-top:0">Belum ada MK yang ditugaskan kepada
|
||||
Anda — hubungi kaprodi agar menetapkan penugasan di halaman Kelola Akun.
|
||||
Sementara itu seluruh MK ditampilkan (hanya-baca).</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="filter-baris">
|
||||
<label for="filter-smt">Tampilkan semester:</label>
|
||||
<select id="filter-smt" style="width:auto">
|
||||
<option value="">Semua</option>
|
||||
<option value="ganjil">Ganjil (1, 3, 5, 7)</option>
|
||||
<option value="genap">Genap (2, 4, 6, 8)</option>
|
||||
{% for s in smt.values()|select|unique|sort %}
|
||||
<option value="{{ s }}">Semester {{ s }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span id="filter-info" class="keterangan" style="margin:0"></span>
|
||||
</div>
|
||||
|
||||
{% macro stempel(st) -%}
|
||||
{% if st and st.status == 'selesai' %}<span class="stamp stamp-ok">SELESAI</span>
|
||||
{% elif st %}<span class="stamp stamp-draft">DRAFT</span>
|
||||
{% else %}<span class="stamp stamp-no">BELUM</span>{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:56px">No.</th>
|
||||
<th style="width:112px">Kode</th>
|
||||
<th class="tengah" style="width:52px">Smt</th>
|
||||
<th>Mata Kuliah</th>
|
||||
<th style="width:150px">Pengampu</th>
|
||||
<th style="width:120px">CPL Dipikul</th>
|
||||
<th class="tengah" style="width:104px">RPS</th>
|
||||
<th class="tengah" style="width:104px">Nilai</th>
|
||||
<th class="tengah" style="width:104px">Portofolio</th>
|
||||
<th style="width:170px">Pemutakhiran Terakhir</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mk in daftar %}
|
||||
{% set st = status.get(mk.kode) %}
|
||||
{% set sn = st_nilai.get(mk.kode) %}
|
||||
{% set sp = st_porto.get(mk.kode) %}
|
||||
<tr data-smt="{{ smt.get(mk.kode) or '' }}"
|
||||
data-rps="{{ st.status if st else '' }}"
|
||||
data-nilai="{{ sn.status if sn else '' }}"
|
||||
data-porto="{{ sp.status if sp else '' }}">
|
||||
<td class="tengah">{{ mk.no }}</td>
|
||||
<td>{{ mk.kode }}</td>
|
||||
<td class="tengah">{{ smt.get(mk.kode) or '—' }}</td>
|
||||
<td>{{ mk.nama }}<br>
|
||||
<span style="font-size:12px;color:var(--grey)">{{ mk.n_cpmk }} CPMK · {{ mk.n_sub }} Sub-CPMK</span>
|
||||
</td>
|
||||
<td style="font-size:13px">{{ pengampu.get(mk.kode, [])|join(', ') or '—' }}</td>
|
||||
<td>{{ mk.cpls|map('replace','CPL-','')|join(', ') }}</td>
|
||||
<td class="tengah"><a href="/rps/{{ mk.kode }}" style="text-decoration:none">{{ stempel(st) }}</a></td>
|
||||
<td class="tengah"><a href="/nilai/{{ mk.kode }}" style="text-decoration:none">{{ stempel(sn) }}</a></td>
|
||||
<td class="tengah"><a href="/portofolio/{{ mk.kode }}" style="text-decoration:none">{{ stempel(sp) }}</a></td>
|
||||
<td style="font-size:12.5px">
|
||||
{% set terakhir = [st, sn, sp]|select|sort(attribute='updated_at')|last %}
|
||||
{% if terakhir %}{{ terakhir.updated_at }}{% if terakhir.updated_by %} — {{ terakhir.updated_by }}{% endif %}{% else %}—{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var pilih = document.getElementById('filter-smt');
|
||||
var info = document.getElementById('filter-info');
|
||||
var baris = document.querySelectorAll('table.grid tbody tr');
|
||||
|
||||
function cocok(tr, v) {
|
||||
var smt = tr.dataset.smt;
|
||||
return v === '' ? true :
|
||||
v === 'ganjil' ? smt !== '' && smt % 2 === 1 :
|
||||
v === 'genap' ? smt !== '' && smt % 2 === 0 :
|
||||
smt === v;
|
||||
}
|
||||
|
||||
function isiPct(id, n, total) {
|
||||
var el = document.getElementById(id);
|
||||
if (!total) { el.textContent = ''; el.className = 'kpi-pct'; return; }
|
||||
var p = Math.round(100 * n / total);
|
||||
el.textContent = p + ' %';
|
||||
el.className = 'kpi-pct ' +
|
||||
(p >= 75 ? 'kpi-hijau' : p >= 40 ? 'kpi-kuning' : 'kpi-merah');
|
||||
}
|
||||
|
||||
function perbarui() {
|
||||
var v = pilih.value, n = 0, rSel = 0, rDraft = 0, nilai = 0, porto = 0;
|
||||
baris.forEach(function (tr) {
|
||||
var tampil = cocok(tr, v);
|
||||
tr.style.display = tampil ? '' : 'none';
|
||||
if (!tampil) return;
|
||||
n++;
|
||||
if (tr.dataset.rps === 'selesai') rSel++;
|
||||
if (tr.dataset.rps === 'draft') rDraft++;
|
||||
if (tr.dataset.nilai !== '') nilai++;
|
||||
if (tr.dataset.porto === 'selesai') porto++;
|
||||
});
|
||||
document.getElementById('kpi-mk').textContent = n;
|
||||
document.getElementById('kpi-rps').textContent = rSel;
|
||||
document.getElementById('kpi-draft').textContent = rDraft;
|
||||
document.getElementById('kpi-nilai').textContent = nilai;
|
||||
document.getElementById('kpi-porto').textContent = porto;
|
||||
isiPct('pct-rps', rSel, n);
|
||||
isiPct('pct-nilai', nilai, n);
|
||||
isiPct('pct-porto', porto, n);
|
||||
info.textContent = v === '' ? '' : n + ' dari ' + baris.length + ' MK ditampilkan';
|
||||
}
|
||||
|
||||
pilih.addEventListener('change', perbarui);
|
||||
perbarui();
|
||||
})();
|
||||
</script>
|
||||
<p class="keterangan" style="margin-top:10px">Klik stempel untuk membuka form terkait. Alur per MK: <b>RPS</b> (rencana & bobot) → <b>Nilai</b> (unggah Excel / rata-rata) → <b>Portofolio</b> (refleksi & dokumen akhir). Rekap prodi menggulung otomatis dari nilai yang masuk.</p>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user