Pertemuan 12 - Presensi Pegawai

Pertemuan 12 - Presensi Pegawai

Membuat menu Presensi Pegawai, servlet presensi, DAO presensi, dan halaman JSP untuk tombol Datang dan Pulang.

Tujuan Praktikum

Setelah praktikum ini, mahasiswa mampu:

  1. Menambahkan menu Presensi untuk role pegawai.
  2. Membuat model Presensi untuk data presensi harian.
  3. Membuat PresensiDAO untuk membaca dan menyimpan presensi datang maupun pulang.
  4. Membuat PresensiServlet untuk menampilkan halaman presensi dan memproses aksi tombol.
  5. Menampilkan halaman JSP presensi beserta riwayat presensi terbaru.
  6. Menghubungkan menu presensi dengan dashboard pegawai.

Persiapan

Siapkan kebutuhan berikut:

text
- Project Java Web Starter
- Folder `src/java/controller`
- Folder `src/java/dao`
- Folder `src/java/model`
- Folder `web/WEB-INF/views`
- File `src/java/model/User.java`
- File `src/java/dao/UserDAO.java`
- File `src/java/dao/PegawaiDAO.java`
- File `web/WEB-INF/views/partials/sidebar.jsp`
- File `web/WEB-INF/views/dashboard/pegawai.jsp`
- Tabel `presensi` pada database

Konsep Singkat

Fitur presensi pegawai dipakai oleh user dengan role pegawai untuk mencatat jam datang dan jam pulang pada hari berjalan.

Alur sederhananya seperti ini:

text
Sidebar Pegawai
   -> /presensi
      -> PresensiServlet.java
         -> PegawaiDAO.java
         -> PresensiDAO.java
            -> tabel presensi
         -> presensi.jsp

Di sisi role, menu ini hanya muncul untuk pegawai:

text
pegawai -> Home, Profile, Presensi
admin   -> Home, Entry Data Bagian, Entry Data Pegawai, Manajemen User

Logika utama fitur presensi:

  1. Sistem membaca user login dari session.
  2. Sistem memastikan user adalah pegawai dan punya id_pegawai.
  3. Sistem membaca data pegawai dan presensi hari ini.
  4. Jika user klik Datang, sistem menyimpan jam_masuk.
  5. Jika user klik Pulang, sistem menyimpan jam_pulang.
  6. Sistem menampilkan riwayat presensi terbaru.

Struktur File yang Dibutuhkan

Untuk membentuk menu presensi pegawai, file yang dipakai adalah:

text
src/java/model/Presensi.java
src/java/dao/PresensiDAO.java
src/java/controller/PresensiServlet.java
web/WEB-INF/views/presensi.jsp
web/WEB-INF/views/partials/sidebar.jsp
web/WEB-INF/views/dashboard/pegawai.jsp
src/java/dao/PegawaiDAO.java
src/java/model/Pegawai.java
src/java/model/User.java

Peran tiap file:

  • Presensi.java = model data presensi.
  • PresensiDAO.java = query presensi datang, pulang, dan riwayat.
  • PresensiServlet.java = controller untuk halaman dan aksi presensi.
  • presensi.jsp = tampilan halaman presensi.
  • sidebar.jsp = menu Presensi untuk pegawai.
  • dashboard/pegawai.jsp = shortcut dari dashboard ke halaman presensi.

Langkah 1 - Buat Model Presensi

Buat file src/java/model/Presensi.java.

Model ini menyimpan data utama dari tabel presensi.

java
package model;

public class Presensi {

    private int id;
    private int pegawaiId;
    private String tanggal;
    private String jamMasuk;
    private String jamPulang;
    private String statusPresensi;
    private String keterangan;

    public Presensi() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getPegawaiId() {
        return pegawaiId;
    }

    public void setPegawaiId(int pegawaiId) {
        this.pegawaiId = pegawaiId;
    }

    public String getTanggal() {
        return tanggal;
    }

    public void setTanggal(String tanggal) {
        this.tanggal = tanggal;
    }

