Modul 07: Portal Siswa (Katalog, Pinjam & Buku Saya)
Modul 07: Portal Siswa (Katalog, Pinjam & Buku Saya)
Berikut adalah seluruh berkas kode program aktual yang digunakan dalam aplikasi SIPAS tanpa potongan atau placeholder. Kode dapat dipelajari dan diketikkan langsung pada Sublime Text 3.
1
Controller Dashboard Siswa (user/Dashboard.php)
Lokasi Berkas: application/controllers/user/Dashboard.php
Gambar: Pratinjau antarmuka dashboard siswa dengan counter buku pinjaman aktif.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* User Dashboard Controller
*/
class Dashboard extends CI_Controller {
public function __construct() {
parent::__construct();
check_user();
$this->load->model('Dashboard_model');
$this->load->model('Transaksi_model');
}
public function index() {
$user_id = $this->session->userdata('user_id');
$data['title'] = 'Dashboard Siswa - SIPAS';
$data['stats'] = $this->Dashboard_model->get_user_stats($user_id);
$data['buku_pinjam'] = $this->Transaksi_model->get_active_by_user($user_id);
$this->load->view('user/dashboard', $data);
}
}
๐ก Catatan: Baris komentar (// atau /* */)
bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
2
Controller Katalog Buku Siswa (user/Buku.php)
Lokasi Berkas: application/controllers/user/Buku.php
Gambar: Pratinjau katalog pencarian buku siswa format kartu 3 kolom.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* User Buku Controller
* Siswa mencari buku, melihat detail buku, dan meminjam buku
*/
class Buku extends CI_Controller {
public function __construct() {
parent::__construct();
check_user();
$this->load->model('Buku_model');
$this->load->model('Transaksi_model');
$this->load->library('pagination');
}
/**
* Katalog / Pencarian Buku Siswa (Grid View Card)
*/
public function index() {
$search = $this->input->get('keyword', TRUE);
$kategori = $this->input->get('kategori', TRUE);
$limit = 9; // 3x3 Grid
$page = $this->input->get('page') ? (int) $this->input->get('page') : 1;
$offset = ($page - 1) * $limit;
$total_rows = $this->Buku_model->count_all($search, $kategori);
// Pagination setup
$config['base_url'] = site_url('user/buku');
$config['total_rows'] = $total_rows;
$config['per_page'] = $limit;
$config['page_query_string'] = TRUE;
$config['query_string_segment'] = 'page';
$config['cur_page'] = (string) $page;
$config['reuse_query_string'] = TRUE;
$config['full_tag_open'] = '<nav><ul class="pagination pagination-sm justify-content-center mb-0">';
$config['full_tag_close'] = '</ul></nav>';
$config['first_link'] = 'ยซ';
$config['first_tag_open'] = '<li class="page-item">';
$config['first_tag_close'] = '</li>';
$config['last_link'] = 'ยป';
$config['last_tag_open'] = '<li class="page-item">';
$config['last_tag_close'] = '</li>';
$config['next_link'] = 'โบ';
$config['next_tag_open'] = '<li class="page-item">';
$config['next_tag_close'] = '</li>';
$config['prev_link'] = 'โน';
$config['prev_tag_open'] = '<li class="page-item">';
$config['prev_tag_close'] = '</li>';
$config['cur_tag_open'] = '<li class="page-item active"><span class="page-link">';
$config['cur_tag_close'] = '</span></li>';
$config['num_tag_open'] = '<li class="page-item">';
$config['num_tag_close'] = '</li>';
$config['attributes'] = ['class' => 'page-link'];
$this->pagination->initialize($config);
$data['title'] = 'Koleksi Buku - SIPAS - Sistem Informasi Perpustakaan Ardli Sela';
$data['buku'] = $this->Buku_model->search($search, $kategori, $limit, $offset);
$data['categories'] = $this->Buku_model->get_categories();
$data['pagination'] = $this->pagination->create_links();
$data['total_rows'] = $total_rows;
$data['keyword'] = $search;
$data['selected_kategori'] = $kategori;
$this->load->view('user/buku/index', $data);
}
/**
* Detail Buku Siswa
*/
public function detail($id) {
$buku = $this->Buku_model->get_by_id($id);
if (!$buku) {
$this->session->set_flashdata('error', 'Buku tidak ditemukan.');
redirect('user/buku');
}
$user_id = $this->session->userdata('user_id');
$is_borrowed = $this->Transaksi_model->is_book_borrowed_by_user($user_id, $id);
$data['title'] = 'Detail Buku: ' . $buku->judul;
$data['buku'] = $buku;
$data['is_borrowed'] = $is_borrowed;
$this->load->view('user/buku/detail', $data);
}
/**
* Proses Peminjaman Buku
*/
public function pinjam($id) {
$user_id = $this->session->userdata('user_id');
// Panggil model transaksi yang memuat database transaction dan 4 level validasi
$result = $this->Transaksi_model->create_peminjaman($user_id, $id);
if ($result['status']) {
$this->session->set_flashdata('success', $result['message']);
redirect('user/peminjaman');
} else {
$this->session->set_flashdata('error', $result['message']);
redirect('user/buku/detail/' . $id);
}
}
}
๐ก Catatan: Baris komentar (// atau /* */)
bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
3
Controller Peminjaman & Buku Saya (user/Peminjaman.php)
Lokasi Berkas: application/controllers/user/Peminjaman.php
Gambar: Pratinjau modal konfirmasi peminjaman buku mandiri oleh siswa.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* User Peminjaman Controller
* Buku Saya & Riwayat Peminjaman Siswa
*/
class Peminjaman extends CI_Controller {
public function __construct() {
parent::__construct();
check_user();
$this->load->model('Transaksi_model');
$this->load->library('pagination');
}
/**
* Halaman Buku Saya (/user/peminjaman)
* Menampilkan buku yang sedang dipinjam oleh user aktif
*/
public function index() {
$user_id = $this->session->userdata('user_id');
$data['title'] = 'Buku Saya - SIPAS - Sistem Informasi Perpustakaan Ardli Sela';
$data['buku_pinjam'] = $this->Transaksi_model->get_active_by_user($user_id);
$this->load->view('user/peminjaman/index', $data);
}
/**
* Siswa melakukan pengembalian buku mandiri
*/
public function kembalikan($transaksi_id) {
$user_id = $this->session->userdata('user_id');
// Panggil model proses pengembalian dengan proteksi user_id
$result = $this->Transaksi_model->proses_pengembalian($transaksi_id, $user_id);
if ($result['status']) {
$this->session->set_flashdata('success', $result['message']);
} else {
$this->session->set_flashdata('error', $result['message']);
}
redirect('user/peminjaman');
}
/**
* Halaman Riwayat Transaksi Peminjaman (/user/riwayat)
*/
public function riwayat() {
$user_id = $this->session->userdata('user_id');
$search = $this->input->get('keyword', TRUE);
$status = $this->input->get('status', TRUE);
$limit = 10;
$page = $this->input->get('page') ? (int) $this->input->get('page') : 1;
$offset = ($page - 1) * $limit;
$total_rows = $this->Transaksi_model->count_by_user($user_id, $status, $search);
// Pagination setup
$config['base_url'] = site_url('user/riwayat');
$config['total_rows'] = $total_rows;
$config['per_page'] = $limit;
$config['page_query_string'] = TRUE;
$config['query_string_segment'] = 'page';
$config['cur_page'] = (string) $page;
$config['reuse_query_string'] = TRUE;
$config['full_tag_open'] = '<nav><ul class="pagination pagination-sm mb-0">';
$config['full_tag_close'] = '</ul></nav>';
$config['first_link'] = 'ยซ';
$config['first_tag_open'] = '<li class="page-item">';
$config['first_tag_close'] = '</li>';
$config['last_link'] = 'ยป';
$config['last_tag_open'] = '<li class="page-item">';
$config['last_tag_close'] = '</li>';
$config['next_link'] = 'โบ';
$config['next_tag_open'] = '<li class="page-item">';
$config['next_tag_close'] = '</li>';
$config['prev_link'] = 'โน';
$config['prev_tag_open'] = '<li class="page-item">';
$config['prev_tag_close'] = '</li>';
$config['cur_tag_open'] = '<li class="page-item active"><span class="page-link">';
$config['cur_tag_close'] = '</span></li>';
$config['num_tag_open'] = '<li class="page-item">';
$config['num_tag_close'] = '</li>';
$config['attributes'] = ['class' => 'page-link'];
$this->pagination->initialize($config);
$data['title'] = 'Riwayat Peminjaman - SIPAS';
$data['riwayat'] = $this->Transaksi_model->get_by_user($user_id, $status, $search, $limit, $offset);
$data['pagination'] = $this->pagination->create_links();
$data['total_rows'] = $total_rows;
$data['offset'] = $offset;
$data['limit'] = $limit;
$data['keyword'] = $search;
$data['selected_status'] = $status ? $status : 'semua';
$this->load->view('user/peminjaman/riwayat', $data);
}
}
๐ก Catatan: Baris komentar (// atau /* */)
bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
4
Controller Profil Anggota Siswa (user/Profil.php)
Lokasi Berkas: application/controllers/user/Profil.php
Gambar: Pratinjau halaman buku saya dan tombol pengembalian buku mandiri.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* User Profil Controller
*/
class Profil extends CI_Controller {
public function __construct() {
parent::__construct();
check_user();
$this->load->model('User_model');
$this->load->model('Transaksi_model');
}
public function index() {
$user_id = $this->session->userdata('user_id');
$user = $this->User_model->get_by_id($user_id);
$data['title'] = 'Profil Saya - SIPAS - Sistem Informasi Perpustakaan Ardli Sela';
$data['user'] = $user;
$data['total_pinjam'] = $this->Transaksi_model->count_by_user($user_id);
$this->load->view('user/profil/index', $data);
}
/**
* Update Foto Profil Siswa
*/
public function update_foto() {
$user_id = $this->session->userdata('user_id');
$user = $this->User_model->get_by_id($user_id);
if (empty($_FILES['foto']['name'])) {
$this->session->set_flashdata('error', 'Silakan pilih file foto terlebih dahulu.');
redirect('user/profil');
return;
}
$upload_config['upload_path'] = './uploads/profile/';
$upload_config['allowed_types'] = 'jpg|jpeg|png|webp';
$upload_config['max_size'] = 2048; // 2MB
$upload_config['encrypt_name'] = TRUE;
$this->load->library('upload', $upload_config);
if ($this->upload->do_upload('foto')) {
$upload_res = $this->upload->data();
$new_foto = $upload_res['file_name'];
// Update di DB
$this->User_model->update($user_id, [
'foto' => $new_foto,
'updated_at' => date('Y-m-d H:i:s')
]);
// Update session jika perlu
$this->session->set_userdata('foto', $new_foto);
// Hapus foto lama jika ada
if (!empty($user->foto) && file_exists(FCPATH . 'uploads/profile/' . $user->foto)) {
@unlink(FCPATH . 'uploads/profile/' . $user->foto);
}
$this->session->set_flashdata('success', 'Foto profil berhasil diperbarui.');
} else {
$this->session->set_flashdata('error', 'Gagal upload foto: ' . $this->upload->display_errors('', ''));
}
redirect('user/profil');
}
}
๐ก Catatan: Baris komentar (// atau /* */)
bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
5
View Dashboard Portal Siswa (user/dashboard.php)
Lokasi Berkas: application/views/user/dashboard.php
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_user'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Selamat Datang, <?= html_escape($this->session->userdata('nama')) ?></h1>
<p class="page-subtitle">Temukan dan pinjam berbagai buku pelajaran dan referensi sekolah</p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<!-- Stats Row -->
<div class="row g-3 mb-4">
<div class="col-sm-4">
<div class="card border-0 mb-0">
<div class="stat-card">
<div class="stat-icon">
<i class="bi bi-book"></i>
</div>
<div>
<div class="stat-number"><?= $stats['buku_tersedia'] ?></div>
<div class="stat-label">Buku Tersedia</div>
<div class="stat-sub">Siap dipinjam</div>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card border-0 mb-0">
<div class="stat-card">
<div class="stat-icon" style="background-color: var(--warning-soft); color: var(--warning);">
<i class="bi bi-journal-arrow-up"></i>
</div>
<div>
<div class="stat-number"><?= $stats['sedang_dipinjam'] ?></div>
<div class="stat-label">Sedang Dipinjam</div>
<div class="stat-sub">Buku aktif kamu</div>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card border-0 mb-0">
<div class="stat-card">
<div class="stat-icon" style="background-color: var(--success-soft); color: var(--success);">
<i class="bi bi-check2-circle"></i>
</div>
<div>
<div class="stat-number"><?= $stats['sudah_kembali'] ?></div>
<div class="stat-label">Sudah Dikembalikan</div>
<div class="stat-sub">Riwayat selesai</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Search Form -->
<div class="card border-0 mb-4">
<div class="card-body p-4">
<h6 class="fw-bold text-dark mb-2">Cari Buku di Perpustakaan</h6>
<form action="<?= site_url('user/buku') ?>" method="GET" class="d-flex gap-2">
<div class="search-input-group flex-grow-1">
<i class="bi bi-search"></i>
<input type="text" class="form-control" name="keyword" placeholder="Ketik judul, pengarang atau kategori buku...">
</div>
<button type="submit" class="btn btn-primary px-4">
Cari Buku
</button>
</form>
</div>
</div>
<!-- Sedang Kamu Pinjam Section -->
<div class="card border-0">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-journal-check me-1 text-primary"></i> Sedang Kamu Pinjam</span>
<a href="<?= site_url('user/peminjaman') ?>" class="text-decoration-none small fw-semibold text-primary">
Lihat Buku Saya <i class="bi bi-arrow-right"></i>
</a>
</div>
<div class="card-body p-0">
<?php if (empty($buku_pinjam)): ?>
<div class="text-center py-5 text-muted">
<i class="bi bi-emoji-smile fs-3 d-block mb-2 text-primary"></i>
<p class="mb-1 fw-semibold text-dark">Kamu sedang tidak meminjam buku.</p>
<span class="small text-secondary">Ayo jelajahi koleksi buku dan pinjam sekarang!</span>
</div>
<?php else: ?>
<div class="list-group list-group-flush">
<?php foreach ($buku_pinjam as $item): ?>
<div class="list-group-item d-flex justify-content-between align-items-center p-3 flex-wrap gap-2">
<div class="d-flex align-items-center gap-3">
<div class="book-card-icon mb-0" style="width: 40px; height: 40px; font-size: 1.25rem;">
<i class="bi bi-book"></i>
</div>
<div>
<div class="fw-bold text-dark mb-0"><?= html_escape($item->judul_buku) ?></div>
<div class="text-muted small">
Batas pengembalian:
<span class="<?= (strtotime($item->batas_kembali) < strtotime(date('Y-m-d'))) ? 'text-danger fw-bold' : '' ?>">
<?= format_tanggal_indo($item->batas_kembali) ?>
</span>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<span class="badge badge-soft-warning">Dipinjam</span>
<a href="<?= site_url('user/buku/detail/' . $item->buku_id) ?>" class="btn btn-light btn-sm">
Lihat Detail
</a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
6
View Katalog Buku Siswa (user/buku/index.php)
Lokasi Berkas: application/views/user/buku/index.php
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_user'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Cari Buku</h1>
<p class="page-subtitle">Temukan koleksi buku yang tersedia di perpustakaan sekolah</p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<!-- Search Bar Standard -->
<div class="card border-0 mb-4">
<div class="card-body p-3">
<form action="<?= site_url('user/buku') ?>" method="GET" class="row g-2 align-items-center">
<div class="col-md-8">
<div class="search-input-group">
<i class="bi bi-search"></i>
<input type="text" class="form-control" name="keyword" value="<?= html_escape($keyword) ?>" placeholder="Cari judul buku, kode, penulis atau kategori...">
</div>
</div>
<div class="col-md-3">
<select name="kategori" class="form-select" onchange="this.form.submit()">
<option value="">Semua Kategori</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= html_escape($cat->kategori) ?>" <?= $selected_kategori === $cat->kategori ? 'selected' : '' ?>>
<?= html_escape($cat->kategori) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-1 d-flex gap-1">
<button type="submit" class="btn btn-primary w-100 justify-content-center">
Cari
</button>
<?php if (!empty($keyword) || !empty($selected_kategori)): ?>
<a href="<?= site_url('user/buku') ?>" class="btn btn-light" title="Reset filter">
<i class="bi bi-x-lg"></i>
</a>
<?php endif; ?>
</div>
</form>
</div>
</div>
<!-- Book Grid (3 Card per Row Desktop, 2 Tablet, 1 Mobile) -->
<?php if (empty($buku)): ?>
<div class="card border-0">
<div class="card-body">
<div class="empty-state">
<div class="empty-state-icon"><i class="bi bi-search"></i></div>
<h6 class="fw-bold text-dark">Tidak ada hasil ditemukan</h6>
<p class="text-secondary small mb-3">
<?php if (!empty($keyword)): ?>
Tidak ditemukan buku dengan kata kunci "<strong><?= html_escape($keyword) ?></strong>".
<?php else: ?>
Belum ada buku dalam katalog perpustakaan.
<?php endif; ?>
</p>
<?php if (!empty($keyword) || !empty($selected_kategori)): ?>
<a href="<?= site_url('user/buku') ?>" class="btn btn-light btn-sm">
Hapus Pencarian
</a>
<?php endif; ?>
</div>
</div>
</div>
<?php else: ?>
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-4 mb-4">
<?php foreach ($buku as $item): ?>
<div class="col">
<div class="card book-card border-0">
<div class="card-body p-4 d-flex flex-column justify-content-between">
<div>
<div class="d-flex justify-content-between align-items-start mb-3">
<?php $cover_url = get_book_cover_url($item->gambar); if ($cover_url): ?>
<img src="<?= $cover_url ?>" alt="<?= html_escape($item->judul) ?>" class="rounded border shadow-sm" style="width: 52px; height: 70px; object-fit: cover;" loading="lazy">
<?php else: ?>
<div class="book-card-icon">
<i class="bi bi-book"></i>
</div>
<?php endif; ?>
<span class="badge badge-soft-secondary"><?= html_escape($item->kode_buku) ?></span>
</div>
<h5 class="book-title"><?= html_escape($item->judul) ?></h5>
<div class="book-author"><?= html_escape($item->penulis ? $item->penulis : 'Penulis tidak dicantumkan') ?></div>
<div class="mb-3">
<span class="badge badge-soft-info me-1"><?= html_escape($item->kategori ? $item->kategori : 'Umum') ?></span>
<span class="text-muted small">Rak: <?= html_escape($item->rak ? $item->rak : 'โ') ?></span>
</div>
</div>
<div class="pt-3 border-top mt-2 d-flex justify-content-between align-items-center">
<div>
<?php if ($item->stok > 0): ?>
<span class="text-success small fw-semibold">
<i class="bi bi-check-circle-fill me-1"></i> <?= $item->stok ?> tersedia
</span>
<?php else: ?>
<span class="text-danger small fw-semibold">
<i class="bi bi-x-circle-fill me-1"></i> Stok habis
</span>
<?php endif; ?>
</div>
<a href="<?= site_url('user/buku/detail/' . $item->id) ?>" class="btn btn-primary btn-sm px-3">
Lihat Detail
</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Pagination -->
<?php if ($total_rows > 9): ?>
<div class="d-flex justify-content-center mb-4">
<?= $pagination ?>
</div>
<?php endif; ?>
<?php endif; ?>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
7
View Buku Saya & Modal Pengembalian (user/peminjaman/index.php)
Lokasi Berkas: application/views/user/peminjaman/index.php
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_user'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Buku Saya</h1>
<p class="page-subtitle">Daftar buku yang sedang kamu pinjam saat ini</p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<?php if (empty($buku_pinjam)): ?>
<div class="card border-0">
<div class="card-body">
<div class="empty-state">
<div class="empty-state-icon"><i class="bi bi-book"></i></div>
<h6 class="fw-bold text-dark">Tidak ada buku yang sedang dipinjam</h6>
<p class="text-secondary small mb-3">Kamu belum meminjam buku apa pun saat ini.</p>
<a href="<?= site_url('user/buku') ?>" class="btn btn-primary btn-sm">
<i class="bi bi-search"></i> Cari Buku Sekarang
</a>
</div>
</div>
</div>
<?php else: ?>
<div class="row g-3">
<?php foreach ($buku_pinjam as $item): ?>
<div class="col-md-6 col-lg-6">
<div class="card border-0 p-3 h-100">
<div class="d-flex justify-content-between align-items-start mb-3">
<div class="d-flex gap-3 align-items-center">
<?php $cover_url = get_book_cover_url($item->gambar_buku); if ($cover_url): ?>
<img src="<?= $cover_url ?>" alt="<?= html_escape($item->judul_buku) ?>" class="rounded border shadow-sm" style="width: 46px; height: 60px; object-fit: cover;" loading="lazy">
<?php else: ?>
<div class="book-card-icon mb-0" style="width: 46px; height: 46px; font-size: 1.35rem;">
<i class="bi bi-book"></i>
</div>
<?php endif; ?>
<div>
<span class="badge badge-soft-secondary mb-1"><?= html_escape($item->kode_buku) ?></span>
<h5 class="fw-bold text-dark mb-1" style="font-size: 1.05rem;"><?= html_escape($item->judul_buku) ?></h5>
<div class="text-secondary small"><?= html_escape($item->penulis ? $item->penulis : 'โ') ?></div>
</div>
</div>
<span class="badge badge-soft-warning">Dipinjam</span>
</div>
<div class="bg-light p-3 rounded mb-3 small">
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Kode Transaksi:</span>
<span class="fw-semibold text-dark"><?= html_escape($item->kode_transaksi) ?></span>
</div>
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Tanggal Pinjam:</span>
<span class="fw-semibold text-dark"><?= format_tanggal_indo($item->tanggal_pinjam) ?></span>
</div>
<div class="d-flex justify-content-between">
<span class="text-muted">Batas Pengembalian:</span>
<span class="fw-semibold <?= (strtotime($item->batas_kembali) < strtotime(date('Y-m-d'))) ? 'text-danger' : 'text-dark' ?>">
<?= format_tanggal_indo($item->batas_kembali) ?>
</span>
</div>
</div>
<div class="d-flex justify-content-end gap-2 mt-auto">
<a href="<?= site_url('user/buku/detail/' . $item->buku_id) ?>" class="btn btn-light btn-sm">
Detail Buku
</a>
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#kembalikanModal<?= $item->id ?>">
<i class="bi bi-arrow-counterclockwise"></i> Kembalikan Buku
</button>
</div>
<!-- Modal Konfirmasi Pengembalian (Sesuai UI/UX Spec 27) -->
<div class="modal fade" id="kembalikanModal<?= $item->id ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width: 400px;">
<div class="modal-content border-0 shadow">
<div class="modal-header border-bottom-0 pb-0">
<h6 class="modal-title fw-bold text-dark">Kembalikan Buku?</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body py-3">
<p class="text-secondary small mb-2">Pastikan buku fisik sudah siap dikembalikan ke perpustakaan:</p>
<div class="p-2 bg-light rounded mb-2">
<strong class="text-dark d-block"><?= html_escape($item->judul_buku) ?></strong>
<span class="text-muted small">Kode: <?= html_escape($item->kode_buku) ?></span>
</div>
<p class="text-muted small mb-0">Setelah dikembalikan, status transaksi akan dipindahkan ke riwayat.</p>
</div>
<div class="modal-footer border-top-0 pt-0">
<button type="button" class="btn btn-light btn-sm" data-bs-dismiss="modal">Batal</button>
<a href="<?= site_url('user/peminjaman/kembalikan/' . $item->id) ?>" class="btn btn-primary btn-sm">
Ya, Kembalikan
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
8
View Detail Buku Siswa & Modal Pinjam (user/buku/detail.php)
Lokasi Berkas: application/views/user/buku/detail.php
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_user'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="mb-3">
<a href="<?= site_url('user/buku') ?>" class="text-decoration-none text-secondary small fw-semibold">
<i class="bi bi-chevron-left"></i> Kembali ke Cari Buku
</a>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<div class="card border-0 detail-page mb-4">
<div class="card-body p-4">
<div class="d-flex flex-column flex-md-row gap-4 mb-4 pb-4 border-bottom align-items-center align-items-md-start">
<?php $cover_url = get_book_cover_url($buku->gambar); if ($cover_url): ?>
<img src="<?= $cover_url ?>" alt="<?= html_escape($buku->judul) ?>" class="rounded border shadow-sm mx-auto mx-md-0" style="width: 100px; height: 135px; object-fit: cover; flex-shrink: 0;">
<?php else: ?>
<div class="book-card-icon mx-auto mx-md-0" style="width: 84px; height: 84px; font-size: 2.5rem; border-radius: 14px;">
<i class="bi bi-book"></i>
</div>
<?php endif; ?>
<div class="flex-grow-1 text-center text-md-start">
<span class="badge badge-soft-secondary mb-2"><?= html_escape($buku->kode_buku) ?></span>
<h3 class="fw-bold text-dark mb-1"><?= html_escape($buku->judul) ?></h3>
<div class="text-secondary mb-2"><?= html_escape($buku->penulis ? $buku->penulis : 'Penulis tidak dicantumkan') ?></div>
<div class="d-flex flex-wrap gap-2 justify-content-center justify-content-md-start align-items-center mb-3">
<span class="badge badge-soft-info"><?= html_escape($buku->kategori ? $buku->kategori : 'Umum') ?></span>
<span class="text-muted small">|</span>
<?php if ($buku->stok > 0): ?>
<span class="badge badge-soft-success"><i class="bi bi-check-circle me-1"></i> <?= $buku->stok ?> buku tersedia</span>
<?php else: ?>
<span class="badge badge-soft-danger"><i class="bi bi-x-circle me-1"></i> Stok habis</span>
<?php endif; ?>
</div>
<div class="row g-2 text-start small mb-3">
<div class="col-sm-4">
<span class="text-muted">Penerbit:</span> <strong><?= html_escape($buku->penerbit ? $buku->penerbit : 'โ') ?></strong>
</div>
<div class="col-sm-4">
<span class="text-muted">Tahun:</span> <strong><?= $buku->tahun_terbit ? $buku->tahun_terbit : 'โ' ?></strong>
</div>
<div class="col-sm-4">
<span class="text-muted">Lokasi Rak:</span> <strong><?= html_escape($buku->rak ? $buku->rak : 'โ') ?></strong>
</div>
</div>
<!-- Tombol Pinjam / Status Pinjam -->
<div>
<?php if ($is_borrowed): ?>
<button type="button" class="btn btn-warning disabled" disabled>
<i class="bi bi-info-circle"></i> Sedang Kamu Pinjam
</button>
<?php elseif ($buku->stok <= 0): ?>
<button type="button" class="btn btn-light disabled" disabled>
<i class="bi bi-dash-circle"></i> Stok Sedang Habis
</button>
<?php else: ?>
<button type="button" class="btn btn-primary px-4" data-bs-toggle="modal" data-bs-target="#confirmPinjamModal">
<i class="bi bi-journal-plus"></i> Pinjam Buku Ini
</button>
<?php endif; ?>
</div>
</div>
</div>
<!-- Tentang Buku -->
<div>
<h6 class="fw-bold text-dark mb-2">Tentang Buku</h6>
<div class="p-3 bg-light rounded text-secondary" style="line-height: 1.6;">
<?= !empty($buku->deskripsi) ? nl2br(html_escape($buku->deskripsi)) : 'Tidak ada ringkasan deskripsi untuk buku ini.' ?>
</div>
</div>
</div>
</div>
<!-- Modal Konfirmasi Peminjaman (Sesuai UI/UX Spec 25) -->
<?php if (!$is_borrowed && $buku->stok > 0): ?>
<div class="modal fade" id="confirmPinjamModal" tabindex="-1" aria-labelledby="confirmPinjamLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width: 420px;">
<div class="modal-content border-0 shadow">
<div class="modal-header border-bottom-0 pb-0">
<h6 class="modal-title fw-bold text-dark" id="confirmPinjamLabel">Pinjam Buku?</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body py-3">
<p class="text-secondary small mb-3">Kamu akan meminjam koleksi perpustakaan:</p>
<div class="p-3 bg-light rounded mb-3">
<div class="fw-bold text-dark"><?= html_escape($buku->judul) ?></div>
<div class="text-muted small"><?= html_escape($buku->penulis) ?></div>
</div>
<div class="row g-2 small text-secondary">
<div class="col-6">Tanggal Pinjam:</div>
<div class="col-6 fw-semibold text-dark text-end"><?= format_tanggal_indo(date('Y-m-d')) ?></div>
<div class="col-6">Batas Waktu:</div>
<div class="col-6 fw-semibold text-dark text-end"><?= format_tanggal_indo(date('Y-m-d', strtotime('+7 days'))) ?></div>
</div>
</div>
<div class="modal-footer border-top-0 pt-0">
<button type="button" class="btn btn-light btn-sm" data-bs-dismiss="modal">Batal</button>
<a href="<?= site_url('user/buku/pinjam/' . $buku->id) ?>" class="btn btn-primary btn-sm">
Ya, Pinjam Buku
</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
9
View Riwayat Peminjaman Siswa (user/peminjaman/riwayat.php)
Lokasi Berkas: application/views/user/peminjaman/riwayat.php
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_user'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Riwayat Peminjaman</h1>
<p class="page-subtitle">Lihat seluruh riwayat aktivitas peminjaman buku kamu</p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<!-- Filter & Search Toolbar -->
<div class="card border-0 mb-3">
<div class="card-body p-3">
<form action="<?= site_url('user/riwayat') ?>" method="GET" class="row g-2 align-items-center">
<div class="col-md-7 col-lg-8">
<div class="search-input-group">
<i class="bi bi-search"></i>
<input type="text" class="form-control" name="keyword" value="<?= html_escape($keyword) ?>" placeholder="Cari judul buku atau kode transaksi...">
</div>
</div>
<div class="col-md-3 col-lg-3">
<select name="status" class="form-select" onchange="this.form.submit()">
<option value="semua">Semua Status</option>
<option value="dipinjam" <?= $selected_status === 'dipinjam' ? 'selected' : '' ?>>Sedang Dipinjam</option>
<option value="dikembalikan" <?= $selected_status === 'dikembalikan' ? 'selected' : '' ?>>Sudah Dikembalikan</option>
</select>
</div>
<div class="col-md-2 col-lg-1 d-flex gap-1">
<button type="submit" class="btn btn-primary w-100 justify-content-center">
Cari
</button>
<?php if (!empty($keyword) || (!empty($selected_status) && $selected_status !== 'semua')): ?>
<a href="<?= site_url('user/riwayat') ?>" class="btn btn-light" title="Reset">
<i class="bi bi-x-lg"></i>
</a>
<?php endif; ?>
</div>
</form>
</div>
</div>
<!-- Table Card -->
<div class="card border-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>KODE TRX</th>
<th>BUKU</th>
<th>TANGGAL PINJAM</th>
<th>TANGGAL KEMBALI</th>
<th>STATUS</th>
</tr>
</thead>
<tbody>
<?php if (empty($riwayat)): ?>
<tr>
<td colspan="5">
<div class="empty-state">
<div class="empty-state-icon"><i class="bi bi-clock-history"></i></div>
<h6 class="fw-bold text-dark">Belum ada riwayat transaksi</h6>
<p class="text-secondary small mb-0">Riwayat peminjaman buku kamu akan muncul di sini.</p>
</div>
</td>
</tr>
<?php else: ?>
<?php foreach ($riwayat as $item): ?>
<tr>
<td class="fw-semibold text-dark"><?= html_escape($item->kode_transaksi) ?></td>
<td>
<div class="fw-semibold text-dark"><?= html_escape($item->judul_buku) ?></div>
<div class="text-muted small">Kode: <?= html_escape($item->kode_buku) ?> | Penulis: <?= html_escape($item->penulis ? $item->penulis : 'โ') ?></div>
</td>
<td><?= format_tanggal_indo($item->tanggal_pinjam) ?></td>
<td><?= format_tanggal_indo($item->tanggal_kembali) ?></td>
<td>
<?php if ($item->status === 'dipinjam'): ?>
<span class="badge badge-soft-warning"><i class="bi bi-clock-history me-1"></i> Dipinjam</span>
<?php else: ?>
<span class="badge badge-soft-success"><i class="bi bi-check-circle me-1"></i> Dikembalikan</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Footer pagination -->
<?php if ($total_rows > 0): ?>
<div class="card-footer bg-white d-flex justify-content-between align-items-center flex-wrap gap-2 py-3">
<span class="text-secondary small">
Menampilkan <?= min($offset + 1, $total_rows) ?>โ<?= min($offset + $limit, $total_rows) ?> dari <?= $total_rows ?> riwayat
</span>
<div>
<?= $pagination ?>
</div>
</div>
<?php endif; ?>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
10
View Profil Siswa & Upload Foto (user/profil/index.php)
Lokasi Berkas: application/views/user/profil/index.php
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_user'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Profil Saya</h1>
<p class="page-subtitle">Informasi identitas akun anggota perpustakaan digital</p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<div class="card border-0 detail-page" style="max-width: 680px;">
<div class="card-body p-4">
<div class="text-center pb-4 mb-4 border-bottom">
<div class="mx-auto mb-3 d-inline-block position-relative">
<?= get_user_avatar_html($user->foto, $user->nama, 84, 'shadow-sm border') ?>
</div>
<h4 class="fw-bold text-dark mb-1"><?= html_escape($user->nama) ?></h4>
<div class="text-secondary mb-3"><?= html_escape($user->kelas ? $user->kelas : 'Siswa Sekolah') ?></div>
<!-- Form Unggah Foto Profil -->
<div class="d-flex justify-content-center">
<button type="button" class="btn btn-outline-primary btn-sm px-3" data-bs-toggle="modal" data-bs-target="#modalUploadFoto">
<i class="bi bi-camera me-1"></i> Ganti Foto Profil
</button>
</div>
</div>
<!-- Modal Ganti Foto Profil -->
<div class="modal fade" id="modalUploadFoto" tabindex="-1" aria-labelledby="modalUploadFotoLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width: 420px;">
<div class="modal-content border-0 shadow">
<div class="modal-header border-bottom-0 pb-0">
<h6 class="modal-title fw-bold text-dark" id="modalUploadFotoLabel">Perbarui Foto Profil</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<?= form_open_multipart('user/profil/update_foto') ?>
<div class="modal-body py-3">
<p class="text-muted small mb-3">Pilih file gambar foto profil baru (Format: JPG, PNG, WEBP. Maks 2MB).</p>
<input type="file" name="foto" class="form-control" accept="image/png, image/jpeg, image/webp" required>
</div>
<div class="modal-footer border-top-0 pt-0">
<button type="button" class="btn btn-light btn-sm" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary btn-sm px-3">
<i class="bi bi-upload me-1"></i> Simpan Foto
</button>
</div>
<?= form_close() ?>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-sm-6">
<div class="p-3 bg-light rounded">
<span class="text-muted small d-block">Nomor Induk Siswa (NIS)</span>
<strong class="text-dark fs-6"><?= html_escape($user->nis ? $user->nis : 'โ') ?></strong>
</div>
</div>
<div class="col-sm-6">
<div class="p-3 bg-light rounded">
<span class="text-muted small d-block">Kelas</span>
<strong class="text-dark fs-6"><?= html_escape($user->kelas ? $user->kelas : 'โ') ?></strong>
</div>
</div>
<div class="col-sm-6">
<div class="p-3 bg-light rounded">
<span class="text-muted small d-block">Username</span>
<strong class="text-dark fs-6"><?= html_escape($user->username) ?></strong>
</div>
</div>
<div class="col-sm-6">
<div class="p-3 bg-light rounded">
<span class="text-muted small d-block">Status Keanggotaan</span>
<span class="badge badge-soft-success fs-6"><i class="bi bi-check-circle-fill me-1"></i> Aktif</span>
</div>
</div>
<div class="col-12">
<div class="p-3 bg-light rounded d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small d-block">Total Aktivitas Peminjaman</span>
<strong class="text-dark fs-6"><?= $total_pinjam ?> Kali Meminjam Buku</strong>
</div>
<a href="<?= site_url('user/riwayat') ?>" class="btn btn-light btn-sm">
Lihat Riwayat
</a>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>