Initial commit BIJ CI4
This commit is contained in:
112
app/Controllers/Admin/Auth.php
Normal file
112
app/Controllers/Admin/Auth.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Services\Admin\AdminUsersLoginService;
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Login admin → token API mobile disimpan di sesi.
|
||||
*/
|
||||
class Auth extends BaseController
|
||||
{
|
||||
public function login(): ResponseInterface|string
|
||||
{
|
||||
if (session()->get('admin_mobile_token')) {
|
||||
return redirect()->to(site_url('admin'));
|
||||
}
|
||||
|
||||
return view('admin/auth/login');
|
||||
}
|
||||
|
||||
public function attempt(): RedirectResponse
|
||||
{
|
||||
$user = (string) $this->request->getPost('username');
|
||||
$pass = (string) $this->request->getPost('password');
|
||||
|
||||
$client = new ApiClient();
|
||||
$res = $client->postMobile('login', [
|
||||
'username' => $user,
|
||||
'password' => $pass,
|
||||
]);
|
||||
|
||||
$json = $res['json'];
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($json) && is_array($json) && ! empty($json['token'])) {
|
||||
$token = (string) $json['token'];
|
||||
$loginSvc = new AdminUsersLoginService();
|
||||
$pid = $loginSvc->resolvePegawaiIdFromCredentials($user);
|
||||
$linked = ($pid !== null && $pid > 0) ? $loginSvc->findLinkedAdminForPegawaiId($pid) : null;
|
||||
|
||||
if ($linked !== null) {
|
||||
$dispUser = $linked['username'] !== '' ? $linked['username'] : $user;
|
||||
session()->set([
|
||||
'admin_mobile_token' => $token,
|
||||
'admin_username' => $dispUser,
|
||||
'admin_auth_source' => 'admin_users',
|
||||
'admin_ion_user_id' => $linked['admin_user_id'],
|
||||
'admin_ion_groups' => $linked['group_names'],
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('admin'))->with('message', 'Login berhasil (akun admin / grup terhubung).');
|
||||
}
|
||||
|
||||
session()->remove(['admin_ion_user_id', 'admin_ion_groups']);
|
||||
session()->set([
|
||||
'admin_mobile_token' => $token,
|
||||
'admin_username' => $user,
|
||||
'admin_auth_source' => 'pegawai',
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('admin'))->with('message', 'Login berhasil.');
|
||||
}
|
||||
|
||||
$ion = (new AdminUsersLoginService())->tryLogin($user, $pass);
|
||||
if (($ion['ok'] ?? false) === true) {
|
||||
session()->set([
|
||||
'admin_mobile_token' => (string) $ion['token'],
|
||||
'admin_username' => (string) $ion['username'],
|
||||
'admin_auth_source' => 'admin_users',
|
||||
'admin_ion_user_id' => (int) $ion['admin_user_id'],
|
||||
'admin_ion_groups' => $ion['group_names'],
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('admin'))->with('message', 'Login berhasil (Ion Auth / admin_users).');
|
||||
}
|
||||
|
||||
if (($ion['reason'] ?? '') === 'no_group') {
|
||||
return redirect()->back()->withInput()->with(
|
||||
'error',
|
||||
'Akun admin_users tidak memiliki grup di admin_users_groups — login ditolak (sesuai struktur Ion Auth).'
|
||||
);
|
||||
}
|
||||
|
||||
if (($ion['reason'] ?? '') === 'no_proxy') {
|
||||
return redirect()->back()->withInput()->with(
|
||||
'error',
|
||||
'Akun admin_users valid, tetapi tidak ada pegawai untuk token API. Isi ADMIN_LOGIN_PROXY_PEGAWAI_ID di .env (id_pegawai) atau pastikan tabel pegawai berisi data.'
|
||||
);
|
||||
}
|
||||
|
||||
$msg = is_array($json) ? (string) ($json['pesan'] ?? 'Login gagal.') : ($res['error'] ?? 'Login gagal.');
|
||||
|
||||
return redirect()->back()->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function logout(): RedirectResponse
|
||||
{
|
||||
session()->remove([
|
||||
'admin_mobile_token',
|
||||
'admin_username',
|
||||
'admin_auth_source',
|
||||
'admin_ion_user_id',
|
||||
'admin_ion_groups',
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('admin/login'))->with('message', 'Anda telah keluar.');
|
||||
}
|
||||
}
|
||||
112
app/Controllers/Admin/BaseAdminController.php
Normal file
112
app/Controllers/Admin/BaseAdminController.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
abstract class BaseAdminController extends BaseController
|
||||
{
|
||||
protected ApiClient $apiClient;
|
||||
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger): void
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
helper('rbac');
|
||||
$this->apiClient = new ApiClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* RBAC fitur (`Config\AdminAccess` + `canAccess()`). Null = boleh lanjut.
|
||||
*/
|
||||
protected function enforceAccess(string $feature): ?ResponseInterface
|
||||
{
|
||||
if (canAccess($feature)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(403)->setJSON([
|
||||
'status' => 0,
|
||||
'pesan' => 'Akses ditolak untuk peran Anda.',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(site_url('admin'))->with('error', 'Akses ditolak untuk peran Anda.');
|
||||
}
|
||||
|
||||
protected function adminToken(): ?string
|
||||
{
|
||||
$t = session()->get('admin_mobile_token');
|
||||
|
||||
return is_string($t) && $t !== '' ? $t : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar|null> $extra
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
protected function apiMobile(string $method, array $extra = []): array
|
||||
{
|
||||
$token = $this->adminToken();
|
||||
if ($token === null) {
|
||||
return [
|
||||
'transport_ok' => false,
|
||||
'http_code' => 0,
|
||||
'json' => null,
|
||||
'error' => 'Belum login — tidak ada token API.',
|
||||
'raw' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->apiClient->postMobileWithToken($method, $token, $extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar|null> $query
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
protected function apiAdminGet(string $path, array $query = []): array
|
||||
{
|
||||
$token = $this->adminToken();
|
||||
if ($token === null) {
|
||||
return [
|
||||
'transport_ok' => false,
|
||||
'http_code' => 0,
|
||||
'json' => null,
|
||||
'error' => 'Belum login — tidak ada token API.',
|
||||
'raw' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->apiClient->getAdmin($path, $token, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar|null> $form
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
protected function apiAdminPost(string $path, array $form = []): array
|
||||
{
|
||||
$token = $this->adminToken();
|
||||
if ($token === null) {
|
||||
return [
|
||||
'transport_ok' => false,
|
||||
'http_code' => 0,
|
||||
'json' => null,
|
||||
'error' => 'Belum login — tidak ada token API.',
|
||||
'raw' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->apiClient->postAdmin($path, $token, $form);
|
||||
}
|
||||
}
|
||||
267
app/Controllers/Admin/Company.php
Normal file
267
app/Controllers/Admin/Company.php
Normal file
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Master perusahaan — memanggil `/api/admin/company/*`.
|
||||
*/
|
||||
class Company extends BaseAdminController
|
||||
{
|
||||
public function kantor(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->loadExtra('company/kantor', 'admin/perusahaan/kantor');
|
||||
}
|
||||
|
||||
public function kantorSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/kantor/save', 'admin/perusahaan/kantor');
|
||||
}
|
||||
|
||||
public function kantorDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/kantor/delete/' . $id, 'admin/perusahaan/kantor', []);
|
||||
}
|
||||
|
||||
public function unitKerja(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->loadExtra('company/unit_kerja', 'admin/perusahaan/unit_kerja');
|
||||
}
|
||||
|
||||
public function unitKerjaSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/unit_kerja/save', 'admin/perusahaan/unit_kerja');
|
||||
}
|
||||
|
||||
public function unitKerjaDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/unit_kerja/delete/' . $id, 'admin/perusahaan/unit_kerja', []);
|
||||
}
|
||||
|
||||
public function golongan(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->loadExtra('company/golongan', 'admin/perusahaan/golongan');
|
||||
}
|
||||
|
||||
public function golonganSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/golongan/save', 'admin/perusahaan/golongan');
|
||||
}
|
||||
|
||||
public function golonganDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/golongan/delete/' . $id, 'admin/perusahaan/golongan', []);
|
||||
}
|
||||
|
||||
public function jabatan(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->loadExtra('company/jabatan', 'admin/perusahaan/jabatan');
|
||||
}
|
||||
|
||||
public function jabatanSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/jabatan/save', 'admin/perusahaan/jabatan');
|
||||
}
|
||||
|
||||
public function jabatanDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/jabatan/delete/' . $id, 'admin/perusahaan/jabatan', []);
|
||||
}
|
||||
|
||||
public function berita(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->loadExtra('company/berita', 'admin/perusahaan/berita');
|
||||
}
|
||||
|
||||
public function beritaSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$post = $this->request->getPost() ?? [];
|
||||
$existing = (string) ($this->request->getPost('photo_existing') ?? '');
|
||||
if (($err = $this->mergeBeritaPhotoIntoPost($post, $existing)) !== null) {
|
||||
return redirect()->to(site_url('admin/perusahaan/berita'))->with('error', $err);
|
||||
}
|
||||
unset($post['photo_existing']);
|
||||
|
||||
$r = $this->apiAdminPost('company/berita/save', $post);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/perusahaan/berita'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/perusahaan/berita'))->with('error', $msg);
|
||||
}
|
||||
|
||||
public function beritaDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('perusahaan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->postExtra('company/berita/delete/' . $id, 'admin/perusahaan/berita', []);
|
||||
}
|
||||
|
||||
private function loadExtra(string $apiPath, string $view): string
|
||||
{
|
||||
$errors = [];
|
||||
$data = null;
|
||||
$r = $this->apiAdminGet($apiPath);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$data = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat data') : 'Gagal memuat data');
|
||||
}
|
||||
|
||||
return view($view, [
|
||||
'payload' => is_array($data) ? $data : null,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
private function postExtra(string $apiPath, string $redirectUri, ?array $mergePost = null): ResponseInterface
|
||||
{
|
||||
$post = array_merge($this->request->getPost(), $mergePost ?? []);
|
||||
$r = $this->apiAdminPost($apiPath, $post);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url($redirectUri))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url($redirectUri))->with('error', $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selaras CI3: `assets/uploads/berita/`.
|
||||
*/
|
||||
private function beritaPhotoUploadDir(): string
|
||||
{
|
||||
$base = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'berita';
|
||||
if (! is_dir($base)) {
|
||||
mkdir($base, 0755, true);
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unggah ke folder berita atau nama file manual. Kolom DB kosong di API disimpan sebagai '-'.
|
||||
*
|
||||
* @param array<string, mixed> $post
|
||||
*
|
||||
* @return string|null pesan error, atau null jika OK
|
||||
*/
|
||||
private function mergeBeritaPhotoIntoPost(array &$post, string $photoExistingFromDb): ?string
|
||||
{
|
||||
$file = $this->request->getFile('photo_file');
|
||||
if ($file !== null && $file->getError() !== UPLOAD_ERR_NO_FILE) {
|
||||
if (! $file->isValid()) {
|
||||
return 'Unggah foto tidak valid.';
|
||||
}
|
||||
$mime = (string) $file->getMimeType();
|
||||
$allowedMime = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (! in_array($mime, $allowedMime, true)) {
|
||||
return 'Format foto harus JPG, PNG, GIF, atau WebP.';
|
||||
}
|
||||
if ($file->getSize() > 2_097_152) {
|
||||
return 'Ukuran foto maksimal 2 MB.';
|
||||
}
|
||||
$ext = strtolower((string) ($file->guessExtension() ?: $file->getClientExtension()));
|
||||
if (! in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
|
||||
return 'Ekstensi foto tidak didukung.';
|
||||
}
|
||||
$dir = $this->beritaPhotoUploadDir();
|
||||
$rawName = pathinfo($file->getClientName(), PATHINFO_FILENAME);
|
||||
$safeOriginal = preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) $rawName) ?: 'berita';
|
||||
$safeOriginal = substr($safeOriginal, 0, 80);
|
||||
$filename = uniqid((string) mt_rand(), true) . '-' . $safeOriginal . '.' . $ext;
|
||||
|
||||
if (! $file->move($dir, $filename, true)) {
|
||||
return 'Gagal menyimpan file foto ke server.';
|
||||
}
|
||||
|
||||
$old = trim($photoExistingFromDb);
|
||||
if ($old !== '' && $old !== '-' && $old !== $filename) {
|
||||
$oldPath = $dir . DIRECTORY_SEPARATOR . basename(str_replace('\\', '/', $old));
|
||||
if (is_file($oldPath)) {
|
||||
@unlink($oldPath);
|
||||
}
|
||||
}
|
||||
$post['photo'] = $filename;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$manual = isset($post['photo']) ? trim((string) $post['photo']) : '';
|
||||
if ($manual === '' || $manual === '-') {
|
||||
$post['photo'] = '';
|
||||
|
||||
return null;
|
||||
}
|
||||
$base = basename(str_replace('\\', '/', $manual));
|
||||
if (! preg_match('/^[A-Za-z0-9._-]+$/', $base)) {
|
||||
return 'Nama file foto hanya boleh huruf, angka, titik, garis bawah, dan tanda hubung (atau unggah file).';
|
||||
}
|
||||
$post['photo'] = substr($base, 0, 255);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
153
app/Controllers/Admin/Cuti.php
Normal file
153
app/Controllers/Admin/Cuti.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Manajemen cuti pegawai (setara `admin/pegawai/cuti` CI3) via `/api/admin/cuti*`.
|
||||
*/
|
||||
class Cuti extends BaseAdminController
|
||||
{
|
||||
public function index(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('cuti')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$status = (string) ($this->request->getGet('status') ?? 'Waiting');
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
|
||||
$r = $this->apiAdminGet('cuti', [
|
||||
'status' => $status,
|
||||
'page' => (string) $page,
|
||||
'per_page' => '30',
|
||||
]);
|
||||
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat cuti') : 'Gagal memuat cuti');
|
||||
}
|
||||
|
||||
helper('cuti_display');
|
||||
|
||||
return view('admin/cuti/index', [
|
||||
'payload' => $payload,
|
||||
'errors' => $errors,
|
||||
'status' => $status,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layani dokumen cuti dari disk (setara file statis CI3 di assets/uploads/dokcuti/).
|
||||
* Dipakai saat berkas belum dilayani langsung oleh web server (mis. file belum di-copy ke public).
|
||||
*/
|
||||
public function dokumen(string $file): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('cuti')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$safe = basename(rawurldecode($file));
|
||||
if ($safe === '' || $safe === '.' || $safe === '..') {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
if (! preg_match('/^[A-Za-z0-9._-]+$/', $safe)) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$path = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'dokcuti' . DIRECTORY_SEPARATOR . $safe;
|
||||
if (! is_file($path)) {
|
||||
throw PageNotFoundException::forPageNotFound(
|
||||
'Berkas tidak ada di server. Salin isi folder dokcuti dari CI3 ke: public/assets/uploads/dokcuti/',
|
||||
);
|
||||
}
|
||||
|
||||
$ext = strtolower((string) pathinfo($safe, PATHINFO_EXTENSION));
|
||||
$mime = match ($ext) {
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
'pdf' => 'application/pdf',
|
||||
default => 'application/octet-stream',
|
||||
};
|
||||
|
||||
$this->response->setHeader('Content-Type', $mime);
|
||||
$this->response->setHeader('Content-Disposition', 'inline; filename="' . addcslashes($safe, '"\\') . '"');
|
||||
|
||||
return $this->response->setBody((string) file_get_contents($path));
|
||||
}
|
||||
|
||||
public function detail(int $id): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('cuti')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$bundle = null;
|
||||
|
||||
$r = $this->apiAdminGet('cuti/' . $id);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$bundle = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat detail') : 'Gagal memuat detail');
|
||||
}
|
||||
|
||||
helper('cuti_display');
|
||||
|
||||
return view('admin/cuti/detail', [
|
||||
'bundle' => is_array($bundle) ? $bundle : null,
|
||||
'errors' => $errors,
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function approve(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('cuti')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$res = $this->apiAdminPost('cuti/approve', ['id_cuti' => (string) $id]);
|
||||
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($res['json'])) {
|
||||
return redirect()->to(site_url('admin/cuti'))->with('message', (string) ($res['json']['pesan'] ?? 'Disetujui'));
|
||||
}
|
||||
|
||||
$msg = is_array($res['json']) ? (string) ($res['json']['pesan'] ?? 'Gagal') : ($res['error'] ?? 'Gagal');
|
||||
|
||||
return redirect()->back()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function reject(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('cuti')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$alasan = (string) ($this->request->getPost('alasan_tolak') ?? '');
|
||||
$res = $this->apiAdminPost('cuti/reject', [
|
||||
'id_cuti' => (string) $id,
|
||||
'alasan_tolak' => $alasan,
|
||||
]);
|
||||
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($res['json'])) {
|
||||
return redirect()->to(site_url('admin/cuti'))->with('message', (string) ($res['json']['pesan'] ?? 'Ditolak'));
|
||||
}
|
||||
|
||||
$msg = is_array($res['json']) ? (string) ($res['json']['pesan'] ?? 'Gagal') : ($res['error'] ?? 'Gagal');
|
||||
|
||||
return redirect()->back()->withInput()->with('error', $msg);
|
||||
}
|
||||
}
|
||||
62
app/Controllers/Admin/Dashboard.php
Normal file
62
app/Controllers/Admin/Dashboard.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Beranda: statistik admin (`/api/admin/dashboard`) + profil/berita mobile (opsional).
|
||||
*/
|
||||
class Dashboard extends BaseAdminController
|
||||
{
|
||||
public function index(): string|ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('dashboard')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$token = $this->adminToken();
|
||||
$profil = null;
|
||||
$berita = null;
|
||||
$errors = [];
|
||||
$summary = null;
|
||||
|
||||
if ($token === null) {
|
||||
$errors[] = 'Silakan login untuk memuat data dari API.';
|
||||
} else {
|
||||
$d = $this->apiAdminGet('dashboard');
|
||||
if ($d['transport_ok'] && ApiClient::isSuccess($d['json'])) {
|
||||
$summary = $d['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $d['error'] ?? (is_array($d['json']) ? (string) ($d['json']['pesan'] ?? 'Ringkasan dashboard tidak dapat dimuat.') : 'Ringkasan dashboard tidak dapat dimuat.');
|
||||
}
|
||||
|
||||
$p = $this->apiMobile('profil', []);
|
||||
if ($p['transport_ok'] && ApiClient::isSuccess($p['json'])) {
|
||||
$profil = $p['json']['pegawai'] ?? null;
|
||||
} else {
|
||||
$errors[] = $p['error'] ?? (is_array($p['json']) ? (string) ($p['json']['pesan'] ?? 'Profil tidak dapat dimuat.') : 'Profil tidak dapat dimuat.');
|
||||
}
|
||||
|
||||
$b = $this->apiMobile('berita', ['dari' => '0', 'jumlah' => '5']);
|
||||
if ($b['transport_ok'] && ApiClient::isSuccess($b['json'])) {
|
||||
$berita = $b['json']['data'] ?? [];
|
||||
} else {
|
||||
$errors[] = $b['error'] ?? (is_array($b['json']) ? (string) ($b['json']['pesan'] ?? 'Berita tidak dapat dimuat.') : 'Berita tidak dapat dimuat.');
|
||||
}
|
||||
}
|
||||
|
||||
helper('cuti_display');
|
||||
|
||||
return view('admin/dashboard/index', [
|
||||
'summary' => is_array($summary) ? $summary : null,
|
||||
'profil' => $profil,
|
||||
'berita' => is_array($berita) ? $berita : [],
|
||||
'errors' => $errors,
|
||||
'token' => $token !== null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
75
app/Controllers/Admin/Laporan.php
Normal file
75
app/Controllers/Admin/Laporan.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Ringkasan laporan (dashboard angka) lewat `/api/admin/laporan`.
|
||||
*/
|
||||
class Laporan extends BaseAdminController
|
||||
{
|
||||
public function index(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('laporan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('sampai') ?? $today);
|
||||
|
||||
$errors = [];
|
||||
$summary = null;
|
||||
|
||||
$r = $this->apiAdminGet('laporan', ['dari' => $dari, 'sampai' => $sampai]);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$summary = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat laporan') : 'Gagal memuat laporan');
|
||||
}
|
||||
|
||||
return view('admin/laporan/index', [
|
||||
'summary' => is_array($summary) ? $summary : null,
|
||||
'errors' => $errors,
|
||||
'dari' => $dari,
|
||||
'sampai' => $sampai,
|
||||
]);
|
||||
}
|
||||
|
||||
public function cuti(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('laporan')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('sampai') ?? $today);
|
||||
|
||||
$errors = [];
|
||||
$rows = [];
|
||||
$meta = ['dari' => $dari, 'sampai' => $sampai];
|
||||
$r = $this->apiAdminGet('laporan/cuti', ['dari' => $dari, 'sampai' => $sampai]);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$d = $r['json']['data'] ?? [];
|
||||
$rows = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
$meta = [
|
||||
'dari' => (string) ($d['dari'] ?? $dari),
|
||||
'sampai' => (string) ($d['sampai'] ?? $sampai),
|
||||
];
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
|
||||
return view('admin/laporan/cuti', [
|
||||
'rows' => $rows,
|
||||
'errors' => $errors,
|
||||
'dari' => $meta['dari'],
|
||||
'sampai' => $meta['sampai'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
308
app/Controllers/Admin/Panel.php
Normal file
308
app/Controllers/Admin/Panel.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Panel pengguna admin (Ion) — API `/api/admin/panel/*`.
|
||||
*/
|
||||
class Panel extends BaseAdminController
|
||||
{
|
||||
public function users(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$rows = [];
|
||||
$r = $this->apiAdminGet('panel/users');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$d = $r['json']['data'] ?? [];
|
||||
$rows = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
|
||||
return view('admin/panel/users', ['rows' => $rows, 'errors' => $errors]);
|
||||
}
|
||||
|
||||
public function groups(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$rows = [];
|
||||
$r = $this->apiAdminGet('panel/groups');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$d = $r['json']['data'] ?? [];
|
||||
$rows = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
|
||||
return view('admin/panel/groups', ['rows' => $rows, 'errors' => $errors]);
|
||||
}
|
||||
|
||||
public function groupCreate(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return view('admin/panel/group_create', ['errors' => []]);
|
||||
}
|
||||
|
||||
public function groupStore(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('panel/groups/create', $this->request->getPost());
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/panel/groups'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/panel/groups/create'))->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function groupEdit(int $id): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$row = null;
|
||||
$r = $this->apiAdminGet('panel/groups');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$d = $r['json']['data'] ?? [];
|
||||
$list = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
foreach ($list as $g) {
|
||||
if ((int) ($g['id'] ?? 0) === $id) {
|
||||
$row = $g;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($row === null) {
|
||||
$errors[] = 'Grup tidak ditemukan.';
|
||||
}
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
|
||||
if ($row === null && $errors === []) {
|
||||
$errors[] = 'Grup tidak ditemukan.';
|
||||
}
|
||||
|
||||
return view('admin/panel/group_edit', ['id' => $id, 'row' => $row, 'errors' => $errors]);
|
||||
}
|
||||
|
||||
public function groupUpdate(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('panel/groups/update/' . $id, $this->request->getPost());
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/panel/groups'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/panel/groups/edit/' . $id))->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function groupDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('panel/groups/delete/' . $id, $this->request->getPost());
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/panel/groups'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/panel/groups'))->with('error', $msg);
|
||||
}
|
||||
|
||||
public function userCreate(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$groups = [];
|
||||
$pegawaiRows = [];
|
||||
$r = $this->apiAdminGet('panel/groups');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$d = $r['json']['data'] ?? [];
|
||||
$groups = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? 'Gagal memuat grup';
|
||||
}
|
||||
|
||||
$pegawaiRows = $this->fetchPegawaiRowsForSelect($errors);
|
||||
|
||||
return view('admin/panel/user_create', [
|
||||
'groups' => $groups,
|
||||
'pegawai_rows' => $pegawaiRows,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function userStore(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('panel/users/create', $this->request->getPost());
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/panel/users'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/panel/users/create'))->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function userEdit(int $id): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$user = null;
|
||||
$r = $this->apiAdminGet('panel/users/' . $id);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$d = $r['json']['data'] ?? null;
|
||||
$user = is_array($d) ? $d : null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat pengguna') : 'Gagal memuat pengguna');
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
$gr = $this->apiAdminGet('panel/groups');
|
||||
if ($gr['transport_ok'] && ApiClient::isSuccess($gr['json'])) {
|
||||
$gd = $gr['json']['data'] ?? [];
|
||||
$groups = is_array($gd['rows'] ?? null) ? $gd['rows'] : [];
|
||||
} else {
|
||||
$errors[] = $gr['error'] ?? 'Gagal memuat grup';
|
||||
}
|
||||
|
||||
$pegawaiRows = $this->fetchPegawaiRowsForSelect($errors);
|
||||
|
||||
return view('admin/panel/user_edit', [
|
||||
'id' => $id,
|
||||
'user' => $user,
|
||||
'groups' => $groups,
|
||||
'pegawai_rows' => $pegawaiRows,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function userUpdate(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('panel/users/update/' . $id, $this->request->getPost());
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/panel/users'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/panel/users/edit/' . $id))->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function userReset(int $id): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return view('admin/panel/user_reset', ['id' => $id]);
|
||||
}
|
||||
|
||||
public function userResetPassword(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('panel')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('panel/users/reset_password/' . $id, $this->request->getPost());
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/panel/users'))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/panel/users/reset/' . $id))->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gabungkan semua halaman `GET api/admin/pegawai` — satu request cuma mengembalikan `per_page` baris.
|
||||
*
|
||||
* @param list<string> $errors
|
||||
*
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function fetchPegawaiRowsForSelect(array &$errors): array
|
||||
{
|
||||
$byId = [];
|
||||
$page = 1;
|
||||
$maxPage = 80;
|
||||
|
||||
while ($page <= $maxPage) {
|
||||
$pr = $this->apiAdminGet('pegawai', [
|
||||
'page' => (string) $page,
|
||||
'per_page' => '500',
|
||||
'q' => '',
|
||||
]);
|
||||
if (! $pr['transport_ok'] || ! ApiClient::isSuccess($pr['json'])) {
|
||||
if ($page === 1) {
|
||||
$errors[] = $pr['error'] ?? (is_array($pr['json']) ? (string) ($pr['json']['pesan'] ?? 'Gagal memuat daftar pegawai') : 'Gagal memuat daftar pegawai');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$pd = $pr['json']['data'] ?? [];
|
||||
$chunk = is_array($pd['rows'] ?? null) ? $pd['rows'] : [];
|
||||
foreach ($chunk as $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$pid = (int) ($row['id_pegawai'] ?? 0);
|
||||
if ($pid > 0) {
|
||||
$byId[$pid] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$totalPage = (int) ($pd['total_page'] ?? 1);
|
||||
if ($page >= $totalPage || $chunk === []) {
|
||||
break;
|
||||
}
|
||||
$page++;
|
||||
}
|
||||
|
||||
$out = array_values($byId);
|
||||
usort($out, static function (array $a, array $b): int {
|
||||
return strcasecmp((string) ($a['nama_lengkap'] ?? ''), (string) ($b['nama_lengkap'] ?? ''));
|
||||
});
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
288
app/Controllers/Admin/Pegawai.php
Normal file
288
app/Controllers/Admin/Pegawai.php
Normal file
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* CRUD pegawai lewat API `/api/admin/pegawai/*` (tanpa query DB di controller).
|
||||
*/
|
||||
class Pegawai extends BaseAdminController
|
||||
{
|
||||
public function index(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$q = (string) ($this->request->getGet('q') ?? '');
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
|
||||
$r = $this->apiAdminGet('pegawai', [
|
||||
'page' => (string) $page,
|
||||
'per_page' => '20',
|
||||
'q' => $q,
|
||||
]);
|
||||
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat daftar pegawai') : 'Gagal memuat daftar pegawai');
|
||||
}
|
||||
|
||||
return view('admin/pegawai/index', [
|
||||
'payload' => $payload,
|
||||
'errors' => $errors,
|
||||
'q' => $q,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai_tambah')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$refs = $this->loadReferences();
|
||||
|
||||
return view('admin/pegawai/form', [
|
||||
'mode' => 'create',
|
||||
'row' => null,
|
||||
'refs' => $refs['data'],
|
||||
'errors' => $refs['errors'],
|
||||
'apiError' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai_tambah')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$post = $this->sanitizePegawaiPost($this->request->getPost() ?? []);
|
||||
if (($err = $this->mergePegawaiPhotoIntoPost($post, null)) !== null) {
|
||||
return redirect()->back()->withInput()->with('error', $err);
|
||||
}
|
||||
|
||||
$res = $this->apiAdminPost('pegawai/create', $post);
|
||||
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($res['json'])) {
|
||||
return redirect()->to(site_url('admin/pegawai'))->with('message', (string) ($res['json']['pesan'] ?? 'Data berhasil disimpan'));
|
||||
}
|
||||
|
||||
$msg = is_array($res['json']) ? (string) ($res['json']['pesan'] ?? 'Gagal menyimpan') : ($res['error'] ?? 'Gagal menyimpan');
|
||||
|
||||
return redirect()->back()->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function edit(int $id): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$refs = $this->loadReferences();
|
||||
$row = null;
|
||||
$err = $refs['errors'];
|
||||
|
||||
$r = $this->apiAdminGet('pegawai/' . $id);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$row = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$err[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat pegawai') : 'Gagal memuat pegawai');
|
||||
}
|
||||
|
||||
return view('admin/pegawai/form', [
|
||||
'mode' => 'edit',
|
||||
'row' => is_array($row) ? $row : null,
|
||||
'refs' => $refs['data'],
|
||||
'errors' => $err,
|
||||
'apiError' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$post = $this->sanitizePegawaiPost($this->request->getPost() ?? []);
|
||||
$post['id_pegawai'] = $id;
|
||||
$photoExisting = (string) ($this->request->getPost('photo_existing') ?? '');
|
||||
if (($err = $this->mergePegawaiPhotoIntoPost($post, $photoExisting)) !== null) {
|
||||
return redirect()->back()->withInput()->with('error', $err);
|
||||
}
|
||||
|
||||
$res = $this->apiAdminPost('pegawai/update', $post);
|
||||
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($res['json'])) {
|
||||
return redirect()->to(site_url('admin/pegawai'))->with('message', (string) ($res['json']['pesan'] ?? 'Data berhasil diperbarui'));
|
||||
}
|
||||
|
||||
$msg = is_array($res['json']) ? (string) ($res['json']['pesan'] ?? 'Gagal memperbarui') : ($res['error'] ?? 'Gagal memperbarui');
|
||||
|
||||
return redirect()->back()->withInput()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function delete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$res = $this->apiAdminPost('pegawai/delete', ['id_pegawai' => (string) $id]);
|
||||
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($res['json'])) {
|
||||
return redirect()->to(site_url('admin/pegawai'))->with('message', (string) ($res['json']['pesan'] ?? 'Terhapus'));
|
||||
}
|
||||
|
||||
$msg = is_array($res['json']) ? (string) ($res['json']['pesan'] ?? 'Gagal menghapus') : ($res['error'] ?? 'Gagal menghapus');
|
||||
|
||||
return redirect()->back()->with('error', $msg);
|
||||
}
|
||||
|
||||
public function reset(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('pegawai')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$res = $this->apiAdminPost('pegawai/reset_password', ['id_pegawai' => (string) $id]);
|
||||
|
||||
if ($res['transport_ok'] && ApiClient::isSuccess($res['json'])) {
|
||||
return redirect()->to(site_url('admin/pegawai'))->with('message', (string) ($res['json']['pesan'] ?? 'Password direset'));
|
||||
}
|
||||
|
||||
$msg = is_array($res['json']) ? (string) ($res['json']['pesan'] ?? 'Gagal reset') : ($res['error'] ?? 'Gagal reset');
|
||||
|
||||
return redirect()->back()->with('error', $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{data: array<string, mixed>|null, errors: list<string>}
|
||||
*/
|
||||
private function loadReferences(): array
|
||||
{
|
||||
$errors = [];
|
||||
$data = null;
|
||||
$r = $this->apiAdminGet('references');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$data = is_array($r['json']['data'] ?? null) ? $r['json']['data'] : null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? 'Referensi jabatan/unit tidak dapat dimuat.';
|
||||
}
|
||||
|
||||
return ['data' => $data, 'errors' => $errors];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $post
|
||||
*
|
||||
* @return array<string, scalar|null>
|
||||
*/
|
||||
private function sanitizePegawaiPost(array $post): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($post as $k => $v) {
|
||||
if (! is_scalar($v) && $v !== null) {
|
||||
continue;
|
||||
}
|
||||
$key = (string) $k;
|
||||
if (in_array($key, ['nip', 'nama_lengkap', 'jenis_kelamin', 'tempat_lahir', 'tanggal_lahir', 'email', 'jabatan', 'unit_kerja', 'golongan_pekerjaan', 'kantor', 'status_kepegawaian', 'tanggal_bergabung', 'jadwal', 'super_akses', 'username', 'password', 'photo'], true)) {
|
||||
$out[$key] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['jabatan', 'unit_kerja', 'golongan_pekerjaan', 'kantor', 'jadwal'] as $intKey) {
|
||||
if (isset($out[$intKey]) && $out[$intKey] !== '' && $out[$intKey] !== null) {
|
||||
$out[$intKey] = (int) $out[$intKey];
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sama dengan aplikasi mobile: `public/assets/uploads/pengguna/`.
|
||||
*/
|
||||
private function pegawaiPhotoUploadDir(): string
|
||||
{
|
||||
$base = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'pengguna';
|
||||
if (! is_dir($base)) {
|
||||
mkdir($base, 0755, true);
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unggah file ke folder pengguna atau pakai nama file manual. Mengisi `$post['photo']`.
|
||||
*
|
||||
* @param array<string, scalar|null> $post
|
||||
*
|
||||
* @return string|null pesan error, atau null jika OK
|
||||
*/
|
||||
private function mergePegawaiPhotoIntoPost(array &$post, ?string $photoExistingFromDb): ?string
|
||||
{
|
||||
$file = $this->request->getFile('photo_file');
|
||||
if ($file !== null && $file->getError() !== UPLOAD_ERR_NO_FILE) {
|
||||
if (! $file->isValid()) {
|
||||
return 'Unggah foto tidak valid.';
|
||||
}
|
||||
$mime = (string) $file->getMimeType();
|
||||
$allowedMime = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (! in_array($mime, $allowedMime, true)) {
|
||||
return 'Format foto harus JPG, PNG, GIF, atau WebP.';
|
||||
}
|
||||
if ($file->getSize() > 2_097_152) {
|
||||
return 'Ukuran foto maksimal 2 MB.';
|
||||
}
|
||||
$ext = strtolower((string) ($file->guessExtension() ?: $file->getClientExtension()));
|
||||
if (! in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
|
||||
return 'Ekstensi foto tidak didukung.';
|
||||
}
|
||||
$dir = $this->pegawaiPhotoUploadDir();
|
||||
$rawName = pathinfo($file->getClientName(), PATHINFO_FILENAME);
|
||||
$safeOriginal = preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) $rawName) ?: 'photo';
|
||||
$safeOriginal = substr($safeOriginal, 0, 80);
|
||||
$filename = uniqid((string) mt_rand(), true) . '-' . $safeOriginal . '.' . $ext;
|
||||
|
||||
if (! $file->move($dir, $filename, true)) {
|
||||
return 'Gagal menyimpan file foto ke server.';
|
||||
}
|
||||
|
||||
$old = $photoExistingFromDb !== null ? trim($photoExistingFromDb) : '';
|
||||
if ($old !== '' && $old !== '-' && $old !== $filename) {
|
||||
$oldPath = $dir . DIRECTORY_SEPARATOR . basename(str_replace('\\', '/', $old));
|
||||
if (is_file($oldPath)) {
|
||||
@unlink($oldPath);
|
||||
}
|
||||
}
|
||||
$post['photo'] = $filename;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$manual = isset($post['photo']) ? trim((string) $post['photo']) : '';
|
||||
if ($manual === '' || $manual === '-') {
|
||||
$post['photo'] = '';
|
||||
|
||||
return null;
|
||||
}
|
||||
$base = basename(str_replace('\\', '/', $manual));
|
||||
if (! preg_match('/^[A-Za-z0-9._-]+$/', $base)) {
|
||||
return 'Nama file foto hanya boleh huruf, angka, titik, garis bawah, dan tanda hubung (atau unggah file).';
|
||||
}
|
||||
$post['photo'] = substr($base, 0, 255);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
645
app/Controllers/Admin/Presensi.php
Normal file
645
app/Controllers/Admin/Presensi.php
Normal file
@@ -0,0 +1,645 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
/**
|
||||
* Daftar & detail presensi lewat `/api/admin/presensi*`.
|
||||
*/
|
||||
class Presensi extends BaseAdminController
|
||||
{
|
||||
public function index(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('tanggal_dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('tanggal_sampai') ?? $today);
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$q = (string) ($this->request->getGet('q') ?? '');
|
||||
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
|
||||
$r = $this->apiAdminGet('presensi', [
|
||||
'tanggal_dari' => $dari,
|
||||
'tanggal_sampai' => $sampai,
|
||||
'page' => (string) $page,
|
||||
'per_page' => '30',
|
||||
'q' => $q,
|
||||
]);
|
||||
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat presensi') : 'Gagal memuat presensi');
|
||||
}
|
||||
|
||||
return view('admin/presensi/index', [
|
||||
'payload' => $payload,
|
||||
'errors' => $errors,
|
||||
'dari' => $dari,
|
||||
'sampai' => $sampai,
|
||||
'page' => $page,
|
||||
'q' => $q,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ambil semua baris presensi untuk ekspor (sama dengan filter list; maks. 100 halaman × 200 baris).
|
||||
*
|
||||
* @return array{ok: true, rows: list<array<string, mixed>>, dari: string, sampai: string, q: string}|array{ok: false, error: string, dari: string, sampai: string, q: string}
|
||||
*/
|
||||
private function fetchPresensiExportRows(): array
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('tanggal_dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('tanggal_sampai') ?? $today);
|
||||
$q = (string) ($this->request->getGet('q') ?? '');
|
||||
$per = 200;
|
||||
$maxPg = 100;
|
||||
$allRows = [];
|
||||
$totalPage = 1;
|
||||
|
||||
for ($page = 1; $page <= $totalPage && $page <= $maxPg; $page++) {
|
||||
$r = $this->apiAdminGet('presensi', [
|
||||
'tanggal_dari' => $dari,
|
||||
'tanggal_sampai' => $sampai,
|
||||
'page' => (string) $page,
|
||||
'per_page' => (string) $per,
|
||||
'q' => $q,
|
||||
]);
|
||||
|
||||
if (! $r['transport_ok'] || ! ApiClient::isSuccess($r['json'])) {
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal mengekspor') : 'Gagal mengekspor');
|
||||
|
||||
return ['ok' => false, 'error' => $msg, 'dari' => $dari, 'sampai' => $sampai, 'q' => $q];
|
||||
}
|
||||
|
||||
$payload = $r['json']['data'] ?? [];
|
||||
$rows = is_array($payload['rows'] ?? null) ? $payload['rows'] : [];
|
||||
if ($page === 1) {
|
||||
$totalPage = max(1, min($maxPg, (int) ($payload['total_page'] ?? 1)));
|
||||
}
|
||||
$allRows = array_merge($allRows, $rows);
|
||||
}
|
||||
|
||||
return ['ok' => true, 'rows' => $allRows, 'dari' => $dari, 'sampai' => $sampai, 'q' => $q];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unduh CSV semua baris presensi untuk filter yang sama (paginasi digabung, maks. 100 halaman × 200 baris).
|
||||
*/
|
||||
public function exportCsv(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$fetched = $this->fetchPresensiExportRows();
|
||||
if (! $fetched['ok']) {
|
||||
return redirect()->to(site_url('admin/presensi?' . http_build_query(array_filter([
|
||||
'tanggal_dari' => $fetched['dari'],
|
||||
'tanggal_sampai' => $fetched['sampai'],
|
||||
'q' => $fetched['q'],
|
||||
]))))->with('error', $fetched['error']);
|
||||
}
|
||||
|
||||
$allRows = $fetched['rows'];
|
||||
$dari = $fetched['dari'];
|
||||
$sampai = $fetched['sampai'];
|
||||
|
||||
$fh = fopen('php://memory', 'r+b');
|
||||
if ($fh === false) {
|
||||
return redirect()->to(site_url('admin/presensi'))->with('error', 'Gagal membuat file CSV.');
|
||||
}
|
||||
|
||||
fwrite($fh, "\xEF\xBB\xBF");
|
||||
fputcsv($fh, [
|
||||
'id_presensi',
|
||||
'tanggal',
|
||||
'nama_lengkap',
|
||||
'nip',
|
||||
'status',
|
||||
'jam_masuk',
|
||||
'ket_masuk',
|
||||
'jam_pulang',
|
||||
'ket_pulang',
|
||||
'istirahat',
|
||||
]);
|
||||
|
||||
foreach ($allRows as $pr) {
|
||||
if (! is_array($pr)) {
|
||||
continue;
|
||||
}
|
||||
$jm = $pr['jam_masuk'] ?? null;
|
||||
$jp = $pr['jam_pulang'] ?? null;
|
||||
$mi = $pr['mulai_istirahat'] ?? null;
|
||||
$bi = $pr['beres_istirahat'] ?? null;
|
||||
$ist = '';
|
||||
if (! empty($mi) || ! empty($bi)) {
|
||||
$ist = (empty($mi) ? '—' : self::formatTimeForCsv((string) $mi)) . ' → ' . (empty($bi) ? '—' : self::formatTimeForCsv((string) $bi));
|
||||
}
|
||||
|
||||
fputcsv($fh, [
|
||||
(string) ($pr['id_presensi'] ?? ''),
|
||||
(string) ($pr['tanggal'] ?? ''),
|
||||
(string) ($pr['nama_lengkap'] ?? ''),
|
||||
(string) ($pr['nip'] ?? ''),
|
||||
self::presensiStatusLabelForCsv($pr),
|
||||
self::formatTimeForCsv($jm !== null ? (string) $jm : ''),
|
||||
(string) ($pr['ket_masuk'] ?? ''),
|
||||
self::formatTimeForCsv($jp !== null ? (string) $jp : ''),
|
||||
(string) ($pr['ket_pulang'] ?? ''),
|
||||
$ist,
|
||||
]);
|
||||
}
|
||||
|
||||
rewind($fh);
|
||||
$csv = stream_get_contents($fh) ?: '';
|
||||
fclose($fh);
|
||||
|
||||
$fn = 'presensi_' . preg_replace('/[^0-9-]/', '', $dari) . '_' . preg_replace('/[^0-9-]/', '', $sampai) . '_' . date('His') . '.csv';
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'text/csv; charset=UTF-8')
|
||||
->setHeader('Content-Disposition', 'attachment; filename="' . str_replace(['"', "\r", "\n"], '', $fn) . '"')
|
||||
->setBody($csv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unduh Excel (.xlsx) dengan kolom rapi: header tebal, filter, lebar kolom, teks ket. dibungkus.
|
||||
*/
|
||||
public function exportXlsx(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$fetched = $this->fetchPresensiExportRows();
|
||||
if (! $fetched['ok']) {
|
||||
return redirect()->to(site_url('admin/presensi?' . http_build_query(array_filter([
|
||||
'tanggal_dari' => $fetched['dari'],
|
||||
'tanggal_sampai' => $fetched['sampai'],
|
||||
'q' => $fetched['q'],
|
||||
]))))->with('error', $fetched['error']);
|
||||
}
|
||||
|
||||
$allRows = $fetched['rows'];
|
||||
$dari = $fetched['dari'];
|
||||
$sampai = $fetched['sampai'];
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Presensi');
|
||||
|
||||
$headers = [
|
||||
'ID presensi',
|
||||
'Tanggal',
|
||||
'Nama lengkap',
|
||||
'NIP',
|
||||
'Status',
|
||||
'Jam masuk',
|
||||
'Ket. masuk',
|
||||
'Jam pulang',
|
||||
'Ket. pulang',
|
||||
'Istirahat',
|
||||
];
|
||||
foreach ($headers as $i => $h) {
|
||||
$sheet->setCellValue(Coordinate::stringFromColumnIndex($i + 1) . '1', $h);
|
||||
}
|
||||
|
||||
$headerStyle = [
|
||||
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
|
||||
'fill' => [
|
||||
'fillType' => Fill::FILL_SOLID,
|
||||
'startColor' => ['rgb' => '1F4E79'],
|
||||
],
|
||||
'alignment' => [
|
||||
'horizontal' => Alignment::HORIZONTAL_CENTER,
|
||||
'vertical' => Alignment::VERTICAL_CENTER,
|
||||
],
|
||||
'borders' => [
|
||||
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'CCCCCC']],
|
||||
],
|
||||
];
|
||||
$sheet->getStyle('A1:J1')->applyFromArray($headerStyle);
|
||||
$sheet->getRowDimension(1)->setRowHeight(22);
|
||||
|
||||
$rowNum = 2;
|
||||
foreach ($allRows as $pr) {
|
||||
if (! is_array($pr)) {
|
||||
continue;
|
||||
}
|
||||
$jm = $pr['jam_masuk'] ?? null;
|
||||
$jp = $pr['jam_pulang'] ?? null;
|
||||
$mi = $pr['mulai_istirahat'] ?? null;
|
||||
$bi = $pr['beres_istirahat'] ?? null;
|
||||
$ist = '';
|
||||
if (! empty($mi) || ! empty($bi)) {
|
||||
$ist = (empty($mi) ? '—' : self::formatTimeForCsv((string) $mi)) . ' → ' . (empty($bi) ? '—' : self::formatTimeForCsv((string) $bi));
|
||||
}
|
||||
|
||||
$sheet->setCellValue('A' . $rowNum, (string) ($pr['id_presensi'] ?? ''));
|
||||
$sheet->setCellValue('B' . $rowNum, (string) ($pr['tanggal'] ?? ''));
|
||||
$sheet->setCellValue('C' . $rowNum, (string) ($pr['nama_lengkap'] ?? ''));
|
||||
$sheet->setCellValue('D' . $rowNum, (string) ($pr['nip'] ?? ''));
|
||||
$sheet->setCellValue('E' . $rowNum, self::presensiStatusLabelForCsv($pr));
|
||||
$sheet->setCellValue('F' . $rowNum, self::formatTimeForCsv($jm !== null ? (string) $jm : ''));
|
||||
$sheet->setCellValue('G' . $rowNum, (string) ($pr['ket_masuk'] ?? ''));
|
||||
$sheet->setCellValue('H' . $rowNum, self::formatTimeForCsv($jp !== null ? (string) $jp : ''));
|
||||
$sheet->setCellValue('I' . $rowNum, (string) ($pr['ket_pulang'] ?? ''));
|
||||
$sheet->setCellValue('J' . $rowNum, $ist);
|
||||
$rowNum++;
|
||||
}
|
||||
|
||||
$lastRow = max(2, $rowNum - 1);
|
||||
if ($lastRow >= 2) {
|
||||
$sheet->getStyle('A2:J' . $lastRow)->applyFromArray([
|
||||
'borders' => [
|
||||
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'DDDDDD']],
|
||||
],
|
||||
'alignment' => [
|
||||
'vertical' => Alignment::VERTICAL_TOP,
|
||||
],
|
||||
]);
|
||||
$sheet->getStyle('G2:G' . $lastRow)->getAlignment()->setWrapText(true);
|
||||
$sheet->getStyle('I2:I' . $lastRow)->getAlignment()->setWrapText(true);
|
||||
$sheet->getStyle('A2:A' . $lastRow)->getNumberFormat()->setFormatCode('@');
|
||||
$sheet->getStyle('D2:D' . $lastRow)->getNumberFormat()->setFormatCode('@');
|
||||
}
|
||||
|
||||
$sheet->freezePane('A2');
|
||||
$sheet->setAutoFilter('A1:J1');
|
||||
|
||||
$sheet->getColumnDimension('A')->setWidth(14);
|
||||
$sheet->getColumnDimension('B')->setWidth(12);
|
||||
$sheet->getColumnDimension('C')->setWidth(28);
|
||||
$sheet->getColumnDimension('D')->setWidth(18);
|
||||
$sheet->getColumnDimension('E')->setWidth(14);
|
||||
$sheet->getColumnDimension('F')->setWidth(11);
|
||||
$sheet->getColumnDimension('G')->setWidth(36);
|
||||
$sheet->getColumnDimension('H')->setWidth(11);
|
||||
$sheet->getColumnDimension('I')->setWidth(36);
|
||||
$sheet->getColumnDimension('J')->setWidth(22);
|
||||
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'prxlsx');
|
||||
if ($tmp === false) {
|
||||
return redirect()->to(site_url('admin/presensi'))->with('error', 'Gagal membuat file Excel.');
|
||||
}
|
||||
|
||||
try {
|
||||
(new Xlsx($spreadsheet))->save($tmp);
|
||||
} catch (\Throwable $e) {
|
||||
@unlink($tmp);
|
||||
log_message('error', 'exportXlsx: {message}', ['message' => $e->getMessage()]);
|
||||
|
||||
return redirect()->to(site_url('admin/presensi?' . http_build_query(array_filter([
|
||||
'tanggal_dari' => $dari,
|
||||
'tanggal_sampai' => $sampai,
|
||||
'q' => $fetched['q'],
|
||||
]))))->with('error', 'Gagal menulis file Excel. Pastikan ekstensi PHP zip aktif.');
|
||||
}
|
||||
|
||||
$binary = (string) file_get_contents($tmp);
|
||||
@unlink($tmp);
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
|
||||
$fn = 'presensi_' . preg_replace('/[^0-9-]/', '', $dari) . '_' . preg_replace('/[^0-9-]/', '', $sampai) . '_' . date('His') . '.xlsx';
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->setHeader('Content-Disposition', 'attachment; filename="' . str_replace(['"', "\r", "\n"], '', $fn) . '"')
|
||||
->setBody($binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $pr
|
||||
*/
|
||||
private static function presensiStatusLabelForCsv(array $pr): string
|
||||
{
|
||||
$jm = $pr['jam_masuk'] ?? null;
|
||||
$jp = $pr['jam_pulang'] ?? null;
|
||||
$hadirMasuk = $jm !== null && $jm !== '';
|
||||
$hadirPulang = $jp !== null && $jp !== '';
|
||||
|
||||
if ($hadirMasuk && $hadirPulang) {
|
||||
return 'Lengkap';
|
||||
}
|
||||
if ($hadirMasuk || $hadirPulang) {
|
||||
return 'Sebagian';
|
||||
}
|
||||
|
||||
return 'Belum rekam';
|
||||
}
|
||||
|
||||
private static function formatTimeForCsv(string $t): string
|
||||
{
|
||||
if ($t === '') {
|
||||
return '';
|
||||
}
|
||||
$ts = strtotime($t);
|
||||
|
||||
return $ts ? date('H:i', $ts) : $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layani foto absen masuk/pulang dari disk (public/assets/uploads/absen/…).
|
||||
*/
|
||||
public function foto(string $jenis, string $file): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$jenis = strtolower($jenis);
|
||||
if ($jenis !== 'masuk' && $jenis !== 'pulang') {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$safe = basename(rawurldecode($file));
|
||||
if ($safe === '' || $safe === '.' || $safe === '..') {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
if (! preg_match('/^[A-Za-z0-9._-]+$/', $safe)) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$path = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'absen' . DIRECTORY_SEPARATOR . $jenis . DIRECTORY_SEPARATOR . $safe;
|
||||
if (! is_file($path)) {
|
||||
throw PageNotFoundException::forPageNotFound(
|
||||
'Foto tidak ada di server. Salin folder absen/masuk dan absen/pulang dari CI3 ke: public/assets/uploads/absen/',
|
||||
);
|
||||
}
|
||||
|
||||
$ext = strtolower((string) pathinfo($safe, PATHINFO_EXTENSION));
|
||||
$mime = match ($ext) {
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
default => 'application/octet-stream',
|
||||
};
|
||||
|
||||
$this->response->setHeader('Content-Type', $mime);
|
||||
$this->response->setHeader('Content-Disposition', 'inline; filename="' . addcslashes($safe, '"\\') . '"');
|
||||
|
||||
return $this->response->setBody((string) file_get_contents($path));
|
||||
}
|
||||
|
||||
public function detail(int $id): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$row = null;
|
||||
|
||||
$r = $this->apiAdminGet('presensi/' . $id);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$row = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat detail') : 'Gagal memuat detail');
|
||||
}
|
||||
|
||||
return view('admin/presensi/detail', [
|
||||
'row' => is_array($row) ? $row : null,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function lapangan(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
$pegawai = [];
|
||||
$r = $this->apiAdminGet('presensi/dilapangan');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
$p = $this->apiAdminGet('pegawai', ['page' => '1', 'per_page' => '500', 'q' => '']);
|
||||
if ($p['transport_ok'] && ApiClient::isSuccess($p['json'])) {
|
||||
$d = $p['json']['data'] ?? [];
|
||||
$pegawai = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
}
|
||||
|
||||
return view('admin/presensi/lapangan', [
|
||||
'payload' => is_array($payload) ? $payload : null,
|
||||
'pegawai' => $pegawai,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function lapanganSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/dilapangan/save', 'admin/presensi/lapangan');
|
||||
}
|
||||
|
||||
public function lapanganDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/dilapangan/delete/' . $id, 'admin/presensi/lapangan', []);
|
||||
}
|
||||
|
||||
public function lembur(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
$pegawai = [];
|
||||
$r = $this->apiAdminGet('presensi/lembur');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
$p = $this->apiAdminGet('pegawai', ['page' => '1', 'per_page' => '500', 'q' => '']);
|
||||
if ($p['transport_ok'] && ApiClient::isSuccess($p['json'])) {
|
||||
$d = $p['json']['data'] ?? [];
|
||||
$pegawai = is_array($d['rows'] ?? null) ? $d['rows'] : [];
|
||||
}
|
||||
|
||||
return view('admin/presensi/lembur', [
|
||||
'payload' => is_array($payload) ? $payload : null,
|
||||
'pegawai' => $pegawai,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function lemburSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/lembur/save', 'admin/presensi/lembur');
|
||||
}
|
||||
|
||||
public function lemburDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/lembur/delete/' . $id, 'admin/presensi/lembur', []);
|
||||
}
|
||||
|
||||
public function libur(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi_libur')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPage('presensi/libur', 'admin/presensi/libur', false);
|
||||
}
|
||||
|
||||
public function liburSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi_libur')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/libur/save', 'admin/presensi/libur');
|
||||
}
|
||||
|
||||
public function liburDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi_libur')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/libur/delete/' . $id, 'admin/presensi/libur', []);
|
||||
}
|
||||
|
||||
public function jadwal(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi_jadwal')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPage('presensi/jadwal', 'admin/presensi/jadwal', false);
|
||||
}
|
||||
|
||||
public function jadwalSave(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi_jadwal')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/jadwal/save', 'admin/presensi/jadwal');
|
||||
}
|
||||
|
||||
public function jadwalDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi_jadwal')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/jadwal/delete/' . $id, 'admin/presensi/jadwal', []);
|
||||
}
|
||||
|
||||
public function aktivitas(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
$r = $this->apiAdminGet('presensi/aktivitas', ['page' => (string) $page, 'per_page' => '20']);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
|
||||
return view('admin/presensi/aktivitas', [
|
||||
'payload' => is_array($payload) ? $payload : null,
|
||||
'errors' => $errors,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
public function aktivitasDelete(int $id): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('presensi')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
return $this->toolPost('presensi/aktivitas/delete/' . $id, 'admin/presensi/aktivitas', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface|string
|
||||
*/
|
||||
private function toolPage(string $apiPath, string $view, bool $loadRefs)
|
||||
{
|
||||
$errors = [];
|
||||
$payload = null;
|
||||
$refs = null;
|
||||
$r = $this->apiAdminGet($apiPath);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$payload = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal memuat data') : 'Gagal memuat data');
|
||||
}
|
||||
if ($loadRefs) {
|
||||
$ref = $this->apiAdminGet('references');
|
||||
if ($ref['transport_ok'] && ApiClient::isSuccess($ref['json'])) {
|
||||
$refs = $ref['json']['data'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return view($view, [
|
||||
'payload' => is_array($payload) ? $payload : null,
|
||||
'refs' => is_array($refs) ? $refs : null,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar|null> $merge
|
||||
*/
|
||||
private function toolPost(string $apiPath, string $redirectPath, array $merge = []): ResponseInterface
|
||||
{
|
||||
$post = array_merge($this->request->getPost(), $merge);
|
||||
$r = $this->apiAdminPost($apiPath, $post);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url($redirectPath))->with('message', (string) ($r['json']['pesan'] ?? 'OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url($redirectPath))->with('error', $msg);
|
||||
}
|
||||
}
|
||||
87
app/Controllers/Admin/Util.php
Normal file
87
app/Controllers/Admin/Util.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Services\Admin\AdminExtraApiService;
|
||||
use App\Services\ApiClient;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Utilitas backup DB — file di <code>writable/admin_db_backup/</code>.
|
||||
*/
|
||||
class Util extends BaseAdminController
|
||||
{
|
||||
public function backup(): ResponseInterface|string
|
||||
{
|
||||
if (($deny = $this->enforceAccess('utilitas')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$data = null;
|
||||
$r = $this->apiAdminGet('backup');
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
$data = $r['json']['data'] ?? null;
|
||||
} else {
|
||||
$errors[] = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
}
|
||||
|
||||
return view('admin/util/backup', [
|
||||
'payload' => is_array($data) ? $data : null,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function backupRun(): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('utilitas')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$post = $this->request->getPost();
|
||||
$r = $this->apiAdminPost('backup/run', $post);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/util/backup'))->with('message', (string) ($r['json']['pesan'] ?? 'Backup OK'));
|
||||
}
|
||||
$msg = $r['error'] ?? (is_array($r['json']) ? (string) ($r['json']['pesan'] ?? 'Gagal') : 'Gagal');
|
||||
|
||||
return redirect()->to(site_url('admin/util/backup'))->with('error', $msg);
|
||||
}
|
||||
|
||||
public function backupDelete(string $file): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('utilitas')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$r = $this->apiAdminPost('backup/delete/' . rawurlencode($file), []);
|
||||
if ($r['transport_ok'] && ApiClient::isSuccess($r['json'])) {
|
||||
return redirect()->to(site_url('admin/util/backup'))->with('message', 'File dihapus');
|
||||
}
|
||||
$msg = $r['error'] ?? 'Gagal hapus';
|
||||
|
||||
return redirect()->to(site_url('admin/util/backup'))->with('error', $msg);
|
||||
}
|
||||
|
||||
public function backupDownload(string $file): ResponseInterface
|
||||
{
|
||||
if (($deny = $this->enforceAccess('utilitas')) !== null) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$extra = new AdminExtraApiService();
|
||||
$path = $extra->backupFilePath($file);
|
||||
if ($path === null) {
|
||||
return redirect()->to(site_url('admin/util/backup'))->with('error', 'File tidak ditemukan');
|
||||
}
|
||||
|
||||
$dl = $this->response->download($path, null);
|
||||
if ($dl === null) {
|
||||
return redirect()->to(site_url('admin/util/backup'))->with('error', 'Gagal menyiapkan unduhan');
|
||||
}
|
||||
|
||||
return $dl->setFileName(basename($path));
|
||||
}
|
||||
}
|
||||
269
app/Controllers/Api/Admin/BaseAdminApiController.php
Normal file
269
app/Controllers/Api/Admin/BaseAdminApiController.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Services\Admin\AdminApiService;
|
||||
use App\Services\Admin\AdminExtraApiService;
|
||||
use App\Services\AdminAuditService;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Dasar API `/api/admin/*`: wajib **sesi panel admin** + token yang sama dengan sesi;
|
||||
* RBAC per fitur (`canAccess`). Tanpa cookie sesi, panggilan hanya dengan `?token=` ditolak.
|
||||
*
|
||||
* Audit: setelah otorisasi sukses, controller anak memanggil {@see auditAuthorized()}.
|
||||
* Percobaan ditolak dicatat ke DB + `log_message`.
|
||||
*/
|
||||
abstract class BaseAdminApiController extends BaseController
|
||||
{
|
||||
protected AdminApiService $adminApi;
|
||||
|
||||
protected AdminExtraApiService $adminExtra;
|
||||
|
||||
private AdminAuditService $audit;
|
||||
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger): void
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
helper('rbac');
|
||||
$this->adminApi = new AdminApiService();
|
||||
$this->adminExtra = new AdminExtraApiService();
|
||||
$this->audit = new AdminAuditService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
protected function auditAuthorized(string $action, array $actor, array $payload = []): void
|
||||
{
|
||||
$payload['actor'] = $this->actorAuditSummary($actor);
|
||||
$this->audit->log($action, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $actor
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function actorAuditSummary(array $actor): array
|
||||
{
|
||||
return [
|
||||
'id_pegawai' => $actor['id_pegawai'] ?? null,
|
||||
'nip' => $actor['nip'] ?? null,
|
||||
'nama_lengkap' => $actor['nama_lengkap'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ringkasan GET/POST untuk payload audit (dibatasi ukuran).
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function auditRequestParams(): array
|
||||
{
|
||||
$get = $this->request->getGet();
|
||||
$post = $this->request->getPost();
|
||||
|
||||
return [
|
||||
'query' => is_array($get) ? $this->truncateParamMap($get, 40) : [],
|
||||
'post' => is_array($post) ? $this->truncateParamMap($post, 40) : [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $map
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function truncateParamMap(array $map, int $maxKeys): array
|
||||
{
|
||||
$i = 0;
|
||||
$out = [];
|
||||
foreach ($map as $k => $v) {
|
||||
if ($i++ >= $maxKeys) {
|
||||
$out['_truncated'] = true;
|
||||
|
||||
break;
|
||||
}
|
||||
if (is_scalar($v) || $v === null) {
|
||||
$out[(string) $k] = $v;
|
||||
} elseif (is_array($v)) {
|
||||
$out[(string) $k] = '[array]';
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
protected function respond(array $payload, int $code = 200): ResponseInterface
|
||||
{
|
||||
return $this->response->setStatusCode($code)->setJSON($payload);
|
||||
}
|
||||
|
||||
protected function extractToken(): string
|
||||
{
|
||||
$t = $this->request->getGet('token')
|
||||
?? $this->request->getPost('token')
|
||||
?? $this->request->getHeaderLine('X-Admin-Token');
|
||||
|
||||
return is_string($t) ? trim($t) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sesi admin panel aktif + token permintaan sama dengan `admin_mobile_token` di sesi.
|
||||
*
|
||||
* @return array{actor: array<string, mixed>|null, response: null}|array{actor: null, response: ResponseInterface}
|
||||
*/
|
||||
protected function requireAdminSessionBoundToken(): array
|
||||
{
|
||||
$sess = session();
|
||||
$sessTok = $sess->get('admin_mobile_token');
|
||||
if (! is_string($sessTok) || $sessTok === '') {
|
||||
$this->logAdminSecurityDenied('no_admin_session', 'unauthorized');
|
||||
|
||||
return [
|
||||
'actor' => null,
|
||||
'response' => $this->respond(['status' => 0, 'pesan' => 'Unauthorized'], 401),
|
||||
];
|
||||
}
|
||||
|
||||
$reqTok = $this->extractToken();
|
||||
if ($reqTok === '' || $reqTok !== $sessTok) {
|
||||
$this->logAdminSecurityDenied('token_not_bound_to_session', 'unauthorized');
|
||||
|
||||
return [
|
||||
'actor' => null,
|
||||
'response' => $this->respond(['status' => 0, 'pesan' => 'Unauthorized'], 401),
|
||||
];
|
||||
}
|
||||
|
||||
$actor = $this->adminApi->actorFromToken($reqTok);
|
||||
if ($actor === null) {
|
||||
$this->logAdminSecurityDenied('invalid_token_actor', 'unauthorized');
|
||||
|
||||
return [
|
||||
'actor' => null,
|
||||
'response' => $this->respond(['status' => 0, 'pesan' => 'Token tidak valid'], 403),
|
||||
];
|
||||
}
|
||||
|
||||
return ['actor' => $actor, 'response' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* RBAC fitur — memakai `admin_ion_groups` + `Config\AdminAccess` (sama lapisan web).
|
||||
*/
|
||||
protected function requireAdminFeature(string $feature): ?ResponseInterface
|
||||
{
|
||||
if (canAccess($feature)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->logAdminSecurityDenied('forbidden_feature:' . $feature, 'forbidden');
|
||||
|
||||
return $this->respond(['status' => 0, 'pesan' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gabungan autentikasi sesi + RBAC satu pintu untuk endpoint admin API.
|
||||
*
|
||||
* @return array{actor: array<string, mixed>|null, response: null}|array{actor: null, response: ResponseInterface}
|
||||
*/
|
||||
protected function requireAdminApiAccess(string $feature): array
|
||||
{
|
||||
$auth = $this->requireAdminSessionBoundToken();
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth;
|
||||
}
|
||||
|
||||
$deny = $this->requireAdminFeature($feature);
|
||||
if ($deny !== null) {
|
||||
return ['actor' => $auth['actor'], 'response' => $deny];
|
||||
}
|
||||
|
||||
return ['actor' => $auth['actor'], 'response' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter cabang untuk grup Ion `supervisor`: data pegawai/cuti/presensi dibatasi ke `pegawai.kantor`
|
||||
* yang sama dengan baris pegawai pemegang token (bukan dari jabatan).
|
||||
*
|
||||
* @return array{scoped: bool, kantor_id: int|null} scoped=true & kantor_id=null → pegawai supervisor belum punya kantor
|
||||
*/
|
||||
protected function cabangScopeFromActor(?array $actor): array
|
||||
{
|
||||
if ($actor === null || ! rbac_enforce_ion()) {
|
||||
return ['scoped' => false, 'kantor_id' => null];
|
||||
}
|
||||
if (hasRole('webmaster') || hasRole('hrd')) {
|
||||
return ['scoped' => false, 'kantor_id' => null];
|
||||
}
|
||||
if (! hasRole('supervisor')) {
|
||||
return ['scoped' => false, 'kantor_id' => null];
|
||||
}
|
||||
$kid = (int) ($actor['kantor'] ?? 0);
|
||||
if ($kid <= 0) {
|
||||
return ['scoped' => true, 'kantor_id' => null];
|
||||
}
|
||||
|
||||
return ['scoped' => true, 'kantor_id' => $kid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Supervisor wajib punya `kantor` pada baris pegawai (token) agar scope cabang terdefinisi.
|
||||
*/
|
||||
protected function denyIfSupervisorCabangInvalid(array $scope): ?ResponseInterface
|
||||
{
|
||||
if ($scope['scoped'] && $scope['kantor_id'] === null) {
|
||||
return $this->respond([
|
||||
'status' => 0,
|
||||
'pesan' => 'Akun supervisor belum memiliki cabang (kantor) pada data pegawai. Hubungi HRD untuk mengisi kolom kantor.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return int|null null = tanpa filter cabang */
|
||||
protected function cabangKantorIdForQueries(array $scope): ?int
|
||||
{
|
||||
return $scope['scoped'] ? $scope['kantor_id'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setelah {@see requireAdminApiAccess()} sukses: validasi kantor supervisor & siapkan filter cabang.
|
||||
*
|
||||
* @return array{kid: int|null, response: ResponseInterface|null}
|
||||
*/
|
||||
protected function cabangKantorAfterAuth(?array $actor): array
|
||||
{
|
||||
$scope = $this->cabangScopeFromActor($actor);
|
||||
if (($deny = $this->denyIfSupervisorCabangInvalid($scope)) !== null) {
|
||||
return ['kid' => null, 'response' => $deny];
|
||||
}
|
||||
|
||||
return ['kid' => $this->cabangKantorIdForQueries($scope), 'response' => null];
|
||||
}
|
||||
|
||||
private function logAdminSecurityDenied(string $reason, string $outcome): void
|
||||
{
|
||||
$ip = $this->request->getIPAddress();
|
||||
$path = $this->request->getUri()->getPath();
|
||||
$when = date('c');
|
||||
log_message('warning', "[api/admin] denied={$reason} outcome={$outcome} ip={$ip} path={$path} at={$when}");
|
||||
|
||||
$action = $outcome === 'forbidden' ? 'api.admin.security.forbidden' : 'api.admin.security.unauthorized';
|
||||
$this->audit->log($action, [
|
||||
'reason' => $reason,
|
||||
'__outcome' => $outcome,
|
||||
]);
|
||||
}
|
||||
}
|
||||
189
app/Controllers/Api/Admin/CompanyDataApiController.php
Normal file
189
app/Controllers/Api/Admin/CompanyDataApiController.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Master data perusahaan (kantor, unit kerja, golongan, jabatan, berita).
|
||||
*/
|
||||
class CompanyDataApiController extends BaseAdminApiController
|
||||
{
|
||||
public function kantor(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.kantor.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->kantorList());
|
||||
}
|
||||
|
||||
public function kantorSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_kantor'] ?? 0);
|
||||
|
||||
$this->auditAuthorized('api.admin.company.kantor.save', $auth['actor'], ['id_kantor' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->kantorSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function kantorDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.kantor.delete', $auth['actor'], ['id_kantor' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->kantorDelete($id));
|
||||
}
|
||||
|
||||
public function unitKerja(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.unit_kerja.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->unitKerjaList());
|
||||
}
|
||||
|
||||
public function unitKerjaSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_unit_kerja'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.company.unit_kerja.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->unitKerjaSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function unitKerjaDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.unit_kerja.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->unitKerjaDelete($id));
|
||||
}
|
||||
|
||||
public function golongan(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.golongan.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->golonganList());
|
||||
}
|
||||
|
||||
public function golonganSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_golongan'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.company.golongan.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->golonganSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function golonganDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.golongan.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->golonganDelete($id));
|
||||
}
|
||||
|
||||
public function jabatan(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.jabatan.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->jabatanList());
|
||||
}
|
||||
|
||||
public function jabatanSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_jabatan'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.company.jabatan.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->jabatanSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function jabatanDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.jabatan.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->jabatanDelete($id));
|
||||
}
|
||||
|
||||
public function berita(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.berita.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->beritaList());
|
||||
}
|
||||
|
||||
public function beritaSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_berita'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.company.berita.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->beritaSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function beritaDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('perusahaan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.company.berita.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->beritaDelete($id));
|
||||
}
|
||||
}
|
||||
107
app/Controllers/Api/Admin/CutiController.php
Normal file
107
app/Controllers/Api/Admin/CutiController.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class CutiController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('cuti');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.cuti.index', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
$status = (string) ($this->request->getGet('status') ?? 'Waiting');
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$perPage = max(5, min(200, (int) ($this->request->getGet('per_page') ?? 30)));
|
||||
|
||||
return $this->respond($this->adminApi->cutiList($status, $page, $perPage, $cb['kid']));
|
||||
}
|
||||
|
||||
public function show(?string $id = null): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('cuti');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.cuti.show', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
$idInt = (int) ($id ?? 0);
|
||||
if ($idInt <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
|
||||
}
|
||||
|
||||
return $this->respond($this->adminApi->cutiShow($idInt, $cb['kid']));
|
||||
}
|
||||
|
||||
public function approve(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('cuti');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id_cuti') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'id_cuti wajib'], 400);
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.cuti.approve', $auth['actor'], [
|
||||
'cuti' => ['id_cuti' => $id],
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->cutiApprove($id, $cb['kid']));
|
||||
}
|
||||
|
||||
public function reject(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('cuti');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id_cuti') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'id_cuti wajib'], 400);
|
||||
}
|
||||
|
||||
$alasan = (string) ($this->request->getPost('alasan_tolak') ?? '');
|
||||
|
||||
$this->auditAuthorized('api.admin.cuti.reject', $auth['actor'], [
|
||||
'cuti' => [
|
||||
'id_cuti' => $id,
|
||||
'alasan_tolak' => function_exists('mb_substr') ? mb_substr($alasan, 0, 500) : substr($alasan, 0, 500),
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->cutiReject($id, $alasan, $cb['kid']));
|
||||
}
|
||||
}
|
||||
36
app/Controllers/Api/Admin/DashboardController.php
Normal file
36
app/Controllers/Api/Admin/DashboardController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class DashboardController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('dashboard');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.dashboard.index', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
$soloPegawaiId = null;
|
||||
if (! rbac_enforce_ion()) {
|
||||
$soloPegawaiId = (int) ($auth['actor']['id_pegawai'] ?? 0);
|
||||
if ($soloPegawaiId <= 0) {
|
||||
$soloPegawaiId = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respond($this->adminApi->dashboardHome($cb['kid'], $soloPegawaiId));
|
||||
}
|
||||
}
|
||||
50
app/Controllers/Api/Admin/DatabaseBackupApiController.php
Normal file
50
app/Controllers/Api/Admin/DatabaseBackupApiController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Utilitas: daftar file backup SQL di writable, buat backup, hapus file.
|
||||
*/
|
||||
class DatabaseBackupApiController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('utilitas');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.backup.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->backupListFiles());
|
||||
}
|
||||
|
||||
public function run(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('utilitas');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$also = (string) ($this->request->getPost('save_latest') ?? '') === '1';
|
||||
$this->auditAuthorized('api.admin.backup.run', $auth['actor'], [
|
||||
'save_latest' => $also,
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminExtra->backupRun($also));
|
||||
}
|
||||
|
||||
public function delete(string $file): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('utilitas');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.backup.delete', $auth['actor'], ['file' => $file]);
|
||||
|
||||
return $this->respond($this->adminExtra->backupDelete($file));
|
||||
}
|
||||
}
|
||||
72
app/Controllers/Api/Admin/LaporanController.php
Normal file
72
app/Controllers/Api/Admin/LaporanController.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class LaporanController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('laporan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.laporan.index', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('sampai') ?? $today);
|
||||
|
||||
if (strtotime($dari) === false || strtotime($sampai) === false) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'Format tanggal tidak valid'], 400);
|
||||
}
|
||||
|
||||
if ($dari > $sampai) {
|
||||
[$dari, $sampai] = [$sampai, $dari];
|
||||
}
|
||||
|
||||
return $this->respond($this->adminApi->laporanSummary($dari, $sampai, $cb['kid']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Laporan cuti rentang tanggal (parity CI3 laporan/fcuti — data tabel).
|
||||
*/
|
||||
public function cutiRentang(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('laporan');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.laporan.cuti_rentang', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('sampai') ?? $today);
|
||||
|
||||
if (strtotime($dari) === false || strtotime($sampai) === false) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'Format tanggal tidak valid'], 400);
|
||||
}
|
||||
if ($dari > $sampai) {
|
||||
[$dari, $sampai] = [$sampai, $dari];
|
||||
}
|
||||
|
||||
return $this->respond($this->adminExtra->laporanCutiRentang($dari, $sampai, $cb['kid']));
|
||||
}
|
||||
}
|
||||
139
app/Controllers/Api/Admin/PanelUsersApiController.php
Normal file
139
app/Controllers/Api/Admin/PanelUsersApiController.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Panel Ion Auth: daftar pengguna admin, grup, buat user, reset password.
|
||||
*/
|
||||
class PanelUsersApiController extends BaseAdminApiController
|
||||
{
|
||||
public function users(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.users.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminUsersList());
|
||||
}
|
||||
|
||||
public function userShow(?string $id = null): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$idInt = (int) ($id ?? 0);
|
||||
if ($idInt <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.users.show', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
'user_id' => $idInt,
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminUserShow($idInt));
|
||||
}
|
||||
|
||||
public function userUpdate(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.users.update', $auth['actor'], [
|
||||
'user_id' => $id,
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminUserUpdate($id, $this->request->getPost()));
|
||||
}
|
||||
|
||||
public function groups(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.groups.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminGroupsList());
|
||||
}
|
||||
|
||||
public function groupCreate(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.groups.create', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminGroupCreate($this->request->getPost()));
|
||||
}
|
||||
|
||||
public function groupUpdate(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID grup tidak valid'], 400);
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.groups.update', $auth['actor'], [
|
||||
'group_id' => $id,
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminGroupUpdate($id, $this->request->getPost()));
|
||||
}
|
||||
|
||||
public function groupDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID grup tidak valid'], 400);
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.groups.delete', $auth['actor'], ['group_id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminGroupDelete($id));
|
||||
}
|
||||
|
||||
public function userCreate(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.users.create', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminUserCreate($this->request->getPost()));
|
||||
}
|
||||
|
||||
public function userResetPassword(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('panel');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.panel.users.reset_password', $auth['actor'], [
|
||||
'user_id' => $id,
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminExtra->adminUserResetPassword($id, $this->request->getPost()));
|
||||
}
|
||||
}
|
||||
164
app/Controllers/Api/Admin/PegawaiController.php
Normal file
164
app/Controllers/Api/Admin/PegawaiController.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class PegawaiController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('pegawai');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.pegawai.index', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||||
$perPage = (int) ($this->request->getGet('per_page') ?? 20);
|
||||
$search = (string) ($this->request->getGet('q') ?? '');
|
||||
|
||||
return $this->respond($this->adminApi->pegawaiList($page, $perPage, $search, $cb['kid']));
|
||||
}
|
||||
|
||||
public function show(?string $id = null): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('pegawai');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.pegawai.show', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
$idInt = (int) ($id ?? 0);
|
||||
if ($idInt <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
|
||||
}
|
||||
|
||||
return $this->respond($this->adminApi->pegawaiShow($idInt, $cb['kid']));
|
||||
}
|
||||
|
||||
public function create(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('pegawai_tambah');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$input = $this->normalizePegawaiInput($this->request->getPost());
|
||||
|
||||
$this->auditAuthorized('api.admin.pegawai.create', $auth['actor'], [
|
||||
'pegawai' => $input,
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->pegawaiCreate($input, $cb['kid']));
|
||||
}
|
||||
|
||||
public function update(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('pegawai');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id_pegawai') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'id_pegawai wajib'], 400);
|
||||
}
|
||||
|
||||
$input = $this->normalizePegawaiInput($this->request->getPost());
|
||||
|
||||
$this->auditAuthorized('api.admin.pegawai.update', $auth['actor'], [
|
||||
'pegawai' => array_merge(['id_pegawai' => $id], $input),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->pegawaiUpdate($id, $input, $cb['kid']));
|
||||
}
|
||||
|
||||
public function delete(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('pegawai');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id_pegawai') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'id_pegawai wajib'], 400);
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.pegawai.delete', $auth['actor'], [
|
||||
'pegawai' => ['id_pegawai' => $id],
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->pegawaiDelete($id, $cb['kid']));
|
||||
}
|
||||
|
||||
public function resetPassword(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('pegawai');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id_pegawai') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'id_pegawai wajib'], 400);
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.pegawai.reset_password', $auth['actor'], [
|
||||
'pegawai' => ['id_pegawai' => $id],
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->pegawaiResetPassword($id, $cb['kid']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $post
|
||||
*
|
||||
* @return array<string, scalar|null>
|
||||
*/
|
||||
private function normalizePegawaiInput(array $post): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($post as $k => $v) {
|
||||
if (is_scalar($v) || $v === null) {
|
||||
$out[(string) $k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
68
app/Controllers/Api/Admin/PresensiController.php
Normal file
68
app/Controllers/Api/Admin/PresensiController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class PresensiController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.presensi.index', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$dari = (string) ($this->request->getGet('tanggal_dari') ?? $today);
|
||||
$sampai = (string) ($this->request->getGet('tanggal_sampai') ?? $today);
|
||||
|
||||
if (strtotime($dari) === false || strtotime($sampai) === false) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'Format tanggal tidak valid'], 400);
|
||||
}
|
||||
|
||||
if ($dari > $sampai) {
|
||||
[$dari, $sampai] = [$sampai, $dari];
|
||||
}
|
||||
|
||||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||||
$perPage = (int) ($this->request->getGet('per_page') ?? 30);
|
||||
$q = (string) ($this->request->getGet('q') ?? '');
|
||||
|
||||
return $this->respond($this->adminApi->presensiList($dari, $sampai, $page, $perPage, $q, $cb['kid']));
|
||||
}
|
||||
|
||||
public function show(?string $id = null): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.presensi.show', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
$idInt = (int) ($id ?? 0);
|
||||
if ($idInt <= 0) {
|
||||
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
|
||||
}
|
||||
|
||||
return $this->respond($this->adminApi->presensiShow($idInt, $cb['kid']));
|
||||
}
|
||||
}
|
||||
209
app/Controllers/Api/Admin/PresensiToolsApiController.php
Normal file
209
app/Controllers/Api/Admin/PresensiToolsApiController.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Presensi: tugas luar, lembur, libur, jadwal, aktivitas harian.
|
||||
*/
|
||||
class PresensiToolsApiController extends BaseAdminApiController
|
||||
{
|
||||
public function dilapangan(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.dilapangan.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->dilapanganList($cb['kid']));
|
||||
}
|
||||
|
||||
public function dilapanganSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_dilapangan'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.presensi.dilapangan.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->dilapanganSave($post, $id > 0 ? $id : null, $cb['kid']));
|
||||
}
|
||||
|
||||
public function dilapanganDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.dilapangan.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->dilapanganDelete($id, $cb['kid']));
|
||||
}
|
||||
|
||||
public function lembur(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.lembur.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->lemburList($cb['kid']));
|
||||
}
|
||||
|
||||
public function lemburSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_lembur'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.presensi.lembur.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->lemburSave($post, $id > 0 ? $id : null, $cb['kid']));
|
||||
}
|
||||
|
||||
public function lemburDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.lembur.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->lemburDelete($id, $cb['kid']));
|
||||
}
|
||||
|
||||
public function libur(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi_libur');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.libur.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->liburList());
|
||||
}
|
||||
|
||||
public function liburSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi_libur');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_libur'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.presensi.libur.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->liburSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function liburDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi_libur');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.libur.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->liburDelete($id));
|
||||
}
|
||||
|
||||
public function jadwal(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi_jadwal');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.jadwal.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->jadwalList());
|
||||
}
|
||||
|
||||
public function jadwalSave(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi_jadwal');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$post = $this->request->getPost();
|
||||
$id = (int) ($post['id_jadwal'] ?? 0);
|
||||
$this->auditAuthorized('api.admin.presensi.jadwal.save', $auth['actor'], ['id' => $id ?: null]);
|
||||
|
||||
return $this->respond($this->adminExtra->jadwalSave($post, $id > 0 ? $id : null));
|
||||
}
|
||||
|
||||
public function jadwalDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi_jadwal');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.jadwal.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->jadwalDelete($id));
|
||||
}
|
||||
|
||||
public function aktivitas(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$perPage = max(5, min(100, (int) ($this->request->getGet('per_page') ?? 20)));
|
||||
$this->auditAuthorized('api.admin.presensi.aktivitas.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
|
||||
|
||||
return $this->respond($this->adminExtra->aktifitasList($page, $perPage, $cb['kid']));
|
||||
}
|
||||
|
||||
public function aktivitasDelete(int $id): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('presensi');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
$this->auditAuthorized('api.admin.presensi.aktivitas.delete', $auth['actor'], ['id' => $id]);
|
||||
|
||||
return $this->respond($this->adminExtra->aktifitasDelete($id, $cb['kid']));
|
||||
}
|
||||
}
|
||||
28
app/Controllers/Api/Admin/ReferenceController.php
Normal file
28
app/Controllers/Api/Admin/ReferenceController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Api\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class ReferenceController extends BaseAdminApiController
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$auth = $this->requireAdminApiAccess('references');
|
||||
if ($auth['response'] !== null) {
|
||||
return $auth['response'];
|
||||
}
|
||||
$cb = $this->cabangKantorAfterAuth($auth['actor']);
|
||||
if ($cb['response'] !== null) {
|
||||
return $cb['response'];
|
||||
}
|
||||
|
||||
$this->auditAuthorized('api.admin.references.index', $auth['actor'], [
|
||||
'request' => $this->auditRequestParams(),
|
||||
]);
|
||||
|
||||
return $this->respond($this->adminApi->references($cb['kid']));
|
||||
}
|
||||
}
|
||||
214
app/Controllers/Api/MobileJsonController.php
Normal file
214
app/Controllers/Api/MobileJsonController.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Libraries\ApiParityLogger;
|
||||
use App\Libraries\LegacyUtf8Encoder;
|
||||
use App\Services\Mobile\MobileJsonService;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Pengganti CI3 `application/controllers/Json.php`.
|
||||
*
|
||||
* URL CI4: POST /api/mobile/{method}
|
||||
* URL CI3: POST /index.php/json/{method}
|
||||
*
|
||||
* @see docs/migration/json_api_map.md
|
||||
*/
|
||||
class MobileJsonController extends BaseController
|
||||
{
|
||||
protected MobileJsonService $mobileJson;
|
||||
|
||||
public function initController(\CodeIgniter\HTTP\RequestInterface $request, ResponseInterface $response, \Psr\Log\LoggerInterface $logger): void
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
$this->mobileJson = new MobileJsonService();
|
||||
$this->response->setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (ApiParityLogger::enabled()) {
|
||||
ApiParityLogger::logRequest($this->request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
private function respondLegacy(array $payload): ResponseInterface
|
||||
{
|
||||
$body = LegacyUtf8Encoder::utf8ize($payload);
|
||||
// CI3 memakai json_encode() default (tanpa JSON_UNESCAPED_UNICODE / PRETTY_PRINT).
|
||||
$json = json_encode($body, 0, 512);
|
||||
if ($json === false) {
|
||||
$json = '{"status":0,"pesan":"JSON_ENCODE_ERROR"}';
|
||||
}
|
||||
|
||||
if (ApiParityLogger::enabled()) {
|
||||
ApiParityLogger::logResponse($json);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)
|
||||
->setContentType('application/json', 'UTF-8')
|
||||
->setBody($json);
|
||||
}
|
||||
|
||||
public function login_w_token(): ResponseInterface
|
||||
{
|
||||
$token = (string) $this->request->getPost('token');
|
||||
|
||||
return $this->respondLegacy($this->mobileJson->loginWToken($token));
|
||||
}
|
||||
|
||||
public function login(): ResponseInterface
|
||||
{
|
||||
$user = (string) $this->request->getPost('username');
|
||||
$pass = (string) $this->request->getPost('password');
|
||||
|
||||
return $this->respondLegacy($this->mobileJson->login($user, $pass));
|
||||
}
|
||||
|
||||
public function profil(): ResponseInterface
|
||||
{
|
||||
$token = (string) $this->request->getPost('token');
|
||||
|
||||
return $this->respondLegacy($this->mobileJson->profil($token));
|
||||
}
|
||||
|
||||
public function save_cuti(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->saveCuti(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('nama_photo'),
|
||||
(string) $this->request->getPost('photo'),
|
||||
(string) $this->request->getPost('tanggal'),
|
||||
(string) $this->request->getPost('alasan'),
|
||||
(string) $this->request->getPost('tipe'),
|
||||
));
|
||||
}
|
||||
|
||||
public function batalkan_cuti(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->batalkanCuti(
|
||||
(string) $this->request->getPost('token'),
|
||||
$this->request->getPost('id'),
|
||||
));
|
||||
}
|
||||
|
||||
public function save_aktifitas(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->saveAktifitas(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('nama_photo'),
|
||||
(string) $this->request->getPost('photo'),
|
||||
(string) $this->request->getPost('tanggal'),
|
||||
(string) $this->request->getPost('deksripsi'),
|
||||
));
|
||||
}
|
||||
|
||||
public function save_masuk(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->saveMasuk(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('nama_photo'),
|
||||
(string) $this->request->getPost('photo'),
|
||||
(string) $this->request->getPost('lat'),
|
||||
(string) $this->request->getPost('lng'),
|
||||
(string) $this->request->getPost('jarak'),
|
||||
));
|
||||
}
|
||||
|
||||
public function save_pulang(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->savePulang(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('nama_photo'),
|
||||
(string) $this->request->getPost('photo'),
|
||||
(string) $this->request->getPost('lat'),
|
||||
(string) $this->request->getPost('lng'),
|
||||
(string) $this->request->getPost('jarak'),
|
||||
));
|
||||
}
|
||||
|
||||
public function save_istirahat(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->saveIstirahat(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('mulai'),
|
||||
(string) $this->request->getPost('selesai'),
|
||||
));
|
||||
}
|
||||
|
||||
public function presensi_today(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->presensiToday((string) $this->request->getPost('token')));
|
||||
}
|
||||
|
||||
public function presensi(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->presensi((string) $this->request->getPost('token')));
|
||||
}
|
||||
|
||||
public function daftar_today(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->daftarToday((string) $this->request->getPost('token')));
|
||||
}
|
||||
|
||||
public function berita(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->berita(
|
||||
(string) $this->request->getPost('token'),
|
||||
$this->request->getPost('dari'),
|
||||
$this->request->getPost('jumlah'),
|
||||
));
|
||||
}
|
||||
|
||||
public function cuti(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->cuti(
|
||||
(string) $this->request->getPost('token'),
|
||||
$this->request->getPost('dari'),
|
||||
$this->request->getPost('jumlah'),
|
||||
));
|
||||
}
|
||||
|
||||
public function lembur(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->lembur(
|
||||
(string) $this->request->getPost('token'),
|
||||
$this->request->getPost('dari'),
|
||||
$this->request->getPost('jumlah'),
|
||||
));
|
||||
}
|
||||
|
||||
public function libur(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->libur((string) $this->request->getPost('token')));
|
||||
}
|
||||
|
||||
public function aktifitas(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->aktifitas(
|
||||
(string) $this->request->getPost('token'),
|
||||
$this->request->getPost('dari'),
|
||||
$this->request->getPost('jumlah'),
|
||||
));
|
||||
}
|
||||
|
||||
public function save_pp(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->savePp(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('nama_photo'),
|
||||
(string) $this->request->getPost('photo'),
|
||||
));
|
||||
}
|
||||
|
||||
public function save_password(): ResponseInterface
|
||||
{
|
||||
return $this->respondLegacy($this->mobileJson->savePassword(
|
||||
(string) $this->request->getPost('token'),
|
||||
(string) $this->request->getPost('pass_lama'),
|
||||
(string) $this->request->getPost('pass_baru'),
|
||||
));
|
||||
}
|
||||
}
|
||||
45
app/Controllers/BaseController.php
Normal file
45
app/Controllers/BaseController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* BaseController provides a convenient place for loading components
|
||||
* and performing functions that are needed by all your controllers.
|
||||
*
|
||||
* Extend this class in any new controllers:
|
||||
* ```
|
||||
* class Home extends BaseController
|
||||
* ```
|
||||
*
|
||||
* For security, be sure to declare any new methods as protected or private.
|
||||
*/
|
||||
abstract class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Be sure to declare properties for any property fetch you initialized.
|
||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||
*/
|
||||
|
||||
// protected $session;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
// Load here all helpers you want to be available in your controllers that extend BaseController.
|
||||
// Caution: Do not put the this below the parent::initController() call below.
|
||||
$this->helpers = array_merge($this->helpers ?? [], ['url']);
|
||||
|
||||
// Caution: Do not edit this line.
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
// Preload any models, libraries, etc, here.
|
||||
// $this->session = service('session');
|
||||
}
|
||||
}
|
||||
11
app/Controllers/Home.php
Normal file
11
app/Controllers/Home.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
class Home extends BaseController
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
return view('welcome_message');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user