Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9482ee23 | |||
| e5d45cdcdd | |||
| a3efa9fef3 | |||
| 6375bc88f4 | |||
| efc14dc7a7 | |||
| 9c0fe8baa0 | |||
| 38d309e7b1 |
@@ -185,6 +185,16 @@ def init_db() -> None:
|
|||||||
updated_by TEXT NOT NULL
|
updated_by TEXT NOT NULL
|
||||||
)"""
|
)"""
|
||||||
)
|
)
|
||||||
|
conn.execute(
|
||||||
|
"""CREATE TABLE IF NOT EXISTS nilai_arsip_hapus (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
mk_kode TEXT NOT NULL,
|
||||||
|
tahun TEXT NOT NULL,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
dihapus_at TEXT NOT NULL,
|
||||||
|
dihapus_oleh TEXT NOT NULL
|
||||||
|
)"""
|
||||||
|
)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""CREATE TABLE IF NOT EXISTS pengaturan (
|
"""CREATE TABLE IF NOT EXISTS pengaturan (
|
||||||
kunci TEXT PRIMARY KEY,
|
kunci TEXT PRIMARY KEY,
|
||||||
@@ -292,6 +302,19 @@ def simpan_dok(tabel: str, mk_kode: str, data: dict, status: str, updated_by: st
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def arsipkan_nilai_hapus(mk_kode: str, tahun: str, data: dict, dihapus_oleh: str) -> None:
|
||||||
|
"""Simpan salinan nilai sebelum dihapus (jaring pengaman) — tak ada UI
|
||||||
|
pemulihan otomatis, tapi baris ini tetap ikut ke-backup lewat /cadangan.db
|
||||||
|
dan bisa diambil manual bila suatu saat perlu dipulihkan."""
|
||||||
|
now = datetime.now(WIB).strftime("%Y-%m-%d %H:%M")
|
||||||
|
with get_conn() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"""INSERT INTO nilai_arsip_hapus (mk_kode, tahun, data, dihapus_at, dihapus_oleh)
|
||||||
|
VALUES (?,?,?,?,?)""",
|
||||||
|
(mk_kode, tahun, json.dumps(data, ensure_ascii=False), now, dihapus_oleh),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def hapus_dok(tabel: str, mk_kode: str, tahun: str | None = None) -> None:
|
def hapus_dok(tabel: str, mk_kode: str, tahun: str | None = None) -> None:
|
||||||
assert tabel in _TABEL_DOK
|
assert tabel in _TABEL_DOK
|
||||||
tahun = tahun or tahun_aktif()
|
tahun = tahun or tahun_aktif()
|
||||||
@@ -362,11 +385,11 @@ def dump_semua() -> dict:
|
|||||||
"""Seluruh isi database sebagai dict (untuk cadangan JSON)."""
|
"""Seluruh isi database sebagai dict (untuk cadangan JSON)."""
|
||||||
out = {}
|
out = {}
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
for tabel in (*_TABEL_DOK, "rps_arsip", "pengaturan",
|
for tabel in (*_TABEL_DOK, "rps_arsip", "nilai_arsip_hapus", "pengaturan",
|
||||||
"pengguna", "penugasan", "mahasiswa"):
|
"pengguna", "penugasan", "mahasiswa"):
|
||||||
rows = conn.execute(f"SELECT * FROM {tabel}").fetchall()
|
rows = conn.execute(f"SELECT * FROM {tabel}").fetchall()
|
||||||
out[tabel] = [dict(r) for r in rows]
|
out[tabel] = [dict(r) for r in rows]
|
||||||
for tabel in _TABEL_DOK + ("rps_arsip",):
|
for tabel in _TABEL_DOK + ("rps_arsip", "nilai_arsip_hapus"):
|
||||||
for r in out.get(tabel, []):
|
for r in out.get(tabel, []):
|
||||||
r["data"] = json.loads(r["data"])
|
r["data"] = json.loads(r["data"])
|
||||||
return out
|
return out
|
||||||
|
|||||||
+29
-1
@@ -327,6 +327,19 @@ def _normalisasi_komponen(tersimpan: list[dict]) -> list[dict]:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _komponen_tanpa_sub(rps_data: dict) -> list[str]:
|
||||||
|
"""Nama komponen RPS bagian G yang bobotnya sudah diisi tapi belum ada
|
||||||
|
Sub-CPMK dicentang. Komponen begini masih ikut dihitung ke total 100% di
|
||||||
|
bagian G (jadi mudah lolos tanpa disadari), tapi disaring habis oleh
|
||||||
|
_komponen_daftar() — tidak akan pernah muncul di halaman Nilai."""
|
||||||
|
out = []
|
||||||
|
for k in _normalisasi_komponen(rps_data.get("komponen", [])):
|
||||||
|
bobot_terisi = str(k.get("bobot") or "").strip() not in ("", "0", "0.0")
|
||||||
|
if bobot_terisi and not k.get("subs"):
|
||||||
|
out.append(k["nama"])
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def minggu_default() -> list[dict]:
|
def minggu_default() -> list[dict]:
|
||||||
rows = []
|
rows = []
|
||||||
for w in range(1, 17):
|
for w in range(1, 17):
|
||||||
@@ -456,6 +469,7 @@ def form_rps(request: Request, kode: str, pesan: str = ""):
|
|||||||
f.setdefault("referensi_teks", "\n".join(f.get("referensi", [])))
|
f.setdefault("referensi_teks", "\n".join(f.get("referensi", [])))
|
||||||
f["komponen"] = _normalisasi_komponen(f.get("komponen", []))
|
f["komponen"] = _normalisasi_komponen(f.get("komponen", []))
|
||||||
ctx_status = tersimpan
|
ctx_status = tersimpan
|
||||||
|
komponen_tanpa_sub = _komponen_tanpa_sub(f)
|
||||||
else:
|
else:
|
||||||
f = form_kosong()
|
f = form_kosong()
|
||||||
# prefill SKS & semester dari data kurikulum (sheet '14. Susunan MK')
|
# prefill SKS & semester dari data kurikulum (sheet '14. Susunan MK')
|
||||||
@@ -465,6 +479,7 @@ def form_rps(request: Request, kode: str, pesan: str = ""):
|
|||||||
if info.get("semester"):
|
if info.get("semester"):
|
||||||
f["identitas"]["semester"] = str(info["semester"])
|
f["identitas"]["semester"] = str(info["semester"])
|
||||||
ctx_status = {"status": "", "updated_at": "", "updated_by": ""}
|
ctx_status = {"status": "", "updated_at": "", "updated_by": ""}
|
||||||
|
komponen_tanpa_sub = [] # form belum pernah disimpan — bobot masih bawaan default
|
||||||
peta = _terapkan_override_sub(peta, f)
|
peta = _terapkan_override_sub(peta, f)
|
||||||
return render(
|
return render(
|
||||||
request, "form_rps.html",
|
request, "form_rps.html",
|
||||||
@@ -473,6 +488,7 @@ def form_rps(request: Request, kode: str, pesan: str = ""):
|
|||||||
"tahun": db.tahun_aktif(), "pesan": pesan,
|
"tahun": db.tahun_aktif(), "pesan": pesan,
|
||||||
"boleh_edit": boleh_rps(request.state.user, kode),
|
"boleh_edit": boleh_rps(request.state.user, kode),
|
||||||
"tahun_lain": db.tahun_rps_lain(kode) if not tersimpan else [],
|
"tahun_lain": db.tahun_rps_lain(kode) if not tersimpan else [],
|
||||||
|
"komponen_tanpa_sub": komponen_tanpa_sub,
|
||||||
"sub_uraian": {s["kode"]: s["uraian"] for s in peta["subs"]}},
|
"sub_uraian": {s["kode"]: s["uraian"] for s in peta["subs"]}},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -946,6 +962,8 @@ def _konteks_nilai(request, kode, pesan="", peringatan=None):
|
|||||||
terdaftar = db.mahasiswa_mk(kode)
|
terdaftar = db.mahasiswa_mk(kode)
|
||||||
grid = _grid_mahasiswa(tersimpan, terdaftar, kelas_dosen)
|
grid = _grid_mahasiswa(tersimpan, terdaftar, kelas_dosen)
|
||||||
komponen_daftar = _komponen_daftar(kode)
|
komponen_daftar = _komponen_daftar(kode)
|
||||||
|
rps_cek = db.ambil_rps(kode)
|
||||||
|
komponen_tanpa_sub = _komponen_tanpa_sub(rps_cek["data"]) if rps_cek else []
|
||||||
sesi_semua = (tersimpan["data"].get("sesi") or {}) if tersimpan else {}
|
sesi_semua = (tersimpan["data"].get("sesi") or {}) if tersimpan else {}
|
||||||
bobot_komponen, bobot_sub_rincian = _rincian_bobot(kode)
|
bobot_komponen, bobot_sub_rincian = _rincian_bobot(kode)
|
||||||
# Nilai Akhir dihitung hanya dari Sub-CPMK yang sudah ternilai (lihat
|
# Nilai Akhir dihitung hanya dari Sub-CPMK yang sudah ternilai (lihat
|
||||||
@@ -983,6 +1001,7 @@ def _konteks_nilai(request, kode, pesan="", peringatan=None):
|
|||||||
"grid": grid, "kelas_dosen": kelas_dosen, "tidak_lengkap": tidak_lengkap,
|
"grid": grid, "kelas_dosen": kelas_dosen, "tidak_lengkap": tidak_lengkap,
|
||||||
"dikatrol": dikatrol,
|
"dikatrol": dikatrol,
|
||||||
"komponen_daftar": komponen_daftar,
|
"komponen_daftar": komponen_daftar,
|
||||||
|
"komponen_tanpa_sub": komponen_tanpa_sub,
|
||||||
"tabel_komponen": _tabel_komponen(grid, komponen_daftar, sesi_semua) if komponen_daftar else [],
|
"tabel_komponen": _tabel_komponen(grid, komponen_daftar, sesi_semua) if komponen_daftar else [],
|
||||||
"bobot_komponen": bobot_komponen, "bobot_sub_rincian": bobot_sub_rincian,
|
"bobot_komponen": bobot_komponen, "bobot_sub_rincian": bobot_sub_rincian,
|
||||||
"tabel_poin": _tabel_poin(grid, bobot_sub_rincian) if bobot_sub_rincian else [],
|
"tabel_poin": _tabel_poin(grid, bobot_sub_rincian) if bobot_sub_rincian else [],
|
||||||
@@ -1165,10 +1184,19 @@ async def simpan_nilai(request: Request, kode: str):
|
|||||||
return _konteks_nilai(request, kode, peringatan=[
|
return _konteks_nilai(request, kode, peringatan=[
|
||||||
"Anda hanya diampukan sebagian kelas MK ini — menghapus seluruh nilai "
|
"Anda hanya diampukan sebagian kelas MK ini — menghapus seluruh nilai "
|
||||||
"hanya bisa dilakukan kaprodi atau dosen yang mengampu seluruh kelas."])
|
"hanya bisa dilakukan kaprodi atau dosen yang mengampu seluruh kelas."])
|
||||||
|
akun = db.pengguna_by_id(request.state.user["id"])
|
||||||
|
password = (form.get("password") or "").strip()
|
||||||
|
if not akun or not auth.verifikasi_password(password, akun["pw_hash"]):
|
||||||
|
return _konteks_nilai(request, kode, peringatan=[
|
||||||
|
"Password salah — nilai TIDAK jadi dihapus."])
|
||||||
|
lama = db.ambil_dok("nilai", kode)
|
||||||
|
if lama:
|
||||||
|
db.arsipkan_nilai_hapus(kode, db.tahun_aktif(), lama["data"], updated_by)
|
||||||
db.hapus_dok("nilai", kode)
|
db.hapus_dok("nilai", kode)
|
||||||
return _konteks_nilai(
|
return _konteks_nilai(
|
||||||
request, kode,
|
request, kode,
|
||||||
pesan="Seluruh nilai tersimpan MK ini dihapus — silakan mulai dari awal.")
|
pesan="Seluruh nilai tersimpan MK ini dihapus (salinan sebelumnya "
|
||||||
|
"tersimpan otomatis sebagai arsip) — silakan mulai dari awal.")
|
||||||
|
|
||||||
if aksi == "komponen":
|
if aksi == "komponen":
|
||||||
kelas_dosen = _kelas_dosen(request.state.user, kode)
|
kelas_dosen = _kelas_dosen(request.state.user, kode)
|
||||||
|
|||||||
@@ -17,6 +17,13 @@
|
|||||||
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
{% if pesan %}<div class="info">{{ pesan }}</div>{% endif %}
|
||||||
{% for w in peringatan %}<div class="peringatan">{{ w }}</div>{% endfor %}
|
{% for w in peringatan %}<div class="peringatan">{{ w }}</div>{% endfor %}
|
||||||
|
|
||||||
|
{% if komponen_tanpa_sub %}
|
||||||
|
<div class="peringatan">RPS MK ini sudah punya bobot untuk <b>{{ komponen_tanpa_sub|join(', ') }}</b>,
|
||||||
|
tapi belum ada Sub-CPMK yang dicentang di bagian G — makanya komponen itu
|
||||||
|
{% if not komponen_daftar %}tidak muncul sama sekali{% else %}belum ikut muncul{% endif %}
|
||||||
|
di "Isi per Komponen" di bawah. <a href="/rps/{{ peta.kode }}">Buka RPS →</a> lalu centang Sub-CPMK-nya.</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if komponen_daftar %}
|
{% if komponen_daftar %}
|
||||||
<h2 class="bagian">ISI PER KOMPONEN</h2>
|
<h2 class="bagian">ISI PER KOMPONEN</h2>
|
||||||
<table class="grid" style="max-width:760px">
|
<table class="grid" style="max-width:760px">
|
||||||
@@ -54,15 +61,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if tersimpan %}
|
{% if tersimpan %}
|
||||||
|
{% set n_mhs = tersimpan.data.mahasiswa|length if tersimpan.data.mode == 'per_mahasiswa' else none %}
|
||||||
<div class="info">
|
<div class="info">
|
||||||
Data tersimpan: mode
|
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>
|
<b>{% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ n_mhs }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %}</b>
|
||||||
— terakhir disimpan {{ tersimpan.updated_at }}{% if tersimpan.updated_by %} oleh {{ tersimpan.updated_by }}{% endif %}.
|
— terakhir disimpan {{ tersimpan.updated_at }}{% if tersimpan.updated_by %} oleh {{ tersimpan.updated_by }}{% endif %}.
|
||||||
{% if riwayat_ringkas %}<br>Ringkasan pengisian — team teaching:
|
{% if riwayat_ringkas %}<br>Ringkasan pengisian — team teaching:
|
||||||
{% for k in riwayat_ringkas %}{{ k.komponen }}: {% for d in k.per_dosen %}{{ d.n }}× {{ d.nama }}{{ ', ' if not loop.last }}{% endfor %}{{ ' · ' if not loop.last }}{% endfor %}.{% endif %}
|
{% for k in riwayat_ringkas %}{{ k.komponen }}: {% for d in k.per_dosen %}{{ d.n }}× {{ d.nama }}{{ ', ' if not loop.last }}{% endfor %}{{ ' · ' if not loop.last }}{% endfor %}.{% endif %}
|
||||||
<form method="post" action="/nilai/{{ peta.kode }}" style="display:inline;margin-left:8px"
|
<form method="post" action="/nilai/{{ peta.kode }}" style="display:flex;align-items:center;gap:8px;margin-top:8px;flex-wrap:wrap"
|
||||||
onsubmit="return confirm('Hapus SELURUH nilai tersimpan MK ini untuk {{ tahun }}? Tindakan ini tidak bisa dibatalkan.')">
|
onsubmit="return confirm('Hapus SELURUH nilai{% if n_mhs %} {{ n_mhs }} mahasiswa{% endif %} tersimpan MK ini untuk {{ tahun }}?\n\nSalinan sebelumnya akan diarsipkan otomatis, tapi tidak ada tombol pulihkan di aplikasi — hubungi kaprodi/admin bila perlu dikembalikan.\n\nTindakan ini tidak bisa dibatalkan sendiri. Lanjutkan?')">
|
||||||
<button class="tombol garis" type="submit" name="aksi" value="hapus"
|
<input type="hidden" name="aksi" value="hapus">
|
||||||
|
<label style="font-size:13px;color:var(--grey)">Konfirmasi password Anda:
|
||||||
|
<input type="password" name="password" required autocomplete="current-password"
|
||||||
|
style="display:inline-block;width:auto;padding:3px 6px;font-size:13px;margin-left:4px">
|
||||||
|
</label>
|
||||||
|
<button class="tombol garis" type="submit"
|
||||||
style="color:var(--red);border-color:var(--red);padding:2px 10px;font-size:13px">Mulai Ulang (hapus semua nilai)</button>
|
style="color:var(--red);border-color:var(--red);padding:2px 10px;font-size:13px">Mulai Ulang (hapus semua nilai)</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,10 +102,25 @@
|
|||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% set ada_kelas = grid and grid|selectattr('kelas')|list|length > 0 %}
|
||||||
|
|
||||||
{% if grid and tabel_komponen and komponen_daftar %}
|
{% if grid and tabel_komponen and komponen_daftar %}
|
||||||
<h2 class="bagian">NILAI PER KOMPONEN <span style="font-weight:normal;font-style:italic;color:var(--grey)">— nilai mahasiswa berdasarkan komponen</span></h2>
|
<h2 class="bagian">NILAI PER KOMPONEN <span style="font-weight:normal;font-style:italic;color:var(--grey)">— nilai mahasiswa berdasarkan komponen</span></h2>
|
||||||
<p class="keterangan">Angka = rata-rata skor mentah tiap sesi/tugas komponen itu yang diikuti mahasiswa (bukan lewat Sub-CPMK). Ketik untuk menimpa —
|
<p class="keterangan">Angka = rata-rata skor mentah tiap sesi/tugas komponen itu yang diikuti mahasiswa (bukan lewat Sub-CPMK). Ketik untuk menimpa —
|
||||||
ditulis rata ke semua Sub-CPMK komponen itu; rincian per Sub-CPMK tetap presisi di grid bawah.</p>
|
ditulis rata ke semua Sub-CPMK komponen itu; rincian per Sub-CPMK tetap presisi di grid bawah.</p>
|
||||||
|
{% if ada_kelas %}
|
||||||
|
{% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %}
|
||||||
|
<div class="filter-baris">
|
||||||
|
<label for="filter-kelas">Tampilkan kelas:</label>
|
||||||
|
<select id="filter-kelas" style="width:auto">
|
||||||
|
<option value="">Semua</option>
|
||||||
|
{% for k in semua_kelas %}
|
||||||
|
<option value="{{ k }}">Kelas {{ k }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<span id="filter-kelas-info" class="keterangan" style="margin:0"></span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<form method="post" action="/nilai/{{ peta.kode }}">
|
<form method="post" action="/nilai/{{ peta.kode }}">
|
||||||
<input type="hidden" name="aksi" value="komponen">
|
<input type="hidden" name="aksi" value="komponen">
|
||||||
<div class="gulir">
|
<div class="gulir">
|
||||||
@@ -109,7 +137,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for r in tabel_komponen %}
|
{% for r in tabel_komponen %}
|
||||||
{% set ri = loop.index0 %}
|
{% set ri = loop.index0 %}
|
||||||
<tr>
|
<tr data-kelas="{{ r.kelas or '' }}">
|
||||||
<td class="tengah">{{ loop.index }}
|
<td class="tengah">{{ loop.index }}
|
||||||
<input type="hidden" name="nim_{{ ri }}" value="{{ r.nim }}">
|
<input type="hidden" name="nim_{{ ri }}" value="{{ r.nim }}">
|
||||||
<input type="hidden" name="nama_{{ ri }}" value="{{ r.nama }}"></td>
|
<input type="hidden" name="nama_{{ ri }}" value="{{ r.nama }}"></td>
|
||||||
@@ -140,9 +168,21 @@
|
|||||||
{% if grid %}
|
{% if grid %}
|
||||||
{% set per_mhs = tersimpan and tersimpan.data.mode == 'per_mahasiswa' %}
|
{% 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)">— nilai mahasiswa berdasarkan Sub-CPMK</span></h2>
|
<h2 class="bagian">NILAI PER MAHASISWA <span style="font-weight:normal;font-style:italic;color:var(--grey)">— nilai mahasiswa berdasarkan Sub-CPMK</span></h2>
|
||||||
|
{% if ada_kelas and not (tabel_komponen and komponen_daftar) %}
|
||||||
|
{% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %}
|
||||||
|
<div class="filter-baris">
|
||||||
|
<label for="filter-kelas">Tampilkan kelas:</label>
|
||||||
|
<select id="filter-kelas" style="width:auto">
|
||||||
|
<option value="">Semua</option>
|
||||||
|
{% for k in semua_kelas %}
|
||||||
|
<option value="{{ k }}">Kelas {{ k }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<span id="filter-kelas-info" class="keterangan" style="margin:0"></span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<form method="post" action="/nilai/{{ peta.kode }}">
|
<form method="post" action="/nilai/{{ peta.kode }}">
|
||||||
<div class="gulir">
|
<div class="gulir">
|
||||||
{% set ada_kelas = grid|selectattr('kelas')|list|length > 0 %}
|
|
||||||
{% set n_kiri = 4 if ada_kelas else 3 %}
|
{% set n_kiri = 4 if ada_kelas else 3 %}
|
||||||
<table class="grid">
|
<table class="grid">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -158,7 +198,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for m in grid %}
|
{% for m in grid %}
|
||||||
{% set i = loop.index0 %}
|
{% set i = loop.index0 %}
|
||||||
<tr>
|
<tr data-kelas="{{ m.kelas or '' }}">
|
||||||
<td class="tengah">{{ loop.index }}
|
<td class="tengah">{{ loop.index }}
|
||||||
<input type="hidden" name="nim_{{ i }}" value="{{ m.nim }}">
|
<input type="hidden" name="nim_{{ i }}" value="{{ m.nim }}">
|
||||||
<input type="hidden" name="nama_{{ i }}" value="{{ m.nama }}"></td>
|
<input type="hidden" name="nama_{{ i }}" value="{{ m.nama }}"></td>
|
||||||
@@ -174,7 +214,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if per_mhs and hasil %}
|
{% if per_mhs and hasil %}
|
||||||
<tr class="total">
|
<tr class="total" data-kelas="__total__" title="Dihitung dari seluruh mahasiswa MK ini, tidak per kelas">
|
||||||
<td colspan="{{ n_kiri }}" style="text-align:right">Rata-rata kelas (tersimpan)</td>
|
<td colspan="{{ n_kiri }}" style="text-align:right">Rata-rata kelas (tersimpan)</td>
|
||||||
{% for s in peta.subs %}
|
{% for s in peta.subs %}
|
||||||
{% set info = hasil.sub.get(s.kode) %}
|
{% set info = hasil.sub.get(s.kode) %}
|
||||||
@@ -182,7 +222,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<td class="tengah">{{ '%.1f'|format(akhir.values()|sum / akhir|length) if akhir else '—' }}</td>
|
<td class="tengah">{{ '%.1f'|format(akhir.values()|sum / akhir|length) if akhir else '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="total">
|
<tr class="total" data-kelas="__total__" title="Dihitung dari seluruh mahasiswa MK ini, tidak per kelas">
|
||||||
<td colspan="{{ n_kiri }}" style="text-align:right">% lulus (≥ {{ ambang|round(0)|int }})</td>
|
<td colspan="{{ n_kiri }}" style="text-align:right">% lulus (≥ {{ ambang|round(0)|int }})</td>
|
||||||
{% for s in peta.subs %}
|
{% for s in peta.subs %}
|
||||||
{% set info = hasil.sub.get(s.kode) %}
|
{% set info = hasil.sub.get(s.kode) %}
|
||||||
@@ -383,4 +423,34 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ada_kelas %}
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var pilih = document.getElementById('filter-kelas');
|
||||||
|
var info = document.getElementById('filter-kelas-info');
|
||||||
|
var baris = document.querySelectorAll('table.grid tbody tr[data-kelas]');
|
||||||
|
|
||||||
|
function perbarui() {
|
||||||
|
var v = pilih.value, n = 0, total = 0;
|
||||||
|
baris.forEach(function (tr) {
|
||||||
|
var kelas = tr.dataset.kelas;
|
||||||
|
if (kelas === '__total__') {
|
||||||
|
// baris rata-rata/% lulus: dihitung dari seluruh MK, cuma masuk akal saat "Semua"
|
||||||
|
tr.style.display = v === '' ? '' : 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
total++;
|
||||||
|
var tampil = v === '' || kelas === v;
|
||||||
|
tr.style.display = tampil ? '' : 'none';
|
||||||
|
if (tampil) n++;
|
||||||
|
});
|
||||||
|
info.textContent = v === '' ? '' : n + ' dari ' + total + ' mahasiswa ditampilkan';
|
||||||
|
}
|
||||||
|
|
||||||
|
pilih.addEventListener('change', perbarui);
|
||||||
|
perbarui();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -96,6 +96,20 @@
|
|||||||
</label>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2 class="bagian">NILAI MAHASISWA</h2>
|
<h2 class="bagian">NILAI MAHASISWA</h2>
|
||||||
|
{% set ada_kelas = grid|selectattr('kelas')|list|length > 0 %}
|
||||||
|
{% if ada_kelas %}
|
||||||
|
{% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %}
|
||||||
|
<div class="filter-baris">
|
||||||
|
<label for="filter-kelas">Tampilkan kelas:</label>
|
||||||
|
<select id="filter-kelas" style="width:auto">
|
||||||
|
<option value="">Semua</option>
|
||||||
|
{% for k in semua_kelas %}
|
||||||
|
<option value="{{ k }}">Kelas {{ k }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<span id="filter-kelas-info" class="keterangan" style="margin:0"></span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="gulir" style="max-height:480px;overflow-y:auto">
|
<div class="gulir" style="max-height:480px;overflow-y:auto">
|
||||||
<table class="grid">
|
<table class="grid">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -103,17 +117,19 @@
|
|||||||
<th style="width:44px" class="tengah">No.</th>
|
<th style="width:44px" class="tengah">No.</th>
|
||||||
<th style="width:112px">NIM</th>
|
<th style="width:112px">NIM</th>
|
||||||
<th style="min-width:150px">Nama</th>
|
<th style="min-width:150px">Nama</th>
|
||||||
|
{% if ada_kelas %}<th style="width:60px" class="tengah">Kelas</th>{% endif %}
|
||||||
<th class="tengah" style="width:140px">Nilai (0–100)</th>
|
<th class="tengah" style="width:140px">Nilai (0–100)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in grid %}
|
{% for m in grid %}
|
||||||
<tr>
|
<tr data-kelas="{{ m.kelas or '' }}">
|
||||||
<td class="tengah">{{ loop.index }}
|
<td class="tengah">{{ loop.index }}
|
||||||
<input type="hidden" name="nim_{{ loop.index0 }}" value="{{ m.nim }}">
|
<input type="hidden" name="nim_{{ loop.index0 }}" value="{{ m.nim }}">
|
||||||
<input type="hidden" name="nama_{{ loop.index0 }}" value="{{ m.nama }}"></td>
|
<input type="hidden" name="nama_{{ loop.index0 }}" value="{{ m.nama }}"></td>
|
||||||
<td>{{ m.nim }}</td>
|
<td>{{ m.nim }}</td>
|
||||||
<td>{{ m.nama }}</td>
|
<td>{{ m.nama }}</td>
|
||||||
|
{% if ada_kelas %}<td class="tengah">{{ m.kelas or '—' }}</td>{% endif %}
|
||||||
<td class="isian"><input type="number" name="nilai_{{ loop.index0 }}"
|
<td class="isian"><input type="number" name="nilai_{{ loop.index0 }}"
|
||||||
min="0" max="100" step="any"
|
min="0" max="100" step="any"
|
||||||
value="{{ '%g'|format(m.nilai_awal) if m.nilai_awal is not none else '' }}"></td>
|
value="{{ '%g'|format(m.nilai_awal) if m.nilai_awal is not none else '' }}"></td>
|
||||||
@@ -129,6 +145,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{% if ada_kelas %}
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var pilih = document.getElementById('filter-kelas');
|
||||||
|
var info = document.getElementById('filter-kelas-info');
|
||||||
|
var baris = document.querySelectorAll('table.grid tbody tr[data-kelas]');
|
||||||
|
|
||||||
|
function perbarui() {
|
||||||
|
var v = pilih.value, n = 0;
|
||||||
|
baris.forEach(function (tr) {
|
||||||
|
var tampil = v === '' || tr.dataset.kelas === v;
|
||||||
|
tr.style.display = tampil ? '' : 'none';
|
||||||
|
if (tampil) n++;
|
||||||
|
});
|
||||||
|
info.textContent = v === '' ? '' : n + ' dari ' + baris.length + ' mahasiswa ditampilkan';
|
||||||
|
}
|
||||||
|
|
||||||
|
pilih.addEventListener('change', perbarui);
|
||||||
|
perbarui();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if komponen.tugas %}
|
{% if komponen.tugas %}
|
||||||
<script>
|
<script>
|
||||||
const BENTUK_TEMPLATE = {
|
const BENTUK_TEMPLATE = {
|
||||||
|
|||||||
@@ -178,6 +178,12 @@
|
|||||||
komponen yang tidak dipakai MK ini (opsional), tapi total seluruh komponen yang diisi harus
|
komponen yang tidak dipakai MK ini (opsional), tapi total seluruh komponen yang diisi harus
|
||||||
tepat 100%. Centang Sub-CPMK yang dinilai tiap komponen; setiap Sub-CPMK harus tercakup minimal
|
tepat 100%. Centang Sub-CPMK yang dinilai tiap komponen; setiap Sub-CPMK harus tercakup minimal
|
||||||
satu komponen agar CPMK/CPL-nya terukur.</p>
|
satu komponen agar CPMK/CPL-nya terukur.</p>
|
||||||
|
{% if komponen_tanpa_sub %}
|
||||||
|
<div class="peringatan">Bobot sudah terisi tapi <b>belum ada Sub-CPMK dicentang</b> untuk:
|
||||||
|
{{ komponen_tanpa_sub|join(', ') }}. Komponen begini ikut dihitung ke total 100% di bawah,
|
||||||
|
tapi <b>tidak akan muncul</b> di halaman Input Nilai — centang minimal satu Sub-CPMK di
|
||||||
|
tiap baris sebelum menyimpan.</div>
|
||||||
|
{% endif %}
|
||||||
<div class="gulir">
|
<div class="gulir">
|
||||||
<table class="grid">
|
<table class="grid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
Reference in New Issue
Block a user