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.

Fokus Materi: Statistik & CRUD Buku Komponen: Controller, Model & 4 View Fitur: Upload Cover & Pagination
1

Model Dashboard Statistik (Dashboard_model.php)

Lokasi Berkas: application/models/Dashboard_model.php

application/models/Dashboard_model.php 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

application/models/Buku_model.php 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

application/controllers/admin/Buku.php 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

application/controllers/admin/Dashboard.php 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

Tampilan Dashboard Admin
Gambar: Pratinjau antarmuka statistik dan ringkasan transaksi pada Dashboard Administrator (/admin/dashboard).
application/views/admin/dashboard.php PHP
<?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

Tampilan Tabel Data Buku
Gambar: Pratinjau tabel koleksi buku dengan fitur pencarian, filter kategori, dan pagination (/admin/buku).
application/views/admin/buku/index.php PHP
<?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

Tampilan Tambah Buku
Gambar: Pratinjau antarmuka formulir tambah koleksi buku baru (/admin/buku/tambah).
application/views/admin/buku/tambah.php PHP
<?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

Tampilan Edit Buku
Gambar: Pratinjau antarmuka pembaruan data dan penggantian cover buku (/admin/buku/edit/{id}).
application/views/admin/buku/edit.php PHP
<?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

Tampilan Detail Buku
Gambar: Pratinjau informasi lengkap buku dan status ketersediaan stok (/admin/buku/detail/{id}).
application/views/admin/buku/detail.php PHP
<?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.