Pendahuluan
Dalam era digital saat ini, pengelolaan presensi dan aktivitas harian secara manual mulai ditinggalkan. Sekolah, instansi, maupun lembaga pelatihan kini banyak beralih ke sistem berbasis web untuk meningkatkan efisiensi, transparansi, dan akurasi data. Artikel ini akan membahas langkah demi langkah bagaimana cara membuat website sederhana untuk absensi dan aktivitas harian (daily activity) menggunakan PHP dan MySQL, yang di-hosting secara lokal menggunakan Laragon.
Tidak hanya untuk pengembang berpengalaman, tutorial ini ditujukan juga bagi pemula yang ingin belajar membuat sistem presensi digital dari nol.
Daftar Isi
- Apa Itu Laragon?
- Persiapan Alat dan Bahan
- Membuat Database dan Tabel MySQL
- Struktur Folder dan File Project
- Membuat Halaman Login
- Dashboard Siswa dan Pembimbing
- Fitur Input Presensi
- Fitur Input Aktivitas Harian
- Validasi Aktivitas oleh Pembimbing
- Menambahkan Fitur Logout
- Tampilan Desain dengan Bootstrap
- Tips Keamanan Sederhana
- Kesimpulan
1. Apa Itu Laragon?
Laragon adalah sebuah portable local server untuk Windows yang memudahkan Anda membuat dan mengelola aplikasi berbasis PHP, MySQL, Apache, dan lain-lain. Laragon sangat ringan, mudah diinstal, dan siap pakai.
Keunggulan Laragon:
- Instalasi mudah dan cepat.
- Mendukung PHP versi terbaru.
- Tersedia phpMyAdmin bawaan.
- Mendukung auto virtual host.
2. Persiapan Alat dan Bahan
Aplikasi yang dibutuhkan:
- Laragon
- Teks editor (VS Code / Sublime / Notepad++)
- Browser (Chrome/Firefox)
Langkah Instalasi Laragon:
- Unduh dan install Laragon dari situs resmi.
- Buka Laragon, klik “Start All”.
- Klik kanan >
www> “Open Folder” untuk membuka direktori root project Anda. - Klik “Menu > Quick App > Blank” untuk membuat folder project baru.
3. Membuat Database dan Tabel MySQL
Akses phpMyAdmin:
Buka browser dan kunjungi:
http://localhost/phpmyadmin
Buat Database Baru:
CREATE DATABASE absensi;
Buat Tabel users:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
role ENUM('siswa', 'pembimbing')
);
Tabel presensi:
CREATE TABLE presensi (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
tanggal DATE,
jam DATETIME,
status ENUM('hadir', 'tidak hadir'),
FOREIGN KEY (user_id) REFERENCES users(id)
);
Tabel aktivitas:
CREATE TABLE aktivitas (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
tanggal DATE,
aktivitas TEXT,
status_validasi ENUM('belum divalidasi', 'disetujui', 'ditolak'),
FOREIGN KEY (user_id) REFERENCES users(id)
);
4. Struktur Folder dan File Project
magang-edusoft/
├── config/
│ └── koneksi.php
├── auth/
│ ├── login.php
│ └── logout.php
├── siswa/
│ ├── dashboard.php
│ ├── presensi.php
│ └── aktivitas.php
├── pembimbing/
│ └── dashboard.php
├── proses/
│ └── proses_validasi.php
5. Membuat Halaman Login

