Update simulasi-mk.html
This commit is contained in:
377
simulasi-mk.html
377
simulasi-mk.html
@@ -25,8 +25,13 @@
|
|||||||
.course-card.dragging { opacity: 0.5; border: 2px dashed #a8a29e; background-color: #f5f5f4; }
|
.course-card.dragging { opacity: 0.5; border: 2px dashed #a8a29e; background-color: #f5f5f4; }
|
||||||
|
|
||||||
/* Drop Zone Styles */
|
/* Drop Zone Styles */
|
||||||
.semester-list { min-height: 120px; transition: background-color 0.2s; }
|
.semester-list { min-height: 120px; transition: background-color 0.2s, opacity 0.2s; }
|
||||||
.semester-list.drag-over { background-color: #f0fdf4; border: 2px dashed #4ade80; }
|
|
||||||
|
/* Valid Drop Target */
|
||||||
|
.semester-list.valid-target { background-color: #f0fdf4; border: 2px dashed #4ade80; }
|
||||||
|
|
||||||
|
/* Invalid Drop Target (Wrong Parity) */
|
||||||
|
.semester-list.invalid-target { opacity: 0.3; background-color: #e5e5e5; pointer-events: none; border: 2px solid #d4d4d4; }
|
||||||
|
|
||||||
/* Warning States */
|
/* Warning States */
|
||||||
.semester-container { transition: all 0.3s; }
|
.semester-container { transition: all 0.3s; }
|
||||||
@@ -54,11 +59,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-stone-50 text-stone-800">
|
<body class="bg-stone-50 text-stone-800">
|
||||||
|
|
||||||
<!-- Chosen Palette: Warm Neutrals (Stone) with Logic-based Alerts (Red/Green) -->
|
<!-- Chosen Palette: Warm Neutrals with Logic Alerts -->
|
||||||
<!-- Application Structure Plan:
|
<!-- Application Structure Plan:
|
||||||
- Added "GPA Simulation" controls to each semester header.
|
- Updated Drag & Drop Logic: Enforce Odd/Even semester constraints.
|
||||||
- Semester containers now react visually to SKS Overload based on previous semester's GPA.
|
- Visual Feedback: Grey out invalid semesters when dragging.
|
||||||
- Drag & Drop enables users to "Fail/Postpone" courses to future semesters to fit the SKS limit.
|
|
||||||
-->
|
-->
|
||||||
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
|
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
|
||||||
|
|
||||||
@@ -68,7 +72,9 @@
|
|||||||
<header class="mb-10 text-center">
|
<header class="mb-10 text-center">
|
||||||
<h1 class="text-3xl md:text-4xl font-bold text-stone-900 mb-4">Simulasi Perjalanan Mahasiswa & KRS</h1>
|
<h1 class="text-3xl md:text-4xl font-bold text-stone-900 mb-4">Simulasi Perjalanan Mahasiswa & KRS</h1>
|
||||||
<p class="text-lg text-stone-600 max-w-3xl mx-auto">
|
<p class="text-lg text-stone-600 max-w-3xl mx-auto">
|
||||||
Atur strategi studimu. <span class="font-bold text-orange-600">Simulasikan IP rendah</span>, lihat dampak batas SKS, dan geser mata kuliah untuk menyusun ulang rencana (mengulang/menunda).
|
Atur strategi studimu. <span class="font-bold text-orange-600">Simulasikan IP rendah</span>, lihat dampak batas SKS, dan geser mata kuliah untuk menyusun ulang rencana.
|
||||||
|
<br>
|
||||||
|
<span class="text-sm bg-red-100 text-red-800 px-2 py-1 rounded mt-2 inline-block font-bold">Aturan Baru: MK Ganjil hanya bisa ke Sem Ganjil, Genap ke Genap.</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 flex flex-wrap justify-center gap-4 text-sm">
|
<div class="mt-6 flex flex-wrap justify-center gap-4 text-sm">
|
||||||
@@ -169,7 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="semester-list p-3 space-y-2 flex-grow" ondrop="drop(event)" ondragover="allowDrop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)">
|
<div class="semester-list p-3 space-y-2 flex-grow relative" ondrop="drop(event)" ondragover="allowDrop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)">
|
||||||
<!-- Course Cards go here -->
|
<!-- Course Cards go here -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -181,45 +187,46 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// --- DATA ---
|
// --- DATA ---
|
||||||
|
|
||||||
const generalistSemestersBlueprint = {
|
const generalistSemestersBlueprint = {
|
||||||
1: [
|
1: [
|
||||||
{code: "MK06", name: "Dasar Pemrograman*", sks: 3},
|
{code: "MK06", name: "Dasar Pemrograman*", sks: 3, priority: "core"},
|
||||||
{code: "MK07", name: "Algoritma & Logika", sks: 2},
|
{code: "MK07", name: "Algoritma & Logika", sks: 2, priority: "core"},
|
||||||
{code: "MK04", name: "Kalkulus", sks: 3},
|
{code: "MK04", name: "Kalkulus", sks: 3, priority: "core"},
|
||||||
{code: "MK05", name: "Pengantar Informatika", sks: 2},
|
{code: "MK05", name: "Pengantar Informatika", sks: 2, priority: "standard"},
|
||||||
{code: "MK01", name: "Pendidikan Pancasila", sks: 2},
|
{code: "MK01", name: "Pendidikan Pancasila", sks: 2, priority: "standard"},
|
||||||
{code: "MK02", name: "Pendidikan Agama", sks: 3},
|
{code: "MK02", name: "Pendidikan Agama", sks: 3, priority: "standard"},
|
||||||
{code: "MK03", name: "Bahasa Inggris", sks: 2},
|
{code: "MK03", name: "Bahasa Inggris", sks: 2, priority: "support"},
|
||||||
{code: "MK08", name: "Kom. Profesional", sks: 2}
|
{code: "MK08", name: "Kom. Profesional", sks: 2, priority: "support"}
|
||||||
],
|
],
|
||||||
2: [
|
2: [
|
||||||
{code: "MK15", name: "Struktur Data & Algo*", sks: 3},
|
{code: "MK15", name: "Struktur Data & Algo*", sks: 3, priority: "core"},
|
||||||
{code: "MK12", name: "Matematika Diskrit", sks: 3},
|
{code: "MK12", name: "Matematika Diskrit", sks: 3, priority: "core"},
|
||||||
{code: "MK11", name: "Sistem Digital", sks: 2},
|
{code: "MK11", name: "Sistem Digital", sks: 2, priority: "core"},
|
||||||
{code: "MK13", name: "Org. & Ars. Komputer", sks: 3},
|
{code: "MK13", name: "Org. & Ars. Komputer", sks: 3, priority: "core"},
|
||||||
{code: "MK14", name: "Sistem Operasi*", sks: 3},
|
{code: "MK14", name: "Sistem Operasi*", sks: 3, priority: "core"},
|
||||||
{code: "MK16", name: "Literasi Digital", sks: 2},
|
{code: "MK16", name: "Literasi Digital", sks: 2, priority: "support"},
|
||||||
{code: "MK10", name: "Bahasa Indonesia", sks: 2},
|
{code: "MK10", name: "Bahasa Indonesia", sks: 2, priority: "support"},
|
||||||
{code: "MK09", name: "Kewarganegaraan", sks: 2}
|
{code: "MK09", name: "Kewarganegaraan", sks: 2, priority: "support"}
|
||||||
],
|
],
|
||||||
3: [
|
3: [
|
||||||
{code: "MK20", name: "Basis Data*", sks: 3},
|
{code: "MK20", name: "Basis Data*", sks: 3, priority: "core"},
|
||||||
{code: "MK21", name: "Jaringan Komputer*", sks: 3},
|
{code: "MK21", name: "Jaringan Komputer*", sks: 3, priority: "core"},
|
||||||
{code: "MK27", name: "PBO*", sks: 3},
|
{code: "MK27", name: "PBO*", sks: 3, priority: "core"},
|
||||||
{code: "MK24", name: "IMK", sks: 2},
|
{code: "MK22", name: "Strategi Algoritma*", sks: 3, priority: "core"},
|
||||||
{code: "MK22", name: "Strategi Algoritma*", sks: 3},
|
{code: "MK17", name: "Aljabar Linier", sks: 3, priority: "core"},
|
||||||
{code: "MK17", name: "Aljabar Linier", sks: 3},
|
{code: "MK24", name: "IMK", sks: 2, priority: "support"},
|
||||||
{code: "MK19", name: "Sistem Informasi", sks: 2},
|
{code: "MK19", name: "Sistem Informasi", sks: 2, priority: "support"},
|
||||||
{code: "MK18", name: "Etika & Keberlanjutan", sks: 2}
|
{code: "MK18", name: "Etika & Keberlanjutan", sks: 2, priority: "support"}
|
||||||
],
|
],
|
||||||
4: [
|
4: [
|
||||||
{code: "MK23", name: "RPL*", sks: 3},
|
{code: "MK23", name: "RPL*", sks: 3, priority: "core"},
|
||||||
{code: "MK32", name: "Probstat", sks: 3},
|
{code: "MK32", name: "Probstat", sks: 3, priority: "core"},
|
||||||
{code: "MK31", name: "Pemrograman Web*", sks: 3},
|
{code: "MK31", name: "Pemrograman Web*", sks: 3, priority: "core"},
|
||||||
{code: "MK30", name: "Kecerdasan Buatan", sks: 3},
|
{code: "MK30", name: "Kecerdasan Buatan", sks: 3, priority: "core"},
|
||||||
{code: "MK25", name: "APSI", sks: 3},
|
{code: "MK25", name: "APSI", sks: 3, priority: "standard"},
|
||||||
{code: "MK29", name: "Pemrog. Jaringan*", sks: 3},
|
{code: "MK29", name: "Pemrog. Jaringan*", sks: 3, priority: "standard"},
|
||||||
{code: "MK26", name: "Sis. Paralel", sks: 2}
|
{code: "MK26", name: "Sis. Paralel", sks: 2, priority: "standard"}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,31 +236,31 @@
|
|||||||
color: "text-blue-800",
|
color: "text-blue-800",
|
||||||
desc: "Fokus: Data, ML, Vision.",
|
desc: "Fokus: Data, ML, Vision.",
|
||||||
sem5: [
|
sem5: [
|
||||||
{code: "MK35", name: "Machine Learning", sks: 3},
|
{code: "MK35", name: "Machine Learning", sks: 3, priority: "core"},
|
||||||
{code: "MK28", name: "Metode Numerik*", sks: 3},
|
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "core"},
|
||||||
{code: "MK36", name: "Proyek PL*", sks: 3},
|
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
|
||||||
{code: "MK37", name: "Proposal TA", sks: 2},
|
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"},
|
||||||
{code: "MKP", name: "Data Mining", sks: 3},
|
{code: "MKP", name: "Data Mining", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Big Data", sks: 3},
|
{code: "MKP", name: "Big Data", sks: 3, priority: "standard"},
|
||||||
{code: "MK33", name: "IoT", sks: 3}
|
{code: "MK33", name: "IoT", sks: 3, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem6: [
|
sem6: [
|
||||||
{code: "MK38", name: "Comp. Vision*", sks: 3},
|
{code: "MK38", name: "Comp. Vision*", sks: 3, priority: "core"},
|
||||||
{code: "MK43", name: "NLP", sks: 3},
|
{code: "MK43", name: "NLP", sks: 3, priority: "core"},
|
||||||
{code: "MK44", name: "Kerja Praktik", sks: 2},
|
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
|
||||||
{code: "MKP", name: "Deep Learning", sks: 3},
|
{code: "MKP", name: "Deep Learning", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "AI Ethics", sks: 2},
|
{code: "MKP", name: "AI Ethics", sks: 2, priority: "standard"},
|
||||||
{code: "MKP", name: "Pilihan Lintas KK", sks: 3},
|
{code: "MKP", name: "Pilihan Lintas KK", sks: 3, priority: "standard"},
|
||||||
{code: "MK42", name: "Keamanan Info*", sks: 2}
|
{code: "MK42", name: "Keamanan Info*", sks: 2, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem7: [
|
sem7: [
|
||||||
{code: "MK48", name: "TA 1", sks: 2},
|
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
|
||||||
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
|
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
|
||||||
{code: "MK47", name: "Uji PL", sks: 3},
|
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
|
||||||
{code: "MK42", name: "Keamanan Info", sks: 3},
|
{code: "MK42", name: "Keamanan Info", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Health Info", sks: 3},
|
{code: "MKP", name: "Health Info", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Reinforcement L.", sks: 3},
|
{code: "MKP", name: "Reinforcement L.", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Quantum/Bebas", sks: 5}
|
{code: "MKP", name: "Quantum/Bebas", sks: 5, priority: "standard"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
rpl: {
|
rpl: {
|
||||||
@@ -261,30 +268,30 @@
|
|||||||
color: "text-orange-800",
|
color: "text-orange-800",
|
||||||
desc: "Fokus: Apps, Architecture, UX.",
|
desc: "Fokus: Apps, Architecture, UX.",
|
||||||
sem5: [
|
sem5: [
|
||||||
{code: "MK36", name: "Proyek PL*", sks: 3},
|
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
|
||||||
{code: "MKP", name: "UX/UI Design", sks: 3},
|
{code: "MKP", name: "UX/UI Design", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Secure SE", sks: 3},
|
{code: "MKP", name: "Secure SE", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Web Lanjut", sks: 3},
|
{code: "MKP", name: "Web Lanjut", sks: 3, priority: "standard"},
|
||||||
{code: "MK28", name: "Metode Numerik*", sks: 3},
|
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "standard"},
|
||||||
{code: "MK37", name: "Proposal TA", sks: 2},
|
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"},
|
||||||
{code: "MK33", name: "IoT", sks: 3}
|
{code: "MK33", name: "IoT", sks: 3, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem6: [
|
sem6: [
|
||||||
{code: "MK39", name: "Pemrog. Mobile*", sks: 3},
|
{code: "MK39", name: "Pemrog. Mobile*", sks: 3, priority: "core"},
|
||||||
{code: "MKP", name: "Microservices", sks: 3},
|
{code: "MKP", name: "Microservices", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Game Dev", sks: 3},
|
{code: "MKP", name: "Game Dev", sks: 3, priority: "standard"},
|
||||||
{code: "MK44", name: "Kerja Praktik", sks: 2},
|
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
|
||||||
{code: "MK41", name: "Sis. Enterprise", sks: 3},
|
{code: "MK41", name: "Sis. Enterprise", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pil. Lintas KK", sks: 4}
|
{code: "MKP", name: "Pil. Lintas KK", sks: 4, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem7: [
|
sem7: [
|
||||||
{code: "MK48", name: "TA 1", sks: 2},
|
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
|
||||||
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
|
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
|
||||||
{code: "MK47", name: "Uji PL", sks: 3},
|
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
|
||||||
{code: "MK42", name: "Keamanan Info", sks: 3},
|
{code: "MK42", name: "Keamanan Info", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "DevOps", sks: 3},
|
{code: "MKP", name: "DevOps", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Soft. Arch", sks: 3},
|
{code: "MKP", name: "Soft. Arch", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pil. Bebas", sks: 5}
|
{code: "MKP", name: "Pil. Bebas", sks: 5, priority: "standard"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
net: {
|
net: {
|
||||||
@@ -292,30 +299,30 @@
|
|||||||
color: "text-emerald-800",
|
color: "text-emerald-800",
|
||||||
desc: "Fokus: Infra, Security, IoT.",
|
desc: "Fokus: Infra, Security, IoT.",
|
||||||
sem5: [
|
sem5: [
|
||||||
{code: "MK33", name: "IoT", sks: 3},
|
{code: "MK33", name: "IoT", sks: 3, priority: "core"},
|
||||||
{code: "MK34", name: "Manajemen Jar.*", sks: 3},
|
{code: "MK34", name: "Manajemen Jar.*", sks: 3, priority: "core"},
|
||||||
{code: "MKP", name: "Embedded Sys", sks: 3},
|
{code: "MKP", name: "Embedded Sys", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Ethical Hack", sks: 3},
|
{code: "MKP", name: "Ethical Hack", sks: 3, priority: "standard"},
|
||||||
{code: "MK36", name: "Proyek PL*", sks: 3},
|
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
|
||||||
{code: "MK28", name: "Metode Numerik*", sks: 3},
|
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "standard"},
|
||||||
{code: "MK37", name: "Proposal TA", sks: 2}
|
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"}
|
||||||
],
|
],
|
||||||
sem6: [
|
sem6: [
|
||||||
{code: "MK42", name: "Keamanan Info*", sks: 3},
|
{code: "MK42", name: "Keamanan Info*", sks: 3, priority: "core"},
|
||||||
{code: "MKP", name: "Wireless Sensor", sks: 3},
|
{code: "MKP", name: "Wireless Sensor", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Cryptography", sks: 3},
|
{code: "MKP", name: "Cryptography", sks: 3, priority: "standard"},
|
||||||
{code: "MK44", name: "Kerja Praktik", sks: 2},
|
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
|
||||||
{code: "MK39", name: "Mobile (IoT)*", sks: 3},
|
{code: "MK39", name: "Mobile (IoT)*", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pil. Lintas KK", sks: 3}
|
{code: "MKP", name: "Pil. Lintas KK", sks: 3, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem7: [
|
sem7: [
|
||||||
{code: "MK48", name: "TA 1", sks: 2},
|
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
|
||||||
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
|
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
|
||||||
{code: "MK47", name: "Uji PL", sks: 3},
|
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Cloud IoT", sks: 3},
|
{code: "MKP", name: "Cloud IoT", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Blockchain", sks: 3},
|
{code: "MKP", name: "Blockchain", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pil. Bebas 1", sks: 3},
|
{code: "MKP", name: "Pil. Bebas 1", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pil. Bebas 2", sks: 6}
|
{code: "MKP", name: "Pil. Bebas 2", sks: 6, priority: "standard"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
si: {
|
si: {
|
||||||
@@ -323,38 +330,38 @@
|
|||||||
color: "text-purple-800",
|
color: "text-purple-800",
|
||||||
desc: "Fokus: Business, GIS, Governance.",
|
desc: "Fokus: Business, GIS, Governance.",
|
||||||
sem5: [
|
sem5: [
|
||||||
{code: "MKP", name: "Business Intel", sks: 3},
|
{code: "MKP", name: "Business Intel", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "GIS II", sks: 3},
|
{code: "MKP", name: "GIS II", sks: 3, priority: "standard"},
|
||||||
{code: "MK36", name: "Proyek PL*", sks: 3},
|
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
|
||||||
{code: "MK28", name: "Metode Numerik*", sks: 3},
|
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "standard"},
|
||||||
{code: "MK37", name: "Proposal TA", sks: 2},
|
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"},
|
||||||
{code: "MKP", name: "E-Business", sks: 3},
|
{code: "MKP", name: "E-Business", sks: 3, priority: "standard"},
|
||||||
{code: "MK33", name: "IoT", sks: 3}
|
{code: "MK33", name: "IoT", sks: 3, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem6: [
|
sem6: [
|
||||||
{code: "MK41", name: "Sis. Enterprise", sks: 3},
|
{code: "MK41", name: "Sis. Enterprise", sks: 3, priority: "core"},
|
||||||
{code: "MK40", name: "SIG", sks: 3},
|
{code: "MK40", name: "SIG", sks: 3, priority: "core"},
|
||||||
{code: "MKP", name: "Enterprise Arch", sks: 3},
|
{code: "MKP", name: "Enterprise Arch", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Smart City", sks: 3},
|
{code: "MKP", name: "Smart City", sks: 3, priority: "standard"},
|
||||||
{code: "MK44", name: "Kerja Praktik", sks: 2},
|
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
|
||||||
{code: "MKP", name: "Pil. Lintas KK", sks: 3}
|
{code: "MKP", name: "Pil. Lintas KK", sks: 3, priority: "standard"}
|
||||||
],
|
],
|
||||||
sem7: [
|
sem7: [
|
||||||
{code: "MK48", name: "TA 1", sks: 2},
|
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
|
||||||
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
|
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
|
||||||
{code: "MK47", name: "Uji PL", sks: 3},
|
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
|
||||||
{code: "MK42", name: "Keamanan Info", sks: 3},
|
{code: "MK42", name: "Keamanan Info", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "SCM", sks: 3},
|
{code: "MKP", name: "SCM", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "IT Governance", sks: 3},
|
{code: "MKP", name: "IT Governance", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pil. Bebas", sks: 6}
|
{code: "MKP", name: "Pil. Bebas", sks: 6, priority: "standard"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mbkmData = [
|
const mbkmData = [
|
||||||
{code: "MK46", name: "PMKM (Wajib)", sks: 2},
|
{code: "MK46", name: "PMKM (Wajib)", sks: 2, priority: "core"},
|
||||||
{code: "MK45", name: "Teknopreneur (Konversi)", sks: 3},
|
{code: "MK45", name: "Teknopreneur (Konversi)", sks: 3, priority: "standard"},
|
||||||
{code: "MKP", name: "Pilihan Bebas (Ops)", sks: 3}
|
{code: "MKP", name: "Pilihan Bebas (Ops)", sks: 3, priority: "standard"}
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- STATE ---
|
// --- STATE ---
|
||||||
@@ -391,7 +398,7 @@
|
|||||||
currentPlan[5] = JSON.parse(JSON.stringify(track.sem5));
|
currentPlan[5] = JSON.parse(JSON.stringify(track.sem5));
|
||||||
currentPlan[6] = JSON.parse(JSON.stringify(track.sem6));
|
currentPlan[6] = JSON.parse(JSON.stringify(track.sem6));
|
||||||
currentPlan[7] = JSON.parse(JSON.stringify(track.sem7));
|
currentPlan[7] = JSON.parse(JSON.stringify(track.sem7));
|
||||||
currentPlan[8] = [{code: "MK49", name: "TA 2 (Sidang)", sks: 4}];
|
currentPlan[8] = [{code: "MK49", name: "TA 2 (Sidang)", sks: 4, priority: "core"}];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSem(semId, containerId) {
|
function renderSem(semId, containerId) {
|
||||||
@@ -451,21 +458,24 @@
|
|||||||
semesterStats[semId].gpa = val;
|
semesterStats[semId].gpa = val;
|
||||||
gpaValEl.textContent = val.toFixed(2);
|
gpaValEl.textContent = val.toFixed(2);
|
||||||
|
|
||||||
// Update display of next limit immediately
|
|
||||||
if(semId < 8) nextMaxEl.textContent = calculateMaxSKS(val);
|
if(semId < 8) nextMaxEl.textContent = calculateMaxSKS(val);
|
||||||
|
|
||||||
// Trigger re-render of NEXT semester (to show overload warning)
|
|
||||||
// We re-render everything to be safe and simple
|
|
||||||
// Debounce slighty could be good but for now direct call
|
|
||||||
});
|
});
|
||||||
gpaSlider.addEventListener('change', () => {
|
gpaSlider.addEventListener('change', () => {
|
||||||
renderAll(); // Re-render all to propagate red status to next semester
|
renderAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Populate List
|
// Populate List
|
||||||
const listContainer = clone.querySelector('.semester-list');
|
const listContainer = clone.querySelector('.semester-list');
|
||||||
listContainer.id = `list-sem-${semId}`; // For drag logic ref
|
listContainer.id = `list-sem-${semId}`;
|
||||||
listContainer.setAttribute('ondrop', `drop(event, ${semId})`); // Update drop target ID
|
|
||||||
|
// --- NEW: Parity Enforcement Logic ---
|
||||||
|
const isOddSem = semId % 2 !== 0;
|
||||||
|
const parityClass = isOddSem ? 'odd-semester' : 'even-semester';
|
||||||
|
listContainer.classList.add(parityClass);
|
||||||
|
listContainer.dataset.parity = isOddSem ? 'odd' : 'even';
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
listContainer.setAttribute('ondrop', `drop(event, ${semId})`);
|
||||||
|
|
||||||
courses.forEach((c, idx) => {
|
courses.forEach((c, idx) => {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
@@ -473,12 +483,24 @@
|
|||||||
el.setAttribute('draggable', 'true');
|
el.setAttribute('draggable', 'true');
|
||||||
el.setAttribute('ondragstart', `drag(event, ${semId}, ${idx})`);
|
el.setAttribute('ondragstart', `drag(event, ${semId}, ${idx})`);
|
||||||
|
|
||||||
// Styling for special items
|
// Styling
|
||||||
if(c.name.includes('KONVERSI')) el.classList.add('bg-green-50', 'border-green-200');
|
if(c.name.includes('KONVERSI')) el.classList.add('bg-green-50', 'border-green-200');
|
||||||
|
|
||||||
|
let badgeHTML = '';
|
||||||
|
if(c.priority === 'core') {
|
||||||
|
el.classList.add('border-l-4', 'border-l-blue-500');
|
||||||
|
badgeHTML = '<span class="text-[9px] bg-blue-100 text-blue-700 px-1 rounded ml-1 border border-blue-200" title="Pondasi Wajib">PONDASI</span>';
|
||||||
|
} else if(c.priority === 'support') {
|
||||||
|
el.classList.add('bg-stone-50', 'opacity-90');
|
||||||
|
badgeHTML = '<span class="text-[9px] bg-stone-200 text-stone-600 px-1 rounded ml-1 border border-stone-300" title="Bisa Ditunda">TUNDA?</span>';
|
||||||
|
}
|
||||||
|
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<div class="text-[10px] text-stone-400 font-mono">${c.code}</div>
|
<div class="flex items-center gap-1">
|
||||||
|
<div class="text-[10px] text-stone-400 font-mono">${c.code}</div>
|
||||||
|
${badgeHTML}
|
||||||
|
</div>
|
||||||
<div class="text-xs font-bold text-stone-700 truncate w-32" title="${c.name}">${c.name}</div>
|
<div class="text-xs font-bold text-stone-700 truncate w-32" title="${c.name}">${c.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs font-bold bg-stone-100 text-stone-600 px-1.5 py-0.5 rounded border border-stone-200 ml-2">${c.sks}</div>
|
<div class="text-xs font-bold bg-stone-100 text-stone-600 px-1.5 py-0.5 rounded border border-stone-200 ml-2">${c.sks}</div>
|
||||||
@@ -491,18 +513,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAll() {
|
function renderAll() {
|
||||||
// Render 1-4
|
|
||||||
renderSem(1, 'container-sem-1');
|
renderSem(1, 'container-sem-1');
|
||||||
renderSem(2, 'container-sem-2');
|
renderSem(2, 'container-sem-2');
|
||||||
renderSem(3, 'container-sem-3');
|
renderSem(3, 'container-sem-3');
|
||||||
renderSem(4, 'container-sem-4');
|
renderSem(4, 'container-sem-4');
|
||||||
|
|
||||||
// Render 5-8 (Inside Specialist Section)
|
|
||||||
const track = tracksBlueprint[currentTrack];
|
const track = tracksBlueprint[currentTrack];
|
||||||
const contentDiv = document.getElementById('specialist-content');
|
const contentDiv = document.getElementById('specialist-content');
|
||||||
|
|
||||||
// We recreate the grid structure for 5-8 inside the content div
|
|
||||||
// This is a bit brute force but keeps logic simple
|
|
||||||
contentDiv.innerHTML = `
|
contentDiv.innerHTML = `
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h3 class="text-xl font-bold ${track.color}">${track.name}</h3>
|
<h3 class="text-xl font-bold ${track.color}">${track.name}</h3>
|
||||||
@@ -510,7 +528,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
<div id="container-sem-5"></div>
|
<div id="container-sem-5"></div>
|
||||||
<!-- MBKM Slot Visual -->
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="bg-gradient-to-br from-green-50 to-emerald-100 rounded-xl border border-green-200 p-3 shadow-sm h-auto relative">
|
<div class="bg-gradient-to-br from-green-50 to-emerald-100 rounded-xl border border-green-200 p-3 shadow-sm h-auto relative">
|
||||||
<div class="absolute top-0 right-0 bg-green-200 text-green-800 text-[9px] font-bold px-2 py-1 rounded-bl">LIBURAN</div>
|
<div class="absolute top-0 right-0 bg-green-200 text-green-800 text-[9px] font-bold px-2 py-1 rounded-bl">LIBURAN</div>
|
||||||
@@ -518,7 +535,10 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
${mbkmData.map(c => `
|
${mbkmData.map(c => `
|
||||||
<div class="bg-white/60 p-2 rounded border border-green-100 flex justify-between items-center">
|
<div class="bg-white/60 p-2 rounded border border-green-100 flex justify-between items-center">
|
||||||
<span class="text-[10px] font-bold text-green-800">${c.name}</span>
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[10px] font-bold text-green-800">${c.name}</span>
|
||||||
|
<span class="text-[8px] text-green-600">${c.priority === 'core' ? 'Wajib' : 'Opsional'}</span>
|
||||||
|
</div>
|
||||||
<span class="text-[9px] bg-green-200 text-green-800 px-1 rounded">${c.sks}</span>
|
<span class="text-[9px] bg-green-200 text-green-800 px-1 rounded">${c.sks}</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
@@ -546,30 +566,85 @@
|
|||||||
renderAll();
|
renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- DRAG & DROP ---
|
// --- DRAG & DROP WITH PARITY CHECK ---
|
||||||
let draggedItem = null;
|
let draggedItem = null;
|
||||||
|
|
||||||
function drag(ev, semId, idx) {
|
function drag(ev, semId, idx) {
|
||||||
draggedItem = { semId, idx };
|
draggedItem = { semId, idx };
|
||||||
ev.dataTransfer.effectAllowed = "move";
|
ev.dataTransfer.effectAllowed = "move";
|
||||||
ev.target.style.opacity = '0.4';
|
ev.target.style.opacity = '0.4';
|
||||||
|
|
||||||
|
// Set global parity state for visual feedback
|
||||||
|
const isOddSource = semId % 2 !== 0;
|
||||||
|
document.body.classList.add(isOddSource ? 'dragging-odd' : 'dragging-even');
|
||||||
|
|
||||||
|
// Visual Update: Dim invalid targets
|
||||||
|
document.querySelectorAll('.semester-list').forEach(el => {
|
||||||
|
const targetIsOdd = el.dataset.parity === 'odd';
|
||||||
|
if(isOddSource !== targetIsOdd) {
|
||||||
|
el.classList.add('invalid-target');
|
||||||
|
} else {
|
||||||
|
el.classList.add('valid-target-hint'); // Optional hint style
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEndCleanup() {
|
||||||
|
document.body.classList.remove('dragging-odd', 'dragging-even');
|
||||||
|
document.querySelectorAll('.semester-list').forEach(el => {
|
||||||
|
el.classList.remove('drag-over', 'valid-target', 'invalid-target', 'valid-target-hint');
|
||||||
|
});
|
||||||
|
if(draggedItem) {
|
||||||
|
// Reset opacity if drag failed/cancelled
|
||||||
|
// Ideally found by element ref, but re-render fixes it anyway
|
||||||
|
}
|
||||||
|
draggedItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allowDrop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowDrop(ev) { ev.preventDefault(); }
|
|
||||||
function dragEnter(ev) {
|
function dragEnter(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.currentTarget.classList.add('drag-over');
|
const target = ev.currentTarget;
|
||||||
|
|
||||||
|
// Check parity match visual
|
||||||
|
if(!draggedItem) return;
|
||||||
|
const sourceParity = draggedItem.semId % 2 !== 0 ? 'odd' : 'even';
|
||||||
|
const targetParity = target.dataset.parity;
|
||||||
|
|
||||||
|
if(sourceParity === targetParity) {
|
||||||
|
target.classList.add('valid-target');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragLeave(ev) {
|
function dragLeave(ev) {
|
||||||
ev.currentTarget.classList.remove('drag-over');
|
ev.currentTarget.classList.remove('valid-target');
|
||||||
}
|
}
|
||||||
|
|
||||||
function drop(ev, targetSem) {
|
function drop(ev, targetSem) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
document.querySelectorAll('.semester-list').forEach(el => el.classList.remove('drag-over'));
|
|
||||||
|
|
||||||
if (!draggedItem) return;
|
if (!draggedItem) {
|
||||||
|
dragEndCleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { semId, idx } = draggedItem;
|
const { semId, idx } = draggedItem;
|
||||||
|
|
||||||
|
// --- VALIDATION: PARITY CHECK ---
|
||||||
|
const sourceParity = semId % 2;
|
||||||
|
const targetParity = targetSem % 2;
|
||||||
|
|
||||||
|
if (sourceParity !== targetParity) {
|
||||||
|
// Invalid Drop
|
||||||
|
// alert("Mata kuliah Ganjil hanya bisa diambil di Semester Ganjil, dan Genap di Genap.");
|
||||||
|
dragEndCleanup();
|
||||||
|
renderAll(); // Reset visual state
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// --------------------------------
|
||||||
|
|
||||||
if (semId !== targetSem) {
|
if (semId !== targetSem) {
|
||||||
// Move logic
|
// Move logic
|
||||||
@@ -577,12 +652,16 @@
|
|||||||
currentPlan[semId].splice(idx, 1);
|
currentPlan[semId].splice(idx, 1);
|
||||||
currentPlan[targetSem].push(course);
|
currentPlan[targetSem].push(course);
|
||||||
|
|
||||||
renderAll(); // Re-calc status
|
dragEndCleanup();
|
||||||
|
renderAll();
|
||||||
} else {
|
} else {
|
||||||
renderAll(); // Just reset opacity
|
dragEndCleanup();
|
||||||
|
renderAll();
|
||||||
}
|
}
|
||||||
draggedItem = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add global dragend listener to ensure cleanup happens even if dropped outside
|
||||||
|
document.addEventListener('dragend', dragEndCleanup);
|
||||||
|
|
||||||
// --- CHARTS ---
|
// --- CHARTS ---
|
||||||
let sksChart = null;
|
let sksChart = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user