    public String getJamMasuk() {
        return jamMasuk;
    }

    public void setJamMasuk(String jamMasuk) {
        this.jamMasuk = jamMasuk;
    }

    public String getJamPulang() {
        return jamPulang;
    }

    public void setJamPulang(String jamPulang) {
        this.jamPulang = jamPulang;
    }

    public String getStatusPresensi() {
        return statusPresensi;
    }

    public void setStatusPresensi(String statusPresensi) {
        this.statusPresensi = statusPresensi;
    }

    public String getKeterangan() {
        return keterangan;
    }

    public void setKeterangan(String keterangan) {
        this.keterangan = keterangan;
    }
}

Langkah 2 - Buat DAO Presensi

Buat file src/java/dao/PresensiDAO.java.

DAO ini dipakai untuk:

  • mencari presensi hari ini,
  • membaca riwayat presensi terbaru,
  • menyimpan presensi datang,
  • menyimpan presensi pulang.
java
package dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import koneksi.DBKoneksi;
import model.Presensi;

public class PresensiDAO {

    public Presensi findTodayByPegawaiId(int pegawaiId) throws SQLException {
        String sql = "SELECT id_presensi AS id, id_pegawai, tanggal, jam_masuk, jam_pulang, "
                   + "status_presensi, keterangan "
                   + "FROM presensi "
                   + "WHERE id_pegawai = ? AND tanggal = CURDATE() "
                   + "ORDER BY id_presensi DESC "
                   + "LIMIT 1";

        try (PreparedStatement ps = DBKoneksi.getConnection().prepareStatement(sql)) {
            ps.setInt(1, pegawaiId);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    return mapRow(rs);
                }
            }
        }

        return null;
    }

    public List<Presensi> findRecentByPegawaiId(int pegawaiId, int limit) throws SQLException {
        String sql = "SELECT id_presensi AS id, id_pegawai, tanggal, jam_masuk, jam_pulang, "
                   + "status_presensi, keterangan "
                   + "FROM presensi "
                   + "WHERE id_pegawai = ? "
                   + "ORDER BY tanggal DESC, id_presensi DESC "
                   + "LIMIT ?";

        List<Presensi> presensiList = new ArrayList<>();

        try (PreparedStatement ps = DBKoneksi.getConnection().prepareStatement(sql)) {
            ps.setInt(1, pegawaiId);
            ps.setInt(2, limit);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    presensiList.add(mapRow(rs));
                }
            }
        }

        return presensiList;
    }

    public void insertDatang(int pegawaiId) throws SQLException {
        String sql = "INSERT INTO presensi (id_pegawai, tanggal, jam_masuk, status_presensi) "
                   + "VALUES (?, CURDATE(), CURTIME(), 'Hadir')";

        try (PreparedStatement ps = DBKoneksi.getConnection().prepareStatement(sql)) {
            ps.setInt(1, pegawaiId);
            ps.executeUpdate();
        }
    }

    public void updateDatang(int presensiId) throws SQLException {
        String sql = "UPDATE presensi SET jam_masuk = CURTIME(), status_presensi = 'Hadir' "
                   + "WHERE id_presensi = ?";

        try (PreparedStatement ps = DBKoneksi.getConnection().prepareStatement(sql)) {
            ps.setInt(1, presensiId);
            ps.executeUpdate();
        }
    }

    public void updatePulang(int presensiId) throws SQLException {
        String sql = "UPDATE presensi SET jam_pulang = CURTIME() WHERE id_presensi = ?";

        try (PreparedStatement ps = DBKoneksi.getConnection().prepareStatement(sql)) {
            ps.setInt(1, presensiId);
            ps.executeUpdate();
        }
    }

    private Presensi mapRow(ResultSet rs) throws SQLException {
        Presensi presensi = new Presensi();
        presensi.setId(rs.getInt("id"));
        presensi.setPegawaiId(rs.getInt("id_pegawai"));
        presensi.setTanggal(rs.getString("tanggal"));
        presensi.setJamMasuk(rs.getString("jam_masuk"));
        presensi.setJamPulang(rs.getString("jam_pulang"));
        presensi.setStatusPresensi(rs.getString("status_presensi"));
        presensi.setKeterangan(rs.getString("keterangan"));
        return presensi;
    }
}