File: login.php
php
<?php
session_start();
include '../config/koneksi.php';
$pesan = '';
if (isset($_POST['login'])) {
$username = $_POST['username'];
$password = $_POST['password'];
$query = mysqli_query($koneksi, "SELECT * FROM users WHERE username='$username'");
$data = mysqli_fetch_assoc($query);
if ($data && password_verify($password, $data['password'])) {
$_SESSION['user_id'] = $data['id'];
$_SESSION['role'] = $data['role'];
if ($data['role'] == 'siswa') {
header("Location: ../siswa/dashboard.php");
} else {
header("Location: ../pembimbing/dashboard.php");
}
exit;
} else {
$pesan = "Login gagal! Username atau password salah.";
}
}
?>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
position: relative;
}
.login-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 12px 12px 0 0;
}
h2 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 28px;
font-weight: 600;
}
.pesan-error {
background: #fee;
color: #c33;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #fcc;
font-size: 14px;
text-align: center;
}
form {
display: flex;
flex-direction: column;
gap: 20px;
}
input[type="text"],
input[type="password"],
input[name="username"],
input[name="password"] {
width: 100%;
padding: 15px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
transition: all 0.3s ease;
background: #fff;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[name="username"]:focus,
input[name="password"]:focus {
outline: none;
border-color: #667eea;
background: white;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
input[type="text"]::placeholder,
input[type="password"]::placeholder,
input[name="username"]::placeholder,
input[name="password"]::placeholder {
color: #999;
}
button {
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
button:active {
transform: translateY(0);
}
p {
text-align: center;
margin-top: 25px;
color: #666;
font-size: 14px;
}
p a {
color: #667eea;
text-decoration: none;
font-weight: 500;
transition: color 0.3s ease;
}
p a:hover {
color: #764ba2;
text-decoration: underline;
}
/* Password container for eye toggle */
.password-container {
position: relative;
width: 100%;
}
.password-container input {
width: 100%;
padding-right: 45px;
}
/* Eye toggle icon hover effect */
#toggleIcon {
transition: opacity 0.3s ease;
}
#toggleIcon:hover {
opacity: 0.7;
}
/* Responsive design */
@media (max-width: 480px) {
.login-container {
padding: 30px 20px;
margin: 10px;
}
h2 {
font-size: 24px;
}
input[type="text"],
input[type="password"],
input[name="username"],
input[name="password"] {
padding: 12px;
font-size: 14px;
}
button {
padding: 12px;
font-size: 14px;
}
}
/* Loading animation for button */
button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
/* Smooth animations */
.login-container {
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
<div class="login-container">
<h2>Login</h2>
<?php if ($pesan): ?>
<div class="pesan-error"><?= $pesan ?></div>
<?php endif; ?>
<form method="POST">
<input name="username" placeholder="Username" required>
<div style="position:relative;">
<input id="pw" name="password" type="password" placeholder="Password" required style="padding-right:38px;">
<span onclick="togglePw()" id="toggleIcon" style="position:absolute;top:50%;right:10px;transform:translateY(-50%);cursor:pointer;">
<!-- Mata terbuka (default) -->
<svg id="eyeOpen" xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 24 24"><path stroke="#888" stroke-width="2" d="M1.5 12S5.5 5.5 12 5.5 22.5 12 22.5 12 18.5 18.5 12 18.5 1.5 12 1.5 12Z"/><circle cx="12" cy="12" r="3.5" stroke="#888" stroke-width="2"/></svg>
<!-- Mata terpejam (hidden) -->
<svg id="eyeClosed" xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 24 24" style="display:none;"><path stroke="#888" stroke-width="2" d="M3 3l18 18M1.5 12S5.5 5.5 12 5.5c2.2 0 4.1.6 5.7 1.5M22.5 12S18.5 18.5 12 18.5c-2.2 0-4.1-.6-5.7-1.5"/><circle cx="12" cy="12" r="3.5" stroke="#888" stroke-width="2"/></svg>
</span>
</div>
<button name="login">Login</button>
</form>
<p>Belum punya akun? <a href="register.php">Daftar di sini</a></p>
</div>
<script>
function togglePw() {
const pw = document.getElementById('pw');
const eyeOpen = document.getElementById('eyeOpen');
const eyeClosed = document.getElementById('eyeClosed');
if (pw.type === "password") {
pw.type = "text";
eyeOpen.style.display = "none";
eyeClosed.style.display = "inline";
} else {
pw.type = "password";
eyeOpen.style.display = "inline";
eyeClosed.style.display = "none";
}
}
</script>
File: index.php
php
<?php
header("Location: auth/login.php");
exit;

File: Registrasi.php
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registrasi Akun</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.register-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
max-width: 420px;
width: 100%;
padding: 40px 35px;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.register-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
}
.register-container h2 {
text-align: center;
margin-bottom: 30px;
color: #2d3a4b;
font-size: 28px;
font-weight: 600;
position: relative;
}
.register-container h2::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 3px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 2px;
}
.form-group {
margin-bottom: 20px;
position: relative;
}
.register-container input,
.register-container select {
width: 100%;
padding: 15px 20px;
border: 2px solid #e1e5e9;
border-radius: 12px;
font-size: 16px;
background: #f8f9fa;
transition: all 0.3s ease;
color: #2d3a4b;
}
.register-container input:focus,
.register-container select:focus {
border-color: #667eea;
outline: none;
background: #fff;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
}
.register-container input::placeholder {
color: #9ca3af;
font-weight: 400;
}
.register-container select {
cursor: pointer;
}
.register-container select option {
padding: 10px;
background: #fff;
color: #2d3a4b;
}
.register-container button {
width: 100%;
padding: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 10px;
}
.register-container button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.register-container button:active {
transform: translateY(0);
}
.register-container p {
text-align: center;
margin-top: 25px;
font-size: 14px;
color: #6b7280;
}
.register-container a {
color: #667eea;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
}
.register-container a:hover {
color: #764ba2;
text-decoration: underline;
}
.success-message {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
padding: 15px 20px;
border-radius: 12px;
margin-bottom: 20px;
text-align: center;
font-weight: 500;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.2);
border-left: 4px solid #047857;
animation: slideInDown 0.5s ease-out;
}
.error-message {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
padding: 15px 20px;
border-radius: 12px;
margin-bottom: 20px;
text-align: center;
font-weight: 500;
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.2);
border-left: 4px solid #b91c1c;
animation: slideInDown 0.5s ease-out;
}
.info-message {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: white;
padding: 15px 20px;
border-radius: 12px;
margin-bottom: 20px;
text-align: center;
font-weight: 500;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.2);
border-left: 4px solid #1e40af;
animation: slideInDown 0.5s ease-out;
}
.notification {
position: relative;
display: flex;
align-items: center;
gap: 12px;
}
.notification::before {
content: '';
width: 20px;
height: 20px;
background-size: contain;
background-repeat: no-repeat;
flex-shrink: 0;
}
.success-message.notification::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'/%3E%3C/svg%3E");
}
.error-message.notification::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z'/%3E%3C/svg%3E");
}
.info-message.notification::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z'/%3E%3C/svg%3E");
}
.notification a {
color: white;
text-decoration: underline;
font-weight: 600;
transition: opacity 0.3s ease;
}
.notification a:hover {
opacity: 0.8;
}
@keyframes slideInDown {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Close button for notifications */
.notification-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 18px;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.3s ease;
}
.notification-close:hover {
background-color: rgba(255, 255, 255, 0.2);
}
/* Responsive Design */
@media (max-width: 480px) {
.register-container {
padding: 30px 25px;
margin: 10px;
}
.register-container h2 {
font-size: 24px;
}
}
/* Loading animation for button */
.loading {
position: relative;
color: transparent;
}
.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 2px solid #ffffff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Input icons */
.form-group {
position: relative;
}
.form-group::before {
content: '';
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
background-size: contain;
background-repeat: no-repeat;
z-index: 1;
opacity: 0.5;
}
.form-group.name::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z'/%3E%3C/svg%3E");
}
.form-group.username::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 7a2 2 0 012 2m0 0a2 2 0 012 2m-2-2a2 2 0 00-2 2m2-2V5a2 2 0 00-2-2H9a2 2 0 00-2 2v2m6 0H9m6 0a2 2 0 012 2m0 0a2 2 0 01-2 2H9a2 2 0 01-2-2m0 0a2 2 0 012-2h6a2 2 0 012 2z'/%3E%3C/svg%3E");
}
.form-group.password::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z'/%3E%3C/svg%3E");
}
/* Password toggle eye icon */
.password-toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
width: 22px;
height: 22px;
color: #9ca3af;
transition: color 0.3s ease;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
}
.password-toggle:hover {
color: #667eea;
}
.password-toggle svg {
width: 100%;
height: 100%;
stroke: currentColor;
}
/* Adjust padding for password input to make room for eye icon */
.form-group.password input {
padding-right: 55px;
}
.form-group.role::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z'/%3E%3C/svg%3E");
}
/* Custom Dropdown Styles */
.custom-dropdown {
position: relative;
width: 100%;
}
.dropdown-selected {
width: 100%;
padding: 15px 50px 15px 50px;
border: 2px solid #e1e5e9;
border-radius: 12px;
font-size: 16px;
background: #f8f9fa;
cursor: pointer;
color: #9ca3af;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.dropdown-selected.active {
border-color: #667eea;
background: #fff;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
color: #2d3a4b;
}
.dropdown-selected.has-value {
color: #2d3a4b;
}
.dropdown-arrow {
width: 20px;
height: 20px;
color: #9ca3af;
transition: transform 0.3s ease, color 0.3s ease;
}
.dropdown-selected.active .dropdown-arrow {
transform: rotate(180deg);
color: #667eea;
}
.dropdown-options {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border: 2px solid #667eea;
border-top: none;
border-radius: 0 0 12px 12px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
z-index: 1000;
max-height: 0;
overflow: hidden;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
}
.dropdown-options.show {
max-height: 200px;
opacity: 1;
transform: translateY(0);
}
.dropdown-option {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
color: #2d3a4b;
display: flex;
align-items: center;
gap: 12px;
}
.dropdown-option:hover {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.dropdown-option:first-child {
border-top: 1px solid #e1e5e9;
}
.dropdown-option:last-child {
border-radius: 0 0 10px 10px;
}
.dropdown-option-icon {
width: 20px;
height: 20px;
opacity: 0.7;
}
.dropdown-option:hover .dropdown-option-icon {
opacity: 1;
}
/* Hide original select */
.form-group.role select {
display: none;
}
.form-group input,
.form-group select {
padding-left: 50px;
}
/* Specific padding for password input */
.form-group.password input {
padding-left: 50px;
padding-right: 55px;
}
</style>
</head>
<body>
<div class="register-container">
<h2>Registrasi Akun</h2>
<!-- Example notifications - uncomment and use in your PHP -->
<!-- Success Message -->
<!-- <div class="success-message notification">
<span>Registrasi berhasil! Silakan <a href="login.php">login</a>.</span>
<button class="notification-close" onclick="this.parentElement.style.display='none'">×</button>
</div> -->
<!-- Error Message -->
<!-- <div class="error-message notification">
<span>Username sudah terdaftar!</span>
<button class="notification-close" onclick="this.parentElement.style.display='none'">×</button>
</div> -->
<!-- Info Message -->
<!-- <div class="info-message notification">
<span>Registrasi gagal!</span>
<button class="notification-close" onclick="this.parentElement.style.display='none'">×</button>
</div> -->
<form method="POST">
<div class="form-group name">
<input name="nama" placeholder="Nama Lengkap" required>
</div>
<div class="form-group username">
<input name="username" placeholder="Username" required>
</div>
<div class="form-group password">
<input name="password" type="password" placeholder="Password" required id="password">
<span class="password-toggle" onclick="togglePassword()">
<svg id="eyeIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</span>
</div>
<div class="form-group role">
<select name="role" required id="roleSelect">
<option value="">Pilih Role</option>
<option value="siswa">Siswa</option>
<option value="pembimbing">Pembimbing</option>
</select>
<div class="custom-dropdown">
<div class="dropdown-selected" onclick="toggleDropdown()">
<span id="selectedRole">Pilih Role</span>
<svg class="dropdown-arrow" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<div class="dropdown-options" id="dropdownOptions">
<div class="dropdown-option" onclick="selectRole('siswa')">
<svg class="dropdown-option-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
<span>Siswa</span>
</div>
<div class="dropdown-option" onclick="selectRole('pembimbing')">
<svg class="dropdown-option-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<span>Pembimbing</span>
</div>
</div>
</div>
</div>
<button name="register" type="submit">Register</button>
</form>
<p>Sudah punya akun? <a href="login.php">Login di sini</a></p>
</div>
<script>
// Custom dropdown functionality
function toggleDropdown() {
const dropdownOptions = document.getElementById('dropdownOptions');
const selectedElement = document.querySelector('.dropdown-selected');
dropdownOptions.classList.toggle('show');
selectedElement.classList.toggle('active');
}
function selectRole(value) {
const selectedRole = document.getElementById('selectedRole');
const roleSelect = document.getElementById('roleSelect');
const selectedElement = document.querySelector('.dropdown-selected');
// Update display text
selectedRole.textContent = value === 'siswa' ? 'Siswa' : 'Pembimbing';
// Update hidden select value
roleSelect.value = value;
// Add has-value class for styling
selectedElement.classList.add('has-value');
// Close dropdown
document.getElementById('dropdownOptions').classList.remove('show');
selectedElement.classList.remove('active');
}
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.custom-dropdown')) {
document.getElementById('dropdownOptions').classList.remove('show');
document.querySelector('.dropdown-selected').classList.remove('active');
}
});
// Password toggle functionality
function togglePassword() {
const passwordInput = document.getElementById('password');
const eyeIcon = document.getElementById('eyeIcon');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
eyeIcon.innerHTML = `
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
`;
} else {
passwordInput.type = 'password';
eyeIcon.innerHTML = `
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
`;
}
}
// Add loading animation on form submit
document.querySelector('form').addEventListener('submit', function(e) {
const button = document.querySelector('button[name="register"]');
button.classList.add('loading');
button.disabled = true;
});
// Add smooth focus transitions
document.querySelectorAll('input, select').forEach(element => {
element.addEventListener('focus', function() {
this.parentElement.style.transform = 'scale(1.02)';
});
element.addEventListener('blur', function() {
this.parentElement.style.transform = 'scale(1)';
});
});
</script>
</body>
</html>
6. Dashboard Siswa dan Pembimbing
siswa/dashboard.php

