Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
994bb04de0 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
55
core/migrations/0001_initial.py
Normal file
55
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-07 03:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Dosen',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nidn', models.CharField(max_length=20, unique=True)),
|
||||||
|
('nama', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Dosen',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Hari',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nama', models.CharField(max_length=20)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Hari',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProgramStudi',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nama', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Program Studi',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TahunAkademik',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nama', models.CharField(max_length=20)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Tahun Akademik',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-07 03:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Jam',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('range_waktu', models.CharField(max_length=50)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Jam',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Kelas',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nama', models.CharField(max_length=20)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Kelas',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Ruangan',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nama', models.CharField(max_length=50)),
|
||||||
|
('kapasitas', models.IntegerField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Ruangan',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tahunakademik',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MataKuliah',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('kode', models.CharField(max_length=20, unique=True)),
|
||||||
|
('nama', models.CharField(max_length=100)),
|
||||||
|
('sks', models.IntegerField()),
|
||||||
|
('semester', models.IntegerField()),
|
||||||
|
('prodi', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.programstudi')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Mata Kuliah',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DosenPengampu',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('dosen', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.dosen')),
|
||||||
|
('tahun_akademik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.tahunakademik')),
|
||||||
|
('matkul', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.matakuliah')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Dosen Pengampu',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Jadwal',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('dosen_pengampu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.dosenpengampu')),
|
||||||
|
('hari', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.hari')),
|
||||||
|
('jam', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.jam')),
|
||||||
|
('kelas', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.kelas')),
|
||||||
|
('ruangan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.ruangan')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Jadwal',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
24
core/migrations/0003_kelas_prodi_ruangan_prodi.py
Normal file
24
core/migrations/0003_kelas_prodi_ruangan_prodi.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-07 03:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_jam_kelas_ruangan_tahunakademik_is_active_matakuliah_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kelas',
|
||||||
|
name='prodi',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.programstudi'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ruangan',
|
||||||
|
name='prodi',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.programstudi'),
|
||||||
|
),
|
||||||
|
]
|
||||||
27
core/migrations/0004_daftarhadir.py
Normal file
27
core/migrations/0004_daftarhadir.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-07 04:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0003_kelas_prodi_ruangan_prodi'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DaftarHadir',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('tanggal', models.DateField()),
|
||||||
|
('pertemuan_ke', models.IntegerField(default=1)),
|
||||||
|
('keterangan', models.TextField(blank=True, null=True)),
|
||||||
|
('jadwal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.jadwal')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Daftar Hadir',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-07 04:45
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0004_daftarhadir'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosenpengampu',
|
||||||
|
name='kelas',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.kelas'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosenpengampu',
|
||||||
|
name='prodi',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.programstudi'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='matakuliah',
|
||||||
|
name='kode',
|
||||||
|
field=models.CharField(max_length=20),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='matakuliah',
|
||||||
|
unique_together={('kode', 'prodi'), ('nama', 'prodi')},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/migrations/0006_jadwal_is_manual.py
Normal file
18
core/migrations/0006_jadwal_is_manual.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-07 05:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0005_dosenpengampu_kelas_dosenpengampu_prodi_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='jadwal',
|
||||||
|
name='is_manual',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/migrations/__pycache__/0004_daftarhadir.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0004_daftarhadir.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
121
core/models.py
121
core/models.py
@ -1,3 +1,122 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
class ProgramStudi(models.Model):
|
||||||
|
nama = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nama
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Program Studi"
|
||||||
|
|
||||||
|
class Dosen(models.Model):
|
||||||
|
nidn = models.CharField(max_length=20, unique=True)
|
||||||
|
nama = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nama
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Dosen"
|
||||||
|
|
||||||
|
class TahunAkademik(models.Model):
|
||||||
|
nama = models.CharField(max_length=20) # e.g., 2025/2026
|
||||||
|
is_active = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nama
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Tahun Akademik"
|
||||||
|
|
||||||
|
class Hari(models.Model):
|
||||||
|
nama = models.CharField(max_length=20)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nama
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Hari"
|
||||||
|
|
||||||
|
class MataKuliah(models.Model):
|
||||||
|
kode = models.CharField(max_length=20)
|
||||||
|
nama = models.CharField(max_length=100)
|
||||||
|
sks = models.IntegerField()
|
||||||
|
semester = models.IntegerField()
|
||||||
|
prodi = models.ForeignKey(ProgramStudi, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.kode} - {self.nama}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Mata Kuliah"
|
||||||
|
unique_together = [['kode', 'prodi'], ['nama', 'prodi']]
|
||||||
|
|
||||||
|
class Ruangan(models.Model):
|
||||||
|
nama = models.CharField(max_length=50)
|
||||||
|
kapasitas = models.IntegerField()
|
||||||
|
prodi = models.ForeignKey(ProgramStudi, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nama
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Ruangan"
|
||||||
|
|
||||||
|
class Kelas(models.Model):
|
||||||
|
nama = models.CharField(max_length=20) # e.g., A, B, C or 1A, 1B
|
||||||
|
prodi = models.ForeignKey(ProgramStudi, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nama
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Kelas"
|
||||||
|
|
||||||
|
class Jam(models.Model):
|
||||||
|
range_waktu = models.CharField(max_length=50) # e.g., 07:00 - 08:40
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.range_waktu
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Jam"
|
||||||
|
|
||||||
|
class DosenPengampu(models.Model):
|
||||||
|
dosen = models.ForeignKey(Dosen, on_delete=models.CASCADE)
|
||||||
|
matkul = models.ForeignKey(MataKuliah, on_delete=models.CASCADE)
|
||||||
|
tahun_akademik = models.ForeignKey(TahunAkademik, on_delete=models.CASCADE)
|
||||||
|
prodi = models.ForeignKey(ProgramStudi, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
kelas = models.ForeignKey(Kelas, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.dosen.nama} - {self.matkul.nama} ({self.kelas.nama if self.kelas else '-'})"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Dosen Pengampu"
|
||||||
|
|
||||||
|
class Jadwal(models.Model):
|
||||||
|
dosen_pengampu = models.ForeignKey(DosenPengampu, on_delete=models.CASCADE)
|
||||||
|
ruangan = models.ForeignKey(Ruangan, on_delete=models.CASCADE)
|
||||||
|
hari = models.ForeignKey(Hari, on_delete=models.CASCADE)
|
||||||
|
jam = models.ForeignKey(Jam, on_delete=models.CASCADE)
|
||||||
|
kelas = models.ForeignKey(Kelas, on_delete=models.CASCADE)
|
||||||
|
is_manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.dosen_pengampu.matkul.nama} - {self.hari.nama} {self.jam.range_waktu}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Jadwal"
|
||||||
|
|
||||||
|
class DaftarHadir(models.Model):
|
||||||
|
jadwal = models.ForeignKey(Jadwal, on_delete=models.CASCADE)
|
||||||
|
tanggal = models.DateField()
|
||||||
|
pertemuan_ke = models.IntegerField(default=1)
|
||||||
|
keterangan = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Hadir: {self.jadwal} - Tgl: {self.tanggal}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Daftar Hadir"
|
||||||
|
|||||||
@ -1,25 +1,172 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
{% if project_description %}
|
<title>{% block title %}Sistem Jadwal Perkuliahan{% endblock %}</title>
|
||||||
<meta name="description" content="{{ project_description }}">
|
|
||||||
<meta property="og:description" content="{{ project_description }}">
|
<!-- Bootstrap 5 -->
|
||||||
<meta property="twitter:description" content="{{ project_description }}">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
{% endif %}
|
<!-- Font Awesome -->
|
||||||
{% if project_image_url %}
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<meta property="og:image" content="{{ project_image_url }}">
|
<!-- Select2 -->
|
||||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
{% endif %}
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Extra styles for table responsiveness and polish */
|
||||||
|
.table-responsive {
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.btn-check-all {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mass-delete-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.select2-container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div id="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h4 class="mb-0 text-white"><i class="fas fa-calendar-alt me-2"></i>SJP Admin</h4>
|
||||||
|
</div>
|
||||||
|
<nav class="nav flex-column">
|
||||||
|
<a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'dashboard' %}">
|
||||||
|
<i class="fas fa-th-large"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
<div class="sidebar-heading px-3 mt-3 mb-1 text-muted text-uppercase small" style="font-size: 0.7rem; font-weight: 800; letter-spacing: 1px;">Master Data</div>
|
||||||
|
<a class="nav-link {% if 'prodi' in request.path %}active{% endif %}" href="{% url 'prodi_list' %}">
|
||||||
|
<i class="fas fa-university"></i> Program Studi
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'dosen' in request.path and 'pengampu' not in request.path %}active{% endif %}" href="{% url 'dosen_list' %}">
|
||||||
|
<i class="fas fa-user-tie"></i> Dosen
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'matkul' in request.path %}active{% endif %}" href="{% url 'matkul_list' %}">
|
||||||
|
<i class="fas fa-book"></i> Mata Kuliah
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'ruangan' in request.path %}active{% endif %}" href="{% url 'ruangan_list' %}">
|
||||||
|
<i class="fas fa-door-open"></i> Ruangan
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'kelas' in request.path %}active{% endif %}" href="{% url 'kelas_list' %}">
|
||||||
|
<i class="fas fa-users"></i> Kelas
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'jam' in request.path %}active{% endif %}" href="{% url 'jam_list' %}">
|
||||||
|
<i class="fas fa-clock"></i> Waktu / Jam
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'hari' in request.path %}active{% endif %}" href="{% url 'hari_list' %}">
|
||||||
|
<i class="fas fa-calendar-week"></i> Hari
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'tahun' in request.path %}active{% endif %}" href="{% url 'tahun_list' %}">
|
||||||
|
<i class="fas fa-calendar-day"></i> Tahun Akademik
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="sidebar-heading px-3 mt-3 mb-1 text-muted text-uppercase small" style="font-size: 0.7rem; font-weight: 800; letter-spacing: 1px;">Operasional</div>
|
||||||
|
<a class="nav-link {% if 'pengampu' in request.path %}active{% endif %}" href="{% url 'pengampu_list' %}">
|
||||||
|
<i class="fas fa-chalkboard-teacher"></i> Dosen Pengampu
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'buat' in request.path %}active{% endif %}" href="{% url 'buat_jadwal' %}">
|
||||||
|
<i class="fas fa-magic"></i> Buat Jadwal
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'jadwal' in request.path and 'buat' not in request.path %}active{% endif %}" href="{% url 'jadwal_list' %}">
|
||||||
|
<i class="fas fa-list-check"></i> Hasil Jadwal
|
||||||
|
</a>
|
||||||
|
<a class="nav-link {% if 'daftar-hadir' in request.path %}active{% endif %}" href="{% url 'daftar_hadir_list' %}">
|
||||||
|
<i class="fas fa-clipboard-check"></i> Daftar Hadir
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div id="main-content">
|
||||||
|
<div class="top-navbar">
|
||||||
|
<div class="breadcrumb-container">
|
||||||
|
<h5 class="mb-0">{% block page_title %}Dashboard{% endblock %}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="profile-section">
|
||||||
|
<div class="text-end me-3 d-none d-md-block">
|
||||||
|
<p class="mb-0 fw-bold">Administrator</p>
|
||||||
|
<small class="text-muted">Super Admin</small>
|
||||||
|
</div>
|
||||||
|
<div class="profile-img">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
<i class="fas {% if message.tags == 'success' %}fa-check-circle{% else %}fa-exclamation-circle{% endif %} me-2"></i>
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery (needed for Select2) -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Select2 -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Common logic for Bulk Delete checkbox
|
||||||
|
$(document).ready(function() {
|
||||||
|
const checkAll = $('#checkAll');
|
||||||
|
const checkboxes = $('.item-checkbox');
|
||||||
|
const deleteBtn = $('#massDeleteBtn');
|
||||||
|
|
||||||
|
if (checkAll.length) {
|
||||||
|
checkAll.on('change', function() {
|
||||||
|
checkboxes.prop('checked', $(this).prop('checked'));
|
||||||
|
toggleDeleteBtn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkboxes.on('change', toggleDeleteBtn);
|
||||||
|
|
||||||
|
function toggleDeleteBtn() {
|
||||||
|
if (deleteBtn.length) {
|
||||||
|
const anyChecked = checkboxes.filter(':checked').length > 0;
|
||||||
|
deleteBtn.toggle(anyChecked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Select2
|
||||||
|
$('.select2').select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
placeholder: 'Pilih...',
|
||||||
|
allowClear: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-init select2 inside modals when shown
|
||||||
|
$('.modal').on('shown.bs.modal', function() {
|
||||||
|
$(this).find('.select2-modal').select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
dropdownParent: $(this),
|
||||||
|
placeholder: 'Pilih...',
|
||||||
|
allowClear: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
31
core/templates/core/buat_jadwal.html
Normal file
31
core/templates/core/buat_jadwal.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Buat Jadwal - SJP Admin{% endblock %}
|
||||||
|
{% block page_title %}Generate Jadwal Otomatis{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8 text-center">
|
||||||
|
<div class="card border-0 shadow-sm p-5">
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-inline-flex align-items-center justify-content-center" style="width: 100px; height: 100px;">
|
||||||
|
<i class="fas fa-magic fa-3x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 class="fw-bold mb-3">Auto-Generate Jadwal</h2>
|
||||||
|
<p class="text-muted mb-5">Sistem akan menyusun jadwal perkuliahan secara otomatis berdasarkan data master (Dosen Pengampu, Ruangan, Hari, Jam, dan Kelas) yang telah diinputkan. Sistem akan meminimalkan bentrok jadwal.</p>
|
||||||
|
|
||||||
|
<div class="alert alert-info border-0 shadow-none mb-5">
|
||||||
|
<i class="fas fa-info-circle me-2"></i> Pastikan semua data master telah terisi lengkap sebelum memulai proses generate.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg px-5 py-3 rounded-pill fw-bold shadow">
|
||||||
|
<i class="fas fa-play me-2"></i> Mulai Generate Jadwal
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
175
core/templates/core/daftar_hadir_list.html
Normal file
175
core/templates/core/daftar_hadir_list.html
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Hadir Perkuliahan{% endblock %}
|
||||||
|
{% block page_title %}Daftar Hadir Perkuliahan{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Input Kehadiran
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
<th>Pertemuan</th>
|
||||||
|
<th>Jadwal / Matkul</th>
|
||||||
|
<th>Dosen</th>
|
||||||
|
<th>Keterangan</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for hadir in hadir_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ hadir.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>{{ hadir.tanggal|date:"d/m/Y" }}</td>
|
||||||
|
<td>Ke-{{ hadir.pertemuan_ke }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold">{{ hadir.jadwal.dosen_pengampu.matkul.nama }}</div>
|
||||||
|
<small class="text-muted">{{ hadir.jadwal.hari.nama }}, {{ hadir.jadwal.jam.range_waktu }}</small>
|
||||||
|
</td>
|
||||||
|
<td>{{ hadir.jadwal.dosen_pengampu.dosen.nama }}</td>
|
||||||
|
<td>{{ hadir.keterangan|default:"-" }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ hadir.id }}"
|
||||||
|
data-jadwal="{{ hadir.jadwal.id }}"
|
||||||
|
data-tanggal="{{ hadir.tanggal|date:'Y-m-d' }}"
|
||||||
|
data-pertemuan="{{ hadir.pertemuan_ke }}"
|
||||||
|
data-ket="{{ hadir.keterangan|default:'' }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'daftar_hadir_delete' hadir.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted">Belum ada data kehadiran.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Input Kehadiran</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Pilih Jadwal</label>
|
||||||
|
<select name="jadwal" class="form-select select2-modal" required>
|
||||||
|
<option value="">Cari Jadwal...</option>
|
||||||
|
{% for j in jadwal_all %}
|
||||||
|
<option value="{{ j.id }}">
|
||||||
|
{{ j.dosen_pengampu.matkul.nama }} - {{ j.kelas.nama }} ({{ j.hari.nama }} {{ j.jam.range_waktu }})
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Tanggal</label>
|
||||||
|
<input type="date" name="tanggal" class="form-control" value="{% now 'Y-m-d' %}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Pertemuan Ke-</label>
|
||||||
|
<input type="number" name="pertemuan_ke" class="form-control" value="1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Keterangan</label>
|
||||||
|
<textarea name="keterangan" class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Kehadiran</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Jadwal</label>
|
||||||
|
<select name="jadwal" id="edit_jadwal" class="form-select select2-modal" required>
|
||||||
|
{% for j in jadwal_all %}
|
||||||
|
<option value="{{ j.id }}">{{ j.dosen_pengampu.matkul.nama }} - {{ j.kelas.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Tanggal</label>
|
||||||
|
<input type="date" name="tanggal" id="edit_tanggal" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Pertemuan Ke-</label>
|
||||||
|
<input type="number" name="pertemuan_ke" id="edit_pertemuan" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Keterangan</label>
|
||||||
|
<textarea name="keterangan" id="edit_ket" class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
$('#edit_pk').val($(this).data('pk'));
|
||||||
|
$('#edit_jadwal').val($(this).data('jadwal')).trigger('change');
|
||||||
|
$('#edit_tanggal').val($(this).data('tanggal'));
|
||||||
|
$('#edit_pertemuan').val($(this).data('pertemuan'));
|
||||||
|
$('#edit_ket').val($(this).data('ket'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
145
core/templates/core/dashboard.html
Normal file
145
core/templates/core/dashboard.html
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Dashboard - SJP Admin{% endblock %}
|
||||||
|
{% block page_title %}Dashboard Overview{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<!-- Stat Cards -->
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-0 h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div class="stat-icon bg-primary bg-opacity-10 text-primary">
|
||||||
|
<i class="fas fa-university fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-bold mb-1">{{ count_prodi }}</h3>
|
||||||
|
<p class="text-muted mb-0 small text-uppercase fw-bold">Program Studi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-0 h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div class="stat-icon bg-success bg-opacity-10 text-success">
|
||||||
|
<i class="fas fa-user-tie fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-bold mb-1">{{ count_dosen }}</h3>
|
||||||
|
<p class="text-muted mb-0 small text-uppercase fw-bold">Total Dosen</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-0 h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div class="stat-icon bg-info bg-opacity-10 text-info">
|
||||||
|
<i class="fas fa-book fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-bold mb-1">{{ count_matkul }}</h3>
|
||||||
|
<p class="text-muted mb-0 small text-uppercase fw-bold">Mata Kuliah</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-0 h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div class="stat-icon bg-danger bg-opacity-10 text-danger">
|
||||||
|
<i class="fas fa-clipboard-check fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-bold mb-1">{{ count_daftar_hadir }}</h3>
|
||||||
|
<p class="text-muted mb-0 small text-uppercase fw-bold">Log Kehadiran</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card border-0 shadow-sm h-100">
|
||||||
|
<div class="card-header bg-white py-3 border-0">
|
||||||
|
<h6 class="m-0 font-weight-bold text-dark">Quick Actions</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<a href="{% url 'buat_jadwal' %}" class="btn btn-primary w-100 py-4 rounded-4 shadow-sm text-start position-relative overflow-hidden">
|
||||||
|
<div class="position-relative z-index-1">
|
||||||
|
<h5 class="fw-bold mb-1"><i class="fas fa-magic me-2"></i>Generate Jadwal</h5>
|
||||||
|
<p class="mb-0 small opacity-75">Buat jadwal otomatis dengan satu klik.</p>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-magic position-absolute end-0 bottom-0 opacity-10 fa-5x me-n3 mb-n3"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<a href="{% url 'daftar_hadir_list' %}" class="btn btn-danger w-100 py-4 rounded-4 shadow-sm text-start position-relative overflow-hidden">
|
||||||
|
<div class="position-relative z-index-1">
|
||||||
|
<h5 class="fw-bold mb-1"><i class="fas fa-clipboard-list me-2"></i>Input Absensi</h5>
|
||||||
|
<p class="mb-0 small opacity-75">Catat kehadiran dosen hari ini.</p>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-check-double position-absolute end-0 bottom-0 opacity-10 fa-5x me-n3 mb-n3"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<a href="{% url 'jadwal_list' %}" class="btn btn-dark w-100 py-4 rounded-4 shadow-sm text-start position-relative overflow-hidden">
|
||||||
|
<div class="position-relative z-index-1">
|
||||||
|
<h5 class="fw-bold mb-1"><i class="fas fa-list-check me-2"></i>Lihat Jadwal</h5>
|
||||||
|
<p class="mb-0 small opacity-75">Lihat hasil jadwal yang telah dibuat.</p>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-calendar-alt position-absolute end-0 bottom-0 opacity-10 fa-5x me-n3 mb-n3"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<a href="{% url 'dosen_list' %}" class="btn btn-outline-secondary w-100 py-4 rounded-4 shadow-sm text-start position-relative overflow-hidden">
|
||||||
|
<div class="position-relative z-index-1">
|
||||||
|
<h5 class="fw-bold mb-1"><i class="fas fa-users me-2"></i>Kelola Dosen</h5>
|
||||||
|
<p class="mb-0 small">Update data dan import NIDN.</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm h-100">
|
||||||
|
<div class="card-header bg-white py-3 border-0">
|
||||||
|
<h6 class="m-0 font-weight-bold text-dark">Status Sistem</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<div class="stat-icon bg-success rounded-circle me-3" style="width: 12px; height: 12px;"></div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 fw-bold">Jadwal Terdaftar</h6>
|
||||||
|
<p class="text-muted small mb-0">{{ count_jadwal }} Entry</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<div class="stat-icon bg-info rounded-circle me-3" style="width: 12px; height: 12px;"></div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 fw-bold">Kelas Aktif</h6>
|
||||||
|
<p class="text-muted small mb-0">{{ count_kelas }} Kelas</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<div class="stat-icon bg-warning rounded-circle me-3" style="width: 12px; height: 12px;"></div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 fw-bold">Ruangan Terdaftar</h6>
|
||||||
|
<p class="text-muted small mb-0">{{ count_ruangan }} Ruangan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-muted small">Versi Aplikasi 1.1.0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
162
core/templates/core/dosen_list.html
Normal file
162
core/templates/core/dosen_list.html
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Dosen{% endblock %}
|
||||||
|
{% block page_title %}Daftar Dosen{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Dosen
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#importModal">
|
||||||
|
<i class="fas fa-file-import me-2"></i>Import
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'dosen_template' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-download me-2"></i>Template
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>NIDN</th>
|
||||||
|
<th>Nama Dosen</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for dosen in dosen_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ dosen.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>{{ dosen.nidn }}</td>
|
||||||
|
<td>{{ dosen.nama }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ dosen.id }}"
|
||||||
|
data-nidn="{{ dosen.nidn }}"
|
||||||
|
data-nama="{{ dosen.nama }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'dosen_delete' dosen.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-4 text-muted">Belum ada data dosen.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Tambah Dosen</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">NIDN</label>
|
||||||
|
<input type="text" name="nidn" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Lengkap</label>
|
||||||
|
<input type="text" name="nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content" id="editForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Dosen</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">NIDN</label>
|
||||||
|
<input type="text" name="nidn" id="edit_nidn" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Lengkap</label>
|
||||||
|
<input type="text" name="nama" id="edit_nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Import Modal -->
|
||||||
|
<div class="modal fade" id="importModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" action="{% url 'dosen_import' %}" enctype="multipart/form-data" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Import Dosen</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">File CSV</label>
|
||||||
|
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
|
||||||
|
<div class="form-text">Gunakan format sesuai template.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-success">Import</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
$('#edit_pk').val($(this).data('pk'));
|
||||||
|
$('#edit_nidn').val($(this).data('nidn'));
|
||||||
|
$('#edit_nama').val($(this).data('nama'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
26
core/templates/core/hari_list.html
Normal file
26
core/templates/core/hari_list.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}Daftar Hari{% endblock %}
|
||||||
|
{% block page_title %}Daftar Hari{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal"><i class="fas fa-plus me-2"></i>Tambah Hari</button>
|
||||||
|
<form method="POST" id="massDeleteForm">{% csrf_token %}<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn"><i class="fas fa-trash me-2"></i>Hapus Terpilih</button></form>
|
||||||
|
</div>
|
||||||
|
<div class="card border-0 shadow-sm"><div class="card-body"><div class="table-responsive"><table class="table table-hover align-middle mb-0"><thead class="bg-light"><tr><th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th><th>Nama Hari</th><th width="120" class="text-center">Aksi</th></tr></thead><tbody>
|
||||||
|
{% for h in hari_all %}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="selected_ids" value="{{ h.id }}" form="massDeleteForm" class="form-check-input item-checkbox"></td>
|
||||||
|
<td>{{ h.nama }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white edit-btn" data-pk="{{ h.id }}" data-nama="{{ h.nama }}" data-bs-toggle="modal" data-bs-target="#editModal"><i class="fas fa-edit"></i></button>
|
||||||
|
<a href="{% url 'hari_delete' h.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus?')"><i class="fas fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody></table></div></div></div>
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1"><div class="modal-dialog"><form method="POST" class="modal-content">{% csrf_token %}<div class="modal-header"><h5 class="modal-title">Tambah Hari</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="form-label">Nama Hari</label><input type="text" name="nama" class="form-control" required></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">Simpan</button></div></form></div></div>
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1"><div class="modal-dialog"><form method="POST" class="modal-content">{% csrf_token %}<input type="hidden" name="pk" id="edit_pk"><div class="modal-header"><h5 class="modal-title">Edit Hari</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="form-label">Nama Hari</label><input type="text" name="nama" id="edit_nama" class="form-control" required></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">Update</button></div></form></div></div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
<script>$(document).ready(function(){$('.edit-btn').on('click',function(){$('#edit_pk').val($(this).data('pk'));$('#edit_nama').val($(this).data('nama'));});});</script>
|
||||||
|
{% endblock %}
|
||||||
257
core/templates/core/jadwal_list.html
Normal file
257
core/templates/core/jadwal_list.html
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Hasil Jadwal Perkuliahan{% endblock %}
|
||||||
|
{% block page_title %}Hasil Jadwal Perkuliahan{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'buat_jadwal' %}" class="btn btn-warning me-2">
|
||||||
|
<i class="fas fa-sync me-2"></i>Generate Ulang
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalAdd">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Manual
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'jadwal_export_excel' %}" class="btn btn-success">
|
||||||
|
<i class="fas fa-file-excel me-2"></i>Export Excel (.xlsx)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" id="bulkForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="delete_massal" value="1">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Hari & Jam</th>
|
||||||
|
<th>Kode MK</th>
|
||||||
|
<th>Mata Kuliah</th>
|
||||||
|
<th>Dosen</th>
|
||||||
|
<th>Prodi</th>
|
||||||
|
<th>Sem.</th>
|
||||||
|
<th>Kelas</th>
|
||||||
|
<th>Ruangan</th>
|
||||||
|
<th width="100" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for jadwal in jadwal_all %}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="selected_ids" value="{{ jadwal.id }}" class="form-check-input item-checkbox"></td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold">{{ jadwal.hari.nama }}</div>
|
||||||
|
<small class="text-primary">{{ jadwal.jam.range_waktu }}</small>
|
||||||
|
</td>
|
||||||
|
<td><code class="text-dark">{{ jadwal.dosen_pengampu.matkul.kode }}</code></td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold text-dark">{{ jadwal.dosen_pengampu.matkul.nama }}</div>
|
||||||
|
{% if jadwal.is_manual %}
|
||||||
|
<span class="badge bg-info-subtle text-info border border-info-subtle">Manual</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ jadwal.dosen_pengampu.dosen.nama }}</td>
|
||||||
|
<td>{{ jadwal.dosen_pengampu.matkul.prodi.nama }}</td>
|
||||||
|
<td>{{ jadwal.dosen_pengampu.matkul.semester }}</td>
|
||||||
|
<td><span class="badge bg-secondary">{{ jadwal.kelas.nama }}</span></td>
|
||||||
|
<td><span class="badge bg-info text-white">{{ jadwal.ruangan.nama }}</span></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||||
|
onclick="editJadwal('{{ jadwal.id }}', '{{ jadwal.dosen_pengampu.id }}', '{{ jadwal.ruangan.id }}', '{{ jadwal.hari.id }}', '{{ jadwal.jam.id }}', '{{ jadwal.kelas.id }}')">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'jadwal_delete' jadwal.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus jadwal ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="10" class="text-center py-4 text-muted">Belum ada jadwal yang digenerate.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% if jadwal_all %}
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Hapus data yang dipilih?')">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Add -->
|
||||||
|
<div class="modal fade" id="modalAdd" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header border-0 bg-primary text-white">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-plus-circle me-2"></i>Tambah Jadwal Manual</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label class="form-label fw-bold">Dosen Pengampu (Matkul & Dosen)</label>
|
||||||
|
<select name="pengampu" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Pengampu...</option>
|
||||||
|
{% for p in pengampu_all %}
|
||||||
|
<option value="{{ p.id }}">{{ p.matkul.kode }} - {{ p.matkul.nama }} ({{ p.dosen.nama }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Hari</label>
|
||||||
|
<select name="hari" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Hari...</option>
|
||||||
|
{% for h in hari_all %}
|
||||||
|
<option value="{{ h.id }}">{{ h.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Jam</label>
|
||||||
|
<select name="jam" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Jam...</option>
|
||||||
|
{% for j in jam_all %}
|
||||||
|
<option value="{{ j.id }}">{{ j.range_waktu }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Kelas</label>
|
||||||
|
<select name="kelas" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Kelas...</option>
|
||||||
|
{% for k in kelas_all %}
|
||||||
|
<option value="{{ k.id }}">{{ k.nama }} - {{ k.prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Ruangan</label>
|
||||||
|
<select name="ruangan" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Ruangan...</option>
|
||||||
|
{% for r in ruangan_all %}
|
||||||
|
<option value="{{ r.id }}">{{ r.nama }} (Kap: {{ r.kapasitas }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 p-4 pt-0">
|
||||||
|
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">Simpan Jadwal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Edit -->
|
||||||
|
<div class="modal fade" id="modalEdit" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header border-0 bg-info text-white">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-edit me-2"></i>Edit Jadwal</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label class="form-label fw-bold">Dosen Pengampu</label>
|
||||||
|
<select name="pengampu" id="edit_pengampu" class="form-select select2-modal-edit" required>
|
||||||
|
{% for p in pengampu_all %}
|
||||||
|
<option value="{{ p.id }}">{{ p.matkul.kode }} - {{ p.matkul.nama }} ({{ p.dosen.nama }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Hari</label>
|
||||||
|
<select name="hari" id="edit_hari" class="form-select select2-modal-edit" required>
|
||||||
|
{% for h in hari_all %}
|
||||||
|
<option value="{{ h.id }}">{{ h.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Jam</label>
|
||||||
|
<select name="jam" id="edit_jam" class="form-select select2-modal-edit" required>
|
||||||
|
{% for j in jam_all %}
|
||||||
|
<option value="{{ j.id }}">{{ j.range_waktu }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Kelas</label>
|
||||||
|
<select name="kelas" id="edit_kelas" class="form-select select2-modal-edit" required>
|
||||||
|
{% for k in kelas_all %}
|
||||||
|
<option value="{{ k.id }}">{{ k.nama }} - {{ k.prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Ruangan</label>
|
||||||
|
<select name="ruangan" id="edit_ruangan" class="form-select select2-modal-edit" required>
|
||||||
|
{% for r in ruangan_all %}
|
||||||
|
<option value="{{ r.id }}">{{ r.nama }} (Kap: {{ r.kapasitas }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 p-4 pt-0">
|
||||||
|
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-info text-white px-4">Update Jadwal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.select2-modal').select2({
|
||||||
|
dropdownParent: $('#modalAdd'),
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Pilih...'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#checkAll').click(function() {
|
||||||
|
$('.item-checkbox').prop('checked', this.checked);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function editJadwal(id, pengampu, ruangan, hari, jam, kelas) {
|
||||||
|
$('#edit_pk').val(id);
|
||||||
|
$('#edit_pengampu').val(pengampu).trigger('change');
|
||||||
|
$('#edit_ruangan').val(ruangan).trigger('change');
|
||||||
|
$('#edit_hari').val(hari).trigger('change');
|
||||||
|
$('#edit_jam').val(jam).trigger('change');
|
||||||
|
$('#edit_kelas').val(kelas).trigger('change');
|
||||||
|
|
||||||
|
var modalEdit = new bootstrap.Modal(document.getElementById('modalEdit'));
|
||||||
|
|
||||||
|
// Initialize Select2 for Edit Modal if not already done
|
||||||
|
$('.select2-modal-edit').select2({
|
||||||
|
dropdownParent: $('#modalEdit'),
|
||||||
|
width: '100%'
|
||||||
|
});
|
||||||
|
|
||||||
|
modalEdit.show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
168
core/templates/core/jam_list.html
Normal file
168
core/templates/core/jam_list.html
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Waktu / Jam{% endblock %}
|
||||||
|
{% block page_title %}Daftar Waktu / Jam{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Jam
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#generateModal">
|
||||||
|
<i class="fas fa-magic me-2"></i>Generate Otomatis
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Range Waktu</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for jam in jam_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ jam.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>{{ jam.range_waktu }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ jam.id }}"
|
||||||
|
data-range="{{ jam.range_waktu }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'jam_delete' jam.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center py-4 text-muted">Belum ada data jam.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Tambah Jam Manual</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Range Waktu</label>
|
||||||
|
<input type="text" name="range_waktu" class="form-control" placeholder="Contoh: 07:00 - 08:40" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Jam</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Range Waktu</label>
|
||||||
|
<input type="text" name="range_waktu" id="edit_range" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Generate Modal -->
|
||||||
|
<div class="modal fade" id="generateModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" action="{% url 'jam_generate' %}" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Generate Jam Otomatis</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Jam Mulai</label>
|
||||||
|
<input type="time" name="jam_mulai" class="form-control" value="07:00" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Jam Selesai</label>
|
||||||
|
<input type="time" name="jam_selesai" class="form-control" value="17:00" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Menit Per SKS</label>
|
||||||
|
<input type="number" name="durasi_per_sks" class="form-control" value="50" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">SKS Per Slot</label>
|
||||||
|
<input type="number" name="jumlah_sks" class="form-control" value="2" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Waktu Jeda (menit)</label>
|
||||||
|
<input type="number" name="jeda" class="form-control" value="0" placeholder="Contoh: 15" required>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-info py-2 small mb-0">
|
||||||
|
<i class="fas fa-info-circle me-1"></i> Slot akan dibuat berurutan dengan jeda antar slot.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-warning">Generate Now</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
$('#edit_pk').val($(this).data('pk'));
|
||||||
|
$('#edit_range').val($(this).data('range'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
170
core/templates/core/kelas_list.html
Normal file
170
core/templates/core/kelas_list.html
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Kelas{% endblock %}
|
||||||
|
{% block page_title %}Daftar Kelas{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Kelas
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#importModal">
|
||||||
|
<i class="fas fa-file-import me-2"></i>Import
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'kelas_template' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-download me-2"></i>Template
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Nama Kelas</th>
|
||||||
|
<th>Program Studi</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for kelas in kelas_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ kelas.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>{{ kelas.nama }}</td>
|
||||||
|
<td>{{ kelas.prodi.nama|default:"-" }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ kelas.id }}"
|
||||||
|
data-nama="{{ kelas.nama }}"
|
||||||
|
data-prodi="{{ kelas.prodi.id|default:'' }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'kelas_delete' kelas.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-4 text-muted">Belum ada data kelas.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Tambah Kelas</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Kelas</label>
|
||||||
|
<input type="text" name="nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" class="form-select select2-modal">
|
||||||
|
<option value="">Umum (Semua Prodi)</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Kelas</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Kelas</label>
|
||||||
|
<input type="text" name="nama" id="edit_nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" id="edit_prodi" class="form-select select2-modal">
|
||||||
|
<option value="">Umum (Semua Prodi)</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Import Modal -->
|
||||||
|
<div class="modal fade" id="importModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" action="{% url 'kelas_import' %}" enctype="multipart/form-data" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Import Kelas</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">File CSV</label>
|
||||||
|
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-success">Import</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
$('#edit_pk').val($(this).data('pk'));
|
||||||
|
$('#edit_nama').val($(this).data('nama'));
|
||||||
|
$('#edit_prodi').val($(this).data('prodi')).trigger('change');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
211
core/templates/core/matkul_list.html
Normal file
211
core/templates/core/matkul_list.html
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Mata Kuliah{% endblock %}
|
||||||
|
{% block page_title %}Daftar Mata Kuliah{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Matkul
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#importModal">
|
||||||
|
<i class="fas fa-file-import me-2"></i>Import
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'matkul_template' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-download me-2"></i>Template
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Kode</th>
|
||||||
|
<th>Nama Matkul</th>
|
||||||
|
<th>SKS</th>
|
||||||
|
<th>Semester</th>
|
||||||
|
<th>Prodi</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for matkul in matkul_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ matkul.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>{{ matkul.kode }}</td>
|
||||||
|
<td>{{ matkul.nama }}</td>
|
||||||
|
<td>{{ matkul.sks }}</td>
|
||||||
|
<td>{{ matkul.semester }}</td>
|
||||||
|
<td>{{ matkul.prodi.nama }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ matkul.id }}"
|
||||||
|
data-kode="{{ matkul.kode }}"
|
||||||
|
data-nama="{{ matkul.nama }}"
|
||||||
|
data-sks="{{ matkul.sks }}"
|
||||||
|
data-semester="{{ matkul.semester }}"
|
||||||
|
data-prodi="{{ matkul.prodi.id }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'matkul_delete' matkul.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted">Belum ada data mata kuliah.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Tambah Mata Kuliah</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kode Matkul</label>
|
||||||
|
<input type="text" name="kode" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Matkul</label>
|
||||||
|
<input type="text" name="nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">SKS</label>
|
||||||
|
<input type="number" name="sks" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Semester</label>
|
||||||
|
<input type="number" name="semester" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Prodi...</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Mata Kuliah</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kode Matkul</label>
|
||||||
|
<input type="text" name="kode" id="edit_kode" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Matkul</label>
|
||||||
|
<input type="text" name="nama" id="edit_nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">SKS</label>
|
||||||
|
<input type="number" name="sks" id="edit_sks" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Semester</label>
|
||||||
|
<input type="number" name="semester" id="edit_semester" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" id="edit_prodi" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Prodi...</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Import Modal -->
|
||||||
|
<div class="modal fade" id="importModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" action="{% url 'matkul_import' %}" enctype="multipart/form-data" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Import Mata Kuliah</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">File CSV</label>
|
||||||
|
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-success">Import</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
$('#edit_pk').val($(this).data('pk'));
|
||||||
|
$('#edit_kode').val($(this).data('kode'));
|
||||||
|
$('#edit_nama').val($(this).data('nama'));
|
||||||
|
$('#edit_sks').val($(this).data('sks'));
|
||||||
|
$('#edit_semester').val($(this).data('semester'));
|
||||||
|
$('#edit_prodi').val($(this).data('prodi')).trigger('change');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
267
core/templates/core/pengampu_list.html
Normal file
267
core/templates/core/pengampu_list.html
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Dosen Pengampu{% endblock %}
|
||||||
|
{% block page_title %}Daftar Dosen Pengampu{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Dosen Pengampu
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Dosen</th>
|
||||||
|
<th>Program Studi</th>
|
||||||
|
<th>Mata Kuliah</th>
|
||||||
|
<th>Kelas</th>
|
||||||
|
<th>Tahun Akademik</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pengampu in pengampu_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ pengampu.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold">{{ pengampu.dosen.nama }}</div>
|
||||||
|
<small class="text-muted">NIDN: {{ pengampu.dosen.nidn }}</small>
|
||||||
|
</td>
|
||||||
|
<td>{{ pengampu.prodi.nama|default:"-" }}</td>
|
||||||
|
<td>
|
||||||
|
<div>{{ pengampu.matkul.nama }}</div>
|
||||||
|
<small class="text-muted">{{ pengampu.matkul.kode }} ({{ pengampu.matkul.sks }} SKS)</small>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge bg-secondary">{{ pengampu.kelas.nama|default:"-" }}</span></td>
|
||||||
|
<td>{{ pengampu.tahun_akademik.nama }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ pengampu.id }}"
|
||||||
|
data-dosen="{{ pengampu.dosen.id }}"
|
||||||
|
data-prodi="{{ pengampu.prodi.id|default:'' }}"
|
||||||
|
data-matkul="{{ pengampu.matkul.id }}"
|
||||||
|
data-kelas="{{ pengampu.kelas.id|default:'' }}"
|
||||||
|
data-tahun="{{ pengampu.tahun_akademik.id }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'pengampu_delete' pengampu.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted">Belum ada data dosen pengampu.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Tambah Dosen Pengampu</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Dosen</label>
|
||||||
|
<select name="dosen" class="form-select select2-modal" required>
|
||||||
|
<option value="">Cari Dosen...</option>
|
||||||
|
{% for dosen in dosen_all %}
|
||||||
|
<option value="{{ dosen.id }}">{{ dosen.nama }} ({{ dosen.nidn }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" id="add_prodi" class="form-select select2-modal prodi-select" required>
|
||||||
|
<option value="">Pilih Program Studi...</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Mata Kuliah</label>
|
||||||
|
<select name="matkul" id="add_matkul" class="form-select select2-modal matkul-select" required>
|
||||||
|
<option value="">Pilih Program Studi Terlebih Dahulu...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kelas</label>
|
||||||
|
<select name="kelas" id="add_kelas" class="form-select select2-modal kelas-select" required>
|
||||||
|
<option value="">Pilih Program Studi Terlebih Dahulu...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Tahun Akademik</label>
|
||||||
|
<select name="tahun" class="form-select select2-modal" required>
|
||||||
|
<option value="">Pilih Tahun...</option>
|
||||||
|
{% for tahun in tahun_all %}
|
||||||
|
<option value="{{ tahun.id }}" {% if tahun.is_active %}selected{% endif %}>{{ tahun.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Dosen Pengampu</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Dosen</label>
|
||||||
|
<select name="dosen" id="edit_dosen" class="form-select select2-modal" required>
|
||||||
|
{% for dosen in dosen_all %}
|
||||||
|
<option value="{{ dosen.id }}">{{ dosen.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" id="edit_prodi" class="form-select select2-modal prodi-select" required>
|
||||||
|
<option value="">Pilih Program Studi...</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Mata Kuliah</label>
|
||||||
|
<select name="matkul" id="edit_matkul" class="form-select select2-modal matkul-select" required>
|
||||||
|
{% for matkul in matkul_all %}
|
||||||
|
<option value="{{ matkul.id }}">{{ matkul.kode }} - {{ matkul.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kelas</label>
|
||||||
|
<select name="kelas" id="edit_kelas" class="form-select select2-modal kelas-select" required>
|
||||||
|
{% for kelas in kelas_all %}
|
||||||
|
<option value="{{ kelas.id }}">{{ kelas.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Tahun Akademik</label>
|
||||||
|
<select name="tahun" id="edit_tahun" class="form-select select2-modal" required>
|
||||||
|
{% for tahun in tahun_all %}
|
||||||
|
<option value="{{ tahun.id }}">{{ tahun.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
function loadOptions(prodiId, targetMatkul, targetKelas, selectedMatkul = null, selectedKelas = null) {
|
||||||
|
if (!prodiId) {
|
||||||
|
targetMatkul.html('<option value="">Pilih Program Studi Terlebih Dahulu...</option>');
|
||||||
|
targetKelas.html('<option value="">Pilih Program Studi Terlebih Dahulu...</option>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Kelas
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url 'get_kelas_by_prodi' %}",
|
||||||
|
data: { 'prodi_id': prodiId },
|
||||||
|
success: function(data) {
|
||||||
|
targetKelas.empty();
|
||||||
|
targetKelas.append('<option value="">Pilih Kelas...</option>');
|
||||||
|
data.forEach(function(item) {
|
||||||
|
let selected = (item.id == selectedKelas) ? 'selected' : '';
|
||||||
|
targetKelas.append(`<option value="${item.id}" ${selected}>${item.nama}</option>`);
|
||||||
|
});
|
||||||
|
targetKelas.trigger('change.select2');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load Matkul
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url 'get_matkul_by_prodi' %}",
|
||||||
|
data: { 'prodi_id': prodiId },
|
||||||
|
success: function(data) {
|
||||||
|
targetMatkul.empty();
|
||||||
|
targetMatkul.append('<option value="">Pilih Mata Kuliah...</option>');
|
||||||
|
data.forEach(function(item) {
|
||||||
|
let selected = (item.id == selectedMatkul) ? 'selected' : '';
|
||||||
|
targetMatkul.append(`<option value="${item.id}" ${selected}>${item.kode} - ${item.nama}</option>`);
|
||||||
|
});
|
||||||
|
targetMatkul.trigger('change.select2');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.prodi-select').on('change', function() {
|
||||||
|
let prodiId = $(this).val();
|
||||||
|
let modal = $(this).closest('.modal');
|
||||||
|
let targetMatkul = modal.find('.matkul-select');
|
||||||
|
let targetKelas = modal.find('.kelas-select');
|
||||||
|
loadOptions(prodiId, targetMatkul, targetKelas);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
let pk = $(this).data('pk');
|
||||||
|
let dosen = $(this).data('dosen');
|
||||||
|
let prodi = $(this).data('prodi');
|
||||||
|
let matkul = $(this).data('matkul');
|
||||||
|
let kelas = $(this).data('kelas');
|
||||||
|
let tahun = $(this).data('tahun');
|
||||||
|
|
||||||
|
$('#edit_pk').val(pk);
|
||||||
|
$('#edit_dosen').val(dosen).trigger('change');
|
||||||
|
$('#edit_tahun').val(tahun).trigger('change');
|
||||||
|
$('#edit_prodi').val(prodi).trigger('change.select2');
|
||||||
|
|
||||||
|
// For edit modal, we need to load options then set the selected values
|
||||||
|
loadOptions(prodi, $('#edit_matkul'), $('#edit_kelas'), matkul, kelas);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
26
core/templates/core/prodi_list.html
Normal file
26
core/templates/core/prodi_list.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}Program Studi{% endblock %}
|
||||||
|
{% block page_title %}Program Studi{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal"><i class="fas fa-plus me-2"></i>Tambah Prodi</button>
|
||||||
|
<form method="POST" id="massDeleteForm">{% csrf_token %}<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn"><i class="fas fa-trash me-2"></i>Hapus Terpilih</button></form>
|
||||||
|
</div>
|
||||||
|
<div class="card border-0 shadow-sm"><div class="card-body"><div class="table-responsive"><table class="table table-hover align-middle mb-0"><thead class="bg-light"><tr><th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th><th>Nama Program Studi</th><th width="120" class="text-center">Aksi</th></tr></thead><tbody>
|
||||||
|
{% for p in prodi_all %}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="selected_ids" value="{{ p.id }}" form="massDeleteForm" class="form-check-input item-checkbox"></td>
|
||||||
|
<td>{{ p.nama }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white edit-btn" data-pk="{{ p.id }}" data-nama="{{ p.nama }}" data-bs-toggle="modal" data-bs-target="#editModal"><i class="fas fa-edit"></i></button>
|
||||||
|
<a href="{% url 'prodi_delete' p.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus?')"><i class="fas fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody></table></div></div></div>
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1"><div class="modal-dialog"><form method="POST" class="modal-content">{% csrf_token %}<div class="modal-header"><h5 class="modal-title">Tambah Prodi</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="form-label">Nama Program Studi</label><input type="text" name="nama" class="form-control" required></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">Simpan</button></div></form></div></div>
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1"><div class="modal-dialog"><form method="POST" class="modal-content">{% csrf_token %}<input type="hidden" name="pk" id="edit_pk"><div class="modal-header"><h5 class="modal-title">Edit Prodi</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="form-label">Nama Program Studi</label><input type="text" name="nama" id="edit_nama" class="form-control" required></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">Update</button></div></form></div></div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
<script>$(document).ready(function(){$('.edit-btn').on('click',function(){$('#edit_pk').val($(this).data('pk'));$('#edit_nama').val($(this).data('nama'));});});</script>
|
||||||
|
{% endblock %}
|
||||||
182
core/templates/core/ruangan_list.html
Normal file
182
core/templates/core/ruangan_list.html
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Ruangan{% endblock %}
|
||||||
|
{% block page_title %}Daftar Ruangan{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Tambah Ruangan
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#importModal">
|
||||||
|
<i class="fas fa-file-import me-2"></i>Import
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'ruangan_template' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-download me-2"></i>Template
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="massDeleteForm" onsubmit="return confirm('Hapus terpilih?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn">
|
||||||
|
<i class="fas fa-trash me-2"></i>Hapus Terpilih
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th>
|
||||||
|
<th>Nama Ruangan</th>
|
||||||
|
<th>Kapasitas</th>
|
||||||
|
<th>Program Studi</th>
|
||||||
|
<th width="120" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for ruangan in ruangan_all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="selected_ids" value="{{ ruangan.id }}" form="massDeleteForm" class="form-check-input item-checkbox">
|
||||||
|
</td>
|
||||||
|
<td>{{ ruangan.nama }}</td>
|
||||||
|
<td>{{ ruangan.kapasitas }} Kursi</td>
|
||||||
|
<td>{{ ruangan.prodi.nama|default:"-" }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white me-1 edit-btn"
|
||||||
|
data-pk="{{ ruangan.id }}"
|
||||||
|
data-nama="{{ ruangan.nama }}"
|
||||||
|
data-kapasitas="{{ ruangan.kapasitas }}"
|
||||||
|
data-prodi="{{ ruangan.prodi.id|default:'' }}"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'ruangan_delete' ruangan.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus data ini?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-4 text-muted">Belum ada data ruangan.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Tambah Ruangan</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Ruangan</label>
|
||||||
|
<input type="text" name="nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kapasitas</label>
|
||||||
|
<input type="number" name="kapasitas" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" class="form-select select2-modal">
|
||||||
|
<option value="">Umum (Semua Prodi)</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="pk" id="edit_pk">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Ruangan</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nama Ruangan</label>
|
||||||
|
<input type="text" name="nama" id="edit_nama" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kapasitas</label>
|
||||||
|
<input type="number" name="kapasitas" id="edit_kapasitas" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Program Studi</label>
|
||||||
|
<select name="prodi" id="edit_prodi" class="form-select select2-modal">
|
||||||
|
<option value="">Umum (Semua Prodi)</option>
|
||||||
|
{% for prodi in prodi_all %}
|
||||||
|
<option value="{{ prodi.id }}">{{ prodi.nama }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Import Modal -->
|
||||||
|
<div class="modal fade" id="importModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" action="{% url 'ruangan_import' %}" enctype="multipart/form-data" class="modal-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Import Ruangan</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">File CSV</label>
|
||||||
|
<input type="file" name="csv_file" class="form-control" accept=".csv" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-success">Import</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.edit-btn').on('click', function() {
|
||||||
|
$('#edit_pk').val($(this).data('pk'));
|
||||||
|
$('#edit_nama').val($(this).data('nama'));
|
||||||
|
$('#edit_kapasitas').val($(this).data('kapasitas'));
|
||||||
|
$('#edit_prodi').val($(this).data('prodi')).trigger('change');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
27
core/templates/core/tahun_list.html
Normal file
27
core/templates/core/tahun_list.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}Tahun Akademik{% endblock %}
|
||||||
|
{% block page_title %}Tahun Akademik{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal"><i class="fas fa-plus me-2"></i>Tambah Tahun</button>
|
||||||
|
<form method="POST" id="massDeleteForm">{% csrf_token %}<button type="submit" name="delete_massal" class="btn btn-danger mass-delete-btn" id="massDeleteBtn"><i class="fas fa-trash me-2"></i>Hapus Terpilih</button></form>
|
||||||
|
</div>
|
||||||
|
<div class="card border-0 shadow-sm"><div class="card-body"><div class="table-responsive"><table class="table table-hover align-middle mb-0"><thead class="bg-light"><tr><th width="40"><input type="checkbox" id="checkAll" class="form-check-input"></th><th>Tahun Akademik</th><th>Status</th><th width="120" class="text-center">Aksi</th></tr></thead><tbody>
|
||||||
|
{% for t in tahun_all %}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="selected_ids" value="{{ t.id }}" form="massDeleteForm" class="form-check-input item-checkbox"></td>
|
||||||
|
<td>{{ t.nama }}</td>
|
||||||
|
<td>{% if t.is_active %}<span class="badge bg-success">Aktif</span>{% else %}<span class="badge bg-secondary">Non-Aktif</span>{% endif %}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-info text-white edit-btn" data-pk="{{ t.id }}" data-nama="{{ t.nama }}" data-active="{{ t.is_active|yesno:'on,off' }}" data-bs-toggle="modal" data-bs-target="#editModal"><i class="fas fa-edit"></i></button>
|
||||||
|
<a href="{% url 'tahun_delete' t.id %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Hapus?')"><i class="fas fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody></table></div></div></div>
|
||||||
|
<div class="modal fade" id="addModal" tabindex="-1"><div class="modal-dialog"><form method="POST" class="modal-content">{% csrf_token %}<div class="modal-header"><h5 class="modal-title">Tambah Tahun Akademik</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="form-label">Nama Tahun</label><input type="text" name="nama" class="form-control" placeholder="2025/2026" required></div><div class="form-check form-switch"><input class="form-check-input" type="checkbox" name="is_active" id="add_active"><label class="form-check-label" for="add_active">Set Aktif</label></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">Simpan</button></div></form></div></div>
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1"><div class="modal-dialog"><form method="POST" class="modal-content">{% csrf_token %}<input type="hidden" name="pk" id="edit_pk"><div class="modal-header"><h5 class="modal-title">Edit Tahun Akademik</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="form-label">Nama Tahun</label><input type="text" name="nama" id="edit_nama" class="form-control" required></div><div class="form-check form-switch"><input class="form-check-input" type="checkbox" name="is_active" id="edit_active"><label class="form-check-label" for="edit_active">Set Aktif</label></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">Update</button></div></form></div></div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
<script>$(document).ready(function(){$('.edit-btn').on('click',function(){$('#edit_pk').val($(this).data('pk'));$('#edit_nama').val($(this).data('nama'));$('#edit_active').prop('checked',$(this).data('active')==='on');});});</script>
|
||||||
|
{% endblock %}
|
||||||
53
core/urls.py
53
core/urls.py
@ -1,7 +1,54 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
from .views import home
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home, name="home"),
|
path('', views.dashboard, name='dashboard'),
|
||||||
|
|
||||||
|
# AJAX Helpers
|
||||||
|
path('ajax/get-kelas/', views.get_kelas_by_prodi, name='get_kelas_by_prodi'),
|
||||||
|
path('ajax/get-matkul/', views.get_matkul_by_prodi, name='get_matkul_by_prodi'),
|
||||||
|
|
||||||
|
path('prodi/', views.prodi_list, name='prodi_list'),
|
||||||
|
path('prodi/delete/<int:pk>/', views.prodi_delete, name='prodi_delete'),
|
||||||
|
|
||||||
|
path('dosen/', views.dosen_list, name='dosen_list'),
|
||||||
|
path('dosen/delete/<int:pk>/', views.dosen_delete, name='dosen_delete'),
|
||||||
|
path('dosen/template/', views.dosen_template, name='dosen_template'),
|
||||||
|
path('dosen/import/', views.dosen_import, name='dosen_import'),
|
||||||
|
|
||||||
|
path('matkul/', views.matkul_list, name='matkul_list'),
|
||||||
|
path('matkul/delete/<int:pk>/', views.matkul_delete, name='matkul_delete'),
|
||||||
|
path('matkul/template/', views.matkul_template, name='matkul_template'),
|
||||||
|
path('matkul/import/', views.matkul_import, name='matkul_import'),
|
||||||
|
|
||||||
|
path('ruangan/', views.ruangan_list, name='ruangan_list'),
|
||||||
|
path('ruangan/delete/<int:pk>/', views.ruangan_delete, name='ruangan_delete'),
|
||||||
|
path('ruangan/template/', views.ruangan_template, name='ruangan_template'),
|
||||||
|
path('ruangan/import/', views.ruangan_import, name='ruangan_import'),
|
||||||
|
|
||||||
|
path('kelas/', views.kelas_list, name='kelas_list'),
|
||||||
|
path('kelas/delete/<int:pk>/', views.kelas_delete, name='kelas_delete'),
|
||||||
|
path('kelas/template/', views.kelas_template, name='kelas_template'),
|
||||||
|
path('kelas/import/', views.kelas_import, name='kelas_import'),
|
||||||
|
|
||||||
|
path('jam/', views.jam_list, name='jam_list'),
|
||||||
|
path('jam/delete/<int:pk>/', views.jam_delete, name='jam_delete'),
|
||||||
|
path('jam/generate/', views.jam_generate, name='jam_generate'),
|
||||||
|
|
||||||
|
path('tahun/', views.tahun_list, name='tahun_list'),
|
||||||
|
path('tahun/delete/<int:pk>/', views.tahun_delete, name='tahun_delete'),
|
||||||
|
|
||||||
|
path('hari/', views.hari_list, name='hari_list'),
|
||||||
|
path('hari/delete/<int:pk>/', views.hari_delete, name='hari_delete'),
|
||||||
|
|
||||||
|
path('pengampu/', views.pengampu_list, name='pengampu_list'),
|
||||||
|
path('pengampu/delete/<int:pk>/', views.pengampu_delete, name='pengampu_delete'),
|
||||||
|
|
||||||
|
path('daftar-hadir/', views.daftar_hadir_list, name='daftar_hadir_list'),
|
||||||
|
path('daftar-hadir/delete/<int:pk>/', views.daftar_hadir_delete, name='daftar_hadir_delete'),
|
||||||
|
|
||||||
|
path('jadwal/', views.jadwal_list, name='jadwal_list'),
|
||||||
|
path('jadwal/buat/', views.buat_jadwal, name='buat_jadwal'),
|
||||||
|
path('jadwal/delete/<int:pk>/', views.jadwal_delete, name='jadwal_delete'),
|
||||||
|
path('jadwal/export/', views.jadwal_export_excel, name='jadwal_export_excel'),
|
||||||
]
|
]
|
||||||
670
core/views.py
670
core/views.py
@ -1,25 +1,651 @@
|
|||||||
import os
|
import csv
|
||||||
import platform
|
import datetime
|
||||||
|
import random
|
||||||
from django import get_version as django_version
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.shortcuts import render
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.utils import timezone
|
from django.contrib import messages
|
||||||
|
from django.db import IntegrityError
|
||||||
|
from openpyxl import Workbook
|
||||||
def home(request):
|
from .models import (
|
||||||
"""Render the landing screen with loader and environment details."""
|
ProgramStudi, Dosen, TahunAkademik, Hari,
|
||||||
host_name = request.get_host().lower()
|
MataKuliah, Ruangan, Kelas, Jam, DosenPengampu, Jadwal, DaftarHadir
|
||||||
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
)
|
||||||
now = timezone.now()
|
|
||||||
|
|
||||||
|
def dashboard(request):
|
||||||
context = {
|
context = {
|
||||||
"project_name": "New Style",
|
"count_prodi": ProgramStudi.objects.count(),
|
||||||
"agent_brand": agent_brand,
|
"count_dosen": Dosen.objects.count(),
|
||||||
"django_version": django_version(),
|
"count_matkul": MataKuliah.objects.count(),
|
||||||
"python_version": platform.python_version(),
|
"count_ruangan": Ruangan.objects.count(),
|
||||||
"current_time": now,
|
"count_kelas": Kelas.objects.count(),
|
||||||
"host_name": host_name,
|
"count_jadwal": Jadwal.objects.count(),
|
||||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
"count_daftar_hadir": DaftarHadir.objects.count(),
|
||||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
|
||||||
}
|
}
|
||||||
return render(request, "core/index.html", context)
|
return render(request, "core/dashboard.html", context)
|
||||||
|
|
||||||
|
# --- AJAX HELPERS ---
|
||||||
|
def get_kelas_by_prodi(request):
|
||||||
|
prodi_id = request.GET.get('prodi_id')
|
||||||
|
kelas = Kelas.objects.filter(prodi_id=prodi_id).values('id', 'nama')
|
||||||
|
return JsonResponse(list(kelas), safe=False)
|
||||||
|
|
||||||
|
def get_matkul_by_prodi(request):
|
||||||
|
prodi_id = request.GET.get('prodi_id')
|
||||||
|
matkul = MataKuliah.objects.filter(prodi_id=prodi_id).values('id', 'kode', 'nama')
|
||||||
|
return JsonResponse(list(matkul), safe=False)
|
||||||
|
|
||||||
|
# --- PRODI ---
|
||||||
|
def prodi_list(request):
|
||||||
|
prodi_all = ProgramStudi.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
ProgramStudi.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Program Studi.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(ProgramStudi, pk=pk)
|
||||||
|
obj.nama = nama
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Program Studi berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
ProgramStudi.objects.create(nama=nama)
|
||||||
|
messages.success(request, "Program Studi berhasil ditambahkan.")
|
||||||
|
return redirect("prodi_list")
|
||||||
|
return render(request, "core/prodi_list.html", {"prodi_all": prodi_all})
|
||||||
|
|
||||||
|
def prodi_delete(request, pk):
|
||||||
|
get_object_or_404(ProgramStudi, pk=pk).delete()
|
||||||
|
messages.success(request, "Program Studi berhasil dihapus.")
|
||||||
|
return redirect("prodi_list")
|
||||||
|
|
||||||
|
# --- DOSEN ---
|
||||||
|
def dosen_list(request):
|
||||||
|
dosen_all = Dosen.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
Dosen.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Dosen.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
nidn = request.POST.get("nidn")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(Dosen, pk=pk)
|
||||||
|
obj.nidn = nidn
|
||||||
|
obj.nama = nama
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Dosen berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
Dosen.objects.create(nidn=nidn, nama=nama)
|
||||||
|
messages.success(request, "Dosen berhasil ditambahkan.")
|
||||||
|
return redirect("dosen_list")
|
||||||
|
return render(request, "core/dosen_list.html", {"dosen_all": dosen_all})
|
||||||
|
|
||||||
|
def dosen_delete(request, pk):
|
||||||
|
get_object_or_404(Dosen, pk=pk).delete()
|
||||||
|
messages.success(request, "Dosen berhasil dihapus.")
|
||||||
|
return redirect("dosen_list")
|
||||||
|
|
||||||
|
def dosen_template(request):
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="template_dosen.csv"'
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['nidn', 'nama'])
|
||||||
|
writer.writerow(['12345678', 'Dr. Nama Dosen, M.Kom'])
|
||||||
|
return response
|
||||||
|
|
||||||
|
def dosen_import(request):
|
||||||
|
if request.method == 'POST' and request.FILES.get('csv_file'):
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8').splitlines()
|
||||||
|
reader = csv.DictReader(decoded_file)
|
||||||
|
count = 0
|
||||||
|
for row in reader:
|
||||||
|
Dosen.objects.update_or_create(
|
||||||
|
nidn=row['nidn'],
|
||||||
|
defaults={'nama': row['nama']}
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
messages.success(request, f"Berhasil mengimport {count} data Dosen.")
|
||||||
|
return redirect('dosen_list')
|
||||||
|
|
||||||
|
# --- MATA KULIAH ---
|
||||||
|
def matkul_list(request):
|
||||||
|
matkul_all = MataKuliah.objects.all()
|
||||||
|
prodi_all = ProgramStudi.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
MataKuliah.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Mata Kuliah.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
kode = request.POST.get("kode")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
sks = request.POST.get("sks")
|
||||||
|
semester = request.POST.get("semester")
|
||||||
|
prodi_id = request.POST.get("prodi")
|
||||||
|
prodi = get_object_or_404(ProgramStudi, id=prodi_id)
|
||||||
|
try:
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(MataKuliah, pk=pk)
|
||||||
|
obj.kode = kode
|
||||||
|
obj.nama = nama
|
||||||
|
obj.sks = sks
|
||||||
|
obj.semester = semester
|
||||||
|
obj.prodi = prodi
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Mata Kuliah berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
MataKuliah.objects.create(kode=kode, nama=nama, sks=sks, semester=semester, prodi=prodi)
|
||||||
|
messages.success(request, "Mata Kuliah berhasil ditambahkan.")
|
||||||
|
except IntegrityError:
|
||||||
|
messages.error(request, "Gagal menyimpan. Kode atau Nama Mata Kuliah sudah ada di Program Studi ini.")
|
||||||
|
return redirect("matkul_list")
|
||||||
|
return render(request, "core/matkul_list.html", {"matkul_all": matkul_all, "prodi_all": prodi_all})
|
||||||
|
|
||||||
|
def matkul_delete(request, pk):
|
||||||
|
get_object_or_404(MataKuliah, pk=pk).delete()
|
||||||
|
messages.success(request, "Mata Kuliah berhasil dihapus.")
|
||||||
|
return redirect("matkul_list")
|
||||||
|
|
||||||
|
def matkul_template(request):
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="template_matkul.csv"'
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['kode', 'nama', 'sks', 'semester', 'program_studi'])
|
||||||
|
writer.writerow(['MK001', 'Algoritma Pemrograman', '3', '1', 'Teknik Informatika'])
|
||||||
|
return response
|
||||||
|
|
||||||
|
def matkul_import(request):
|
||||||
|
if request.method == 'POST' and request.FILES.get('csv_file'):
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8').splitlines()
|
||||||
|
reader = csv.DictReader(decoded_file)
|
||||||
|
count = 0
|
||||||
|
for row in reader:
|
||||||
|
prodi_nama = row.get('program_studi')
|
||||||
|
prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama)
|
||||||
|
MataKuliah.objects.update_or_create(
|
||||||
|
kode=row['kode'],
|
||||||
|
prodi=prodi,
|
||||||
|
defaults={
|
||||||
|
'nama': row['nama'],
|
||||||
|
'sks': row['sks'],
|
||||||
|
'semester': row['semester']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
messages.success(request, f"Berhasil mengimport {count} data Mata Kuliah.")
|
||||||
|
return redirect('matkul_list')
|
||||||
|
|
||||||
|
# --- RUANGAN ---
|
||||||
|
def ruangan_list(request):
|
||||||
|
ruangan_all = Ruangan.objects.all()
|
||||||
|
prodi_all = ProgramStudi.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
Ruangan.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Ruangan.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
kapasitas = request.POST.get("kapasitas")
|
||||||
|
prodi_id = request.POST.get("prodi")
|
||||||
|
prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(Ruangan, pk=pk)
|
||||||
|
obj.nama = nama
|
||||||
|
obj.kapasitas = kapasitas
|
||||||
|
obj.prodi = prodi
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Ruangan berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
Ruangan.objects.create(nama=nama, kapasitas=kapasitas, prodi=prodi)
|
||||||
|
messages.success(request, "Ruangan berhasil ditambahkan.")
|
||||||
|
return redirect("ruangan_list")
|
||||||
|
return render(request, "core/ruangan_list.html", {"ruangan_all": ruangan_all, "prodi_all": prodi_all})
|
||||||
|
|
||||||
|
def ruangan_delete(request, pk):
|
||||||
|
get_object_or_404(Ruangan, pk=pk).delete()
|
||||||
|
messages.success(request, "Ruangan berhasil dihapus.")
|
||||||
|
return redirect("ruangan_list")
|
||||||
|
|
||||||
|
def ruangan_template(request):
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="template_ruangan.csv"'
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['nama', 'kapasitas', 'program_studi'])
|
||||||
|
writer.writerow(['Ruang A1', '40', 'Teknik Informatika'])
|
||||||
|
return response
|
||||||
|
|
||||||
|
def ruangan_import(request):
|
||||||
|
if request.method == 'POST' and request.FILES.get('csv_file'):
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8').splitlines()
|
||||||
|
reader = csv.DictReader(decoded_file)
|
||||||
|
count = 0
|
||||||
|
for row in reader:
|
||||||
|
prodi_nama = row.get('program_studi')
|
||||||
|
prodi = None
|
||||||
|
if prodi_nama:
|
||||||
|
prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama)
|
||||||
|
Ruangan.objects.create(nama=row['nama'], kapasitas=row['kapasitas'], prodi=prodi)
|
||||||
|
count += 1
|
||||||
|
messages.success(request, f"Berhasil mengimport {count} data Ruangan.")
|
||||||
|
return redirect('ruangan_list')
|
||||||
|
|
||||||
|
# --- KELAS ---
|
||||||
|
def kelas_list(request):
|
||||||
|
kelas_all = Kelas.objects.all()
|
||||||
|
prodi_all = ProgramStudi.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
Kelas.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Kelas.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
prodi_id = request.POST.get("prodi")
|
||||||
|
prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(Kelas, pk=pk)
|
||||||
|
obj.nama = nama
|
||||||
|
obj.prodi = prodi
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Kelas berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
Kelas.objects.create(nama=nama, prodi=prodi)
|
||||||
|
messages.success(request, "Kelas berhasil ditambahkan.")
|
||||||
|
return redirect("kelas_list")
|
||||||
|
return render(request, "core/kelas_list.html", {"kelas_all": kelas_all, "prodi_all": prodi_all})
|
||||||
|
|
||||||
|
def kelas_delete(request, pk):
|
||||||
|
get_object_or_404(Kelas, pk=pk).delete()
|
||||||
|
messages.success(request, "Kelas berhasil dihapus.")
|
||||||
|
return redirect("kelas_list")
|
||||||
|
|
||||||
|
def kelas_template(request):
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="template_kelas.csv"'
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['nama', 'program_studi'])
|
||||||
|
writer.writerow(['Kelas A', 'Teknik Informatika'])
|
||||||
|
return response
|
||||||
|
|
||||||
|
def kelas_import(request):
|
||||||
|
if request.method == 'POST' and request.FILES.get('csv_file'):
|
||||||
|
csv_file = request.FILES['csv_file']
|
||||||
|
decoded_file = csv_file.read().decode('utf-8').splitlines()
|
||||||
|
reader = csv.DictReader(decoded_file)
|
||||||
|
count = 0
|
||||||
|
for row in reader:
|
||||||
|
prodi_nama = row.get('program_studi')
|
||||||
|
prodi = None
|
||||||
|
if prodi_nama:
|
||||||
|
prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama)
|
||||||
|
Kelas.objects.create(nama=row['nama'], prodi=prodi)
|
||||||
|
count += 1
|
||||||
|
messages.success(request, f"Berhasil mengimport {count} data Kelas.")
|
||||||
|
return redirect('kelas_list')
|
||||||
|
|
||||||
|
# --- JAM ---
|
||||||
|
def jam_list(request):
|
||||||
|
jam_all = Jam.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
Jam.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Jam.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
range_waktu = request.POST.get("range_waktu")
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(Jam, pk=pk)
|
||||||
|
obj.range_waktu = range_waktu
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Jam berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
Jam.objects.create(range_waktu=range_waktu)
|
||||||
|
messages.success(request, "Jam berhasil ditambahkan.")
|
||||||
|
return redirect("jam_list")
|
||||||
|
return render(request, "core/jam_list.html", {"jam_all": jam_all})
|
||||||
|
|
||||||
|
def jam_delete(request, pk):
|
||||||
|
get_object_or_404(Jam, pk=pk).delete()
|
||||||
|
messages.success(request, "Jam berhasil dihapus.")
|
||||||
|
return redirect("jam_list")
|
||||||
|
|
||||||
|
def jam_generate(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
start_str = request.POST.get("jam_mulai") # e.g. "07:00"
|
||||||
|
end_str = request.POST.get("jam_selesai") # e.g. "17:00"
|
||||||
|
mins_per_sks = int(request.POST.get("durasi_per_sks", 50))
|
||||||
|
sks_per_slot = int(request.POST.get("jumlah_sks", 2))
|
||||||
|
jeda_mins = int(request.POST.get("jeda", 0))
|
||||||
|
|
||||||
|
start_time = datetime.datetime.strptime(start_str, "%H:%M")
|
||||||
|
end_time = datetime.datetime.strptime(end_str, "%H:%M")
|
||||||
|
|
||||||
|
slot_duration = mins_per_sks * sks_per_slot
|
||||||
|
current_time = start_time
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
while current_time + datetime.timedelta(minutes=slot_duration) <= end_time:
|
||||||
|
slot_end = current_time + datetime.timedelta(minutes=slot_duration)
|
||||||
|
range_waktu = f"{current_time.strftime('%H:%M')} - {slot_end.strftime('%H:%M')}"
|
||||||
|
Jam.objects.get_or_create(range_waktu=range_waktu)
|
||||||
|
current_time = slot_end + datetime.timedelta(minutes=jeda_mins)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
messages.success(request, f"Berhasil menggenerate {count} slot waktu.")
|
||||||
|
return redirect("jam_list")
|
||||||
|
|
||||||
|
# --- TAHUN AKADEMIK ---
|
||||||
|
def tahun_list(request):
|
||||||
|
tahun_all = TahunAkademik.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
TahunAkademik.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Tahun Akademik.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
is_active = request.POST.get("is_active") == "on"
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(TahunAkademik, pk=pk)
|
||||||
|
if is_active:
|
||||||
|
TahunAkademik.objects.update(is_active=False)
|
||||||
|
obj.nama = nama
|
||||||
|
obj.is_active = is_active
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Tahun Akademik berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
if is_active:
|
||||||
|
TahunAkademik.objects.update(is_active=False)
|
||||||
|
TahunAkademik.objects.create(nama=nama, is_active=is_active)
|
||||||
|
messages.success(request, "Tahun Akademik berhasil ditambahkan.")
|
||||||
|
return redirect("tahun_list")
|
||||||
|
return render(request, "core/tahun_list.html", {"tahun_all": tahun_all})
|
||||||
|
|
||||||
|
def tahun_delete(request, pk):
|
||||||
|
get_object_or_404(TahunAkademik, pk=pk).delete()
|
||||||
|
messages.success(request, "Tahun Akademik berhasil dihapus.")
|
||||||
|
return redirect("tahun_list")
|
||||||
|
|
||||||
|
# --- HARI ---
|
||||||
|
def hari_list(request):
|
||||||
|
hari_all = Hari.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
Hari.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Hari.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
nama = request.POST.get("nama")
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(Hari, pk=pk)
|
||||||
|
obj.nama = nama
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Hari berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
Hari.objects.create(nama=nama)
|
||||||
|
messages.success(request, "Hari berhasil ditambahkan.")
|
||||||
|
return redirect("hari_list")
|
||||||
|
return render(request, "core/hari_list.html", {"hari_all": hari_all})
|
||||||
|
|
||||||
|
def hari_delete(request, pk):
|
||||||
|
get_object_or_404(Hari, pk=pk).delete()
|
||||||
|
messages.success(request, "Hari berhasil dihapus.")
|
||||||
|
return redirect("hari_list")
|
||||||
|
|
||||||
|
# --- DOSEN PENGAMPU ---
|
||||||
|
def pengampu_list(request):
|
||||||
|
pengampu_all = DosenPengampu.objects.all()
|
||||||
|
dosen_all = Dosen.objects.all()
|
||||||
|
matkul_all = MataKuliah.objects.all()
|
||||||
|
tahun_all = TahunAkademik.objects.all()
|
||||||
|
prodi_all = ProgramStudi.objects.all()
|
||||||
|
kelas_all = Kelas.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
DosenPengampu.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Dosen Pengampu.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
dosen_id = request.POST.get("dosen")
|
||||||
|
matkul_id = request.POST.get("matkul")
|
||||||
|
tahun_id = request.POST.get("tahun")
|
||||||
|
prodi_id = request.POST.get("prodi")
|
||||||
|
kelas_id = request.POST.get("kelas")
|
||||||
|
|
||||||
|
dosen = get_object_or_404(Dosen, id=dosen_id)
|
||||||
|
matkul = get_object_or_404(MataKuliah, id=matkul_id)
|
||||||
|
tahun = get_object_or_404(TahunAkademik, id=tahun_id)
|
||||||
|
prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None
|
||||||
|
kelas = get_object_or_404(Kelas, id=kelas_id) if kelas_id else None
|
||||||
|
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(DosenPengampu, pk=pk)
|
||||||
|
obj.dosen = dosen
|
||||||
|
obj.matkul = matkul
|
||||||
|
obj.tahun_akademik = tahun
|
||||||
|
obj.prodi = prodi
|
||||||
|
obj.kelas = kelas
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Dosen Pengampu berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
DosenPengampu.objects.create(
|
||||||
|
dosen=dosen, matkul=matkul, tahun_akademik=tahun,
|
||||||
|
prodi=prodi, kelas=kelas
|
||||||
|
)
|
||||||
|
messages.success(request, "Dosen Pengampu berhasil ditambahkan.")
|
||||||
|
return redirect("pengampu_list")
|
||||||
|
return render(request, "core/pengampu_list.html", {
|
||||||
|
"pengampu_all": pengampu_all,
|
||||||
|
"dosen_all": dosen_all,
|
||||||
|
"matkul_all": matkul_all,
|
||||||
|
"tahun_all": tahun_all,
|
||||||
|
"prodi_all": prodi_all,
|
||||||
|
"kelas_all": kelas_all,
|
||||||
|
})
|
||||||
|
|
||||||
|
def pengampu_delete(request, pk):
|
||||||
|
get_object_or_404(DosenPengampu, pk=pk).delete()
|
||||||
|
messages.success(request, "Dosen Pengampu berhasil dihapus.")
|
||||||
|
return redirect("pengampu_list")
|
||||||
|
|
||||||
|
# --- DAFTAR HADIR ---
|
||||||
|
def daftar_hadir_list(request):
|
||||||
|
hadir_all = DaftarHadir.objects.all()
|
||||||
|
jadwal_all = Jadwal.objects.all()
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
DaftarHadir.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Daftar Hadir.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
jadwal_id = request.POST.get("jadwal")
|
||||||
|
tanggal = request.POST.get("tanggal")
|
||||||
|
pertemuan = request.POST.get("pertemuan_ke")
|
||||||
|
ket = request.POST.get("keterangan")
|
||||||
|
jadwal = get_object_or_404(Jadwal, id=jadwal_id)
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(DaftarHadir, pk=pk)
|
||||||
|
obj.jadwal = jadwal
|
||||||
|
obj.tanggal = tanggal
|
||||||
|
obj.pertemuan_ke = pertemuan
|
||||||
|
obj.keterangan = ket
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Daftar Hadir berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
DaftarHadir.objects.create(jadwal=jadwal, tanggal=tanggal, pertemuan_ke=pertemuan, keterangan=ket)
|
||||||
|
messages.success(request, "Daftar Hadir berhasil ditambahkan.")
|
||||||
|
return redirect("daftar_hadir_list")
|
||||||
|
return render(request, "core/daftar_hadir_list.html", {"hadir_all": hadir_all, "jadwal_all": jadwal_all})
|
||||||
|
|
||||||
|
def daftar_hadir_delete(request, pk):
|
||||||
|
get_object_or_404(DaftarHadir, pk=pk).delete()
|
||||||
|
messages.success(request, "Daftar Hadir berhasil dihapus.")
|
||||||
|
return redirect("daftar_hadir_list")
|
||||||
|
|
||||||
|
# --- JADWAL ---
|
||||||
|
def jadwal_list(request):
|
||||||
|
jadwal_all = Jadwal.objects.all().order_by('hari', 'jam')
|
||||||
|
pengampu_all = DosenPengampu.objects.all()
|
||||||
|
ruangan_all = Ruangan.objects.all()
|
||||||
|
hari_all = Hari.objects.all()
|
||||||
|
jam_all = Jam.objects.all()
|
||||||
|
kelas_all = Kelas.objects.all()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
if "delete_massal" in request.POST:
|
||||||
|
ids = request.POST.getlist("selected_ids")
|
||||||
|
Jadwal.objects.filter(id__in=ids).delete()
|
||||||
|
messages.success(request, f"Berhasil menghapus {len(ids)} Jadwal.")
|
||||||
|
else:
|
||||||
|
pk = request.POST.get("pk")
|
||||||
|
pengampu_id = request.POST.get("pengampu")
|
||||||
|
ruangan_id = request.POST.get("ruangan")
|
||||||
|
hari_id = request.POST.get("hari")
|
||||||
|
jam_id = request.POST.get("jam")
|
||||||
|
kelas_id = request.POST.get("kelas")
|
||||||
|
|
||||||
|
pengampu = get_object_or_404(DosenPengampu, id=pengampu_id)
|
||||||
|
ruangan = get_object_or_404(Ruangan, id=ruangan_id)
|
||||||
|
hari = get_object_or_404(Hari, id=hari_id)
|
||||||
|
jam = get_object_or_404(Jam, id=jam_id)
|
||||||
|
kelas = get_object_or_404(Kelas, id=kelas_id)
|
||||||
|
|
||||||
|
if pk:
|
||||||
|
obj = get_object_or_404(Jadwal, pk=pk)
|
||||||
|
obj.dosen_pengampu = pengampu
|
||||||
|
obj.ruangan = ruangan
|
||||||
|
obj.hari = hari
|
||||||
|
obj.jam = jam
|
||||||
|
obj.kelas = kelas
|
||||||
|
obj.save()
|
||||||
|
messages.success(request, "Jadwal berhasil diupdate.")
|
||||||
|
else:
|
||||||
|
Jadwal.objects.create(
|
||||||
|
dosen_pengampu=pengampu, ruangan=ruangan,
|
||||||
|
hari=hari, jam=jam, kelas=kelas, is_manual=True
|
||||||
|
)
|
||||||
|
messages.success(request, "Jadwal berhasil ditambahkan secara manual.")
|
||||||
|
return redirect("jadwal_list")
|
||||||
|
|
||||||
|
return render(request, "core/jadwal_list.html", {
|
||||||
|
"jadwal_all": jadwal_all,
|
||||||
|
"pengampu_all": pengampu_all,
|
||||||
|
"ruangan_all": ruangan_all,
|
||||||
|
"hari_all": hari_all,
|
||||||
|
"jam_all": jam_all,
|
||||||
|
"kelas_all": kelas_all,
|
||||||
|
})
|
||||||
|
|
||||||
|
def buat_jadwal(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
pengampu_all = DosenPengampu.objects.all()
|
||||||
|
ruangan_all = Ruangan.objects.all()
|
||||||
|
hari_all = Hari.objects.all()
|
||||||
|
jam_all = Jam.objects.all()
|
||||||
|
|
||||||
|
if not (pengampu_all and ruangan_all and hari_all and jam_all):
|
||||||
|
messages.error(request, "Data Master belum lengkap.")
|
||||||
|
return redirect("buat_jadwal")
|
||||||
|
|
||||||
|
# Only delete schedules that were not manually created
|
||||||
|
Jadwal.objects.filter(is_manual=False).delete()
|
||||||
|
|
||||||
|
# Get pengampu that don't have a schedule yet (including manual ones)
|
||||||
|
pengampu_with_jadwal = Jadwal.objects.values_list('dosen_pengampu_id', flat=True)
|
||||||
|
pending_pengampu = pengampu_all.exclude(id__in=pengampu_with_jadwal)
|
||||||
|
|
||||||
|
for pengampu in pending_pengampu:
|
||||||
|
# We use the class assigned in DosenPengampu
|
||||||
|
kelas = pengampu.kelas
|
||||||
|
if not kelas:
|
||||||
|
# Fallback to random class if not assigned in Pengampu (for legacy data)
|
||||||
|
kelas_all = Kelas.objects.all()
|
||||||
|
kelas = random.choice(kelas_all) if kelas_all.exists() else None
|
||||||
|
|
||||||
|
if not kelas: continue
|
||||||
|
|
||||||
|
# Simple logic to find an available slot (avoiding collisions for manual ones)
|
||||||
|
# This is still a basic random generator but it now respects existing manual entries
|
||||||
|
max_tries = 50
|
||||||
|
for _ in range(max_tries):
|
||||||
|
h = random.choice(hari_all)
|
||||||
|
j = random.choice(jam_all)
|
||||||
|
r = random.choice(ruangan_all)
|
||||||
|
|
||||||
|
# Check for collisions in that room/time
|
||||||
|
if not Jadwal.objects.filter(hari=h, jam=j, ruangan=r).exists() and \
|
||||||
|
not Jadwal.objects.filter(hari=h, jam=j, kelas=kelas).exists() and \
|
||||||
|
not Jadwal.objects.filter(hari=h, jam=j, dosen_pengampu__dosen=pengampu.dosen).exists():
|
||||||
|
|
||||||
|
Jadwal.objects.create(
|
||||||
|
dosen_pengampu=pengampu,
|
||||||
|
ruangan=r,
|
||||||
|
hari=h,
|
||||||
|
jam=j,
|
||||||
|
kelas=kelas,
|
||||||
|
is_manual=False
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
messages.success(request, "Jadwal berhasil digenerate otomatis (menghindari jadwal manual).")
|
||||||
|
return redirect("jadwal_list")
|
||||||
|
return render(request, "core/buat_jadwal.html")
|
||||||
|
|
||||||
|
def jadwal_delete(request, pk):
|
||||||
|
get_object_or_404(Jadwal, pk=pk).delete()
|
||||||
|
messages.success(request, "Jadwal berhasil dihapus.")
|
||||||
|
return redirect("jadwal_list")
|
||||||
|
|
||||||
|
def jadwal_export_excel(request):
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = "Jadwal Kuliah"
|
||||||
|
|
||||||
|
headers = [
|
||||||
|
'No', 'Hari', 'Jam', 'Kode MK', 'Mata Kuliah', 'SKS', 'Semester',
|
||||||
|
'Dosen', 'Kelas', 'Ruangan', 'Program Studi', 'Tahun Akademik'
|
||||||
|
]
|
||||||
|
ws.append(headers)
|
||||||
|
|
||||||
|
jadwal_all = Jadwal.objects.all().select_related(
|
||||||
|
'hari', 'jam', 'dosen_pengampu__matkul', 'dosen_pengampu__dosen',
|
||||||
|
'kelas', 'ruangan', 'dosen_pengampu__matkul__prodi', 'dosen_pengampu__tahun_akademik'
|
||||||
|
).order_by('hari', 'jam')
|
||||||
|
|
||||||
|
for i, j in enumerate(jadwal_all, 1):
|
||||||
|
ws.append([
|
||||||
|
i,
|
||||||
|
j.hari.nama,
|
||||||
|
j.jam.range_waktu,
|
||||||
|
j.dosen_pengampu.matkul.kode,
|
||||||
|
j.dosen_pengampu.matkul.nama,
|
||||||
|
j.dosen_pengampu.matkul.sks,
|
||||||
|
j.dosen_pengampu.matkul.semester,
|
||||||
|
j.dosen_pengampu.dosen.nama,
|
||||||
|
j.kelas.nama,
|
||||||
|
j.ruangan.nama,
|
||||||
|
j.dosen_pengampu.matkul.prodi.nama,
|
||||||
|
j.dosen_pengampu.tahun_akademik.nama
|
||||||
|
])
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="jadwal_kuliah.xlsx"'
|
||||||
|
wb.save(response)
|
||||||
|
return response
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
Django==5.2.7
|
Django==5.2.7
|
||||||
mysqlclient==2.2.7
|
mysqlclient==2.2.7
|
||||||
python-dotenv==1.1.1
|
python-dotenv==1.1.1
|
||||||
|
openpyxl==3.1.5
|
||||||
|
|||||||
@ -1,4 +1,152 @@
|
|||||||
/* Custom styles for the application */
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@400;600;700&display=swap');
|
||||||
body {
|
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
:root {
|
||||||
|
--primary-color: #00B4D8;
|
||||||
|
--secondary-color: #FFB703;
|
||||||
|
--dark-bg: #212529;
|
||||||
|
--light-bg: #F8F9FA;
|
||||||
|
--sidebar-width: 280px;
|
||||||
|
--text-primary: #333;
|
||||||
|
--text-muted: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: var(--light-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: 'Outfit', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
#sidebar {
|
||||||
|
width: var(--sidebar-width);
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--dark-bg);
|
||||||
|
color: white;
|
||||||
|
z-index: 1000;
|
||||||
|
transition: all 0.3s;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .nav-link {
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
padding: 12px 25px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .nav-link i {
|
||||||
|
margin-right: 15px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .nav-link:hover, #sidebar .nav-link.active {
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .sidebar-header {
|
||||||
|
padding: 20px 25px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
#main-content {
|
||||||
|
margin-left: var(--sidebar-width);
|
||||||
|
padding: 30px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-navbar {
|
||||||
|
background: white;
|
||||||
|
padding: 15px 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stat {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stat i {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0096B4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.table {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
border: none;
|
||||||
|
padding: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody td {
|
||||||
|
padding: 15px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Profile Icon */
|
||||||
|
.profile-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user