Modul 05: Admin Dashboard & CRUD Koleksi Buku
Modul 05: Admin Dashboard & CRUD Koleksi Buku
Panduan lengkap pengelolaan inventaris buku perpustakaan dan statistik dashboard administrator berbasis arsitektur MVC CodeIgniter 3.
1
Model Dashboard Statistik (Dashboard_model.php)
Lokasi Berkas: application/models/Dashboard_model.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Dashboard_model
* Model untuk menyajikan statistik dashboard admin & user
*/
class Dashboard_model extends CI_Model {
public function __construct() {
parent::__construct();
}
/**
* Mengambil ringkasan statistik untuk Admin Dashboard
*/
public function get_admin_stats() {
// Total Judul Buku
$total_buku = $this->db->count_all('buku');
// Total Eksemplar Stok Seluruh Buku
$this->db->select_sum('stok');
$stok_query = $this->db->get('buku')->row();
$total_stok = $stok_query->stok ? (int) $stok_query->stok : 0;
// Total Anggota Siswa (Role = user)
$this->db->where('role', 'user');
$total_anggota = $this->db->count_all_results('users');
// Total Transaksi Berstatus 'dipinjam'
$this->db->where('status', 'dipinjam');
$total_dipinjam = $this->db->count_all_results('transaksi');
return [
'total_buku' => $total_buku,
'total_stok' => $total_stok,
'total_anggota' => $total_anggota,
'total_dipinjam' => $total_dipinjam
];
}
/**
* Mengambil 5-10 transaksi terbaru untuk widget admin dashboard
*/
public function get_recent_transactions($limit = 5) {
$this->db->select('transaksi.*, users.nama as nama_siswa, buku.judul as judul_buku');
$this->db->from('transaksi');
$this->db->join('users', 'users.id = transaksi.user_id', 'left');
$this->db->join('buku', 'buku.id = transaksi.buku_id', 'left');
$this->db->order_by('transaksi.created_at', 'DESC');
$this->db->limit($limit);
return $this->db->get()->result();
}
/**
* Mengambil statistik untuk Siswa / User Dashboard
*/
public function get_user_stats($user_id) {
// Total buku tersedia di perpustakaan (stok > 0)
$this->db->where('stok >', 0);
$buku_tersedia = $this->db->count_all_results('buku');
// Total buku sedang dipinjam oleh user ini
$this->db->where('user_id', $user_id);
$this->db->where('status', 'dipinjam');
$sedang_dipinjam = $this->db->count_all_results('transaksi');
// Total buku sudah dikembalikan oleh user ini
$this->db->where('user_id', $user_id);
$this->db->where('status', 'dikembalikan');
$sudah_kembali = $this->db->count_all_results('transaksi');
return [
'buku_tersedia' => $buku_tersedia,
'sedang_dipinjam' => $sedang_dipinjam,
'sudah_kembali' => $sudah_kembali
];
}
}
๐ก Catatan: Baris komentar (// atau /*
*/) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
2
Model Koleksi Buku (Buku_model.php)
Lokasi Berkas: application/models/Buku_model.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Buku_model
* Model untuk pengelolaan data koleksi buku
*/
class Buku_model extends CI_Model {
private $table = 'buku';
public function __construct() {
parent::__construct();
}
/**
* Mengambil semua data buku
*/
public function get_all($limit = NULL, $offset = NULL) {
$this->db->order_by('created_at', 'DESC');
if ($limit !== NULL) {
$this->db->limit($limit, $offset);
}
return $this->db->get($this->table)->result();
}
/**
* Menghitung total buku untuk pagination
*/
public function count_all($search = NULL, $kategori = NULL) {
if (!empty($search)) {
$this->db->group_start();
$this->db->like('kode_buku', $search);
$this->db->or_like('judul', $search);
$this->db->or_like('penulis', $search);
$this->db->or_like('kategori', $search);
$this->db->group_end();
}
if (!empty($kategori)) {
$this->db->where('kategori', $kategori);
}
return $this->db->count_all_results($this->table);
}
/**
* Pencarian buku dengan filter & pagination
*/
public function search($search = NULL, $kategori = NULL, $limit = 10, $offset = 0) {
if (!empty($search)) {
$this->db->group_start();
$this->db->like('kode_buku', $search);
$this->db->or_like('judul', $search);
$this->db->or_like('penulis', $search);
$this->db->or_like('kategori', $search);
$this->db->group_end();
}
if (!empty($kategori)) {
$this->db->where('kategori', $kategori);
}
$this->db->order_by('created_at', 'DESC');
return $this->db->get($this->table, $limit, $offset)->result();
}
/**
* Mengambil daftar kategori unik buku
*/
public function get_categories() {
$this->db->distinct();
$this->db->select('kategori');
$this->db->where('kategori IS NOT NULL');
$this->db->where("kategori != ''");
$this->db->order_by('kategori', 'ASC');
return $this->db->get($this->table)->result();
}
/**
* Mengambil detail buku berdasarkan ID
*/
public function get_by_id($id) {
return $this->db->get_where($this->table, ['id' => $id])->row();
}
/**
* Mengambil data buku berdasarkan Kode Buku
*/
public function get_by_kode($kode) {
return $this->db->get_where($this->table, ['kode_buku' => $kode])->row();
}
/**
* Tambah data buku
*/
public function create($data) {
return $this->db->insert($this->table, $data);
}
/**
* Perbarui data buku
*/
public function update($id, $data) {
$this->db->where('id', $id);
return $this->db->update($this->table, $data);
}
/**
* Hapus data buku
*/
public function delete($id) {
$this->db->where('id', $id);
return $this->db->delete($this->table);
}
/**
* Kurangi stok buku sebanyak 1
*/
public function decrease_stock($id) {
$this->db->set('stok', 'stok - 1', FALSE);
$this->db->where('id', $id);
$this->db->where('stok >', 0);
return $this->db->update($this->table);
}
/**
* Tambah stok buku sebanyak 1
*/
public function increase_stock($id) {
$this->db->set('stok', 'stok + 1', FALSE);
$this->db->where('id', $id);
return $this->db->update($this->table);
}
/**
* Cek apakah buku memiliki transaksi aktif
*/
public function has_active_transaction($buku_id) {
$this->db->where('buku_id', $buku_id);
$this->db->where('status', 'dipinjam');
return $this->db->count_all_results('transaksi') > 0;
}
}
๐ก Catatan: Baris komentar (// atau /*
*/) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
3
Controller Kelola Buku (admin/Buku.php)
Lokasi Berkas: application/controllers/admin/Buku.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Admin Buku Controller
* CRUD Data Buku
*/
class Buku extends CI_Controller {
public function __construct() {
parent::__construct();
check_admin();
$this->load->model('Buku_model');
$this->load->library('pagination');
}
/**
* Daftar Buku dengan Pencarian & Pagination
*/
public function index() {
$search = $this->input->get('keyword', TRUE);
$kategori = $this->input->get('kategori', TRUE);
$limit = 10;
$page = $this->input->get('page') ? (int) $this->input->get('page') : 1;
$offset = ($page - 1) * $limit;
$total_rows = $this->Buku_model->count_all($search, $kategori);
// Konfigurasi Pagination Bootstrap 5
$config['base_url'] = site_url('admin/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 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'] = 'Data Buku - SIPAS';
$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['offset'] = $offset;
$data['limit'] = $limit;
$data['keyword'] = $search;
$data['selected_kategori'] = $kategori;
$this->load->view('admin/buku/index', $data);
}
/**
* Tambah Buku
*/
public function tambah() {
$this->form_validation->set_rules('kode_buku', 'Kode Buku', 'required|trim|is_unique[buku.kode_buku]', [
'required' => 'Kode buku wajib diisi.',
'is_unique' => 'Kode buku ini sudah digunakan.'
]);
$this->form_validation->set_rules('judul', 'Judul Buku', 'required|trim', [
'required' => 'Judul buku wajib diisi.'
]);
$this->form_validation->set_rules('penulis', 'Penulis', 'trim');
$this->form_validation->set_rules('penerbit', 'Penerbit', 'trim');
$this->form_validation->set_rules('tahun_terbit', 'Tahun Terbit', 'numeric');
$this->form_validation->set_rules('kategori', 'Kategori', 'trim');
$this->form_validation->set_rules('stok', 'Stok', 'required|numeric|greater_than_equal_to[0]', [
'required' => 'Stok buku wajib diisi.',
'greater_than_equal_to' => 'Stok minimal 0.'
]);
$this->form_validation->set_rules('rak', 'Lokasi Rak', 'trim');
$this->form_validation->set_rules('deskripsi', 'Deskripsi', 'trim');
if ($this->form_validation->run() === FALSE) {
$data['title'] = 'Tambah Buku - SIPAS';
$this->load->view('admin/buku/tambah', $data);
} else {
$gambar_filename = NULL;
// Proses Upload Gambar Buku jika dipilih
if (!empty($_FILES['gambar']['name'])) {
$upload_config['upload_path'] = './uploads/buku/';
$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, 'buku_upload');
if ($this->buku_upload->do_upload('gambar')) {
$upload_res = $this->buku_upload->data();
$gambar_filename = $upload_res['file_name'];
} else {
$this->session->set_flashdata('error', 'Gagal upload gambar: ' . $this->buku_upload->display_errors('', ''));
redirect('admin/buku/tambah');
return;
}
}
$insert_data = [
'kode_buku' => strtoupper($this->input->post('kode_buku', TRUE)),
'judul' => $this->input->post('judul', TRUE),
'penulis' => $this->input->post('penulis', TRUE),
'penerbit' => $this->input->post('penerbit', TRUE),
'tahun_terbit' => $this->input->post('tahun_terbit', TRUE) ? (int)$this->input->post('tahun_terbit') : NULL,
'kategori' => $this->input->post('kategori', TRUE),
'stok' => (int)$this->input->post('stok'),
'rak' => $this->input->post('rak', TRUE),
'deskripsi' => $this->input->post('deskripsi', TRUE),
'gambar' => $gambar_filename,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
if ($this->Buku_model->create($insert_data)) {
$this->session->set_flashdata('success', 'Data buku berhasil ditambahkan.');
redirect('admin/buku');
} else {
$this->session->set_flashdata('error', 'Gagal menambahkan data buku.');
redirect('admin/buku/tambah');
}
}
}
/**
* Edit Buku
*/
public function edit($id) {
$buku = $this->Buku_model->get_by_id($id);
if (!$buku) {
$this->session->set_flashdata('error', 'Data buku tidak ditemukan.');
redirect('admin/buku');
}
// Jika kode buku diubah, cek keunikan
$kode_rule = 'required|trim';
if ($this->input->post('kode_buku') !== $buku->kode_buku) {
$kode_rule .= '|is_unique[buku.kode_buku]';
}
$this->form_validation->set_rules('kode_buku', 'Kode Buku', $kode_rule);
$this->form_validation->set_rules('judul', 'Judul Buku', 'required|trim');
$this->form_validation->set_rules('stok', 'Stok', 'required|numeric|greater_than_equal_to[0]');
if ($this->form_validation->run() === FALSE) {
$data['title'] = 'Edit Buku - SIPAS';
$data['buku'] = $buku;
$this->load->view('admin/buku/edit', $data);
} else {
$update_data = [
'kode_buku' => strtoupper($this->input->post('kode_buku', TRUE)),
'judul' => $this->input->post('judul', TRUE),
'penulis' => $this->input->post('penulis', TRUE),
'penerbit' => $this->input->post('penerbit', TRUE),
'tahun_terbit' => $this->input->post('tahun_terbit', TRUE) ? (int)$this->input->post('tahun_terbit') : NULL,
'kategori' => $this->input->post('kategori', TRUE),
'stok' => (int)$this->input->post('stok'),
'rak' => $this->input->post('rak', TRUE),
'deskripsi' => $this->input->post('deskripsi', TRUE),
'updated_at' => date('Y-m-d H:i:s')
];
// Proses Upload Gambar Baru jika dipilih
if (!empty($_FILES['gambar']['name'])) {
$upload_config['upload_path'] = './uploads/buku/';
$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, 'buku_upload');
if ($this->buku_upload->do_upload('gambar')) {
$upload_res = $this->buku_upload->data();
$update_data['gambar'] = $upload_res['file_name'];
// Hapus gambar lama jika ada
if (!empty($buku->gambar) && file_exists(FCPATH . 'uploads/buku/' . $buku->gambar)) {
@unlink(FCPATH . 'uploads/buku/' . $buku->gambar);
}
} else {
$this->session->set_flashdata('error', 'Gagal upload gambar: ' . $this->buku_upload->display_errors('', ''));
redirect('admin/buku/edit/' . $id);
return;
}
}
if ($this->Buku_model->update($id, $update_data)) {
$this->session->set_flashdata('success', 'Data buku berhasil diperbarui.');
redirect('admin/buku');
} else {
$this->session->set_flashdata('error', 'Gagal memperbarui data buku.');
redirect('admin/buku/edit/' . $id);
}
}
}
/**
* Detail Buku
*/
public function detail($id) {
$buku = $this->Buku_model->get_by_id($id);
if (!$buku) {
$this->session->set_flashdata('error', 'Data buku tidak ditemukan.');
redirect('admin/buku');
}
$data['title'] = 'Detail Buku - ' . $buku->judul;
$data['buku'] = $buku;
$this->load->view('admin/buku/detail', $data);
}
/**
* Hapus Buku
*/
public function hapus($id) {
$buku = $this->Buku_model->get_by_id($id);
if (!$buku) {
$this->session->set_flashdata('error', 'Data buku tidak ditemukan.');
redirect('admin/buku');
}
// Cek apakah masih memiliki transaksi aktif
if ($this->Buku_model->has_active_transaction($id)) {
$this->session->set_flashdata('error', 'Buku tidak dapat dihapus karena masih memiliki transaksi aktif peminjaman.');
redirect('admin/buku');
return;
}
$gambar = $buku->gambar;
if ($this->Buku_model->delete($id)) {
if (!empty($gambar) && file_exists(FCPATH . 'uploads/buku/' . $gambar)) {
@unlink(FCPATH . 'uploads/buku/' . $gambar);
}
$this->session->set_flashdata('success', 'Data buku berhasil dihapus.');
} else {
$this->session->set_flashdata('error', 'Gagal menghapus buku. Buku mungkin masih memiliki riwayat transaksi.');
}
redirect('admin/buku');
}
}
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
4
Controller Dashboard Admin (admin/Dashboard.php)
Lokasi Berkas: application/controllers/admin/Dashboard.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Admin Dashboard Controller
*/
class Dashboard extends CI_Controller {
public function __construct() {
parent::__construct();
check_admin();
$this->load->model('Dashboard_model');
}
public function index() {
$data['title'] = 'Dashboard Administrator - SIPAS';
$data['stats'] = $this->Dashboard_model->get_admin_stats();
$data['recent_transactions'] = $this->Dashboard_model->get_recent_transactions(5);
$this->load->view('admin/dashboard', $data);
}
}
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
5
View Dashboard Administrator (admin/dashboard.php)
Lokasi Berkas: application/views/admin/dashboard.php
Gambar: Pratinjau antarmuka statistik dan ringkasan transaksi pada Dashboard Administrator (/admin/dashboard).
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_admin'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Dashboard</h1>
<p class="page-subtitle">Ringkasan aktivitas perpustakaan sekolah hari ini</p>
</div>
<div>
<a href="<?= site_url('admin/buku/tambah') ?>" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Tambah Buku
</a>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<!-- Stat Cards Row -->
<div class="row g-3 mb-4">
<div class="col-sm-6 col-xl-3">
<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"><?= number_format($stats['total_buku']) ?></div>
<div class="stat-label">Total Judul Buku</div>
<div class="stat-sub">Koleksi terdaftar</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<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-layers"></i>
</div>
<div>
<div class="stat-number"><?= number_format($stats['total_stok']) ?></div>
<div class="stat-label">Total Stok Buku</div>
<div class="stat-sub">Eksemplar fisik</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card border-0 mb-0">
<div class="stat-card">
<div class="stat-icon" style="background-color: var(--info-soft); color: var(--info);">
<i class="bi bi-people"></i>
</div>
<div>
<div class="stat-number"><?= number_format($stats['total_anggota']) ?></div>
<div class="stat-label">Anggota Siswa</div>
<div class="stat-sub">Siswa terdaftar</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<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-arrow-up-right"></i>
</div>
<div>
<div class="stat-number"><?= number_format($stats['total_dipinjam']) ?></div>
<div class="stat-label">Sedang Dipinjam</div>
<div class="stat-sub">Buku aktif di luar</div>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Transactions Table -->
<div class="card border-0">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-clock-history me-1 text-primary"></i> Transaksi Terbaru</span>
<a href="<?= site_url('admin/transaksi') ?>" class="text-decoration-none small fw-semibold text-primary">
Lihat semua <i class="bi bi-arrow-right"></i>
</a>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>KODE</th>
<th>SISWA</th>
<th>BUKU</th>
<th>TANGGAL PINJAM</th>
<th>STATUS</th>
</tr>
</thead>
<tbody>
<?php if (empty($recent_transactions)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">
Belum ada aktivitas transaksi peminjaman.
</td>
</tr>
<?php else: ?>
<?php foreach ($recent_transactions as $trx): ?>
<tr>
<td class="fw-semibold text-dark"><?= html_escape($trx->kode_transaksi) ?></td>
<td><?= html_escape($trx->nama_siswa) ?></td>
<td><?= html_escape($trx->judul_buku) ?></td>
<td><?= format_tanggal_indo($trx->tanggal_pinjam) ?></td>
<td>
<?php if ($trx->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>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
6
View Daftar Buku & Pencarian (admin/buku/index.php)
Lokasi Berkas: application/views/admin/buku/index.php
Gambar: Pratinjau tabel koleksi buku dengan fitur pencarian, filter kategori, dan pagination (/admin/buku).
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_admin'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="page-header">
<div>
<h1 class="page-title">Data Buku</h1>
<p class="page-subtitle">Kelola seluruh koleksi buku perpustakaan</p>
</div>
<div>
<a href="<?= site_url('admin/buku/tambah') ?>" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Tambah Buku
</a>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<!-- Search & Filter Card -->
<div class="card border-0 mb-3">
<div class="card-body p-3">
<form action="<?= site_url('admin/buku') ?>" 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 kode, judul atau penulis buku...">
</div>
</div>
<div class="col-md-3 col-lg-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-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_kategori)): ?>
<a href="<?= site_url('admin/buku') ?>" class="btn btn-light" title="Reset filter">
<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 style="width: 50px;">NO</th>
<th>KODE</th>
<th>JUDUL BUKU</th>
<th>PENULIS</th>
<th>KATEGORI</th>
<th>STOK</th>
<th>RAK</th>
<th style="width: 70px;" class="text-center">AKSI</th>
</tr>
</thead>
<tbody>
<?php if (empty($buku)): ?>
<tr>
<td colspan="8">
<div class="empty-state">
<div class="empty-state-icon"><i class="bi bi-book"></i></div>
<h6 class="fw-bold text-dark">Belum ada data buku</h6>
<p class="text-secondary small mb-3">Mulai tambahkan koleksi buku perpustakaan Anda.</p>
<a href="<?= site_url('admin/buku/tambah') ?>" class="btn btn-primary btn-sm">
<i class="bi bi-plus-lg"></i> Tambah Buku Baru
</a>
</div>
</td>
</tr>
<?php else: ?>
<?php $no = $offset + 1; foreach ($buku as $item): ?>
<tr>
<td><?= $no++ ?></td>
<td class="fw-semibold text-dark"><?= html_escape($item->kode_buku) ?></td>
<td>
<div class="d-flex align-items-center gap-2">
<?php $cover_url = get_book_cover_url($item->gambar); if ($cover_url): ?>
<img src="<?= $cover_url ?>" alt="<?= html_escape($item->judul) ?>" class="rounded border" style="width: 36px; height: 48px; object-fit: cover; flex-shrink: 0;" loading="lazy">
<?php else: ?>
<div class="bg-light border rounded d-flex align-items-center justify-content-center text-muted" style="width: 36px; height: 48px; flex-shrink: 0; font-size: 14px;">
<i class="bi bi-book"></i>
</div>
<?php endif; ?>
<div>
<div class="fw-semibold text-dark"><?= html_escape($item->judul) ?></div>
<div class="text-muted small"><?= html_escape($item->penerbit) ?> (<?= $item->tahun_terbit ? $item->tahun_terbit : 'โ' ?>)</div>
</div>
</div>
</td>
<td><?= html_escape($item->penulis ? $item->penulis : 'โ') ?></td>
<td>
<span class="badge badge-soft-secondary"><?= html_escape($item->kategori ? $item->kategori : 'Umum') ?></span>
</td>
<td>
<?php if ($item->stok > 0): ?>
<span class="badge badge-soft-success"><?= $item->stok ?> eks</span>
<?php else: ?>
<span class="badge badge-soft-danger">Habis</span>
<?php endif; ?>
</td>
<td><?= html_escape($item->rak ? $item->rak : 'โ') ?></td>
<td class="text-center">
<div class="dropdown">
<button class="btn btn-light btn-sm p-1 border-0" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
<li>
<a class="dropdown-item py-2 small" href="<?= site_url('admin/buku/detail/' . $item->id) ?>">
<i class="bi bi-eye text-info me-2"></i> Detail
</a>
</li>
<li>
<a class="dropdown-item py-2 small" href="<?= site_url('admin/buku/edit/' . $item->id) ?>">
<i class="bi bi-pencil text-warning me-2"></i> Edit
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<button type="button" class="dropdown-item py-2 small text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal<?= $item->id ?>">
<i class="bi bi-trash me-2"></i> Hapus
</button>
</li>
</ul>
</div>
<!-- Modal Konfirmasi Hapus -->
<div class="modal fade text-start" id="deleteModal<?= $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">Hapus 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="mb-1 text-secondary small">
Buku "<strong><?= html_escape($item->judul) ?></strong>" akan dihapus dari sistem.
</p>
<p class="text-danger small mb-0">Tindakan ini tidak dapat dibatalkan.</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('admin/buku/hapus/' . $item->id) ?>" class="btn btn-danger btn-sm">
Hapus Buku
</a>
</div>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Footer info & 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 ?> data buku
</span>
<div>
<?= $pagination ?>
</div>
</div>
<?php endif; ?>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
7
View Formulir Tambah Buku (admin/buku/tambah.php)
Lokasi Berkas: application/views/admin/buku/tambah.php
Gambar: Pratinjau antarmuka formulir tambah koleksi buku baru (/admin/buku/tambah).
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_admin'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="mb-3">
<a href="<?= site_url('admin/buku') ?>" class="text-decoration-none text-secondary small fw-semibold">
<i class="bi bi-chevron-left"></i> Kembali ke Data Buku
</a>
</div>
<div class="page-header">
<div>
<h1 class="page-title">Tambah Buku</h1>
<p class="page-subtitle">Masukkan informasi lengkap buku baru untuk koleksi perpustakaan</p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<div class="card border-0 form-page">
<div class="card-body p-4">
<?= form_open_multipart('admin/buku/tambah') ?>
<div class="row g-3 mb-3">
<div class="col-md-4">
<label for="kode_buku" class="form-label">Kode Buku <span class="text-danger">*</span></label>
<input type="text" class="form-control text-uppercase" id="kode_buku" name="kode_buku" value="<?= set_value('kode_buku') ?>" placeholder="Contoh: BK013" required>
<div class="form-text">Harus unik, maksimal 30 karakter.</div>
</div>
<div class="col-md-8">
<label for="judul" class="form-label">Judul Buku <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="judul" name="judul" value="<?= set_value('judul') ?>" placeholder="Judul lengkap buku" required>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="penulis" class="form-label">Nama Penulis</label>
<input type="text" class="form-control" id="penulis" name="penulis" value="<?= set_value('penulis') ?>" placeholder="Nama penulis / pengarang">
</div>
<div class="col-md-6">
<label for="penerbit" class="form-label">Penerbit</label>
<input type="text" class="form-control" id="penerbit" name="penerbit" value="<?= set_value('penerbit') ?>" placeholder="Nama penerbit">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-4">
<label for="tahun_terbit" class="form-label">Tahun Terbit</label>
<input type="number" class="form-control" id="tahun_terbit" name="tahun_terbit" value="<?= set_value('tahun_terbit', date('Y')) ?>" min="1900" max="<?= date('Y') + 1 ?>">
</div>
<div class="col-md-4">
<label for="kategori" class="form-label">Kategori</label>
<input type="text" class="form-control" id="kategori" name="kategori" value="<?= set_value('kategori') ?>" placeholder="Contoh: Pemrograman, Jaringan, Novel">
</div>
<div class="col-md-4">
<label for="rak" class="form-label">Lokasi Rak</label>
<input type="text" class="form-control" id="rak" name="rak" value="<?= set_value('rak') ?>" placeholder="Contoh: RPL-A01">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="stok" class="form-label">Jumlah Stok Eksemplar <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stok" name="stok" value="<?= set_value('stok', 1) ?>" min="0" required>
</div>
<div class="col-md-6">
<label for="gambar" class="form-label">Cover / Gambar Buku (Opsional)</label>
<input type="file" class="form-control" id="gambar" name="gambar" accept="image/png, image/jpeg, image/webp">
<div class="form-text">Format: JPG, PNG, WEBP. Maksimal 2MB.</div>
</div>
</div>
<div class="mb-4">
<label for="deskripsi" class="form-label">Deskripsi / Sinopsis Buku</label>
<textarea class="form-control" id="deskripsi" name="deskripsi" rows="3" placeholder="Ringkasan atau gambaran umum isi buku..."><?= set_value('deskripsi') ?></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary px-4">
<i class="bi bi-check-lg"></i> Simpan Buku
</button>
<a href="<?= site_url('admin/buku') ?>" class="btn btn-light px-4">Batal</a>
</div>
</form>
</div>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
8
View Formulir Edit Buku (admin/buku/edit.php)
Lokasi Berkas: application/views/admin/buku/edit.php
Gambar: Pratinjau antarmuka pembaruan data dan penggantian cover buku (/admin/buku/edit/{id}).
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_admin'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="mb-3">
<a href="<?= site_url('admin/buku') ?>" class="text-decoration-none text-secondary small fw-semibold">
<i class="bi bi-chevron-left"></i> Kembali ke Data Buku
</a>
</div>
<div class="page-header">
<div>
<h1 class="page-title">Edit Buku</h1>
<p class="page-subtitle">Perbarui informasi buku <?= html_escape($buku->kode_buku) ?></p>
</div>
</div>
<?php $this->load->view('layouts/flash_message'); ?>
<div class="card border-0 form-page">
<div class="card-body p-4">
<?= form_open_multipart('admin/buku/edit/' . $buku->id) ?>
<div class="row g-3 mb-3">
<div class="col-md-4">
<label for="kode_buku" class="form-label">Kode Buku <span class="text-danger">*</span></label>
<input type="text" class="form-control text-uppercase" id="kode_buku" name="kode_buku" value="<?= set_value('kode_buku', $buku->kode_buku) ?>" required>
</div>
<div class="col-md-8">
<label for="judul" class="form-label">Judul Buku <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="judul" name="judul" value="<?= set_value('judul', $buku->judul) ?>" required>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="penulis" class="form-label">Penulis</label>
<input type="text" class="form-control" id="penulis" name="penulis" value="<?= set_value('penulis', $buku->penulis) ?>">
</div>
<div class="col-md-6">
<label for="penerbit" class="form-label">Penerbit</label>
<input type="text" class="form-control" id="penerbit" name="penerbit" value="<?= set_value('penerbit', $buku->penerbit) ?>">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-4">
<label for="tahun_terbit" class="form-label">Tahun Terbit</label>
<input type="number" class="form-control" id="tahun_terbit" name="tahun_terbit" value="<?= set_value('tahun_terbit', $buku->tahun_terbit) ?>" min="1900" max="<?= date('Y') + 1 ?>">
</div>
<div class="col-md-4">
<label for="kategori" class="form-label">Kategori</label>
<input type="text" class="form-control" id="kategori" name="kategori" value="<?= set_value('kategori', $buku->kategori) ?>">
</div>
<div class="col-md-4">
<label for="rak" class="form-label">Lokasi Rak</label>
<input type="text" class="form-control" id="rak" name="rak" value="<?= set_value('rak', $buku->rak) ?>">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="stok" class="form-label">Jumlah Stok Eksemplar <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stok" name="stok" value="<?= set_value('stok', $buku->stok) ?>" min="0" required>
</div>
<div class="col-md-6">
<label for="gambar" class="form-label">Ganti Cover Buku (Opsional)</label>
<input type="file" class="form-control" id="gambar" name="gambar" accept="image/png, image/jpeg, image/webp">
<div class="form-text">Biarkan kosong jika tidak ingin mengubah cover. Format: JPG, PNG, WEBP.</div>
<?php $cover_url = get_book_cover_url($buku->gambar); if ($cover_url): ?>
<div class="mt-2 d-flex align-items-center gap-2">
<img src="<?= $cover_url ?>" alt="Cover Saat Ini" class="rounded border" style="width: 50px; height: 65px; object-fit: cover;">
<span class="small text-muted">Cover saat ini aktif</span>
</div>
<?php endif; ?>
</div>
</div>
<div class="mb-4">
<label for="deskripsi" class="form-label">Deskripsi / Sinopsis Buku</label>
<textarea class="form-control" id="deskripsi" name="deskripsi" rows="3"><?= set_value('deskripsi', $buku->deskripsi) ?></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary px-4">
<i class="bi bi-check-lg"></i> Simpan Perubahan
</button>
<a href="<?= site_url('admin/buku') ?>" class="btn btn-light px-4">Batal</a>
</div>
</form>
</div>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
9
View Detail Buku (admin/buku/detail.php)
Lokasi Berkas: application/views/admin/buku/detail.php
Gambar: Pratinjau informasi lengkap buku dan status ketersediaan stok (/admin/buku/detail/{id}).
<?php $this->load->view('layouts/header'); ?>
<div class="app-wrapper">
<?php $this->load->view('layouts/sidebar_admin'); ?>
<div class="app-main">
<?php $this->load->view('layouts/navbar'); ?>
<main class="app-content">
<div class="mb-3">
<a href="<?= site_url('admin/buku') ?>" class="text-decoration-none text-secondary small fw-semibold">
<i class="bi bi-chevron-left"></i> Kembali ke Data Buku
</a>
</div>
<div class="page-header">
<div>
<h1 class="page-title">Detail Buku</h1>
<p class="page-subtitle">Informasi lengkap data koleksi perpustakaan</p>
</div>
<div class="d-flex gap-2">
<a href="<?= site_url('admin/buku/edit/' . $buku->id) ?>" class="btn btn-primary">
<i class="bi bi-pencil"></i> Edit Buku
</a>
</div>
</div>
<div class="card border-0 detail-page">
<div class="card-body p-4">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start gap-3 mb-3 pb-3 border-bottom">
<div class="d-flex align-items-start gap-3">
<?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" style="width: 80px; height: 110px; object-fit: cover; flex-shrink: 0;">
<?php else: ?>
<div class="bg-light border rounded d-flex align-items-center justify-content-center text-muted" style="width: 80px; height: 110px; flex-shrink: 0;">
<i class="bi bi-book fs-2"></i>
</div>
<?php endif; ?>
<div>
<span class="badge badge-soft-secondary mb-2"><?= html_escape($buku->kode_buku) ?></span>
<h4 class="fw-bold text-dark mb-1"><?= html_escape($buku->judul) ?></h4>
<div class="text-secondary"><?= html_escape($buku->penulis ? $buku->penulis : 'Penulis tidak dicantumkan') ?></div>
</div>
</div>
<div>
<?php if ($buku->stok > 0): ?>
<span class="badge badge-soft-success fs-6 px-3 py-2"><i class="bi bi-check-circle me-1"></i> Tersedia (<?= $buku->stok ?> buku)</span>
<?php else: ?>
<span class="badge badge-soft-danger fs-6 px-3 py-2"><i class="bi bi-x-circle me-1"></i> Stok Habis</span>
<?php endif; ?>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-sm-6 col-md-3">
<div class="text-muted small">Penerbit</div>
<div class="fw-semibold text-dark"><?= html_escape($buku->penerbit ? $buku->penerbit : 'โ') ?></div>
</div>
<div class="col-sm-6 col-md-3">
<div class="text-muted small">Tahun Terbit</div>
<div class="fw-semibold text-dark"><?= $buku->tahun_terbit ? $buku->tahun_terbit : 'โ' ?></div>
</div>
<div class="col-sm-6 col-md-3">
<div class="text-muted small">Kategori</div>
<div class="fw-semibold text-dark"><?= html_escape($buku->kategori ? $buku->kategori : 'Umum') ?></div>
</div>
<div class="col-sm-6 col-md-3">
<div class="text-muted small">Lokasi Rak</div>
<div class="fw-semibold text-dark"><?= html_escape($buku->rak ? $buku->rak : 'โ') ?></div>
</div>
</div>
<div>
<div class="fw-bold text-dark mb-2">Deskripsi / Sinopsis:</div>
<div class="p-3 bg-light rounded text-secondary" style="line-height: 1.6;">
<?= !empty($buku->deskripsi) ? nl2br(html_escape($buku->deskripsi)) : 'Tidak ada deskripsi untuk buku ini.' ?>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<?php $this->load->view('layouts/footer'); ?>
๐ก Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.