php
<?php
session_start();
include '../config/koneksi.php';
if (!isset($_SESSION['user_id']) || $_SESSION['role'] != 'siswa') {
header("Location: ../auth/login.php");
exit;
}
$user_id = $_SESSION['user_id'];
$tanggal_hari_ini = date("Y-m-d");
// Ambil presensi hari ini
$q_presensi = mysqli_query($koneksi, "SELECT * FROM presensi WHERE user_id='$user_id' AND tanggal='$tanggal_hari_ini'");
$presensi = mysqli_fetch_assoc($q_presensi);
// Ambil aktivitas hari ini
$q_aktivitas = mysqli_query($koneksi, "SELECT * FROM aktivitas WHERE user_id='$user_id' AND tanggal='$tanggal_hari_ini'");
$aktivitas = mysqli_fetch_assoc($q_aktivitas);
// Data untuk profile
$q_user = mysqli_query($koneksi, "SELECT * FROM users WHERE id='$user_id'");
$user = mysqli_fetch_assoc($q_user);
// Proses update profile
if (isset($_POST['update_profile'])) {
$new_nama = mysqli_real_escape_string($koneksi, $_POST['edit_nama']);
$new_username = mysqli_real_escape_string($koneksi, $_POST['edit_username']);
$new_kelas = mysqli_real_escape_string($koneksi, $_POST['edit_kelas']);
$new_jurusan = mysqli_real_escape_string($koneksi, $_POST['edit_jurusan']);
// Validasi input
if (empty($new_nama) || empty($new_username) || empty($new_kelas) || empty($new_jurusan)) {
$error_message = "Semua field harus diisi!";
} else {
// Cek apakah username sudah digunakan user lain
$check_username = mysqli_query($koneksi, "SELECT id FROM users WHERE username='$new_username' AND id != '$user_id'");
if (mysqli_num_rows($check_username) > 0) {
$error_message = "Username sudah digunakan oleh user lain!";
} else {
// Update data
$update = mysqli_query($koneksi, "UPDATE users SET nama='$new_nama', username='$new_username', kelas='$new_kelas', jurusan='$new_jurusan' WHERE id='$user_id'");
if ($update) {
$_SESSION['username'] = $new_username;
$success_message = "Profil berhasil diupdate!";
// Refresh data user
$q_user = mysqli_query($koneksi, "SELECT * FROM users WHERE id='$user_id'");
$user = mysqli_fetch_assoc($q_user);
} else {
$error_message = "Gagal update profil! " . mysqli_error($koneksi);
}
}
}
}
// Cek kelengkapan data profil
$notif_incomplete_profile = false;
if (
empty($user['nama']) ||
empty($user['username']) ||
empty($user['kelas']) ||
empty($user['jurusan'])
) {
$notif_incomplete_profile = true;
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Siswa</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #e0e7ff 0%, #f4f6fb 100%);
min-height: 100vh;
}
.container { display: flex; min-height: 100vh; }
.sidebar {
width: 280px;
background: linear-gradient(180deg, #4f8cff 0%, #3b82f6 100%);
color: white;
padding: 0;
box-shadow: 4px 0 15px rgba(0, 0, 0, 0.1);
position: fixed;
height: 100vh;
overflow-y: auto;
transition: transform 0.3s ease;
}
.sidebar-header {
padding: 30px 25px 25px 25px;
background: rgba(255, 255, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.sidebar-header h2 { font-size: 1.4rem; margin-bottom: 8px; color: white; }
.sidebar-header p { font-size: 0.9rem; opacity: 0.8; color: white; }
.sidebar-menu { padding: 20px 0; }
.menu-item {
display: flex;
align-items: center;
padding: 15px 25px;
color: white;
text-decoration: none;
transition: all 0.3s ease;
border-left: 4px solid transparent;
cursor: pointer;
}
.menu-item:hover {
background: rgba(255, 255, 255, 0.1);
border-left-color: white;
}
.menu-item.active {
background: rgba(255, 255, 255, 0.15);
border-left-color: white;
font-weight: 600;
}
.menu-item .icon { margin-right: 15px; font-size: 1.2rem; width: 24px; text-align: center; }
.logout-item {
position: absolute;
bottom: 20px;
width: 100%;
padding: 15px 25px;
background: rgba(239, 68, 68, 0.2);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.logout-item:hover { background: rgba(239, 68, 68, 0.3); }
.main-content {
flex: 1;
margin-left: 280px;
padding: 30px;
transition: margin-left 0.3s ease;
}
.content-header {
background: white;
padding: 25px 30px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 25px;
}
.content-header h1 { color: #2d3a4b; font-size: 2rem; margin-bottom: 8px; }
.content-header p { color: #64748b; font-size: 1rem; }
.content-section { display: none; animation: fadeIn 0.5s ease; }
.content-section.active { display: block; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(79, 140, 255, 0.1);
}
.card h3 { color: #2d3a4b; margin-bottom: 15px; font-size: 1.3rem; display: flex; align-items: center; }
.card h3 .icon { margin-right: 10px; color: #4f8cff; }
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f1f5f9;
}
.info-item:last-child { border-bottom: none; }
.info-label { color: #64748b; font-weight: 500; }
.info-value { color: #2d3a4b; font-weight: 600; }
.status-badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-valid { background: #dcfce7; color: #166534; }
.status-invalid { background: #fee2e2; color: #dc2626; }
.status-pending { background: #fef3c7; color: #d97706; }
.btn {
display: inline-block;
padding: 12px 24px;
background: #4f8cff;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
border: none;
cursor: pointer;
margin-right: 10px;
}
.btn:hover {
background: #3b82f6;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(79, 140, 255, 0.3);
}
.btn-outline {
background: transparent;
color: #4f8cff;
border: 2px solid #4f8cff;
}
.btn-outline:hover {
background: #4f8cff;
color: white;
}
.btn-success {
background: #22c55e;
}
.btn-success:hover {
background: #16a34a;
}
.btn-warning {
background: #f59e0b;
}
.btn-warning:hover {
background: #d97706;
}
.btn-danger {
background: #ef4444;
}
.btn-danger:hover {
background: #dc2626;
}
.form-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
padding: 30px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2d3a4b;
}
.form-group input, .form-group textarea {
width: 100%;
padding: 12px;
border: 2px solid #e2e8f0;
border-radius: 8px;
background: white;
color: #2d3a4b;
font-size: 1rem;
font-family: inherit;
}
.form-group input:focus, .form-group textarea:focus {
outline: none;
border-color: #4f8cff;
box-shadow: 0 0 0 3px rgba(79, 140, 255, 0.1);
}
.form-group input::placeholder, .form-group textarea::placeholder {
color: #94a3b8;
}
.alert {
padding: 15px;
margin-bottom: 20px;
border-radius: 8px;
font-weight: 500;
}
.alert-success {
background: #dcfce7;
color: #166534;
border: 1px solid #bbf7d0;
}
.alert-error {
background: #fee2e2;
color: #dc2626;
border: 1px solid #fecaca;
}
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); z-index: 1000; }
.sidebar.active { transform: translateX(0); }
.main-content { margin-left: 0; padding: 20px; }
.mobile-toggle {
display: block;
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
background: #4f8cff;
color: white;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
}
}
.mobile-toggle { display: none; }
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.overlay.active { display: block; }
.floating-alert {
position: fixed;
top: 20px;
right: 20px;
background:rgb(245, 85, 11);
color: white;
padding: 8px 18px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 1000;
display: flex;
align-items: center;
gap: 10px;
font-weight: 500;
animation: slideIn 0.5s ease-out, fadeOut 0.5s ease-in 4.5s forwards;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
</style>
</head>
<body>
<button class="mobile-toggle" onclick="toggleSidebar()">☰</button>
<div class="overlay" onclick="toggleSidebar()"></div>
<div class="container">
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<h2>📚 Portal Siswa</h2>
<p>Selamat datang, <?= htmlspecialchars($user['nama'] ?? 'Siswa'); ?>!</p>
</div>
<div class="sidebar-menu">
<a href="#" class="menu-item active" onclick="showSection('dashboard', event)">
<span class="icon">🏠</span>
Dashboard
</a>
<a href="#" class="menu-item" onclick="showSection('presensi', event)">
<span class="icon">📝</span>
Presensi
</a>
<a href="#" class="menu-item" onclick="showSection('aktivitas', event)">
<span class="icon">📋</span>
Aktivitas
</a>
<a href="#" class="menu-item" onclick="showSection('riwayat', event)">
<span class="icon">📊</span>
Riwayat
</a>
<a href="#" class="menu-item" onclick="showSection('profile', event)">
<span class="icon">👤</span>
Profile
</a>
</div>
<div class="logout-item">
<a href="#" class="menu-item" onclick="logout()">
<span class="icon">🚪</span>
Logout
</a>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Dashboard Section -->
<div id="dashboard" class="content-section active">
<div class="content-header">
<h1>Dashboard</h1>
<p>Ringkasan aktivitas hari ini - <span id="currentDate"></span></p>
</div>
<div class="card">
<h3><span class="icon">📅</span>Presensi Hari Ini</h3>
<div class="info-item">
<span class="info-label">Jam Masuk:</span>
<span class="info-value" id="jam-masuk"><?= $presensi && $presensi['jam_masuk'] ? htmlspecialchars($presensi['jam_masuk']) : '-' ?></span>
</div>
<div class="info-item">
<span class="info-label">Jam Keluar:</span>
<span class="info-value" id="jam-keluar"><?= $presensi && $presensi['jam_keluar'] ? htmlspecialchars($presensi['jam_keluar']) : '-' ?></span>
</div>
<div class="info-item">
<span class="info-label">Status:</span>
<?php if ($presensi && $presensi['jam_masuk']): ?>
<span class="status-badge status-valid">Hadir</span>
<?php else: ?>
<span class="status-badge status-invalid">Belum Hadir</span>
<?php endif; ?>
</div>
</div>
<div class="card">
<h3><span class="icon">📋</span>Aktivitas Hari Ini</h3>
<div class="info-item">
<span class="info-label">Deskripsi:</span>
<span class="info-value"><?= $aktivitas ? htmlspecialchars($aktivitas['deskripsi']) : '-' ?></span>
</div>
<div class="info-item">
<span class="info-label">Status Validasi:</span>
<?php if ($aktivitas): ?>
<?php if ($aktivitas['status_validasi'] == 'Valid'): ?>
<span class="status-badge status-valid">Valid</span>
<?php elseif ($aktivitas['status_validasi'] == 'pending'): ?>
<span class="status-badge status-pending">Menunggu</span>
<?php else: ?>
<span class="status-badge status-invalid"><?= htmlspecialchars($aktivitas['status_validasi']) ?></span>
<?php endif; ?>
<?php else: ?>
<span class="status-badge status-invalid">Belum Ada</span>
<?php endif; ?>
</div>
</div>
</div>
<!-- Presensi Section -->
<div id="presensi" class="content-section">
<div class="content-header">
<h1>Presensi</h1>
<p>Kelola presensi harian Anda</p>
</div>
<div class="form-container">
<h3 style="margin-bottom: 20px; color: white;">🌅 Presensi Masuk</h3>
<form method="POST" action="presensi.php">
<div class="form-group">
<label for="jam_masuk">Jam Masuk:</label>
<input type="time" id="jam_masuk" name="jam_masuk" required>
</div>
<button type="submit" name="masuk" class="btn btn-success">Absen Masuk</button>
</form>
</div>
<div class="form-container" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
<h3 style="margin-bottom: 20px; color: white;">🌇 Presensi Keluar</h3>
<form method="POST" action="presensi.php">
<div class="form-group">
<label for="jam_keluar">Jam Keluar:</label>
<input type="time" id="jam_keluar" name="jam_keluar" required>
</div>
<button type="submit" name="keluar" class="btn btn-warning">Absen Keluar</button>
</form>
</div>
</div>
<!-- Aktivitas Section -->
<div id="aktivitas" class="content-section">
<div class="content-header">
<h1>Aktivitas</h1>
<p>Catat aktivitas pembelajaran Anda</p>
</div>
<div class="card">
<h3><span class="icon">📝</span>Input Aktivitas</h3>
<form method="POST" action="aktivitas.php">
<div class="form-group">
<label for="deskripsi">Deskripsi Aktivitas:</label>
<textarea id="deskripsi" name="deskripsi" rows="4" placeholder="Masukkan deskripsi aktivitas hari ini..." required></textarea>
</div>
<button type="submit" class="btn">Simpan Aktivitas</button>
</form>
</div>
</div>
<!-- Riwayat Section -->
<div id="riwayat" class="content-section">
<div class="content-header">
<h1>Riwayat</h1>
<p>Lihat riwayat presensi dan aktivitas</p>
</div>
<div class="card">
<h3><span class="icon">📊</span>Riwayat Presensi</h3>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f8fafc; border-bottom: 2px solid #e2e8f0;">
<th style="padding: 12px; text-align: left;">Tanggal</th>
<th style="padding: 12px; text-align: left;">Jam Masuk</th>
<th style="padding: 12px; text-align: left;">Jam Keluar</th>
<th style="padding: 12px; text-align: left;">Status</th>
</tr>
</thead>
<tbody>
<?php
// Ambil riwayat presensi
$q_riwayat = mysqli_query($koneksi, "SELECT * FROM presensi WHERE user_id='$user_id' ORDER BY tanggal DESC LIMIT 10");
while ($row = mysqli_fetch_assoc($q_riwayat)) :
?>
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 12px;"><?= htmlspecialchars($row['tanggal']) ?></td>
<td style="padding: 12px;"><?= htmlspecialchars($row['jam_masuk']) ?></td>
<td style="padding: 12px;"><?= htmlspecialchars($row['jam_keluar']) ?></td>
<td style="padding: 12px;">
<?php if ($row['jam_masuk']): ?>
<span class="status-badge status-valid">Hadir</span>
<?php else: ?>
<span class="status-badge status-invalid">Tidak Hadir</span>
<?php endif; ?>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
<!-- Profile Section -->
<div id="profile" class="content-section">
<div class="content-header">
<h1>Profile</h1>
<p>Kelola informasi profile Anda</p>
</div>
<?php if (isset($success_message)): ?>
<div class="alert alert-success">
✅ <?= htmlspecialchars($success_message) ?>
</div>
<?php endif; ?>
<?php if (isset($error_message)): ?>
<div class="alert alert-error">
❌ <?= htmlspecialchars($error_message) ?>
</div>
<?php endif; ?>
<div class="card">
<h3><span class="icon">👤</span>Informasi Profile</h3>
<!-- Profile View -->
<div id="profileView">
<div class="info-item">
<span class="info-label">Nama:</span>
<span class="info-value"><?= htmlspecialchars($user['nama'] ?? '-') ?></span>
</div>
<div class="info-item">
<span class="info-label">Username:</span>
<span class="info-value"><?= htmlspecialchars($user['username'] ?? '-') ?></span>
</div>
<div class="info-item">
<span class="info-label">Role:</span>
<span class="info-value"><?= htmlspecialchars($user['role'] ?? '-') ?></span>
</div>
<div class="info-item">
<span class="info-label">Kelas:</span>
<span class="info-value"><?= htmlspecialchars($user['kelas'] ?? '-') ?></span>
</div>
<div class="info-item">
<span class="info-label">Jurusan:</span>
<span class="info-value"><?= htmlspecialchars($user['jurusan'] ?? '-') ?></span>
</div>
<div style="margin-top: 20px;">
<button type="button" class="btn btn-success" onclick="editProfile()">✏️ Edit Profil</button>
</div>
</div>
<!-- Profile Edit Form -->
<div id="formEditProfile" style="display: none;">
<form method="POST" action="">
<div class="form-group">
<label for="edit_nama">Nama Lengkap:</label>
<input type="text" id="edit_nama" name="edit_nama" value="<?= htmlspecialchars($user['nama'] ?? '') ?>" required placeholder="Masukkan nama lengkap">
</div>
<div class="form-group">
<label for="edit_username">Username:</label>
<input type="text" id="edit_username" name="edit_username" value="<?= htmlspecialchars($user['username'] ?? '') ?>" required placeholder="Masukkan username">
</div>
<div class="form-group">
<label for="edit_kelas">Kelas:</label>
<input type="text" id="edit_kelas" name="edit_kelas" value="<?= htmlspecialchars($user['kelas'] ?? '') ?>" required placeholder="Contoh: XII IPA 1">
</div>
<div class="form-group">
<label for="edit_jurusan">Jurusan:</label>
<input type="text" id="edit_jurusan" name="edit_jurusan" value="<?= htmlspecialchars($user['jurusan'] ?? '') ?>" required placeholder="Contoh: IPA, IPS, Multimedia">
</div>
<div style="margin-top: 20px;">
<button type="submit" name="update_profile" class="btn btn-success">💾 Simpan Perubahan</button>
<button type="button" class="btn btn-outline" onclick="batalEdit()">❌ Batal</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<?php if ($notif_incomplete_profile): ?>
<div id="notif-profile-incomplete" class="floating-alert">
⚠️ Data profil Anda belum lengkap. Silakan lengkapi data diri Anda di menu <b>Profile</b>!
</div>
<?php endif; ?>
<script>
function showSection(sectionId, event) {
// Hide all sections
const sections = document.querySelectorAll('.content-section');
sections.forEach(section => {
section.classList.remove('active');
});
// Show selected section
document.getElementById(sectionId).classList.add('active');
// Update active menu item
const menuItems = document.querySelectorAll('.menu-item');
menuItems.forEach(item => item.classList.remove('active'));
if(event) event.target.classList.add('active');
// Close sidebar on mobile
if (window.innerWidth <= 768) {
toggleSidebar();
}
}
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.querySelector('.overlay');
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
}
function logout() {
if (confirm('Apakah Anda yakin ingin logout?')) {
window.location.href = '../auth/logout.php';
}
}
// Update tanggal hari ini
function updateCurrentDate() {
const now = new Date();
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
document.getElementById('currentDate').textContent = now.toLocaleDateString('id-ID', options);
}
updateCurrentDate();
function editProfile() {
document.getElementById('profileView').style.display = 'none';
document.getElementById('formEditProfile').style.display = 'block';
}
function batalEdit() {
document.getElementById('formEditProfile').style.display = 'none';
document.getElementById('profileView').style.display = 'block';
}
// Auto hide alert after 5 seconds
document.addEventListener('DOMContentLoaded', function() {
const alerts = document.querySelectorAll('.alert');
alerts.forEach(alert => {
setTimeout(() => {
alert.style.opacity = '0';
setTimeout(() => alert.remove(), 300);
}, 5000);
});
});
</script>
</body>
</html>
pembimbing/dashboard.php

php
<?php
session_start();
include '../config/koneksi.php';
// Cegah akses langsung jika bukan pembimbing
if (!isset($_SESSION['user_id']) || $_SESSION['role'] != 'pembimbing') {
header("Location: ../auth/login.php");
exit;
}
// Ambil semua data aktivitas siswa (JOIN dengan nama user)
$query = mysqli_query($koneksi, "
SELECT aktivitas.*, users.nama
FROM aktivitas
JOIN users ON aktivitas.user_id = users.id
ORDER BY tanggal DESC
");
// Ambil data presensi siswa untuk rekap (contoh, bisa dikembangkan)
$query_presensi = mysqli_query($koneksi, "
SELECT users.nama, COUNT(presensi.id) as hadir
FROM users
LEFT JOIN presensi ON users.id = presensi.user_id
WHERE users.role = 'siswa'
GROUP BY users.id
");
// Ambil data siswa
$query_siswa = mysqli_query($koneksi, "SELECT * FROM users WHERE role='siswa' ORDER BY nama ASC");
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Pembimbing</title>
<style>
/* ...CSS dari prompt, tidak diubah... */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #e0e7ff 0%, #f4f6fb 100%);
min-height: 100vh;
}
.container { display: flex; min-height: 100vh; }
.sidebar {
width: 280px;
background: linear-gradient(180deg, #4f8cff 0%, #3b82f6 100%);
color: white;
padding: 0;
box-shadow: 4px 0 15px rgba(0, 0, 0, 0.1);
position: fixed;
height: 100vh;
overflow-y: auto;
transition: transform 0.3s ease;
}
.sidebar-header {
padding: 30px 25px 25px 25px;
background: rgba(255, 255, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.sidebar-header h2 { font-size: 1.4rem; margin-bottom: 8px; color: white; }
.sidebar-header p { font-size: 0.9rem; opacity: 0.8; color: white; }
.sidebar-menu { padding: 20px 0; }
.menu-item {
display: flex;
align-items: center;
padding: 15px 25px;
color: white;
text-decoration: none;
transition: all 0.3s ease;
border-left: 4px solid transparent;
cursor: pointer;
}
.menu-item:hover {
background: rgba(255, 255, 255, 0.1);
border-left-color: white;
}
.menu-item.active {
background: rgba(255, 255, 255, 0.15);
border-left-color: white;
font-weight: 600;
}
.menu-item .icon { margin-right: 15px; font-size: 1.2rem; width: 24px; text-align: center; }
.logout-item {
position: absolute;
bottom: 20px;
width: 100%;
padding: 15px 25px;
background: rgba(239, 68, 68, 0.2);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.logout-item:hover { background: rgba(239, 68, 68, 0.3); }
.main-content {
flex: 1;
margin-left: 280px;
padding: 30px;
transition: margin-left 0.3s ease;
}
.content-header {
background: white;
padding: 25px 30px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 25px;
}
.content-header h1 { color: #2d3a4b; font-size: 2rem; margin-bottom: 8px; }
.content-header p { color: #64748b; font-size: 1rem; }
.content-section { display: none; animation: fadeIn 0.5s ease; }
.content-section.active { display: block; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(79, 140, 255, 0.1);
}
/* Jika ingin margin pada card aktivitas terbaru */
#dashboard .card {
margin-top: 32px;
}
.card h3 { color: #2d3a4b; margin-bottom: 20px; font-size: 1.3rem; display: flex; align-items: center; }
.card h3 .icon { margin-right: 10px; color: #4f8cff; }
.table-container {
background: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.table-header {
background: linear-gradient(135deg, #4f8cff 0%, #3b82f6 100%);
color: white;
padding: 20px 25px;
display: flex;
justify-content: space-between;
align-items: center;
}
.table-header h3 { margin: 0; font-size: 1.2rem; }
.search-box {
padding: 8px 12px;
border: none;
border-radius: 6px;
background: rgba(255, 255, 255, 0.2);
color: white;
}
.search-box::placeholder { color: rgba(255, 255, 255, 0.7); }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 15px; text-align: left; border-bottom: 1px solid #f1f5f9; }
th { background: #f8fafc; font-weight: 600; color: #2d3a4b; }
td { color: #64748b; }
tr:hover { background: #f8fafc; }
.status-badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
display: inline-block;
}
.status-pending { background: #fef3c7; color: #d97706; }
.status-approved { background: #dcfce7; color: #166534; }
.status-rejected { background: #fee2e2; color: #dc2626; }
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-approve { background: #22c55e; color: white; }
.btn-approve:hover { background: #16a34a; transform: translateY(-2px); }
.btn-reject { background: #ef4444; color: white; margin-left: 5px; }
.btn-reject:hover { background: #dc2626; transform: translateY(-2px); }
.approved-text { color: #22c55e; font-weight: 600; }
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); z-index: 1000; }
.sidebar.active { transform: translateX(0); }
.main-content { margin-left: 0; padding: 20px; }
.mobile-toggle {
display: block;
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
background: #4f8cff;
color: white;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
}
.table-container { overflow-x: auto; }
}
.mobile-toggle { display: none; }
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.overlay.active { display: block; }
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32px;
margin-top: 32px;
}
.stat-card {
background: linear-gradient(135deg, #7f9cf5 0%, #a78bfa 100%);
border-radius: 20px;
padding: 36px 0 28px 0;
box-shadow: 0 8px 32px rgba(79, 140, 255, 0.10);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 170px;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-6px) scale(1.03);
box-shadow: 0 12px 36px rgba(79, 140, 255, 0.18);
}
.stat-card .icon {
font-size: 2.8rem;
margin-bottom: 18px;
display: block;
}
.stat-card .number {
font-size: 2.6rem;
font-weight: 800;
color: #fff;
margin-bottom: 8px;
line-height: 1;
text-shadow: 0 2px 8px rgba(79, 140, 255, 0.10);
}
.stat-card .label {
font-size: 1.1rem;
color: #f3f4f6;
font-weight: 500;
letter-spacing: 0.5px;
margin-top: 2px;
text-align: center;
}
@media (max-width: 900px) {
.stats-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.stat-card {
min-height: 120px;
padding: 28px 0 20px 0;
}
}
/* Notifikasi aktivitas terbaru */
.activity-notif {
padding: 14px 18px;
border-left: 5px solid #22c55e;
background: #f0fdf4;
margin-bottom: 14px;
border-radius: 8px;
font-size: 1rem;
font-weight: 400;
}
.activity-notif.pending {
border-left-color: #f59e0b;
background: #fffbeb;
}
.activity-notif.valid {
border-left-color: #22c55e;
background: #f0fdf4;
}
.activity-notif.presensi {
border-left-color: #3b82f6;
background: #eff6ff;
}
.activity-notif strong {
font-weight: 700;
}
.activity-notif .time {
font-size: 0.9rem;
color: #64748b;
margin-top: 2px;
display: block;
}
</style>
</head>
<body>
<button class="mobile-toggle" onclick="toggleSidebar()">☰</button>
<div class="overlay" onclick="toggleSidebar()"></div>
<div class="container">
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<h2>👨🏫 Portal Pembimbing</h2>
<p>Selamat datang, Pembimbing!</p>
</div>
<div class="sidebar-menu">
<a href="#" class="menu-item active" onclick="showSection('dashboard', event)">
<span class="icon">🏠</span>
Dashboard
</a>
<a href="#" class="menu-item" onclick="showSection('validasi', event)">
<span class="icon">✅</span>
Validasi Aktivitas
</a>
<a href="#" class="menu-item" onclick="showSection('rekap', event)">
<span class="icon">📊</span>
Rekap Presensi
</a>
<a href="#" class="menu-item" onclick="showSection('siswa', event)">
<span class="icon">👥</span>
Data Siswa
</a>
</div>
<div class="logout-item">
<a href="#" class="menu-item" onclick="logout()">
<span class="icon">🚪</span>
Logout
</a>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Dashboard Section -->
<div id="dashboard" class="content-section active">
<div class="content-header">
<h1>Dashboard</h1>
<p>Ringkasan kegiatan siswa - <span id="currentDate"></span></p>
</div>
<!-- Stats Grid: Pindahkan ke atas -->
<div class="stats-grid">
<div class="stat-card">
<div class="icon">👨🎓</div>
<div class="number">
<?php
// Total siswa
$q_total_siswa = mysqli_query($koneksi, "SELECT COUNT(*) as total FROM users WHERE role='siswa'");
$total_siswa = mysqli_fetch_assoc($q_total_siswa)['total'] ?? 0;
echo $total_siswa;
?>
</div>
<div class="label">Total Siswa</div>
</div>
<div class="stat-card">
<div class="icon">⏳</div>
<div class="number">
<?php
// Menunggu validasi
$q_pending = mysqli_query($koneksi, "SELECT COUNT(*) as total FROM aktivitas WHERE status_validasi='pending'");
$pending = mysqli_fetch_assoc($q_pending)['total'] ?? 0;
echo $pending;
?>
</div>
<div class="label">Menunggu Validasi</div>
</div>
<div class="stat-card">
<div class="icon">✅</div>
<div class="number">
<?php
// Sudah divalidasi khusus siswa
$q_valid = mysqli_query($koneksi, "
SELECT COUNT(*) as total
FROM aktivitas
JOIN users ON aktivitas.user_id = users.id
WHERE aktivitas.status_validasi='disetujui' AND users.role='siswa'
");
$valid = mysqli_fetch_assoc($q_valid)['total'] ?? 0;
echo $valid;
?>
</div>
<div class="label">Sudah Divalidasi</div>
</div>
<div class="stat-card">
<div class="icon">📈</div>
<div class="number">
<?php
// Tingkat kehadiran hari ini
$today = date('Y-m-d');
$q_hadir = mysqli_query($koneksi, "SELECT COUNT(DISTINCT user_id) as hadir FROM presensi WHERE tanggal='$today'");
$hadir = mysqli_fetch_assoc($q_hadir)['hadir'] ?? 0;
$persen = $total_siswa > 0 ? round(($hadir / $total_siswa) * 100) : 0;
echo $persen . '%';
?>
</div>
<div class="label">Tingkat Kehadiran</div>
</div>
</div>
<!-- END Stats Grid -->
<div class="card">
<h3><span class="icon">🔔</span>Aktivitas Terbaru</h3>
<div>
<?php
$q_recent = mysqli_query($koneksi, "
SELECT aktivitas.*, users.nama
FROM aktivitas
JOIN users ON aktivitas.user_id = users.id
ORDER BY aktivitas.tanggal DESC, aktivitas.id DESC
LIMIT 3
");
while ($recent = mysqli_fetch_assoc($q_recent)) :
// Tentukan kelas warna berdasarkan status
$class = 'activity-notif ';
if ($recent['status_validasi'] == 'pending') {
$class .= 'pending';
$status_text = 'Menunggu validasi aktivitas';
} elseif ($recent['status_validasi'] == 'disetujui') {
$class .= 'valid';
$status_text = 'Aktivitas telah divalidasi';
} else {
$class .= 'presensi';
$status_text = 'Presensi masuk tercatat';
}
// Hitung waktu relatif (opsional, sederhana)
$waktu = strtotime($recent['tanggal']);
$now = strtotime(date('Y-m-d'));
$selisih = ($now - $waktu) / 60; // menit
$time_ago = $recent['tanggal'];
?>
<div class="<?= $class ?>">
<strong><?= htmlspecialchars($recent['nama']) ?></strong> -
<?= htmlspecialchars($status_text) ?>
<span class="time"><?= htmlspecialchars($recent['tanggal']) ?></span>
</div>
<?php endwhile; ?>
</div>
</div>
</div>
<!-- Validasi Section -->
<div id="validasi" class="content-section">
<div class="content-header">
<h1>Validasi Aktivitas</h1>
<p>Kelola dan validasi aktivitas siswa</p>
</div>
<div class="table-container">
<div class="table-header">
<h3>Daftar Aktivitas Siswa</h3>
<input type="text" class="search-box" placeholder="Cari nama siswa..." onkeyup="searchTable()">
</div>
<table id="activitiesTable">
<thead>
<tr>
<th>Nama Siswa</th>
<th>Tanggal</th>
<th>Aktivitas</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php while ($data = mysqli_fetch_assoc($query)) : ?>
<tr data-status="<?= $data['status_validasi'] ?>">
<td><?= htmlspecialchars($data['nama']) ?></td>
<td><?= htmlspecialchars($data['tanggal']) ?></td>
<td><?= nl2br(htmlspecialchars($data['deskripsi'])) ?></td>
<td>
<?php if ($data['status_validasi'] == 'pending') : ?>
<span class="status-badge status-pending">Menunggu</span>
<?php elseif ($data['status_validasi'] == 'Valid') : ?>
<span class="status-badge status-approved">Disetujui</span>
<?php else : ?>
<span class="status-badge status-rejected"><?= htmlspecialchars($data['status_validasi']) ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($data['status_validasi'] == 'pending') : ?>
<form method="POST" action="../proses/proses_validasi.php" style="display:inline;">
<input type="hidden" name="aktivitas_id" value="<?= $data['id']; ?>">
<button type="submit" name="setujui" class="btn btn-approve">Setujui</button>
</form>
<?php else : ?>
<span class="approved-text">✅ Sudah Disetujui</span>
<?php endif; ?>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
<!-- Rekap Section -->
<div id="rekap" class="content-section">
<div class="content-header">
<h1>Rekap Presensi</h1>
<p>Rekap kehadiran siswa per periode</p>
</div>
<div class="card">
<h3><span class="icon">📊</span>Rekap Presensi Bulanan</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Nama Siswa</th>
<th>Hadir</th>
</tr>
</thead>
<tbody>
<?php while ($row = mysqli_fetch_assoc($query_presensi)) : ?>
<tr>
<td><?= htmlspecialchars($row['nama']) ?></td>
<td><?= htmlspecialchars($row['hadir']) ?></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
<div class="card">
<h3><span class="icon">📅</span>Rekap Presensi Detail</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Nama Siswa</th>
<th>Tanggal</th>
<th>Jam Masuk</th>
<th>Jam Keluar</th>
<th>Total Jam Kerja</th>
</tr>
</thead>
<tbody>
<?php
// Ambil data presensi detail per siswa
$q_rekap = mysqli_query($koneksi, "
SELECT users.nama, presensi.tanggal, presensi.jam_masuk, presensi.jam_keluar
FROM users
LEFT JOIN presensi ON users.id = presensi.user_id
WHERE users.role = 'siswa'
ORDER BY presensi.tanggal DESC, users.nama ASC
LIMIT 50
");
while ($row = mysqli_fetch_assoc($q_rekap)) :
$total_jam = '-';
if ($row['jam_masuk'] && $row['jam_keluar']) {
$start = strtotime($row['jam_masuk']);
$end = strtotime($row['jam_keluar']);
$diff = $end - $start;
$hours = floor($diff / 3600);
$minutes = floor(($diff % 3600) / 60);
$total_jam = $hours . ' jam' . ($minutes > 0 ? ' ' . $minutes . ' menit' : '');
}
?>
<tr>
<td><?= htmlspecialchars($row['nama']) ?></td>
<td><?= htmlspecialchars($row['tanggal']) ?></td>
<td><?= htmlspecialchars($row['jam_masuk'] ?? '-') ?></td>
<td>
<?php if ($row['jam_keluar']) : ?>
<?= htmlspecialchars($row['jam_keluar']) ?>
<?php else: ?>
<span style="color:#d97706;">Belum Absen Keluar</span>
<?php endif; ?>
</td>
<td>
<?php if ($row['jam_masuk'] && $row['jam_keluar']) : ?>
<?= $total_jam ?>
<?php else: ?>
<span style="color:#64748b;">-</span>
<?php endif; ?>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Data Siswa Section -->
<div id="siswa" class="content-section">
<div class="content-header">
<h1>Data Siswa</h1>
<p>Daftar siswa yang sudah terdaftar di sistem</p>
</div>
<div class="card">
<h3><span class="icon">👥</span>Daftar Siswa</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Nama</th>
<th>Username</th>
<th>Jurusan</th>
<th>Kelas</th>
</tr>
</thead>
<tbody>
<?php while ($siswa = mysqli_fetch_assoc($query_siswa)) : ?>
<tr>
<td><?= htmlspecialchars($siswa['nama']) ?></td>
<td><?= htmlspecialchars($siswa['username']) ?></td>
<td><?= htmlspecialchars($siswa['jurusan'] ?? '-') ?></td>
<td><?= htmlspecialchars($siswa['kelas'] ?? '-') ?></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
function showSection(sectionId, event) {
// Hide all sections
const sections = document.querySelectorAll('.content-section');
sections.forEach(section => {
section.classList.remove('active');
});
// Show selected section
document.getElementById(sectionId).classList.add('active');
// Update active menu item
const menuItems = document.querySelectorAll('.menu-item');
menuItems.forEach(item => item.classList.remove('active'));
if(event) event.target.classList.add('active');
// Close sidebar on mobile
if (window.innerWidth <= 768) {
toggleSidebar();
}
}
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.querySelector('.overlay');
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
}
function logout() {
if (confirm('Apakah Anda yakin ingin logout?')) {
window.location.href = '../auth/logout.php';
}
}
// Search table by name
function searchTable() {
const input = document.querySelector('.search-box');
const filter = input.value.toLowerCase();
const rows = document.querySelectorAll('#activitiesTable tbody tr');
rows.forEach(row => {
const nama = row.children[0].textContent.toLowerCase();
row.style.display = nama.includes(filter) ? '' : 'none';
});
}
// Update tanggal hari ini
function updateCurrentDate() {
const now = new Date();
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
document.getElementById('currentDate').textContent = now.toLocaleDateString('id-ID', options);
}
updateCurrentDate();
</script>
</body>
</html>
7. Fitur Input Presensi
siswa/presensi.php
php
<?php
session_start();
include '../config/koneksi.php';
$user_id = $_SESSION['user_id'];
$tanggal = date("Y-m-d");
// Ambil data presensi hari ini
$q = mysqli_query($koneksi, "SELECT * FROM presensi WHERE user_id='$user_id' AND tanggal='$tanggal'");
$presensi = mysqli_fetch_assoc($q);
// Proses jam masuk
if (isset($_POST['masuk'])) {
$jam_masuk = $_POST['jam_masuk'];
mysqli_query($koneksi, "INSERT INTO presensi (user_id, tanggal, jam_masuk) VALUES ('$user_id', '$tanggal', '$jam_masuk')");
header("Location: presensi.php");
exit;
}
// Proses jam keluar
if (isset($_POST['keluar'])) {
$jam_keluar = $_POST['jam_keluar'];
mysqli_query($koneksi, "UPDATE presensi SET jam_keluar='$jam_keluar' WHERE user_id='$user_id' AND tanggal='$tanggal'");
header("Location: presensi.php");
exit;
}
// Untuk tampilan status
$showMasuk = false;
$showKeluar = false;
$showComplete = false;
$jamMasuk = '';
$jamKeluar = '';
$totalJam = '';
if (!$presensi) {
$showMasuk = true;
} elseif ($presensi && !$presensi['jam_keluar']) {
$showKeluar = true;
$jamMasuk = $presensi['jam_masuk'];
} else {
$showComplete = true;
$jamMasuk = $presensi['jam_masuk'];
$jamKeluar = $presensi['jam_keluar'];
// Hitung total jam kerja
$start = strtotime($jamMasuk);
$end = strtotime($jamKeluar);
$diff = $end - $start;
$hours = floor($diff / 3600);
$minutes = floor(($diff % 3600) / 60);
$totalJam = $hours . ' jam ' . ($minutes > 0 ? $minutes . ' menit' : '');
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sistem Presensi Modern</title>
<style>
/* ...CSS dari prompt, tidak diubah... */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 500px;
width: 100%;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h3 {
font-size: 2.5rem;
color: #2c3e50;
margin-bottom: 10px;
background: linear-gradient(45deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
}
.date-info {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 15px;
border-radius: 15px;
margin-bottom: 30px;
text-align: center;
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.date-info .date {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 5px;
}
.date-info .time {
font-size: 1rem;
opacity: 0.9;
}
.form-container {
background: rgba(255, 255, 255, 0.8);
border-radius: 15px;
padding: 30px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.form-group {
margin-bottom: 25px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2c3e50;
font-size: 1.1rem;
}
input[type="time"] {
width: 100%;
padding: 15px;
border: 2px solid #e1e8ed;
border-radius: 10px;
font-size: 1.1rem;
transition: all 0.3s ease;
background: white;
color: #2c3e50;
}
input[type="time"]:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
}
.btn {
width: 100%;
padding: 15px;
border: none;
border-radius: 10px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-masuk {
background: linear-gradient(45deg, #2ecc71, #27ae60);
color: white;
box-shadow: 0 10px 20px rgba(46, 204, 113, 0.3);
}
.btn-masuk:hover {
transform: translateY(-3px);
box-shadow: 0 15px 30px rgba(46, 204, 113, 0.4);
}
.btn-keluar {
background: linear-gradient(45deg, #e74c3c, #c0392b);
color: white;
box-shadow: 0 10px 20px rgba(231, 76, 60, 0.3);
}
.btn-keluar:hover {
transform: translateY(-3px);
box-shadow: 0 15px 30px rgba(231, 76, 60, 0.4);
}
.status-complete {
background: linear-gradient(45deg, #2ecc71, #27ae60);
color: white;
padding: 30px;
border-radius: 15px;
text-align: center;
box-shadow: 0 10px 20px rgba(46, 204, 113, 0.3);
}
.status-complete h4 {
font-size: 1.5rem;
margin-bottom: 15px;
}
.time-info {
background: rgba(255, 255, 255, 0.2);
padding: 20px;
border-radius: 10px;
margin-top: 15px;
}
.time-info p {
margin: 8px 0;
font-size: 1.1rem;
}
.icon {
font-size: 2rem;
margin-bottom: 10px;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1);}
50% { transform: scale(1.05);}
100% { transform: scale(1);}
}
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px);}
to { opacity: 1; transform: translateY(0);}
}
@media (max-width: 768px) {
.container { padding: 20px; margin: 10px;}
.header h3 { font-size: 2rem;}
.btn { padding: 12px; font-size: 1rem;}
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #ffffff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.success-checkmark {
display: inline-block;
width: 22px;
height: 22px;
margin-right: 10px;
transform: rotate(45deg);
}
.success-checkmark::before {
content: '';
position: absolute;
width: 3px;
height: 9px;
background: white;
left: 11px;
top: 6px;
}
.success-checkmark::after {
content: '';
position: absolute;
width: 6px;
height: 3px;
background: white;
left: 6px;
top: 12px;
}
</style>
</head>
<body>
<div class="container fade-in">
<div class="header">
<h3>📍 Sistem Presensi</h3>
</div>
<div style="text-align:center; margin-bottom:20px;">
<a href="dashboard.php" style="
display:inline-block;
background:linear-gradient(45deg,#4f8cff,#2563eb);
color:#fff;
padding:10px 22px;
border-radius:8px;
text-decoration:none;
font-weight:600;
box-shadow:0 2px 8px rgba(79,140,255,0.10);
transition:background 0.2s;
" onmouseover="this.style.background='#2563eb'" onmouseout="this.style.background='linear-gradient(45deg,#4f8cff,#2563eb)'">
← Kembali ke Dashboard
</a>
</div>
<div class="date-info">
<div class="date" id="currentDate"></div>
<div class="time" id="currentTime"></div>
</div>
<!-- Form Jam Masuk -->
<?php if ($showMasuk): ?>
<div class="form-container" id="form-masuk">
<div class="icon">🌅</div>
<h4 style="margin-bottom: 20px; color: #2c3e50;">Selamat Pagi! Silakan absen masuk</h4>
<form method="POST">
<div class="form-group">
<label for="jam_masuk">Jam Masuk:</label>
<input type="time" name="jam_masuk" id="jam_masuk" required>
</div>
<button type="submit" name="masuk" class="btn btn-masuk pulse">
<span class="success-checkmark"></span>
Absen Masuk
</button>
</form>
</div>
<?php endif; ?>
<!-- Form Jam Keluar -->
<?php if ($showKeluar): ?>
<div class="form-container" id="form-keluar">
<div class="icon">🌇</div>
<h4 style="margin-bottom: 20px; color: #2c3e50;">Waktu pulang! Silakan absen keluar</h4>
<form method="POST">
<div class="form-group">
<label for="jam_keluar">Jam Keluar:</label>
<input type="time" name="jam_keluar" id="jam_keluar" required>
</div>
<button type="submit" name="keluar" class="btn btn-keluar pulse">
<span class="success-checkmark"></span>
Absen Pulang
</button>
</form>
</div>
<?php endif; ?>
<!-- Status Presensi Lengkap -->
<?php if ($showComplete): ?>
<div class="status-complete" id="status-complete">
<div class="icon">✅</div>
<h4>Presensi Hari Ini Sudah Lengkap!</h4>
<div class="time-info">
<p><strong>Jam Masuk:</strong> <span id="jam-masuk-display"><?= htmlspecialchars($jamMasuk) ?></span></p>
<p><strong>Jam Keluar:</strong> <span id="jam-keluar-display"><?= htmlspecialchars($jamKeluar) ?></span></p>
<p><strong>Total Jam Kerja:</strong> <span id="total-jam"><?= htmlspecialchars($totalJam) ?></span></p>
</div>
</div>
<?php endif; ?>
</div>
<script>
// Update waktu real-time
function updateTime() {
const now = new Date();
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
document.getElementById('currentDate').textContent =
now.toLocaleDateString('id-ID', options);
document.getElementById('currentTime').textContent =
now.toLocaleTimeString('id-ID');
}
// Set waktu saat ini sebagai default pada input time
function setCurrentTime() {
const now = new Date();
const timeString = now.toTimeString().slice(0, 5);
const jamMasukInput = document.getElementById('jam_masuk');
const jamKeluarInput = document.getElementById('jam_keluar');
if (jamMasukInput && !jamMasukInput.value) {
jamMasukInput.value = timeString;
}
if (jamKeluarInput && !jamKeluarInput.value) {
jamKeluarInput.value = timeString;
}
}
updateTime();
setCurrentTime();
setInterval(updateTime, 1000);
setInterval(setCurrentTime, 60000);
</script>
</body>
</html>
8. Fitur Aktivitas Harian
siswa/aktivitas.php
php
<?php
session_start();
include '../config/koneksi.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user_id = $_SESSION['user_id'];
$tanggal = date("Y-m-d");
$deskripsi = $_POST['deskripsi'];
mysqli_query($koneksi, "INSERT INTO aktivitas (user_id, tanggal, deskripsi, status_validasi)
VALUES ('$user_id', '$tanggal', '$deskripsi', 'pending')");
header("Location: dashboard.php");
}
?>
<h3>Isi Aktivitas Harian</h3>
<form method="POST">
Deskripsi Aktivitas:<br>
<textarea name="deskripsi" rows="5" cols="40" required></textarea><br>
<button type="submit">Simpan</button>
</form>
9. Validasi Aktivitas oleh Pembimbing
proses/proses_validasi.php
php
<?php
session_start();
include '../config/koneksi.php';
// Cek jika user pembimbing
if (!isset($_SESSION['user_id']) || $_SESSION['role'] != 'pembimbing') {
header("Location: ../auth/login.php");
exit;
}
// Cek jika tombol validasi ditekan
if (isset($_POST['setujui'])) {
$aktivitas_id = $_POST['aktivitas_id'];
// Update status jadi disetujui
$update = mysqli_query($koneksi, "UPDATE aktivitas SET status_validasi='disetujui' WHERE id='$aktivitas_id'");
header("Location: ../pembimbing/dashboard.php");
}
?>
10. Fitur Logout
logout.php
php
<?php
session_start();
session_destroy();
header("Location: login.php");
exit;
?>
11. Tampilan dengan Bootstrap
Agar tampilan lebih rapi dan responsif, gunakan Bootstrap:
html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
Gunakan class Bootstrap untuk form dan tombol:
html
<form class="form-control">
<input class="form-control" type="text">
<button class="btn btn-primary">Login</button>
</form>
12. Tips Keamanan Sederhana
- Gunakan
password_hash()untuk menyimpan password. - Batasi akses halaman menggunakan
session. - Gunakan prepared statement untuk menghindari SQL Injection.
- Tambahkan validasi form input.
Keamanan Dasar
Berikut beberapa keamanan dasar yang wajib kamu terapkan:
a. Escaping Input
Gunakan
htmlspecialchars() dan mysqli_real_escape_string()
saat menerima input dari pengguna:
php
$nama = htmlspecialchars(mysqli_real_escape_string($conn, $_POST['nama']));
b. Session Protection
Pastikan setiap halaman penting dicek session login-nya:
php
if (!isset($_SESSION['username'])) {
header("Location: ../auth/login.php");
exit();
}
c. Gunakan password_hash dan password_verify
Untuk menyimpan password secara aman:
php
// Saat register
$password_hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// Saat login
if (password_verify($_POST['password'], $row['password'])) {
// login berhasil
}
13. Kesimpulan
Membangun sistem presensi dan aktivitas harian berbasis web tidaklah sulit jika dipahami secara bertahap. Dengan menggunakan PHP, MySQL, dan Laragon, Anda bisa mengembangkan sistem lokal yang cepat dan efisien.
Dengan mengikuti langkah-langkah di atas, kamu telah berhasil membuat:
- Sistem login untuk dua peran (siswa dan pembimbing)
- Fitur presensi dan aktivitas harian
- Validasi laporan oleh pembimbing
- Desain berbasis Bootstrap agar modern
- Keamanan dasar agar data lebih aman
Website ini bisa dikembangkan lebih lanjut menjadi:
- Sistem penilaian kinerja magang
- Laporan mingguan/bulanan
- Integrasi Google Calendar untuk jadwal
Dengan menyelesaikan tutorial ini, Anda telah mempelajari:
- Cara mengatur database absensi dan aktivitas.
- Membuat fitur login berdasarkan role pengguna.
- Fitur input presensi dan aktivitas harian.
- Fitur validasi pembimbing.
- Tampilan rapi dengan Bootstrap.