Rapikan tampilan nilai, ganti NPM->NIM, fitur ganti password, kunci RPS view-only
- Tampilan "Isi Per Komponen Penilaian" diganti dari tombol bertumpuk teks jadi tabel (Komponen/Sub-CPMK/Bobot/Status/Aksi) dengan status berupa stempel, lebih mudah dipindai. - Filter pencarian MK (ketik kode/nama) di kotak penugasan dosen /admin. - Istilah NPM diseragamkan jadi NIM di seluruh aplikasi (label, header, template Excel, dokumen portofolio), termasuk migrasi kolom database mahasiswa.npm -> nim yang aman (ALTER TABLE RENAME COLUMN, otomatis saat startup, tanpa kehilangan data; parser Excel tetap terima file lama berheader NPM). - Fitur ganti password mandiri (/akun): verifikasi password lama, sesi lain otomatis keluar setelah berhasil, sesi yang dipakai tetap aktif. - RPS: dosen RPS+MK kini bisa menyesuaikan redaksi kalimat Sub-CPMK (bagian D) untuk MK yang diampu -- kode & kaitan CPMK/CPL tetap dari kurikulum, hanya teksnya yang bisa disesuaikan. Penyesuaian otomatis konsisten di dropdown mingguan (F), checklist komponen (G), serta unduhan RPS & portofolio, tanpa menyentuh data kurikulum induk atau halaman nilai/ dashboard prodi. - Form RPS dibungkus <fieldset disabled> untuk dosen tanpa hak edit -- seluruh field benar-benar terkunci (bukan cuma tombol simpan hilang).
This commit is contained in:
+15
-15
@@ -11,7 +11,7 @@ BIRU_MUDA = "D6E4F0"
|
||||
|
||||
|
||||
def template_mahasiswa(daftar_mk: list[dict], tahun: str) -> bytes:
|
||||
"""Template impor daftar mahasiswa: NPM | Nama | Kode MK | Kelas."""
|
||||
"""Template impor daftar mahasiswa: NIM | Nama | Kode MK | Kelas."""
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Mahasiswa"
|
||||
@@ -19,7 +19,7 @@ def template_mahasiswa(daftar_mk: list[dict], tahun: str) -> bytes:
|
||||
ws.append(["Petunjuk: satu baris per kontrak (mahasiswa yang mengambil 5 MK "
|
||||
"berarti 5 baris). Kode MK harus sesuai sheet 'Kode MK'."])
|
||||
ws.append([])
|
||||
ws.append(["NPM", "Nama Mahasiswa", "Kode MK", "Kelas"])
|
||||
ws.append(["NIM", "Nama Mahasiswa", "Kode MK", "Kelas"])
|
||||
for ci, lebar in enumerate((16, 30, 14, 10), 1):
|
||||
c = ws.cell(row=4, column=ci)
|
||||
c.font = Font(bold=True, color="FFFFFF")
|
||||
@@ -47,8 +47,8 @@ def template_mahasiswa(daftar_mk: list[dict], tahun: str) -> bytes:
|
||||
def parse_mahasiswa(isi: bytes, kode_mk_valid: set[str]) -> tuple[list[dict], list[str]]:
|
||||
"""Baca xlsx daftar mahasiswa → (baris kontrak, peringatan).
|
||||
|
||||
Header dicari otomatis (baris yang memuat 'NPM'); kolom dikenali dari
|
||||
judulnya: NPM, Nama, Kode MK, Kelas.
|
||||
Header dicari otomatis (baris yang memuat 'NIM'); kolom dikenali dari
|
||||
judulnya: NIM, Nama, Kode MK, Kelas.
|
||||
"""
|
||||
wb = openpyxl.load_workbook(io.BytesIO(isi), read_only=True, data_only=True)
|
||||
ws = wb["Mahasiswa"] if "Mahasiswa" in wb.sheetnames else wb.active
|
||||
@@ -58,11 +58,11 @@ def parse_mahasiswa(isi: bytes, kode_mk_valid: set[str]) -> tuple[list[dict], li
|
||||
header_i = None
|
||||
for i, row in enumerate(rows):
|
||||
vals = [str(v).strip().lower() if v is not None else "" for v in row]
|
||||
if "npm" in vals:
|
||||
if "nim" in vals or "npm" in vals:
|
||||
header_i = i
|
||||
for ci, v in enumerate(vals):
|
||||
if v == "npm":
|
||||
kol["npm"] = ci
|
||||
if v in ("nim", "npm"): # 'npm' — kompatibilitas file lama
|
||||
kol["nim"] = ci
|
||||
elif "nama" in v:
|
||||
kol.setdefault("nama", ci)
|
||||
elif "kode" in v or v == "mk":
|
||||
@@ -70,8 +70,8 @@ def parse_mahasiswa(isi: bytes, kode_mk_valid: set[str]) -> tuple[list[dict], li
|
||||
elif "kelas" in v:
|
||||
kol.setdefault("kelas", ci)
|
||||
break
|
||||
if header_i is None or "npm" not in kol or "mk" not in kol:
|
||||
return [], ["Header tidak dikenali: pastikan ada kolom NPM, Nama, "
|
||||
if header_i is None or "nim" not in kol or "mk" not in kol:
|
||||
return [], ["Header tidak dikenali: pastikan ada kolom NIM, Nama, "
|
||||
"Kode MK, dan Kelas (pakai template dari aplikasi)."]
|
||||
|
||||
def ambil(vals, kunci):
|
||||
@@ -79,7 +79,7 @@ def parse_mahasiswa(isi: bytes, kode_mk_valid: set[str]) -> tuple[list[dict], li
|
||||
if ci is None or ci >= len(vals) or vals[ci] is None:
|
||||
return ""
|
||||
v = vals[ci]
|
||||
# NPM/kelas numerik dari Excel → buang '.0'
|
||||
# NIM/kelas numerik dari Excel → buang '.0'
|
||||
if isinstance(v, float) and v.is_integer():
|
||||
v = int(v)
|
||||
return str(v).strip()
|
||||
@@ -88,17 +88,17 @@ def parse_mahasiswa(isi: bytes, kode_mk_valid: set[str]) -> tuple[list[dict], li
|
||||
terlihat = set()
|
||||
for row in rows[header_i + 1:]:
|
||||
vals = list(row)
|
||||
npm = ambil(vals, "npm")
|
||||
nim = ambil(vals, "nim")
|
||||
mk = ambil(vals, "mk").upper()
|
||||
if not npm or not mk:
|
||||
if not nim or not mk:
|
||||
continue
|
||||
if mk not in kode_mk_valid:
|
||||
tak_dikenal.add(mk)
|
||||
continue
|
||||
if (npm, mk) in terlihat:
|
||||
if (nim, mk) in terlihat:
|
||||
continue
|
||||
terlihat.add((npm, mk))
|
||||
baris.append({"npm": npm, "nama": ambil(vals, "nama"),
|
||||
terlihat.add((nim, mk))
|
||||
baris.append({"nim": nim, "nama": ambil(vals, "nama"),
|
||||
"mk_kode": mk, "kelas": ambil(vals, "kelas")})
|
||||
|
||||
if tak_dikenal:
|
||||
|
||||
Reference in New Issue
Block a user