Langkah 3 - Buat Servlet Presensi

Buat file src/java/controller/PresensiServlet.java.

Servlet ini menjadi pusat alur menu presensi:

  • doGet() untuk membuka halaman presensi,
  • doPost() untuk memproses tombol datang atau pulang,
  • validasi role pegawai,
  • membaca user login dari session.
java
package controller;

import dao.PegawaiDAO;
import dao.PresensiDAO;
import model.Pegawai;
import model.Presensi;
import model.User;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;

@WebServlet(name = "PresensiServlet", urlPatterns = {"/presensi"})
public class PresensiServlet extends HttpServlet {

    private static final String PRESENSI_PAGE = "/WEB-INF/views/presensi.jsp";

    private final PegawaiDAO pegawaiDAO = new PegawaiDAO();
    private final PresensiDAO presensiDAO = new PresensiDAO();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        User user = getUserLogin(request);
        if (!isPegawaiWithData(user)) {
            response.sendRedirect(request.getContextPath() + "/index.jsp");
            return;
        }

        try {
            Pegawai pegawai = pegawaiDAO.findById(user.getPegawaiId());
            Presensi presensiHariIni = presensiDAO.findTodayByPegawaiId(user.getPegawaiId());

            request.setAttribute("activeMenu", "presensi");
            request.setAttribute("pegawai", pegawai);
            request.setAttribute("presensiHariIni", presensiHariIni);
            request.setAttribute("presensiList", presensiDAO.findRecentByPegawaiId(user.getPegawaiId(), 10));
            forwardToLayout(request, response);
        } catch (SQLException e) {
            throw new ServletException("Gagal membuka halaman presensi.", e);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        User user = getUserLogin(request);
        if (!isPegawaiWithData(user)) {
            response.sendRedirect(request.getContextPath() + "/index.jsp");
            return;
        }

        String action = request.getParameter("action");

        try {
            if ("datang".equals(action)) {
                handleDatang(user.getPegawaiId(), request, response);
            } else if ("pulang".equals(action)) {
                handlePulang(user.getPegawaiId(), request, response);
            } else {
                redirectWithMessage(response, request, "Aksi presensi tidak valid.", "error");
            }
        } catch (SQLException e) {
            redirectWithMessage(response, request, "Gagal menyimpan presensi.", "error");
        }
    }

    private void handleDatang(int pegawaiId, HttpServletRequest request, HttpServletResponse response)
            throws SQLException, IOException {

        Presensi hariIni = presensiDAO.findTodayByPegawaiId(pegawaiId);
        if (hariIni == null) {
            presensiDAO.insertDatang(pegawaiId);
            redirectWithMessage(response, request, "Presensi datang berhasil disimpan.", "success");
            return;
        }

        if (hariIni.getJamMasuk() == null || hariIni.getJamMasuk().isBlank()) {
            presensiDAO.updateDatang(hariIni.getId());
            redirectWithMessage(response, request, "Presensi datang berhasil diperbarui.", "success");
            return;
        }

        redirectWithMessage(response, request, "Presensi datang hari ini sudah tercatat.", "info");
    }

    private void handlePulang(int pegawaiId, HttpServletRequest request, HttpServletResponse response)
            throws SQLException, IOException {

        Presensi hariIni = presensiDAO.findTodayByPegawaiId(pegawaiId);
        if (hariIni == null || hariIni.getJamMasuk() == null || hariIni.getJamMasuk().isBlank()) {
            redirectWithMessage(response, request, "Klik Datang terlebih dahulu sebelum Pulang.", "error");
            return;
        }

        if (hariIni.getJamPulang() == null || hariIni.getJamPulang().isBlank()) {
            presensiDAO.updatePulang(hariIni.getId());
            redirectWithMessage(response, request, "Presensi pulang berhasil disimpan.", "success");
            return;
        }

        redirectWithMessage(response, request, "Presensi pulang hari ini sudah tercatat.", "info");
    }

