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,121 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Kelola Akun & Mahasiswa — RPS & Portofolio UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<h1>Kelola Akun & Mahasiswa</h1>
|
||||
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
||||
|
||||
<h2 class="bagian">A. Akun Pengguna & Penugasan Mata Kuliah</h2>
|
||||
<p class="keterangan">Dosen hanya dapat mengedit RPS/portofolio dan melihat nilai
|
||||
mahasiswa pada MK yang ditugaskan kepadanya. Kaprodi dapat mengakses semuanya.</p>
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:170px">Nama</th>
|
||||
<th style="width:160px">NIP</th>
|
||||
<th style="width:90px">Peran</th>
|
||||
<th>MK yang Diampu</th>
|
||||
<th style="width:340px">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in pengguna %}
|
||||
<tr>
|
||||
<td>{{ p.nama }}</td>
|
||||
<td>{{ p.username }}</td>
|
||||
<td>{{ 'Kaprodi' if p.role == 'kaprodi' else 'Dosen' }}</td>
|
||||
<td>
|
||||
{% if p.role == 'kaprodi' %}
|
||||
<i style="color:var(--grey)">semua MK</i>
|
||||
{% else %}
|
||||
{{ p.mk|join(', ') if p.mk else '—' }}
|
||||
<details style="margin-top:6px">
|
||||
<summary style="cursor:pointer;color:var(--blue);font-size:13px">Ubah penugasan…</summary>
|
||||
<form method="post" action="/admin/pengguna/{{ p.id }}/tugas">
|
||||
<div class="pilih-sub" style="max-height:220px;margin:6px 0">
|
||||
{% for mk in daftar %}
|
||||
<label><input type="checkbox" name="mk" value="{{ mk.kode }}"
|
||||
{% if mk.kode in p.mk %}checked{% endif %}> {{ mk.kode }} — {{ mk.nama }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button class="tombol garis" type="submit" style="padding:4px 12px;font-size:13px">Simpan Penugasan</button>
|
||||
</form>
|
||||
</details>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="/admin/pengguna/{{ p.id }}/reset"
|
||||
style="display:inline">
|
||||
<button class="tombol garis" type="submit"
|
||||
style="padding:4px 10px;font-size:13px">Reset Password ke NIP</button>
|
||||
</form>
|
||||
{% if p.id != user.id %}
|
||||
<form method="post" action="/admin/pengguna/{{ p.id }}/hapus"
|
||||
style="display:inline" onsubmit="return confirm('Hapus akun {{ p.nama }} ({{ p.username }})?')">
|
||||
<button class="tombol garis" type="submit"
|
||||
style="padding:4px 10px;font-size:13px;color:var(--red);border-color:var(--red)">Hapus</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 class="bagian">B. Buat Akun Baru</h2>
|
||||
<p class="keterangan">NIP menjadi username sekaligus password awal — dosen langsung
|
||||
bisa masuk dengan NIP-nya.</p>
|
||||
<form method="post" action="/admin/pengguna" class="aksi-baris"
|
||||
style="border-top:none;padding-top:0;margin-top:8px">
|
||||
<label>Nama lengkap <input type="text" name="nama" required style="width:230px"></label>
|
||||
<label>NIP <input type="text" name="nip" required style="width:180px"></label>
|
||||
<label>Peran
|
||||
<select name="role" style="width:110px">
|
||||
<option value="dosen" selected>Dosen</option>
|
||||
<option value="kaprodi">Kaprodi</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="tombol" type="submit">Buat Akun</button>
|
||||
</form>
|
||||
|
||||
<h2 class="bagian">C. Daftar Mahasiswa Mengontrak MK — {{ tahun }}</h2>
|
||||
<p class="keterangan">Unggah satu file Excel berisi seluruh kontrak semester ini
|
||||
(kolom: NPM, Nama, Kode MK, Kelas — satu baris per kontrak). Impor baru
|
||||
<b>menggantikan seluruh daftar</b> tahun ajaran aktif. Daftar ini dipakai untuk
|
||||
mengisi otomatis template nilai tiap MK.</p>
|
||||
<div class="aksi-baris" style="border-top:none;padding-top:0;margin-top:8px">
|
||||
<a class="tombol garis" href="/admin/mahasiswa/template.xlsx">Unduh Template Daftar Mahasiswa (.xlsx)</a>
|
||||
<form method="post" action="/admin/mahasiswa" enctype="multipart/form-data"
|
||||
style="display:inline-flex;gap:10px;align-items:center">
|
||||
<input type="file" name="berkas" accept=".xlsx" required>
|
||||
<button class="tombol" type="submit">Impor & Ganti Daftar</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if mhs %}
|
||||
<div class="gulir" style="margin-top:12px">
|
||||
<table class="grid" style="max-width:720px">
|
||||
<thead>
|
||||
<tr><th style="width:120px">Kode MK</th><th class="tengah" style="width:130px">Mahasiswa</th><th>Kelas</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mk in daftar if mk.kode in mhs %}
|
||||
<tr>
|
||||
<td>{{ mk.kode }} — {{ mk.nama }}</td>
|
||||
<td class="tengah">{{ mhs[mk.kode].n }}</td>
|
||||
<td>{{ mhs[mk.kode].kelas|join(', ') if mhs[mk.kode].kelas else '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="keterangan">Belum ada daftar mahasiswa untuk tahun ajaran ini.</p>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="bagian">D. Cadangan Data</h2>
|
||||
<p class="keterangan">Unduh salinan seluruh isian secara berkala dan simpan di
|
||||
tempat aman — terutama sebelum pemutakhiran aplikasi atau pergantian server.</p>
|
||||
<a class="tombol garis" href="/cadangan.json">Unduh cadangan JSON (seluruh isian)</a>
|
||||
<a class="tombol garis" href="/cadangan.db">Unduh cadangan SQLite (file DB mentah)</a>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block judul %}RPS & Portofolio — Informatika UNTAN{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<script src="/static/tabel.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="kertas">
|
||||
<div class="kop">
|
||||
<img class="kop-logo" src="/static/logo-untan.png" alt="Logo Universitas Tanjungpura" width="74" height="74">
|
||||
<div class="kop-teks">
|
||||
<div class="uni">UNIVERSITAS TANJUNGPURA — FAKULTAS TEKNIK</div>
|
||||
<div class="prodi">PROGRAM STUDI INFORMATIKA</div>
|
||||
<div class="app">Sistem Penyusunan RPS & Portofolio Mata Kuliah — Kurikulum OBE · {{ tahun }}</div>
|
||||
</div>
|
||||
<span class="kop-imbang" aria-hidden="true"></span>
|
||||
</div>
|
||||
<hr class="kop-rule"><hr class="kop-rule2">
|
||||
|
||||
{% if user %}
|
||||
{% set path = request.url.path %}
|
||||
{% macro tautan(href, label) -%}
|
||||
<a href="{{ href }}"{% if path == href %} class="aktif" aria-current="page"{% endif %}>{{ label }}</a>
|
||||
{%- endmacro %}
|
||||
<nav class="menu-baris">
|
||||
<span class="menu-kiri">
|
||||
{{ tautan('/', 'Beranda') }}
|
||||
{{ tautan('/pantau', 'Dasbor Monitoring') }}
|
||||
{{ tautan('/rekap', 'Rekap CPL/CPMK') }}
|
||||
{{ tautan('/rekap/riwayat', 'Riwayat') }}
|
||||
{{ tautan('/metodologi', 'Metodologi') }}
|
||||
{{ tautan('/dashboard', 'Pemetaan CPMK') }}
|
||||
{% if user.role == 'kaprodi' %}{{ tautan('/admin', 'Kelola Akun & Mahasiswa') }}{% endif %}
|
||||
</span>
|
||||
<span class="menu-kanan">
|
||||
<b>{{ user.nama }}</b> — {{ 'Kaprodi' if user.role == 'kaprodi' else 'Dosen' }}
|
||||
· <a href="/keluar">Keluar</a>
|
||||
</span>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% block isi %}{% endblock %}
|
||||
|
||||
<div class="kaki">
|
||||
<span>Format keluaran: RPS APTIKOM 2024 (.docx) · Portofolio MK (.docx)</span>
|
||||
<span>Pemetaan: 10 CPL · 34 CPMK · 49 MK · 549 Sub-CPMK</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1,189 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Nilai {{ peta.nama }} — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<div class="nav-baris">
|
||||
<div>MK ini: <a href="/rps/{{ peta.kode }}">RPS</a> · <b>Input Nilai</b> · <a href="/portofolio/{{ peta.kode }}">Portofolio</a></div>
|
||||
</div>
|
||||
|
||||
<h1>Input Nilai — {{ peta.nama }} <span style="color:var(--grey)">({{ peta.kode }})</span></h1>
|
||||
|
||||
<p class="keterangan">Apa pun cara inputnya, pengukuran memakai <b>satu metodologi
|
||||
yang sama</b>: skor per Sub-CPMK digulung ke CPMK lalu CPL
|
||||
(rincian di halaman <a href="/metodologi">Metodologi</a>). Pilih <b>satu</b> cara
|
||||
di bawah — menyimpan lewat cara mana pun menggantikan data nilai MK ini.</p>
|
||||
|
||||
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
||||
{% for w in peringatan %}<div class="peringatan">{{ w }}</div>{% endfor %}
|
||||
|
||||
{% if tersimpan %}
|
||||
<div class="info">
|
||||
Data tersimpan: mode
|
||||
<b>{% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ tersimpan.data.mahasiswa|length }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %}</b>
|
||||
— terakhir disimpan {{ tersimpan.updated_at }}{% if tersimpan.updated_by %} oleh {{ tersimpan.updated_by }}{% endif %}.
|
||||
{% if tersimpan.data.sumber %}<br>Riwayat unggahan:
|
||||
{{ tersimpan.data.sumber if tersimpan.data.sumber is string else tersimpan.data.sumber|join(' · ') }}.{% endif %}
|
||||
<form method="post" action="/nilai/{{ peta.kode }}" style="display:inline;margin-left:8px"
|
||||
onsubmit="return confirm('Hapus SELURUH nilai tersimpan MK ini untuk {{ tahun }}? Tindakan ini tidak bisa dibatalkan.')">
|
||||
<button class="tombol garis" type="submit" name="aksi" value="hapus"
|
||||
style="color:var(--red);border-color:var(--red);padding:2px 10px;font-size:13px">Mulai Ulang (hapus semua nilai)</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if grid %}
|
||||
{% set per_mhs = tersimpan and tersimpan.data.mode == 'per_mahasiswa' %}
|
||||
<h2 class="bagian">NILAI PER MAHASISWA <span style="font-weight:normal;font-style:italic;color:var(--grey)">(ketik langsung di sel, 0–100; kolom Nilai Akhir & baris rata-rata dihitung dari data tersimpan)</span></h2>
|
||||
<p class="keterangan">
|
||||
Daftar mahasiswa dari impor kaprodi. Kosongkan sel yang belum dinilai;
|
||||
<b>Simpan Nilai</b> menyimpan persis isi tabel. Nilai Akhir = tertimbang bobot
|
||||
komponen RPS; angkanya diperbarui setelah disimpan.
|
||||
</p>
|
||||
<form method="post" action="/nilai/{{ peta.kode }}">
|
||||
<div class="gulir" style="max-height:480px;overflow-y:auto">
|
||||
{% set ada_kelas = grid|selectattr('kelas')|list|length > 0 %}
|
||||
{% set n_kiri = 4 if ada_kelas else 3 %}
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:44px" class="tengah">No.</th>
|
||||
<th style="width:112px">NPM</th>
|
||||
<th style="min-width:150px">Nama</th>
|
||||
{% if ada_kelas %}<th style="width:60px" class="tengah">Kelas</th>{% endif %}
|
||||
{% for s in peta.subs %}<th class="tengah" style="font-size:11px;min-width:62px">{{ s.kode }}</th>{% endfor %}
|
||||
<th class="tengah" style="width:80px">Nilai Akhir</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in grid %}
|
||||
{% set i = loop.index0 %}
|
||||
<tr>
|
||||
<td class="tengah">{{ loop.index }}
|
||||
<input type="hidden" name="npm_{{ i }}" value="{{ m.npm }}">
|
||||
<input type="hidden" name="nama_{{ i }}" value="{{ m.nama }}"></td>
|
||||
<td>{{ m.npm }}</td>
|
||||
<td>{{ m.nama }}</td>
|
||||
{% if ada_kelas %}<td class="tengah">{{ m.kelas or '—' }}</td>{% endif %}
|
||||
{% for s in peta.subs %}
|
||||
<td class="isian"><input type="number" name="skor_{{ i }}_{{ s.kode }}"
|
||||
min="0" max="100" step="0.01"
|
||||
value="{{ '%g'|format(m.skor[s.kode]) if s.kode in m.skor else '' }}"></td>
|
||||
{% endfor %}
|
||||
<td class="tengah"><b>{{ '%.1f'|format(akhir.get(m.npm)) if akhir.get(m.npm) is not none else '—' }}</b></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if per_mhs and hasil %}
|
||||
<tr class="total">
|
||||
<td colspan="{{ n_kiri }}" style="text-align:right">Rata-rata kelas (tersimpan)</td>
|
||||
{% for s in peta.subs %}
|
||||
{% set info = hasil.sub.get(s.kode) %}
|
||||
<td class="tengah">{{ '%.1f'|format(info.rata) if info else '—' }}</td>
|
||||
{% endfor %}
|
||||
<td class="tengah">{{ '%.1f'|format(akhir.values()|sum / akhir|length) if akhir else '—' }}</td>
|
||||
</tr>
|
||||
<tr class="total">
|
||||
<td colspan="{{ n_kiri }}" style="text-align:right">% lulus (≥ {{ ambang|round(0)|int }})</td>
|
||||
{% for s in peta.subs %}
|
||||
{% set info = hasil.sub.get(s.kode) %}
|
||||
<td class="tengah" {% if info and info.lulus_pct is not none and info.rata < ambang %}style="color:var(--red)"{% endif %}>{{ '%.0f'|format(info.lulus_pct) ~ '%' if info and info.lulus_pct is not none else '—' }}</td>
|
||||
{% endfor %}
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="aksi-baris">
|
||||
<span style="color:var(--grey);font-size:13px">Disimpan atas nama <b>{{ user.nama }}</b></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="tombol" type="submit" name="aksi" value="grid">Simpan Nilai</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<details class="cara-alt"{% if not grid %} open{% endif %}>
|
||||
<summary>{{ 'ALTERNATIF — ' if grid }}UNGGAH EXCEL PER MAHASISWA <span style="font-weight:normal;font-style:italic;color:var(--grey)">(cocok bila nilai sudah direkap di Excel, atau team teaching mengisi terpisah)</span></summary>
|
||||
<p class="keterangan">
|
||||
Unduh template berkolom Sub-CPMK MK ini, isi nilai 0–100 per mahasiswa, lalu unggah kembali.
|
||||
{% if terdaftar %}Template sudah berisi <b>{{ terdaftar|length }} mahasiswa</b> yang
|
||||
mengontrak MK ini (dari daftar impor kaprodi).
|
||||
{% else %}<b>Daftar mahasiswa MK ini belum diimpor kaprodi</b> — template kosong,
|
||||
isi NPM & nama secara manual.{% endif %}
|
||||
<br><b>Team teaching:</b> tiap dosen cukup mengunggah bagiannya sendiri — kolom
|
||||
Sub-CPMK yang diampunya (mis. sebelum/sesudah UTS) atau baris mahasiswa kelasnya.
|
||||
Unggahan <b>digabung</b>: sel terisi memperbarui nilai, sel kosong tidak menghapus
|
||||
nilai yang sudah diunggah dosen lain.
|
||||
</p>
|
||||
<div class="aksi-baris" style="border-top:none;margin-top:0;padding-top:0">
|
||||
<a class="tombol garis" href="/nilai/{{ peta.kode }}/template.xlsx">Unduh Template Excel</a>
|
||||
</div>
|
||||
<form method="post" action="/nilai/{{ peta.kode }}" enctype="multipart/form-data">
|
||||
<div class="aksi-baris" style="border-top:none">
|
||||
<input type="file" name="berkas" accept=".xlsx" style="width:auto">
|
||||
<button class="tombol" type="submit" name="aksi" value="unggah">Unggah & Hitung</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details class="cara-alt"{% if tersimpan and tersimpan.data.mode == 'agregat' %} open{% endif %}>
|
||||
<summary>ALTERNATIF — KETIK RATA-RATA KELAS PER SUB-CPMK <span style="font-weight:normal;font-style:italic;color:var(--grey)">(hanya bila nilai per mahasiswa tidak tersedia; ditandai "bukti agregat" pada rekap)</span></summary>
|
||||
<form method="post" action="/nilai/{{ peta.kode }}">
|
||||
<div class="gulir" style="max-height:420px;overflow-y:auto">
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:120px">Sub-CPMK</th><th>Uraian</th><th class="tengah" style="width:130px">Rata-rata (0–100)</th></tr></thead>
|
||||
<tbody>
|
||||
{% for s in peta.subs %}
|
||||
<tr>
|
||||
<td class="tengah">{{ s.kode }}</td>
|
||||
<td>{{ s.uraian[:110] }}{% if s.uraian|length > 110 %}…{% endif %}</td>
|
||||
<td class="isian"><input type="number" name="rata_{{ s.kode }}" min="0" max="100" step="0.01"
|
||||
value="{% if tersimpan and tersimpan.data.mode == 'agregat' %}{{ tersimpan.data.agregat.get(s.kode, '') }}{% endif %}"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="aksi-baris">
|
||||
<span style="color:var(--grey);font-size:13px">Disimpan atas nama <b>{{ user.nama }}</b></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="tombol garis" type="submit" name="aksi" value="agregat">Simpan Rata-rata</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
{% if hasil %}
|
||||
<h2 class="bagian">HASIL KETERCAPAIAN MK INI <span style="font-weight:normal;font-style:italic;color:var(--grey)">(ambang {{ ambang|round(0)|int }}; roll-up bagi rata)</span></h2>
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead><tr><th>CPMK</th><th class="tengah">Skor</th><th class="tengah">Sub Ternilai</th><th>CPL Didukung</th></tr></thead>
|
||||
<tbody>
|
||||
{% for kode_c, v in hasil.cpmk.items() %}
|
||||
<tr>
|
||||
<td>{{ kode_c }}</td>
|
||||
<td class="tengah">{% if v.skor is not none %}<b>{{ '%.2f'|format(v.skor) }}</b>{% else %}—{% endif %}</td>
|
||||
<td class="tengah">{{ v.n_ternilai }}/{{ v.n_sub }}</td>
|
||||
<td>{{ v.cpl|join(', ') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="gulir" style="margin-top:10px">
|
||||
<table class="grid">
|
||||
<thead><tr><th>CPL (dalam MK ini)</th><th class="tengah">Skor</th><th class="tengah">Status</th></tr></thead>
|
||||
<tbody>
|
||||
{% for kode_c, v in hasil.cpl.items() %}
|
||||
<tr>
|
||||
<td>{{ kode_c }}</td>
|
||||
<td class="tengah">{% if v.skor is not none %}<b>{{ '%.2f'|format(v.skor) }}</b>{% else %}—{% endif %}</td>
|
||||
<td class="tengah">
|
||||
{% if v.skor is none %}<span class="stamp stamp-draft">BELUM TERUKUR</span>
|
||||
{% elif v.skor >= ambang %}<span class="stamp stamp-ok">TERCAPAI</span>
|
||||
{% else %}<span class="stamp stamp-no">DI BAWAH AMBANG</span>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,83 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Portofolio {{ peta.nama }} — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<div class="nav-baris">
|
||||
<div>MK ini: <a href="/rps/{{ peta.kode }}">RPS</a> · <a href="/nilai/{{ peta.kode }}">Input Nilai</a> · <b>Portofolio</b></div>
|
||||
<div>
|
||||
{% if status == 'selesai' %}<span class="stamp stamp-ok">SELESAI</span>
|
||||
{% elif status == 'draft' %}<span class="stamp stamp-draft">DRAFT</span>
|
||||
{% else %}<span class="stamp stamp-no">BELUM</span>{% endif %}
|
||||
{% if updated_at %}<span style="color:var(--grey);font-size:13px"> · disimpan {{ updated_at }}{% if updated_by %} oleh {{ updated_by }}{% endif %}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Portofolio Mata Kuliah — {{ peta.nama }} <span style="color:var(--grey)">({{ peta.kode }})</span></h1>
|
||||
|
||||
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
||||
|
||||
{% if not boleh_edit %}
|
||||
<div class="info">Anda bukan pengampu MK ini — form terbuka <b>hanya untuk dibaca</b>;
|
||||
unduhan dokumen (memuat nilai mahasiswa) juga dibatasi untuk pengampu dan kaprodi.</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not ada_rps %}
|
||||
<div class="peringatan">RPS MK ini belum disusun — bagian Rencana Penilaian, korelasi bobot, dan nilai akhir tertimbang akan kosong. <a href="/rps/{{ peta.kode }}">Susun RPS dulu →</a></div>
|
||||
{% endif %}
|
||||
{% if not ada_nilai %}
|
||||
<div class="peringatan">Nilai MK ini belum diinput — bagian Penilaian, Evaluasi Ketercapaian, dan Simpulan statistik akan kosong. <a href="/nilai/{{ peta.kode }}">Input nilai dulu →</a></div>
|
||||
{% endif %}
|
||||
{% if ada_rps and ada_nilai %}
|
||||
<div class="info">RPS ✓ dan Nilai ✓ tersedia — tabel korelasi, penilaian, evaluasi ketercapaian, dan statistik akan diisi otomatis di dokumen. Anda hanya melengkapi bagian di bawah.</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/portofolio/{{ peta.kode }}">
|
||||
<h2 class="bagian">IDENTITAS DOKUMEN (LEMBAR PENGESAHAN)</h2>
|
||||
<table class="grid">
|
||||
<tr><td class="label">Nomor Dokumen</td><td class="isian"><input type="text" name="nomor" value="{{ f.dokumen.nomor }}" placeholder="mis. PORTO/INF/2026/001 — boleh kosong"></td></tr>
|
||||
<tr><td class="label">No. Revisi</td><td class="isian"><input type="text" name="revisi" value="{{ f.dokumen.revisi }}"></td></tr>
|
||||
<tr><td class="label">Tanggal Terbit</td><td class="isian"><input type="text" name="tgl_terbit" value="{{ f.dokumen.tgl_terbit }}" placeholder="mis. 20 Desember 2026"></td></tr>
|
||||
<tr><td class="label">Kurikulum Tahun</td><td class="isian"><input type="text" name="kurikulum_tahun" value="{{ f.dokumen.kurikulum_tahun }}"></td></tr>
|
||||
<tr><td class="label">Nama Dekan</td><td class="isian"><input type="text" name="dekan" value="{{ f.dokumen.dekan }}" placeholder="untuk kolom pengesahan"></td></tr>
|
||||
<tr><td class="label">Status Dokumen</td>
|
||||
<td class="isian">
|
||||
<select name="status_dokumen" style="width:auto">
|
||||
<option {% if f.dokumen.status_dokumen == 'Master' %}selected{% endif %}>Master</option>
|
||||
<option {% if f.dokumen.status_dokumen == 'Copy' %}selected{% endif %}>Copy</option>
|
||||
</select>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">REALISASI PEMBELAJARAN</h2>
|
||||
<p class="keterangan">Ceritakan pelaksanaan nyata semester ini: penyesuaian jadwal/materi/metode terhadap rencana di RPS, kehadiran, dan hal penting lain.</p>
|
||||
<textarea name="realisasi" rows="6">{{ f.realisasi }}</textarea>
|
||||
|
||||
<h2 class="bagian">KENDALA & TINDAK LANJUT</h2>
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:50px" class="tengah">No.</th><th>Kendala</th><th>Tindak Lanjut</th></tr></thead>
|
||||
<tbody>
|
||||
{% for k in f.kendala[:5] %}
|
||||
<tr>
|
||||
<td class="tengah">{{ loop.index }}</td>
|
||||
<td class="isian"><textarea name="kendala_{{ loop.index }}" rows="2">{{ k.kendala }}</textarea></td>
|
||||
<td class="isian"><textarea name="tindak_{{ loop.index }}" rows="2">{{ k.tindak }}</textarea></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">SIMPULAN HASIL EVALUASI</h2>
|
||||
<p class="keterangan">Statistik nilai, frekuensi huruf mutu, dan tabel ketercapaian CPL/CPMK dihitung dan dicetak otomatis; tuliskan simpulan naratifnya di sini.</p>
|
||||
<textarea name="simpulan" rows="4">{{ f.simpulan }}</textarea>
|
||||
|
||||
<div class="aksi-baris">
|
||||
{% if boleh_edit %}
|
||||
<span style="color:var(--grey);font-size:13px">Disimpan atas nama <b>{{ user.nama }}</b></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="tombol garis" type="submit" name="aksi" value="draft">Simpan Draft</button>
|
||||
<button class="tombol selesai" type="submit" name="aksi" value="selesai">Simpan & Tandai Selesai</button>
|
||||
{% if status %}<a class="tombol" href="/portofolio/{{ peta.kode }}/unduh">Unduh .docx</a>
|
||||
<a class="tombol garis" href="/portofolio/{{ peta.kode }}/unduh.pdf">PDF</a>{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,228 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}RPS {{ peta.nama }} — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<div class="nav-baris">
|
||||
<div>MK ini: <b>RPS</b> · <a href="/nilai/{{ peta.kode }}">Input Nilai</a> · <a href="/portofolio/{{ peta.kode }}">Portofolio</a></div>
|
||||
<div>
|
||||
{% if status == 'selesai' %}<span class="stamp stamp-ok">SELESAI</span>
|
||||
{% elif status == 'draft' %}<span class="stamp stamp-draft">DRAFT</span>
|
||||
{% else %}<span class="stamp stamp-no">BELUM</span>{% endif %}
|
||||
{% if updated_at %}<span style="color:var(--grey);font-size:13px"> · disimpan {{ updated_at }}{% if updated_by %} oleh {{ updated_by }}{% endif %}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Rencana Pembelajaran Semester — {{ peta.nama }} <span style="color:var(--grey)">({{ peta.kode }})</span></h1>
|
||||
|
||||
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
||||
|
||||
{% if not boleh_edit %}
|
||||
<div class="info">Anda bukan pengampu MK ini — form terbuka <b>hanya untuk dibaca</b>.
|
||||
Hubungi kaprodi bila penugasan MK Anda perlu diubah.</div>
|
||||
{% endif %}
|
||||
|
||||
{% if tahun_lain and boleh_edit %}
|
||||
<div class="info">
|
||||
MK ini punya RPS pada tahun ajaran lain. Salin sebagai titik awal (status draft, bisa diedit):
|
||||
{% for t in tahun_lain %}
|
||||
<form method="post" action="/rps/{{ peta.kode }}/salin" style="display:inline">
|
||||
<input type="hidden" name="dari" value="{{ t }}">
|
||||
<button class="tombol garis" type="submit" style="padding:3px 10px;font-size:13px">Salin dari {{ t }}</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/rps/{{ peta.kode }}">
|
||||
|
||||
<h2 class="bagian">A. IDENTITAS MATA KULIAH</h2>
|
||||
<table class="grid">
|
||||
<tr><td class="label">Nama Mata Kuliah</td><td>{{ peta.nama }}</td></tr>
|
||||
<tr><td class="label">Kode Mata Kuliah</td><td>{{ peta.kode }}</td></tr>
|
||||
<tr><td class="label">Bobot SKS</td>
|
||||
<td class="isian" style="display:flex;gap:8px;align-items:center;border:none">
|
||||
<input type="number" name="sks" min="0" max="8" style="width:70px" value="{{ f.identitas.sks }}"> SKS
|
||||
(Teori: <input type="number" name="sks_teori" min="0" max="8" style="width:70px" value="{{ f.identitas.sks_teori }}">
|
||||
| Praktikum: <input type="number" name="sks_praktikum" min="0" max="8" style="width:70px" value="{{ f.identitas.sks_praktikum }}">)
|
||||
</td></tr>
|
||||
<tr><td class="label">Semester</td><td class="isian"><input type="text" name="semester" value="{{ f.identitas.semester }}" placeholder="mis. 3 (Ganjil)"></td></tr>
|
||||
<tr><td class="label">Rumpun MK</td><td class="isian"><input type="text" name="rumpun" value="{{ f.identitas.rumpun }}" placeholder="mis. MK Inti Keilmuan"></td></tr>
|
||||
<tr><td class="label">MK Prasyarat</td><td class="isian"><input type="text" name="prasyarat" value="{{ f.identitas.prasyarat }}" placeholder="kosongkan bila tidak ada"></td></tr>
|
||||
<tr><td class="label">Dosen Pengampu</td><td class="isian"><input type="text" name="pengampu" value="{{ f.identitas.pengampu }}" placeholder="Nama Dosen 1 | Nama Dosen 2"></td></tr>
|
||||
<tr><td class="label">Dosen Pengembang RPS</td><td class="isian"><input type="text" name="pengembang" value="{{ f.identitas.pengembang }}" placeholder="bila kosong: diisi nama dosen pengampu"></td></tr>
|
||||
<tr><td class="label">Koordinator MK</td><td class="isian"><input type="text" name="koordinator" value="{{ f.identitas.koordinator }}"></td></tr>
|
||||
<tr><td class="label">Ketua Program Studi</td><td class="isian"><input type="text" name="kaprodi" value="{{ f.identitas.kaprodi }}"></td></tr>
|
||||
<tr><td class="label">Tgl Penyusunan</td><td class="isian"><input type="text" name="tgl_penyusunan" value="{{ f.identitas.tgl_penyusunan }}" placeholder="mis. 13 Juli 2026"></td></tr>
|
||||
<tr><td class="label">Kode Dokumen</td><td class="isian"><input type="text" name="kode_dokumen" value="{{ f.identitas.kode_dokumen }}" placeholder="diisi bagian penjaminan mutu, boleh kosong"></td></tr>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">B. CPL YANG DIBEBANKAN PADA MK <span style="font-weight:normal;font-style:italic;color:var(--grey)">— otomatis dari pemetaan kurikulum</span></h2>
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:90px">Kode</th><th>Deskripsi CPL</th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in peta.cpls %}
|
||||
<tr><td class="tengah">{{ c.kode }}</td><td>{{ c.desk }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">C. CPMK <span style="font-weight:normal;font-style:italic;color:var(--grey)">— otomatis dari pemetaan kurikulum</span></h2>
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:110px">Kode</th><th>Deskripsi CPMK</th><th style="width:130px">CPL Didukung</th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in peta.cpmks %}
|
||||
<tr><td class="tengah">{{ c.kode }}</td><td>{{ c.desk }}</td><td class="tengah">{{ c.cpl|join(', ') }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">D. SUB-CPMK <span style="font-weight:normal;font-style:italic;color:var(--grey)">— otomatis dari pemetaan kurikulum ({{ peta.subs|length }} butir)</span></h2>
|
||||
<div class="gulir" style="max-height:320px;overflow-y:auto">
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:110px">Kode</th><th>Uraian Sub-CPMK</th><th style="width:110px">CPMK</th></tr></thead>
|
||||
<tbody>
|
||||
{% for s in peta.subs %}
|
||||
<tr><td class="tengah">{{ s.kode }}</td><td>{{ s.uraian }}</td><td class="tengah">{{ s.cpmk }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 class="bagian">E. DESKRIPSI SINGKAT MATA KULIAH</h2>
|
||||
<textarea name="deskripsi" rows="4" placeholder="Topik utama, pendekatan pembelajaran, relevansi dengan profil lulusan dan CPL. Maksimal ± 100 kata.">{{ f.deskripsi }}</textarea>
|
||||
|
||||
<h2 class="bagian">E2. BAHAN KAJIAN — MATERI PEMBELAJARAN</h2>
|
||||
<textarea name="bahan_kajian" rows="4" placeholder="Materi/bahan kajian MK secara berurutan (sesuai format RPS UNTAN).">{{ f.bahan_kajian }}</textarea>
|
||||
|
||||
<h2 class="bagian">E3. MEDIA PEMBELAJARAN</h2>
|
||||
<textarea name="media" rows="2" placeholder="mis. LMS (e-learning UNTAN), LCD proyektor, perangkat lunak pendukung.">{{ f.media }}</textarea>
|
||||
|
||||
<h2 class="bagian">F. RENCANA MINGGUAN <span id="total-bobot-f" style="font-weight:normal">— total bobot: <b>0</b>%</span></h2>
|
||||
<p class="keterangan">Minggu 8 = UTS, minggu 16 = UAS. Total bobot seluruh minggu harus 100 %.</p>
|
||||
<div class="gulir">
|
||||
<table class="grid" id="tabel-f">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tengah" style="width:52px" rowspan="2">Mg</th>
|
||||
<th style="min-width:190px" rowspan="2">Sub-CPMK</th>
|
||||
<th colspan="2">Penilaian</th>
|
||||
<th colspan="2">Bentuk/Metode Pembelajaran [Estimasi Waktu]</th>
|
||||
<th style="min-width:140px" rowspan="2">Kegiatan Pembelajaran</th>
|
||||
<th style="min-width:150px" rowspan="2">Materi [Pustaka]</th>
|
||||
<th class="tengah" style="width:70px" rowspan="2">Bobot (%)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="sub" style="min-width:150px">Indikator</th>
|
||||
<th class="sub" style="min-width:140px">Kriteria & Teknik</th>
|
||||
<th class="sub" style="min-width:130px">Tatap Muka</th>
|
||||
<th class="sub" style="min-width:120px">Daring</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in f.minggu %}
|
||||
<tr class="{% if m.ke == 8 %}uts{% elif m.ke == 16 %}uas{% endif %}">
|
||||
<td class="tengah">{{ m.ke }}</td>
|
||||
<td class="isian">
|
||||
<select name="m{{ m.ke }}_sub">
|
||||
<option value="">— pilih —</option>
|
||||
{% for s in peta.subs %}
|
||||
<option value="{{ s.kode }}" {% if m.sub == s.kode %}selected{% endif %}>{{ s.kode }} — {{ s.uraian[:60] }}{% if s.uraian|length > 60 %}…{% endif %}</option>
|
||||
{% endfor %}
|
||||
<option value="__uts__" {% if m.sub == '__uts__' %}selected{% endif %}>Sub-CPMK minggu 1–7 (UTS)</option>
|
||||
<option value="__uas__" {% if m.sub == '__uas__' %}selected{% endif %}>Sub-CPMK minggu 9–15 (UAS)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="isian"><textarea name="m{{ m.ke }}_indikator" rows="2">{{ m.indikator }}</textarea></td>
|
||||
<td class="isian"><textarea name="m{{ m.ke }}_kriteria" rows="2">{{ m.kriteria }}</textarea></td>
|
||||
<td class="isian"><textarea name="m{{ m.ke }}_metode" rows="2">{{ m.metode }}</textarea></td>
|
||||
<td class="isian"><textarea name="m{{ m.ke }}_daring" rows="2">{{ m.daring }}</textarea></td>
|
||||
<td class="isian"><textarea name="m{{ m.ke }}_kegiatan" rows="2">{{ m.kegiatan }}</textarea></td>
|
||||
<td class="isian"><textarea name="m{{ m.ke }}_materi" rows="2">{{ m.materi }}</textarea></td>
|
||||
<td class="isian"><input type="number" class="bobot-f" name="m{{ m.ke }}_bobot" min="0" max="100" step="0.5" value="{{ m.bobot }}"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 class="bagian">G. REKAP BOBOT PENILAIAN <span id="total-bobot-g" style="font-weight:normal">— total: <b>0</b>%</span></h2>
|
||||
<p class="keterangan">Centang Sub-CPMK yang dinilai tiap komponen. Setiap Sub-CPMK harus tercakup minimal satu komponen agar CPMK/CPL-nya terukur.</p>
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr><th style="width:190px">Komponen</th><th>Sub-CPMK yang Dinilai</th><th class="tengah" style="width:90px">Bobot (%)</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k in f.komponen %}
|
||||
{% set ki = loop.index %}
|
||||
<tr>
|
||||
<td class="isian"><input type="text" name="k{{ ki }}_nama" value="{{ k.nama }}"></td>
|
||||
<td>
|
||||
<div class="pilih-sub">
|
||||
{% for s in peta.subs %}
|
||||
<label><input type="checkbox" class="sub-k" name="k{{ ki }}_subs" value="{{ s.kode }}" {% if s.kode in k.subs %}checked{% endif %}>{{ s.kode }} — {{ s.uraian[:70] }}{% if s.uraian|length > 70 %}…{% endif %}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="isian"><input type="number" class="bobot-g" name="k{{ ki }}_bobot" min="0" max="100" step="0.5" value="{{ k.bobot }}"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="info" id="cek-cakupan" hidden></div>
|
||||
|
||||
<h2 class="bagian">H. PUSTAKA UTAMA</h2>
|
||||
<p class="keterangan">Satu referensi per baris, format APA.</p>
|
||||
<textarea name="referensi" rows="5" placeholder="Penulis. (Tahun). Judul. Penerbit / Jurnal, Vol(No), Hal. https://doi.org/...">{{ f.referensi_teks }}</textarea>
|
||||
|
||||
<h2 class="bagian">H2. PUSTAKA PENDUKUNG</h2>
|
||||
<textarea name="referensi_pendukung" rows="3" placeholder="Satu referensi per baris; boleh kosong.">{{ f.referensi_pendukung_teks }}</textarea>
|
||||
|
||||
<div class="aksi-baris">
|
||||
{% if boleh_edit %}
|
||||
<span style="color:var(--grey);font-size:13px">Disimpan atas nama <b>{{ user.nama }}</b></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="tombol garis" type="submit" name="aksi" value="draft">Simpan Draft</button>
|
||||
<button class="tombol selesai" type="submit" name="aksi" value="selesai">Simpan & Tandai Selesai</button>
|
||||
{% else %}
|
||||
<span class="spacer"></span>
|
||||
{% endif %}
|
||||
{% if status %}<a class="tombol" href="/rps/{{ peta.kode }}/unduh">Unduh .docx</a>
|
||||
<a class="tombol garis" href="/rps/{{ peta.kode }}/unduh.pdf">PDF</a>{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function hitungTotal(kelas, idLabel) {
|
||||
let t = 0;
|
||||
document.querySelectorAll(kelas).forEach(el => { t += parseFloat(el.value) || 0; });
|
||||
const label = document.querySelector(idLabel);
|
||||
label.querySelector('b').textContent = (Math.round(t * 100) / 100);
|
||||
label.classList.toggle('salah', Math.abs(t - 100) > 0.01);
|
||||
}
|
||||
function cekCakupan() {
|
||||
const dinilai = new Set();
|
||||
document.querySelectorAll('.sub-k:checked').forEach(cb => dinilai.add(cb.value));
|
||||
const semua = [{% for s in peta.subs %}'{{ s.kode }}'{% if not loop.last %},{% endif %}{% endfor %}];
|
||||
const hilang = semua.filter(k => !dinilai.has(k));
|
||||
const kotak = document.getElementById('cek-cakupan');
|
||||
if (hilang.length === 0) {
|
||||
kotak.hidden = false;
|
||||
kotak.className = 'info';
|
||||
kotak.textContent = 'Semua ' + semua.length + ' Sub-CPMK sudah tercakup komponen penilaian — CPMK dan CPL mata kuliah ini akan terukur penuh.';
|
||||
} else {
|
||||
kotak.hidden = false;
|
||||
kotak.className = 'peringatan';
|
||||
kotak.textContent = 'Perhatian: ' + hilang.length + ' Sub-CPMK belum dinilai komponen mana pun (ketercapaian CPMK/CPL-nya tidak akan terukur): ' + hilang.join(', ');
|
||||
}
|
||||
}
|
||||
function segarkan() {
|
||||
hitungTotal('.bobot-f', '#total-bobot-f');
|
||||
hitungTotal('.bobot-g', '#total-bobot-g');
|
||||
cekCakupan();
|
||||
}
|
||||
document.addEventListener('input', segarkan);
|
||||
segarkan();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}{{ 'Penyiapan Awal' if mode == 'setup' else 'Masuk' }} — RPS & Portofolio UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<div class="login-kotak">
|
||||
{% if mode == 'setup' %}
|
||||
<h1>Penyiapan Awal</h1>
|
||||
<p class="keterangan" style="text-align:center">Belum ada akun. Buat akun
|
||||
<b>Ketua Program Studi</b> terlebih dahulu — akun ini yang nanti membuat
|
||||
akun para dosen.</p>
|
||||
{% else %}
|
||||
<h1>Masuk</h1>
|
||||
<p class="keterangan" style="text-align:center">Halaman ini memuat data nilai
|
||||
mahasiswa — akses memerlukan akun. Hubungi kaprodi bila belum punya.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if pesan %}<div class="peringatan">{{ pesan }}</div>{% endif %}
|
||||
|
||||
<form method="post" action="{{ '/setup' if mode == 'setup' else '/login' }}">
|
||||
{% if mode == 'setup' %}
|
||||
<label for="nama">Nama lengkap (dengan gelar)</label>
|
||||
<input type="text" id="nama" name="nama" required autofocus>
|
||||
<label for="nip">NIP</label>
|
||||
<input type="text" id="nip" name="nip" autocomplete="username" required>
|
||||
<p class="keterangan">NIP menjadi username sekaligus password awal.</p>
|
||||
{% else %}
|
||||
<label for="nip">NIP</label>
|
||||
<input type="text" id="nip" name="nip" autocomplete="username" required autofocus>
|
||||
<label for="password">Password <span style="color:var(--grey);font-weight:normal">(awal: NIP Anda)</span></label>
|
||||
<input type="password" id="password" name="password"
|
||||
autocomplete="current-password" required>
|
||||
{% endif %}
|
||||
<div class="aksi-baris" style="border-top:none;padding-top:6px">
|
||||
<button class="tombol" type="submit" style="width:100%">
|
||||
{{ 'Buat Akun Kaprodi & Masuk' if mode == 'setup' else 'Masuk' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,82 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Metodologi Pengukuran — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<div class="nav-baris">
|
||||
<span></span>
|
||||
<a href="javascript:window.print()">Cetak halaman ini</a>
|
||||
</div>
|
||||
|
||||
<h1>Metodologi Pengukuran Ketercapaian CPL/CPMK</h1>
|
||||
<p class="keterangan">Halaman ini adalah dokumentasi resmi metode pengukuran — jawaban baku untuk asesor atas pertanyaan "angka ketercapaian ini dihitung dari mana?".</p>
|
||||
|
||||
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
||||
|
||||
<h2 class="bagian">1. SUMBER DATA</h2>
|
||||
<table class="grid">
|
||||
<tr><td class="label">Relasi CPL–CPMK–MK–Sub-CPMK</td><td>Pemetaan kurikulum OBE prodi (Dokumen 9): 10 CPL · 34 CPMK · 49 MK · 549 Sub-CPMK.</td></tr>
|
||||
<tr><td class="label">Bobot penilaian</td><td>RPS tiap MK: bobot komponen penilaian dan pemetaannya ke Sub-CPMK (kisi-kisi asesmen, porsi bagi rata).</td></tr>
|
||||
<tr><td class="label">Nilai</td><td>Skor 0–100 per mahasiswa per Sub-CPMK (unggah Excel). Input rata-rata kelas diperbolehkan sebagai transisi dan ditandai <i>bukti agregat</i>.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">2. RUMUS ROLL-UP (RATA-RATA SEDERHANA / BAGI RATA)</h2>
|
||||
<table class="grid">
|
||||
<tr><td class="label">Skor Sub-CPMK</td><td>Rata-rata skor seluruh mahasiswa pada Sub-CPMK tersebut.</td></tr>
|
||||
<tr><td class="label">Skor CPMK (dalam MK)</td><td>Rata-rata skor Sub-CPMK pendukungnya dalam MK tersebut.</td></tr>
|
||||
<tr><td class="label">Skor CPMK (prodi)</td><td>Rata-rata skor CPMK tersebut lintas seluruh MK yang mengukurnya{% if p.metode_antar_mk == 'sks' %} — <b>saat ini diatur tertimbang SKS</b>{% endif %}.</td></tr>
|
||||
<tr><td class="label">Skor CPL</td><td>Rata-rata skor seluruh CPMK pendukung CPL tersebut.</td></tr>
|
||||
<tr><td class="label">% Kelulusan</td><td>Persentase mahasiswa dengan skor ≥ ambang (hanya tersedia pada data per mahasiswa).</td></tr>
|
||||
<tr><td class="label">Kelengkapan bukti</td><td>Jumlah titik ukur (pasangan MK×CPMK) yang sudah menyetor nilai, dibanding seluruh titik ukur pemikul.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 class="bagian">3. PENGATURAN PRODI</h2>
|
||||
<form method="post" action="/metodologi">
|
||||
<table class="grid">
|
||||
<tr>
|
||||
<td class="label">Tahun ajaran aktif</td>
|
||||
<td class="isian"><input type="text" name="tahun_aktif" style="width:220px" value="{{ p.tahun_aktif }}" placeholder="mis. 2026/2027 Ganjil">
|
||||
<span style="color:var(--grey)">— semua form & rekap mengikuti tahun ini; data tahun lama tetap tersimpan</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Ambang ketercapaian</td>
|
||||
<td class="isian"><input type="number" name="ambang" min="0" max="100" step="1" style="width:110px" value="{{ p.ambang }}"> <span style="color:var(--grey)">(skor ≥ ambang = tercapai)</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Metode antar-MK</td>
|
||||
<td class="isian">
|
||||
<select name="metode_antar_mk" style="width:auto">
|
||||
<option value="rata" {% if p.metode_antar_mk == 'rata' %}selected{% endif %}>Bagi rata (default — sesuai praktik umum KPT Dikti)</option>
|
||||
<option value="sks" {% if p.metode_antar_mk == 'sks' %}selected{% endif %}>Tertimbang SKS (aktifkan hanya dengan keputusan resmi prodi)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Skala huruf mutu</td>
|
||||
<td class="isian"><textarea name="skala_huruf" rows="10" style="font-family:Consolas,monospace;font-size:13px">{{ p.skala_huruf }}</textarea>
|
||||
<div style="color:var(--grey);font-size:12px;padding:4px 6px">Format per baris: HURUF|MIN|MAX|ANGKA</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="aksi-baris">
|
||||
{% if boleh_ubah %}
|
||||
<span class="spacer"></span>
|
||||
<button class="tombol" type="submit">Simpan Pengaturan</button>
|
||||
{% else %}
|
||||
<span style="color:var(--grey);font-size:13px">Pengaturan prodi hanya dapat diubah oleh kaprodi.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2 class="bagian">4. SKALA HURUF MUTU BERLAKU</h2>
|
||||
<table class="grid" style="max-width:420px">
|
||||
<thead><tr><th>Huruf</th><th class="tengah">Rentang</th><th class="tengah">Angka Mutu</th></tr></thead>
|
||||
<tbody>
|
||||
{% for s in skala %}
|
||||
<tr>
|
||||
<td class="tengah"><b>{{ s.huruf }}</b></td>
|
||||
<td class="tengah">{{ '%g'|format(s.lo) }} – {{ '%g'|format(s.hi) }}</td>
|
||||
<td class="tengah">{{ '%.2f'|format(s.angka) if s.angka is not none else '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,192 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Dasbor Monitoring — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<h1>Dasbor Monitoring Prodi — {{ hasil.tahun }}</h1>
|
||||
|
||||
<div class="kpi-baris">
|
||||
<div><b>{{ hasil.n_mk_setor }}/{{ n_mk }}</b> <span>MK setor nilai</span></div>
|
||||
<div><b>{{ cpl_urut|selectattr('tercapai')|list|length }}/{{ cpl_urut|length }}</b> <span>CPL tercapai</span></div>
|
||||
<div><b>{{ cpmk_urut|rejectattr('skor', 'none')|list|length }}/{{ cpmk_urut|length }}</b> <span>CPMK terukur</span></div>
|
||||
<div><b>{% if rata_cpl is not none %}{{ '%.1f'|format(rata_cpl) }}{% else %}—{% endif %}</b> <span>rata-rata skor CPL</span></div>
|
||||
<div><b>{{ hasil.ambang|round(0)|int }}</b> <span>ambang ketercapaian</span></div>
|
||||
</div>
|
||||
|
||||
{% if hasil.n_mk_setor == 0 %}
|
||||
<div class="info">Belum ada MK yang menyetor nilai pada {{ hasil.tahun }} —
|
||||
grafik ketercapaian akan terisi begitu nilai pertama masuk. Pantau dulu
|
||||
kelengkapan proses dokumen dan daftar tindak lanjut di bawah.</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── A. Ketercapaian CPL (bar vertikal, 10 CPL) ─────────────────────── #}
|
||||
<h2 class="bagian">KETERCAPAIAN CPL — SEMESTER BERJALAN</h2>
|
||||
<p class="keterangan">Skor gabungan tiap CPL vs ambang {{ hasil.ambang|round(0)|int }}.
|
||||
Arahkan kursor ke batang untuk rincian kelengkapan bukti & penyumbang terlemah.
|
||||
Tabel rinci: <a href="/rekap">Rekap</a>.</p>
|
||||
<div class="legenda">
|
||||
<span><i class="chip" style="background:var(--blue)"></i> tercapai (≥ ambang)</span>
|
||||
<span><i class="chip" style="background:var(--red)"></i> di bawah ambang</span>
|
||||
<span><i class="chip chip-kosong"></i> belum terukur</span>
|
||||
<span><i class="chip-garis"></i> garis ambang</span>
|
||||
</div>
|
||||
<div class="gulir">
|
||||
<svg viewBox="0 0 760 252" class="grafik" role="img" aria-label="Grafik batang skor tiap CPL dibandingkan ambang">
|
||||
<defs><clipPath id="plot-cpl"><rect x="46" y="10" width="706" height="206"/></clipPath></defs>
|
||||
{% for g in [0, 25, 50, 75, 100] %}
|
||||
<line x1="46" y1="{{ 216 - g * 2 }}" x2="752" y2="{{ 216 - g * 2 }}" stroke="{{ '#bbb' if g == 0 else '#e4e4e4' }}" stroke-width="1"/>
|
||||
<text x="40" y="{{ 220 - g * 2 }}" class="sumbu" text-anchor="end">{{ g }}</text>
|
||||
{% endfor %}
|
||||
<line x1="46" y1="{{ '%.1f'|format(216 - hasil.ambang * 2) }}" x2="752" y2="{{ '%.1f'|format(216 - hasil.ambang * 2) }}"
|
||||
stroke="var(--red)" stroke-width="1.5" stroke-dasharray="6 4"/>
|
||||
{% set lebar_slot = 706 / cpl_urut|length %}
|
||||
{% for c in cpl_urut %}
|
||||
{% set x_slot = 46 + loop.index0 * lebar_slot %}
|
||||
{% set x_bar = x_slot + (lebar_slot - 30) / 2 %}
|
||||
<g class="bar">
|
||||
<title>{{ c.kode }} — {% if c.skor is not none %}skor {{ '%.2f'|format(c.skor) }} ({{ 'tercapai' if c.tercapai else 'di bawah ambang' }})
|
||||
bukti: {{ c.n_mk_setor }}/{{ c.n_mk_pemikul }} titik ukur{% if c.ada_agregat %} (ada bukti agregat){% endif %}
|
||||
{% if c.mk_terlemah %}terlemah: {{ c.cpmk_terlemah }} — {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% endif %}{% else %}belum terukur — belum ada MK pemikulnya yang setor nilai{% endif %}</title>
|
||||
<rect x="{{ '%.1f'|format(x_slot) }}" y="10" width="{{ '%.1f'|format(lebar_slot) }}" height="206" fill="transparent"/>
|
||||
{% if c.skor is not none %}
|
||||
<rect clip-path="url(#plot-cpl)" x="{{ '%.1f'|format(x_bar) }}" y="{{ '%.1f'|format(216 - c.skor * 2) }}"
|
||||
width="30" height="{{ '%.1f'|format(c.skor * 2 + 6) }}" rx="4"
|
||||
fill="{{ 'var(--blue)' if c.tercapai else 'var(--red)' }}"/>
|
||||
<text x="{{ '%.1f'|format(x_bar + 15) }}" y="{{ '%.1f'|format(211 - c.skor * 2) }}" class="nilai-bar" text-anchor="middle">{{ '%.1f'|format(c.skor) }}</text>
|
||||
{% else %}
|
||||
<rect x="{{ '%.1f'|format(x_bar) }}" y="204" width="30" height="12" rx="4"
|
||||
fill="none" stroke="var(--grey)" stroke-dasharray="3 3"/>
|
||||
{% endif %}
|
||||
<text x="{{ '%.1f'|format(x_slot + lebar_slot / 2) }}" y="232" class="sumbu{{ ' pudar' if c.skor is none }}" text-anchor="middle">{{ c.kode }}</text>
|
||||
<text x="{{ '%.1f'|format(x_slot + lebar_slot / 2) }}" y="246" class="sumbu pudar" text-anchor="middle">{{ c.n_mk_setor }}/{{ c.n_mk_pemikul }} bukti</text>
|
||||
</g>
|
||||
{% endfor %}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{# ── B. Kelengkapan proses dokumen (stacked bar per tahap) ──────────── #}
|
||||
<h2 class="bagian">KELENGKAPAN PROSES DOKUMEN ({{ n_mk }} MK)</h2>
|
||||
<p class="keterangan">Alur per MK: RPS → Nilai → Portofolio. Batang menunjukkan berapa MK yang sudah menuntaskan tiap tahap.</p>
|
||||
<div class="legenda">
|
||||
<span><i class="chip" style="background:var(--blue)"></i> selesai</span>
|
||||
<span><i class="chip" style="background:var(--blue-fill);border:1px solid #9db8cf"></i> draft</span>
|
||||
<span><i class="chip chip-kosong"></i> belum</span>
|
||||
</div>
|
||||
{% for p in proses %}
|
||||
<div class="proses-baris">
|
||||
<span class="proses-label">{{ p.label }}</span>
|
||||
<span class="proses-jalur">
|
||||
{% if p.selesai %}<span class="seg" style="width:{{ '%.2f'|format(100 * p.selesai / n_mk) }}%;background:var(--blue);color:#fff" title="{{ p.selesai }} MK selesai {{ p.label }}">{{ p.selesai }}</span>{% endif %}
|
||||
{% if p.draft %}<span class="seg" style="width:{{ '%.2f'|format(100 * p.draft / n_mk) }}%;background:var(--blue-fill)" title="{{ p.draft }} MK masih draft">{{ p.draft }}</span>{% endif %}
|
||||
{% if p.belum %}<span class="seg seg-kosong" style="width:{{ '%.2f'|format(100 * p.belum / n_mk) }}%" title="{{ p.belum }} MK belum mulai">{{ p.belum }}</span>{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# ── C. Ketercapaian CPMK (bar horizontal, tampil bila ada data) ────── #}
|
||||
{% set cpmk_ternilai = cpmk_urut|rejectattr('skor', 'none')|list %}
|
||||
{% if cpmk_ternilai %}
|
||||
<h2 class="bagian">KETERCAPAIAN CPMK — SEMESTER BERJALAN</h2>
|
||||
<p class="keterangan">Hanya CPMK yang sudah terukur ({{ cpmk_ternilai|length }} dari {{ cpmk_urut|length }}).
|
||||
Garis putus-putus = ambang. Arahkan kursor untuk melihat MK pengukur terlemah.</p>
|
||||
{% set tinggi = cpmk_ternilai|length * 22 + 34 %}
|
||||
<div class="gulir">
|
||||
<svg viewBox="0 0 760 {{ tinggi }}" class="grafik" role="img" aria-label="Grafik batang horizontal skor tiap CPMK">
|
||||
<defs><clipPath id="plot-cpmk"><rect x="100" y="0" width="646" height="{{ tinggi - 30 }}"/></clipPath></defs>
|
||||
{% for g in [0, 25, 50, 75, 100] %}
|
||||
<line x1="{{ 100 + g * 6.46 }}" y1="0" x2="{{ 100 + g * 6.46 }}" y2="{{ tinggi - 30 }}" stroke="{{ '#bbb' if g == 0 else '#e4e4e4' }}" stroke-width="1"/>
|
||||
<text x="{{ 100 + g * 6.46 }}" y="{{ tinggi - 16 }}" class="sumbu" text-anchor="middle">{{ g }}</text>
|
||||
{% endfor %}
|
||||
<line x1="{{ '%.1f'|format(100 + hasil.ambang * 6.46) }}" y1="0" x2="{{ '%.1f'|format(100 + hasil.ambang * 6.46) }}" y2="{{ tinggi - 30 }}"
|
||||
stroke="var(--red)" stroke-width="1.5" stroke-dasharray="6 4"/>
|
||||
{% for c in cpmk_ternilai %}
|
||||
{% set y = loop.index0 * 22 %}
|
||||
<g class="bar">
|
||||
<title>{{ c.kode }} (mendukung {{ c.cpl|join(', ') }}) — skor {{ '%.2f'|format(c.skor) }}
|
||||
setor: {{ c.n_setor }}/{{ c.n_pemikul }} MK pemikul{% if c.ada_agregat %} (ada bukti agregat){% endif %}
|
||||
{% if c.mk_terlemah %}terlemah: {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% endif %}</title>
|
||||
<rect x="0" y="{{ y }}" width="760" height="22" fill="transparent"/>
|
||||
<text x="94" y="{{ y + 15 }}" class="sumbu" text-anchor="end">{{ c.kode }}</text>
|
||||
<rect clip-path="url(#plot-cpmk)" x="94" y="{{ y + 4 }}" width="{{ '%.1f'|format(c.skor * 6.46 + 6) }}" height="14" rx="4"
|
||||
fill="{{ 'var(--blue)' if c.skor >= hasil.ambang else 'var(--red)' }}"/>
|
||||
<text x="{{ '%.1f'|format(104 + c.skor * 6.46) }}" y="{{ y + 15 }}" class="nilai-bar">{{ '%.1f'|format(c.skor) }}</text>
|
||||
</g>
|
||||
{% endfor %}
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── D. Tren lintas semester (sparkline per CPL, bila >1 tahun) ─────── #}
|
||||
{% if tren %}
|
||||
<h2 class="bagian">TREN SKOR CPL LINTAS TAHUN AJARAN</h2>
|
||||
<p class="keterangan">Garis kecil per CPL; titik terakhir = {{ hasil.tahun }}. Rincian angka: <a href="/rekap/riwayat">Riwayat Lintas Semester</a>.</p>
|
||||
<div class="tren-kisi">
|
||||
{% for kode, titik in tren.items() %}
|
||||
{% set ada = titik|selectattr(1, 'ne', none)|list %}
|
||||
<div class="tren-kartu" title="{{ kode }}: {% for t, s in titik %}{{ t }} = {{ '%.1f'|format(s) if s is not none else '—' }}{{ ' · ' if not loop.last }}{% endfor %}">
|
||||
<b>{{ kode }}</b>
|
||||
<svg viewBox="0 0 150 46" class="tren-svg" role="img" aria-label="Tren {{ kode }}">
|
||||
{% set lebar_x = 130 / (titik|length - 1) if titik|length > 1 else 0 %}
|
||||
{# skala vertikal mengikuti rentang data (± 2 poin) agar tren terlihat #}
|
||||
{% set nilai2 = ada|map(attribute=1)|list %}
|
||||
{% set lo = [nilai2|min - 2, 0]|max %}
|
||||
{% set span = [nilai2|max + 2 - lo, 4]|max %}
|
||||
{% if lo <= hasil.ambang <= lo + span %}
|
||||
<line x1="6" y1="{{ '%.1f'|format(42 - (hasil.ambang - lo) * 36 / span) }}" x2="136" y2="{{ '%.1f'|format(42 - (hasil.ambang - lo) * 36 / span) }}" stroke="var(--red)" stroke-width="1" stroke-dasharray="3 3"/>
|
||||
{% endif %}
|
||||
<polyline fill="none" stroke="var(--blue)" stroke-width="2" stroke-linejoin="round"
|
||||
points="{% for t, s in titik %}{% if s is not none %}{{ '%.1f'|format(6 + loop.index0 * lebar_x) }},{{ '%.1f'|format(42 - (s - lo) * 36 / span) }} {% endif %}{% endfor %}"/>
|
||||
{% for t, s in titik %}{% if s is not none %}
|
||||
<circle cx="{{ '%.1f'|format(6 + loop.index0 * lebar_x) }}" cy="{{ '%.1f'|format(42 - (s - lo) * 36 / span) }}" r="3" fill="var(--blue)" stroke="#fff" stroke-width="1.5"/>
|
||||
{% endif %}{% endfor %}
|
||||
</svg>
|
||||
<span class="tren-akhir">{% if ada %}{{ '%.1f'|format(ada[-1][1]) }}{% else %}—{% endif %}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── E. Perlu perhatian ─────────────────────────────────────────────── #}
|
||||
<h2 class="bagian">PERLU PERHATIAN</h2>
|
||||
<div class="perhatian-kisi">
|
||||
<div>
|
||||
<p class="keterangan" style="margin-top:0">CPL di bawah ambang & penyumbang terlemahnya — mulai perbaikan dari sini.</p>
|
||||
{% set cpl_rendah = cpl_urut|rejectattr('skor', 'none')|rejectattr('tercapai')|list %}
|
||||
{% if cpl_rendah %}
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:70px">CPL</th><th class="tengah" style="width:64px">Skor</th><th>CPMK & MK Terlemah</th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in cpl_rendah %}
|
||||
<tr>
|
||||
<td class="tengah"><b>{{ c.kode }}</b></td>
|
||||
<td class="tengah">{{ '%.2f'|format(c.skor) }}</td>
|
||||
<td>{% if c.mk_terlemah %}{{ c.cpmk_terlemah }} — <a href="/nilai/{{ c.mk_terlemah[0] }}">{{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }}</a> ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% else %}—{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="info">Tidak ada CPL terukur yang berada di bawah ambang. ✓</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<p class="keterangan" style="margin-top:0">MK belum setor nilai ({{ belum_nilai|length }}) — hubungi pengampunya.</p>
|
||||
{% if belum_nilai %}
|
||||
<div class="gulir" style="max-height:320px;overflow-y:auto">
|
||||
<table class="grid">
|
||||
<thead><tr><th style="width:100px">Kode</th><th>Mata Kuliah</th><th style="width:180px">Pengampu</th></tr></thead>
|
||||
<tbody>
|
||||
{% for m in belum_nilai %}
|
||||
<tr>
|
||||
<td>{{ m.kode }}</td>
|
||||
<td><a href="/nilai/{{ m.kode }}">{{ m.nama }}</a></td>
|
||||
<td style="font-size:13px">{{ m.pengampu|join(', ') or '— belum ditugaskan' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="info">Semua MK sudah menyetor nilai. ✓</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Rekap Ketercapaian CPL/CPMK — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<h1>Rekap Ketercapaian CPL & CPMK — {{ hasil.tahun }}</h1>
|
||||
<div class="kpi-baris">
|
||||
<div><b>{{ hasil.n_mk_setor }}</b> <span>MK sudah setor nilai</span></div>
|
||||
<div><b>{{ hasil.ambang|round(0)|int }}</b> <span>ambang ketercapaian</span></div>
|
||||
<div><b>{{ 'SKS' if hasil.metode == 'sks' else 'Bagi rata' }}</b> <span>metode antar-MK</span></div>
|
||||
<div><b>{{ cpl_urut|selectattr('tercapai')|list|length }}</b> <span>CPL tercapai</span></div>
|
||||
<div><b>{{ cpl_urut|rejectattr('skor', 'none')|rejectattr('tercapai')|list|length }}</b> <span>CPL di bawah ambang</span></div>
|
||||
</div>
|
||||
|
||||
{% if hasil.n_mk_setor == 0 %}
|
||||
<div class="peringatan">Belum ada MK yang menyetor nilai — seluruh CPL berstatus "belum terukur". Mulai dari halaman <b>Input Nilai</b> tiap MK di beranda.</div>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="bagian">KETERCAPAIAN CPL</h2>
|
||||
<p class="keterangan">Kelengkapan bukti = berapa dari seluruh titik ukur (MK × CPMK pemikul) yang sudah menyetor nilai. Skor dari bukti yang belum lengkap harus dibaca hati-hati.</p>
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80px">CPL</th><th>Deskripsi</th>
|
||||
<th class="tengah" style="width:80px">Skor</th>
|
||||
<th class="tengah" style="width:130px">Status</th>
|
||||
<th class="tengah" style="width:120px">Kelengkapan Bukti</th>
|
||||
<th style="width:210px">Penyumbang Terlemah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in cpl_urut %}
|
||||
<tr>
|
||||
<td class="tengah"><b>{{ c.kode }}</b></td>
|
||||
<td>{{ c.desk[:130] }}{% if c.desk|length > 130 %}…{% endif %}</td>
|
||||
<td class="tengah">{% if c.skor is not none %}<b>{{ '%.2f'|format(c.skor) }}</b>{% else %}—{% endif %}</td>
|
||||
<td class="tengah">
|
||||
{% if c.skor is none %}<span class="stamp stamp-draft">BELUM TERUKUR</span>
|
||||
{% elif c.tercapai %}<span class="stamp stamp-ok">TERCAPAI</span>
|
||||
{% else %}<span class="stamp stamp-no">DI BAWAH AMBANG</span>{% endif %}
|
||||
</td>
|
||||
<td class="tengah">{{ c.n_mk_setor }}/{{ c.n_mk_pemikul }} titik ukur{% if c.ada_agregat %}<br><span style="color:var(--red);font-size:11px">ada bukti agregat</span>{% endif %}</td>
|
||||
<td>
|
||||
{% if c.mk_terlemah %}
|
||||
{{ c.cpmk_terlemah }} — {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }}
|
||||
({{ '%.1f'|format(c.mk_terlemah[1]) }})
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 class="bagian">KETERCAPAIAN CPMK (TINGKAT PRODI)</h2>
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:110px">CPMK</th>
|
||||
<th style="width:120px">CPL Didukung</th>
|
||||
<th class="tengah" style="width:80px">Skor</th>
|
||||
<th class="tengah" style="width:130px">MK Setor / Pemikul</th>
|
||||
<th style="width:230px">MK Terlemah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in cpmk_urut %}
|
||||
<tr>
|
||||
<td class="tengah">{{ c.kode }}</td>
|
||||
<td class="tengah">{{ c.cpl|join(', ') }}</td>
|
||||
<td class="tengah">{% if c.skor is not none %}<b>{{ '%.2f'|format(c.skor) }}</b>{% else %}—{% endif %}</td>
|
||||
<td class="tengah">{{ c.n_setor }}/{{ c.n_pemikul }}</td>
|
||||
<td>{% if c.mk_terlemah %}{{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% else %}—{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,42 @@
|
||||
{% extends "base.html" %}
|
||||
{% block judul %}Riwayat Ketercapaian Lintas Semester — Informatika UNTAN{% endblock %}
|
||||
{% block isi %}
|
||||
<h1>Riwayat Ketercapaian CPL Lintas Semester</h1>
|
||||
<p class="keterangan">Skor CPL tingkat prodi per tahun ajaran — bahan evaluasi tren untuk rapat kurikulum dan akreditasi. Sel kosong = belum ada nilai yang masuk pada periode itu.</p>
|
||||
|
||||
<div class="gulir">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:90px">CPL</th>
|
||||
{% for t in daftar_tahun %}
|
||||
<th class="tengah">{{ t }}{% if t == tahun %}<br><span style="font-size:10px">(aktif)</span>{% endif %}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cp in kode_cpl %}
|
||||
<tr>
|
||||
<td class="tengah"><b>{{ cp }}</b></td>
|
||||
{% for t in daftar_tahun %}
|
||||
{% set v = per_tahun[t].cpl.get(cp) %}
|
||||
<td class="tengah">
|
||||
{% if v and v.skor is not none %}
|
||||
<b>{{ '%.1f'|format(v.skor) }}</b>
|
||||
{% if v.skor < per_tahun[t].ambang %}<span style="color:var(--red)"> ▼</span>{% endif %}
|
||||
<br><span style="font-size:11px;color:var(--grey)">{{ v.n_mk_setor }}/{{ v.n_mk_pemikul }} bukti</span>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="kpi-baris" style="margin-top:14px">
|
||||
{% for t in daftar_tahun %}
|
||||
<div><b>{{ per_tahun[t].n_mk_setor }}</b> <span>MK setor nilai · {{ t }}</span></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user