SJP Admin

This commit is contained in:
Flatlogic Bot 2026-02-07 05:16:18 +00:00
parent c2dbe87056
commit 994bb04de0
34 changed files with 3231 additions and 49 deletions

View 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',
},
),
]

View File

@ -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',
},
),
]

View 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'),
),
]

View 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',
},
),
]

View File

@ -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')},
),
]

View 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),
),
]

View File

@ -1,3 +1,122 @@
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"

View File

@ -1,25 +1,172 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Sistem Jadwal Perkuliahan{% endblock %}</title>
<!-- Bootstrap 5 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Select2 -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<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 %}
<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 %}
</head>
<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>

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View File

@ -1,7 +1,54 @@
from django.urls import path
from .views import home
from . import views
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'),
]

View File

@ -1,25 +1,651 @@
import os
import platform
from django import get_version as django_version
from django.shortcuts import render
from django.utils import timezone
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
import csv
import datetime
import random
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, JsonResponse
from django.contrib import messages
from django.db import IntegrityError
from openpyxl import Workbook
from .models import (
ProgramStudi, Dosen, TahunAkademik, Hari,
MataKuliah, Ruangan, Kelas, Jam, DosenPengampu, Jadwal, DaftarHadir
)
def dashboard(request):
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
"count_prodi": ProgramStudi.objects.count(),
"count_dosen": Dosen.objects.count(),
"count_matkul": MataKuliah.objects.count(),
"count_ruangan": Ruangan.objects.count(),
"count_kelas": Kelas.objects.count(),
"count_jadwal": Jadwal.objects.count(),
"count_daftar_hadir": DaftarHadir.objects.count(),
}
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

View File

@ -1,3 +1,4 @@
Django==5.2.7
mysqlclient==2.2.7
python-dotenv==1.1.1
openpyxl==3.1.5

View File

@ -1,4 +1,152 @@
/* Custom styles for the application */
body {
font-family: system-ui, -apple-system, sans-serif;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@400;600;700&display=swap');
: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;
}