    private boolean isPegawaiWithData(User user) {
        return user != null && user.isPegawai() && user.getPegawaiId() != null && user.getPegawaiId() > 0;
    }

    private User getUserLogin(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }

        Object user = session.getAttribute("userLogin");
        return user instanceof User ? (User) user : null;
    }

    private void forwardToLayout(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setAttribute("contentPage", PRESENSI_PAGE);
        request.getRequestDispatcher("/index.jsp").forward(request, response);
    }

    private void redirectWithMessage(HttpServletResponse response, HttpServletRequest request,
                                     String message, String type) throws IOException {
        String target = request.getContextPath() + "/presensi"
                + "?message=" + URLEncoder.encode(message, StandardCharsets.UTF_8)
                + "&type=" + URLEncoder.encode(type, StandardCharsets.UTF_8);
        response.sendRedirect(target);
    }
}

Catatan penting dari servlet ini:

  • @WebServlet(urlPatterns = {"/presensi"}) membuat URL menu presensi aktif di /presensi.
  • request.setAttribute("activeMenu", "presensi") dipakai agar sidebar menandai menu aktif.
  • findTodayByPegawaiId() dipakai untuk mengetahui apakah user sudah presensi hari ini.

Langkah 4 - Tambahkan Menu Presensi di Sidebar

Buka file web/WEB-INF/views/partials/sidebar.jsp.

Tambahkan menu Presensi hanya untuk role pegawai.

jsp
<c:when test="${sessionScope.userLogin.role == 'pegawai'}">
    <li class="${requestScope.activeMenu == 'profile' ? 'active' : ''}">
        <a href="${pageContext.request.contextPath}/profile">
            Profile
        </a>
    </li>

    <li class="${requestScope.activeMenu == 'presensi' ? 'active' : ''}">
        <a href="${pageContext.request.contextPath}/presensi">
            Presensi
        </a>
    </li>
</c:when>

Hasilnya:

  • admin tidak melihat menu presensi,
  • pegawai melihat menu Home, Profile, dan Presensi.

Langkah 5 - Buat Halaman JSP Presensi

Buat file web/WEB-INF/views/presensi.jsp.

Halaman ini menampilkan:

  • identitas pegawai,
  • status presensi hari ini,
  • tombol Datang,
  • tombol Pulang,
  • riwayat presensi terbaru.

Struktur utamanya seperti ini:

jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<div class="page-header">
    <h2>Presensi Pegawai</h2>
</div>

<c:if test="${not empty param.message}">
    <div class="${empty param.status ? 'info' : param.status}">
        <c:out value="${param.message}"/>
    </div>
</c:if>

<div class="card">
    <h3>Informasi Pegawai</h3>
    <p><strong>NIP:</strong> <c:out value="${pegawai.nip}"/></p>
    <p><strong>Nama:</strong> <c:out value="${pegawai.namaPegawai}"/></p>
    <p><strong>Bagian:</strong> <c:out value="${pegawai.bagianNama}"/></p>
</div>

<div class="card">
    <h3>Presensi Hari Ini</h3>
    <p><strong>Tanggal:</strong> <c:out value="${presensiHariIni.tanggal}" default="-"/></p>
    <p><strong>Jam Masuk:</strong> <c:out value="${presensiHariIni.jamMasuk}" default="-"/></p>
    <p><strong>Jam Pulang:</strong> <c:out value="${presensiHariIni.jamPulang}" default="-"/></p>

    <form method="post" action="${pageContext.request.contextPath}/presensi">
        <button type="submit" name="action" value="datang" class="btn btn-add">Datang</button>
        <button type="submit" name="action" value="pulang" class="btn btn-secondary">Pulang</button>
    </form>
</div>

Lanjutkan dengan tabel riwayat presensi:

