# Admin rebuild (CI4 + TailAdmin + API) Ini **bukan** migrasi modul admin HMVC CodeIgniter 3. Panel admin dibangun ulang di CodeIgniter 4 dengan UI Tailwind (aset TailAdmin di `public/assets/tailadmin/`) dan **tanpa query database langsung dari controller web admin**. Semua data modul Pegawai, Presensi, Laporan ringkasan, Dashboard beranda, dan Cuti diambil lewat **API internal** (`/api/admin/*` dan untuk profil/berita tetap `/api/mobile/*`). ## Prinsip - Controller di `app/Controllers/Admin/` hanya memanggil `ApiClient` → HTTP ke aplikasi yang sama. - Tidak menyalin HMVC CI3, tidak memakai Grocery CRUD. - Akses database untuk fitur admin terpusat di `app/Services/Admin/AdminApiService.php` + model CI4 (`PegawaiModel`, dll.). - Respons API diseragamkan: `{ "status": 0|1, "pesan": "...", "data": ... }` (selaras API mobile). ## RBAC (peran & fitur) - **Konfigurasi:** `app/Config/AdminAccess.php` memetakan **nama fitur** → daftar **slug grup Ion** yang boleh. Daftar kosong artinya **semua admin yang sudah login** (setelah `authadmin`). - **Helper:** `app/Helpers/rbac_helper.php` — `hasRole()`, `hasAnyRole()`, `canAccess($feature)`. Membaca **`admin_ion_groups`** dari sesi (nama grup, perbandingan case-insensitive). - **Kapan aturan Ion dipakai:** jika `admin_auth_source === 'admin_users'`. Login **pegawai** saja (tanpa grup Ion di sesi) mempertahankan perilaku lama: **`canAccess` mengizinkan semua fitur** yang punya daftar grup (kompatibel token proxy). Ini selaras kebutuhan token API pegawai untuk semua endpoint admin. - **Jika `admin_users` tetapi grup kosong:** fitur yang membutuhkan grup tertentu → **ditolak** (menu disembunyikan, `enforceAccess` redirect dengan flash). - **Controller web:** `BaseAdminController::enforceAccess('nama_fitur')` di awal aksi; gagal → redirect ke `/admin` dengan flash **error**, atau JSON 403 untuk AJAX. - **View:** hanya memanggil `canAccess('...')` — **tanpa menuliskan nama peran** di view; semua mapping ada di `AdminAccess`. ### Pemetaan fitur ↔ grup (sama `ci_bootstrap.php` CI3) | Fitur (`canAccess`) | Grup yang boleh | |---------------------|-----------------| | `dashboard` | (semua login admin) | | `presensi` | (semua) | | `perusahaan` | (semua) | | `pegawai` | `webmaster`, `hrd` | | `cuti` | `webmaster`, `hrd` | | `laporan` | `webmaster`, `hrd` | | `panel` | `webmaster` | | `utilitas` | `webmaster` | | `apk_link` | `webmaster`, `penyelenggara`, `operator_cabang`, `operator_ranting`, `operator_sekolah`, `admin_soal`, `operator_soal` | | `references` | (semua login admin) — referensi form pegawai | ## Autentikasi web 1. `GET/POST /admin/login` — `Auth::attempt` urutannya: - **Pegawai:** `POST /api/mobile/login` (MD5 password, tabel `pegawai`). - Jika gagal: **`admin_users`** (Ion Auth CI3): `password_verify` terhadap bcrypt/hash di kolom `password`, `active = 1`. Jika sukses, sistem menerbitkan token API dengan memakai **pegawai proxy** (lihat bawah) dan menyimpan token yang sama di sesi `admin_mobile_token`. Sesi juga menyimpan `admin_auth_source` = `pegawai` | `admin_users`. 2. **Data Ion Auth di DB:** `admin_users`, `admin_groups`, `admin_users_groups`. Login `admin_users` **wajib punya minimal satu baris** di `admin_users_groups` bila tabel itu ada — tanpa grup, login ditolak. 3. **Proxy token (login `admin_users`):** API hanya mengenali token di `pegawai`. Setelah Ion Auth sukses, token ditulis ke pegawai proxy: `ADMIN_LOGIN_PROXY_PEGAWAI_ID` (`.env`), lalu pegawai pertama `super_akses = 'true'`, lalu `id_pegawai` terkecil. Untuk akun **hanya HRD**, bisa set `ADMIN_LOGIN_PROXY_PEGAWAI_ID_HRD`. 4. Sesi menyimpan `admin_ion_user_id` dan **`admin_ion_groups`** (nama grup) bila sumber `admin_users` — dipakai RBAC + header. 5. `GET /admin/logout` — di luar filter. 6. Rute `admin/*` lain memakai filter **`authadmin`**: tanpa token sesi → redirect ke login (atau JSON 401 untuk AJAX). ## API admin (`/api/admin/*`) Parameter **`token`** (sama dengan `admin_mobile_token` di sesi panel) tetap dikirim lewat query GET, field POST, atau header `X-Admin-Token` — **struktur token untuk klien mobile tidak diubah** (`/api/mobile/*` tidak tersentuh). ### Admin API Security - **Sesi panel wajib:** setiap `/api/admin/*` memverifikasi bahwa permintaan membawa **cookie sesi** yang valid dan berisi `admin_mobile_token`, serta bahwa **`token` di URL/body/header sama persis** dengan nilai di sesi. Tanpa itu, respons **`401`** dengan JSON `{ "status": 0, "pesan": "Unauthorized" }` — sehingga **curl/Postman hanya dengan `?token=` pegawai** (tanpa cookie login admin) ditolak. - **RBAC di API:** setelah sesi valid, `BaseAdminApiController::requireAdminApiAccess('fitur')` memanggil `canAccess()` (sama `Config\AdminAccess` + `admin_ion_groups` seperti UI). Gagal → **`403`** dengan `{ "status": 0, "pesan": "Forbidden" }`. - **Penerusan cookie internal:** `ApiClient::getAdmin` / `postAdmin` meneruskan header `Cookie` dari permintaan browser ke hit HTTP internal, dan memanggil `session_write_close()` sebelum CURL agar menghindari **deadlock** file session (sub-request dapat membuka sesi yang sama). - **Logging teks:** percobaan ditolak dicatat ke log aplikasi (`log_message('warning', ...)`) dengan alasan singkat, alamat IP, path URI, dan timestamp ISO-8601; lihat juga **Audit Logging System** di bawah untuk rekaman DB. - **Token admin terpisah:** tidak wajib; tidak mengganti model sesi saat ini. ### Audit Logging System - **Tabel:** `admin_activity_logs` (migrasi `app/Database/Migrations/2026-04-18-140000_CreateAdminActivityLogs.php`). Kolom: `id`, `admin_user`, `action`, `endpoint`, `payload` (JSON teks), `ip_address`, `user_agent`, `auth_source`, `roles_json` (JSON grup Ion dari sesi), `outcome` (`success` | `unauthorized` | `forbidden`), `created_at`. - **Layanan:** `app/Services/AdminAuditService.php` — metode **`log(string $action, array $payload = [])`**. Menyisipkan konteks HTTP (`_http`), panel (`_session_panel`: sumber auth, grup Ion, indikator token admin), ringkasan **actor pegawai** (dari token API), dan payload bisnis. Nilai sensitif (`password`, `token`, dll.) diganti `[redacted]`. - **Integrasi API:** setelah `requireAdminApiAccess` sukses, setiap endpoint di `app/Controllers/Api/Admin/*` memanggil `auditAuthorized()` di `BaseAdminApiController` dengan nama aksi konsisten (`api.admin.cuti.approve`, `api.admin.pegawai.update`, …). Mutasi penting (approve/reject cuti, create/update/delete/reset pegawai) menyertakan ID / field ringkas di payload. - **Keamanan:** percobaan **Unauthorized** / **Forbidden** juga ditulis lewat `log()` dengan `__outcome` = `unauthorized` | `forbidden` dan aksi `api.admin.security.unauthorized` / `api.admin.security.forbidden`. - **Performa:** insert sinkron ringan; payload dibatasi panjang; jika tabel belum ada, tulisan dilewati (debug log) agar tidak memutus deploy. | Metode | Path | Keterangan | |--------|------|------------| | GET | `/api/admin/references` | Dropdown: jabatan, unit_kerja, golongan, kantor, jadwal | | GET | `/api/admin/dashboard` | Statistik beranda + antrian cuti Waiting (20 baris + total) | | GET | `/api/admin/pegawai` | Daftar pegawai + pagination (`page`, `per_page`, `q`) | | GET | `/api/admin/pegawai/{id}` | Detail pegawai | | POST | `/api/admin/pegawai/create` | Tambah pegawai | | POST | `/api/admin/pegawai/update` | Body termasuk `id_pegawai` | | POST | `/api/admin/pegawai/delete` | Body `id_pegawai` | | POST | `/api/admin/pegawai/reset_password` | Body `id_pegawai` | | GET | `/api/admin/presensi` | Daftar presensi (`tanggal_dari`, `tanggal_sampai`, `page`, `per_page`, **`q`** nama/NIP) | | GET | `/api/admin/presensi/{id}` | Detail presensi | | GET | `/api/admin/laporan` | Ringkasan agregat (`dari`, `sampai`) | | GET | `/api/admin/cuti` | Daftar cuti (`status` kosong=semua, `Waiting`, `Approve`, `Rejected`, `Cancelled`, `page`, `per_page`) | | GET | `/api/admin/cuti/{id}` | Detail cuti + daftar file dokumen | | POST | `/api/admin/cuti/approve` | Body `id_cuti` | | POST | `/api/admin/cuti/reject` | Body `id_cuti`, `alasan_tolak` | | GET | `/api/admin/laporan/cuti` | Laporan cuti rentang (`dari`, `sampai`) | | GET/POST | `/api/admin/company/*` | Master perusahaan (kantor, unit, golongan, jabatan, berita) | | GET/POST | `/api/admin/presensi/dilapangan|lembur|libur|jadwal|aktivitas` | Alat presensi CI3 | | GET/POST | `/api/admin/panel/users|groups|…` | Panel pengguna Ion | | GET/POST | `/api/admin/backup*` | Daftar / jalankan / hapus file backup SQL | Controller API: `app/Controllers/Api/Admin/*`. Rute di `app/Config/Routes.php` grup `api/admin`. Dasar kelas: `BaseAdminApiController` (sesi + ikatan token + RBAC per fitur). ## Rute web admin | Path | Fungsi | |------|--------| | `/admin/login`, POST login | Login | | `/admin/logout` | Keluar | | `/admin` | Dashboard (API `dashboard` + profil/berita mobile) | | `/admin/pegawai` | Daftar pegawai | | `/admin/pegawai/create`, POST `store` | Tambah | | `/admin/pegawai/edit/{id}`, POST `update/{id}` | Edit | | POST `pegawai/delete/{id}` | Hapus | | POST `pegawai/reset/{id}` | Reset password | | `/admin/presensi` | Daftar + filter tanggal + **cari nama/NIP** | | `/admin/presensi/detail/{id}` | Detail | | `/admin/presensi/lapangan` … `aktivitas` | Master alat presensi (dilapangan, lembur, libur, jadwal, aktivitas) | | `/admin/perusahaan/kantor` … `berita` | CRUD master perusahaan | | `/admin/laporan` | Ringkasan + filter tanggal | | `/admin/laporan/cuti` | Tabel cuti rentang tanggal | | `/admin/panel/users`, `create`, `reset/{id}` | Pengguna admin Ion | | `/admin/util/backup` | Backup DB (file di `writable/admin_db_backup/`) | | `/admin/cuti` | Daftar cuti + filter status | | `/admin/cuti/detail/{id}` | Detail + approve/reject | | POST `/admin/cuti/approve/{id}` | Setujui | | POST `/admin/cuti/reject/{id}` | Tolak + alasan | ## `ApiClient` & `BaseAdminController` - `getAdmin` / `postAdmin` — ke `/api/admin/*` dengan token otomatis + **penerusan `Cookie`** untuk sesi panel. - `BaseAdminController::enforceAccess($feature)` — RBAC fitur di UI web. ## Konfigurasi `app.baseURL` di `.env` harus benar agar panggilan internal CURLRequest berhasil. ## Modul vs CI3 — parity & celah **Sudah mendekati CI3 (usable):** - Dashboard beranda: kartu pegawai/presensi/cuti hari ini, bar ringkasan, tabel permohonan cuti Waiting + aksi cepat (bila `canAccess('cuti')`). - Cuti admin: daftar, detail, approve/reject (status `Approve` / `Rejected` + `alasan_tolak`), dokumen di `assets/uploads/dokcuti/`. - Pegawai, presensi (dengan badge status baris), laporan ringkasan. **Masih berbeda / lanjutan:** - **Lingkup data** di `AdminApiService` tetap mengikuti baris pegawai yang memiliki `token` (proxy); **siapa boleh memanggil endpoint** kini dijaga sesi + RBAC seperti UI. - Laporan **keuangan** CI3 (piutang, penjualan, laba, dll.) tidak di-port ke modul admin CI4 ini. - **Cetak** khusus seperti PHPExcel di CI3 diganti dengan tabel web + cetak browser / integrasi via endpoint JSON bila diperlukan. ## Peta halaman CI3 (discovery) Daftar lengkap URL, controller, method, peran, dan pemetaan ke CI4 ada di **`docs/migration/admin-pages-map.md`**. File itu menjadi acuan agar tidak ada halaman CI3 yang “hilang” dari checklist tanpa sengaja. ## UI shell TailAdmin (dashboard) **Tujuan:** tampilan konsisten seperti demo TailAdmin (sidebar terang, border `gray-200`, kartu `rounded-2xl`, header sticky, tabel dengan header abu), **tanpa** memuat `public/assets/tailadmin/css/style.css` mentah di browser — file itu memakai `@import "tailwindcss"` / `@apply` dan membutuhkan **build npm**, bukan CSS siap pakai. Untuk kelas utilitas dari markup PHP, tetap memakai **Tailwind CDN** di `layouts/main.php` + **`layouts/auth.php`**; aset **lokal** yang dipakai: Font Awesome (`fa-7.1.0-web/css/all.min.css`) dan logo brand `public/assets/images/bpr-logo.png` (sidebar + halaman login). **Layout admin (`layouts/main.php`):** - `#admin-shell`: flex penuh tinggi viewport, latar `bg-gray-50`. - **Sidebar** (`layouts/sidebar.php`): lebar 290px, putih, border kanan; menu mengikuti `ci_bootstrap.php` CI3; teks judul memakai kelas `sidebar-text` / `nav-section-label` agar bisa disembunyikan saat mode **sempit desktop** (`sidebar-narrow` + localStorage). - **Mobile (< lg):** sidebar `transform: translateX(-100%)`; tombol hamburger di header membuka kelas `mobile-sidebar-open` pada `#admin-shell`; overlay `#sidebar-overlay` menutup panel ketika diketuk. - **Desktop:** tombol kolom di header men-toggle `sidebar-narrow` (lebar sidebar 90px, label disembunyikan). - **Header** (`layouts/header.php`): sticky, flash message, info sesi singkat. - **Footer** (`layouts/footer.php`): strip tipis di bawah konten scroll. **Struktur view modul (rapi):** | Modul | Path view | |-------|------------| | Beranda | `app/Views/admin/dashboard/index.php` | | Login | `app/Views/admin/auth/login.php` | | Pegawai | `app/Views/admin/pegawai/index.php`, `form.php` | | Presensi | `app/Views/admin/presensi/index.php`, `detail.php` | | Cuti | `app/Views/admin/cuti/index.php`, `detail.php` | | Laporan | `app/Views/admin/laporan/index.php`, `laporan/cuti.php` | | Perusahaan | `app/Views/admin/perusahaan/*.php` | | Presensi (alat) | `app/Views/admin/presensi/lapangan.php`, `lembur.php`, `libur.php`, `jadwal.php`, `aktivitas.php` | | Panel | `app/Views/admin/panel/users.php`, `user_create.php`, `user_reset.php` | | Utilitas | `app/Views/admin/util/backup.php` | Semua halaman admin di atas memakai `extend('layouts/main') ?>` (login memakai `layouts/auth`). ## Halaman selesai vs sisa (web admin) **Selesai (parity operasional harian + UI TailAdmin):** - Beranda / dashboard (`/admin`) - Data presensi + detail + pencarian (`/admin/presensi`, detail) - Presensi: tugas luar/lapangan, lembur, management jadwal, hari libur, rekaman aktivitas - Perusahaan: lokasi kerja (kantor), unit kerja, jabatan, golongan, berita/pengumuman - Data pegawai + form tambah/edit + hapus + reset password (`/admin/pegawai`, …) - Data cuti + detail approve/reject (`/admin/cuti`, detail) - Ringkasan laporan (`/admin/laporan`) + laporan cuti rentang (`/admin/laporan/cuti`, cetak via browser) - Panel: daftar pengguna admin Ion, tambah pengguna (bukan webmaster), reset password - Utilitas: daftar backup SQL, jalankan backup, unduh, hapus file (`writable/admin_db_backup/`) - Login (`/admin/login`) **Belum di-port dari CI3 (sengaja / luar cakupan modul ini):** - Laporan modul **keuangan** (jatuhtempo, penjualan, laba, cetak terkait) - Panel: kelola grup (`admin_user_group`), halaman profil/ubah password akun admin terpisah (bisa memakai alur pegawai/API) - Utilitas: **restore** database dari file (hanya backup + unduh + hapus) - Unggah foto berita lewat form file (CI4 saat ini: isi nama file manual jika file sudah di server) ## Catatan parity UX - Urutan dan label menu utama mengikuti **sidemenu / `menu` CI3**; submenu yang sudah di CI4 memakai tautan aktif di `layouts/sidebar.php`. - Tombol primer diseragamkan ke **biru** (`bg-blue-600`) seperti aksen umum TailAdmin; sekunder tetap border gray + putih. - Tabel: header `bg-gray-50`, border baris `divide-gray-100`, footer pagination dengan strip `bg-gray-50/80`.