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|null, errors: list} */ 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 $post * * @return array */ 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 $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; } }