jsp
<div class="card">
    <h3>Riwayat Presensi Terbaru</h3>
    <table class="data-table">
        <thead>
            <tr>
                <th>Tanggal</th>
                <th>Jam Masuk</th>
                <th>Jam Pulang</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            <c:forEach var="presensi" items="${presensiList}">
                <tr>
                    <td><c:out value="${presensi.tanggal}"/></td>
                    <td><c:out value="${presensi.jamMasuk}" default="-"/></td>
                    <td><c:out value="${presensi.jamPulang}" default="-"/></td>
                    <td><c:out value="${presensi.statusPresensi}"/></td>
                </tr>
            </c:forEach>
        </tbody>
    </table>
</div>

Langkah 6 - Hubungkan Dashboard Pegawai dengan Menu Presensi

Agar alur fitur terasa rapi, dashboard pegawai perlu memberi jalan cepat ke halaman presensi.

Buka file web/WEB-INF/views/dashboard/pegawai.jsp.

Tambahkan atau pastikan ada tombol seperti ini:

jsp
<div class="quick-actions">
    <a href="${pageContext.request.contextPath}/presensi" class="btn btn-add">Buka Presensi</a>
    <a href="${pageContext.request.contextPath}/profile" class="btn btn-secondary">Profile</a>
</div>

Dengan ini, pegawai bisa:

  • login,
  • masuk dashboard,
  • klik Buka Presensi,
  • mencatat datang atau pulang.

Langkah 7 - Pastikan Role Pegawai Sudah Siap

Fitur presensi bergantung pada data user login.

Pastikan model User.java punya pengecekan role berikut:

java
public boolean isPegawai() {
    return "pegawai".equals(role);
}

Pastikan juga UserDAO.java membaca id_pegawai, nip, dan nama_pegawai saat login.

Tanpa relasi ini, servlet presensi tidak bisa mengetahui pegawai yang sedang login.

Pengujian

Lakukan pengujian berikut:

  1. Login sebagai user role pegawai.
  2. Pastikan menu Presensi muncul di sidebar.
  3. Buka menu Presensi.
  4. Klik tombol Datang.
  5. Pastikan jam_masuk terisi.
  6. Klik tombol Pulang.
  7. Pastikan jam_pulang terisi.
  8. Buka dashboard pegawai, lalu cek ringkasan presensi hari ini.

Checklist hasil yang diharapkan:

text
- Menu Presensi muncul untuk pegawai
- Halaman presensi bisa dibuka
- Tombol Datang menyimpan jam masuk
- Tombol Pulang menyimpan jam pulang
- Riwayat presensi tampil
- Dashboard pegawai punya shortcut ke presensi

Troubleshooting

Masalah Penyebab Solusi
Menu Presensi tidak muncul Role login bukan pegawai Cek data role pada tabel users
Halaman presensi redirect ke index Session user kosong atau id_pegawai tidak terhubung Pastikan login berhasil dan user terhubung ke data pegawai
Tombol Datang tidak menyimpan data Query insert gagal atau tabel presensi belum siap Cek struktur tabel dan koneksi database
Tombol Pulang gagal Belum ada data datang hari ini Klik Datang dulu sebelum Pulang
Riwayat presensi kosong Belum ada data pada tabel presensi Lakukan presensi datang dan pulang lalu refresh halaman

Tugas Praktikum

  1. Tambahkan kolom keterangan pada form atau logika presensi jika pegawai datang terlambat.
  2. Ubah status presensi sehingga bisa membedakan Hadir, Terlambat, dan Izin.
  3. Batasi jam datang agar setelah jam tertentu otomatis diberi status Terlambat.
  4. Tambahkan validasi agar presensi hanya bisa dilakukan satu kali datang dan satu kali pulang per hari.

Kesimpulan

Pada pertemuan ini, fitur Presensi Pegawai dibangun dengan menghubungkan menu sidebar, servlet, DAO, model, dan halaman JSP.

Fitur ini menjadi pelengkap penting setelah CRUD pegawai, manajemen user, dan dashboard pegawai selesai dibuat. Dengan alur ini, user role pegawai tidak hanya melihat data, tetapi juga mulai berinteraksi langsung dengan proses bisnis aplikasi presensi.