Initial commit BIJ CI4
This commit is contained in:
42
app/Views/admin/auth/login.php
Normal file
42
app/Views/admin/auth/login.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?= $this->extend('layouts/auth') ?>
|
||||
|
||||
<?= $this->section('title') ?>Login Admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-8 shadow-md shadow-gray-200/50 sm:p-10">
|
||||
<div class="mb-6 text-center">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Masuk ke admin</h1>
|
||||
<p class="mt-2 text-sm text-gray-500">Gunakan akun pegawai (sama seperti aplikasi mobile) atau akun panel administrator. Sesi menyimpan token API.</p>
|
||||
</div>
|
||||
|
||||
<?php if (session()->getFlashdata('message')) : ?>
|
||||
<div class="mb-4 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800 shadow-sm" role="status">
|
||||
<?= esc(session()->getFlashdata('message')) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if (session()->getFlashdata('error')) : ?>
|
||||
<div class="mb-4 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800 shadow-sm" role="alert">
|
||||
<?= esc(session()->getFlashdata('error')) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<form action="<?= site_url('admin/login') ?>" method="post" class="space-y-5">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label for="username" class="mb-1.5 block text-sm font-medium text-gray-700">Username</label>
|
||||
<input type="text" name="username" id="username" value="<?= esc(old('username')) ?>"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm text-gray-800 shadow-sm placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
||||
autocomplete="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="mb-1.5 block text-sm font-medium text-gray-700">Password</label>
|
||||
<input type="password" name="password" id="password"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm text-gray-800 shadow-sm placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
||||
autocomplete="current-password" required>
|
||||
</div>
|
||||
<button type="submit" class="flex w-full items-center justify-center rounded-lg bg-blue-600 px-4 py-3 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
110
app/Views/admin/cuti/detail.php
Normal file
110
app/Views/admin/cuti/detail.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Detail Cuti<?= $this->endSection() ?>
|
||||
|
||||
<?php
|
||||
$cuti = is_array($bundle) ? ($bundle['cuti'] ?? null) : null;
|
||||
$dok = is_array($bundle) ? ($bundle['dokumen'] ?? []) : [];
|
||||
$cuti = is_array($cuti) ? $cuti : [];
|
||||
$st = (string) ($cuti['status_cuti'] ?? '');
|
||||
$badgeClass = match ($st) {
|
||||
'Approve' => 'bg-emerald-50 text-emerald-800 ring-1 ring-emerald-100',
|
||||
'Rejected' => 'bg-red-50 text-red-800 ring-1 ring-red-100',
|
||||
'Waiting' => 'bg-amber-50 text-amber-900 ring-1 ring-amber-100',
|
||||
'Cancelled' => 'bg-gray-100 text-gray-600 ring-1 ring-gray-200',
|
||||
default => 'bg-gray-100 text-gray-700 ring-1 ring-gray-200',
|
||||
};
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="mx-auto max-w-3xl space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Detail cuti</h1>
|
||||
<?php if ($cuti !== []) : ?>
|
||||
<p class="mt-2"><span class="inline-flex rounded-full px-3 py-1 text-xs font-semibold <?= $badgeClass ?>"><?= esc($st !== '' ? $st : '—') ?></span></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/cuti') ?>" class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left text-xs text-gray-500"></i> Kembali ke daftar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<?php if ($cuti !== []) : ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500">Ringkasan</h2>
|
||||
<dl class="mt-4 grid gap-4 text-sm sm:grid-cols-2">
|
||||
<div class="sm:col-span-2">
|
||||
<dt class="text-xs font-medium text-gray-500">Pegawai</dt>
|
||||
<dd class="mt-1 font-semibold text-gray-900"><?= esc((string) ($cuti['nama_lengkap'] ?? '')) ?> <span class="font-normal text-gray-500">· NIP <?= esc((string) ($cuti['nip'] ?? '')) ?></span></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-medium text-gray-500">Tanggal cuti</dt>
|
||||
<dd class="mt-1 font-medium text-gray-900"><?= esc(cuti_tanggal_label_from_row($cuti)) ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-medium text-gray-500">Tipe</dt>
|
||||
<dd class="mt-1 text-gray-800"><?= esc((string) ($cuti['tipe_cuti'] ?? '')) ?></dd>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<dt class="text-xs font-medium text-gray-500">Alasan</dt>
|
||||
<dd class="mt-1 text-gray-800"><?= esc((string) ($cuti['alasan_cuti'] ?? '')) ?></dd>
|
||||
</div>
|
||||
<?php if ($st === 'Rejected' && ! empty($cuti['alasan_tolak'])) : ?>
|
||||
<div class="sm:col-span-2 rounded-xl border border-red-100 bg-red-50/50 p-3">
|
||||
<dt class="text-xs font-semibold text-red-800">Alasan tolak</dt>
|
||||
<dd class="mt-1 text-sm text-red-900"><?= esc((string) $cuti['alasan_tolak']) ?></dd>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<?php if ($dok !== []) : ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h2 class="text-sm font-semibold text-gray-900">Dokumen</h2>
|
||||
<ul class="mt-3 space-y-2 text-sm">
|
||||
<?php foreach ($dok as $d) : ?>
|
||||
<?php if (! is_array($d)) {
|
||||
continue;
|
||||
} ?>
|
||||
<?php $fn = (string) ($d['dokumen'] ?? ''); ?>
|
||||
<?php if ($fn !== '') : ?>
|
||||
<li>
|
||||
<a href="<?= esc(site_url('admin/cuti/dokumen/' . rawurlencode($fn))) ?>" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 font-medium text-blue-600 hover:text-blue-800">
|
||||
<i class="fa-regular fa-file text-gray-400"></i> <?= esc($fn) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($st === 'Waiting') : ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm space-y-5">
|
||||
<h2 class="text-sm font-semibold text-gray-900">Persetujuan</h2>
|
||||
<form method="post" action="<?= site_url('admin/cuti/approve/' . $id) ?>" class="flex flex-wrap gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex rounded-lg bg-emerald-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-emerald-700">Setujui (Approve)</button>
|
||||
</form>
|
||||
<form method="post" action="<?= site_url('admin/cuti/reject/' . $id) ?>" class="space-y-3 border-t border-gray-100 pt-5">
|
||||
<?= csrf_field() ?>
|
||||
<label class="block text-xs font-medium text-gray-600">Tolak — alasan wajib</label>
|
||||
<textarea name="alasan_tolak" rows="3" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" placeholder="Jelaskan alasan penolakan"><?= esc(old('alasan_tolak') ?? '') ?></textarea>
|
||||
<button type="submit" class="inline-flex rounded-lg bg-red-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-700">Tolak (Reject)</button>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
<?php else : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-circle-question',
|
||||
'title' => 'Cuti tidak ditemukan',
|
||||
'hint' => 'ID tidak valid atau data sudah tidak tersedia.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
117
app/Views/admin/cuti/index.php
Normal file
117
app/Views/admin/cuti/index.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Data Cuti<?= $this->endSection() ?>
|
||||
|
||||
<?php
|
||||
$rows = is_array($payload) ? ($payload['rows'] ?? []) : [];
|
||||
$total = is_array($payload) ? (int) ($payload['total'] ?? 0) : 0;
|
||||
$totalPage = is_array($payload) ? max(1, (int) ($payload['total_page'] ?? 1)) : 1;
|
||||
$curPage = is_array($payload) ? (int) ($payload['page'] ?? $page) : $page;
|
||||
$statusOpts = ['' => 'Semua', 'Waiting' => 'Waiting', 'Approve' => 'Approve', 'Rejected' => 'Rejected', 'Cancelled' => 'Cancelled'];
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Data cuti pegawai</h1>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">Filter menurut status dan lakukan persetujuan atau penolakan dari sini.</p>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<form method="get" action="<?= site_url('admin/cuti') ?>" class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Status</label>
|
||||
<select name="status" class="mt-1 min-w-[12rem] rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
<?php foreach ($statusOpts as $val => $lab) : ?>
|
||||
<option value="<?= esc($val) ?>" <?= ($status === $val) ? 'selected' : '' ?>><?= esc($lab) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Terapkan</button>
|
||||
<a href="<?= site_url('admin/cuti') ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">Semua status</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Daftar permohonan</h2>
|
||||
<p class="mt-0.5 text-xs text-gray-500">Hijau = disetujui · kuning = menunggu · merah = ditolak.</p>
|
||||
</div>
|
||||
<?php if ($rows === []) : ?>
|
||||
<div class="p-6">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-plane-departure',
|
||||
'title' => 'Tidak ada permohonan cuti',
|
||||
'hint' => 'Pilih status lain atau kembali ke Semua status untuk melihat seluruh riwayat.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Pegawai</th>
|
||||
<th class="px-4 py-3">Tanggal</th>
|
||||
<th class="px-4 py-3">Tipe</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $c) : ?>
|
||||
<?php if (! is_array($c)) {
|
||||
continue;
|
||||
} ?>
|
||||
<?php
|
||||
$st = (string) ($c['status_cuti'] ?? '');
|
||||
$badge = match ($st) {
|
||||
'Approve' => 'bg-emerald-50 text-emerald-800 ring-1 ring-emerald-100',
|
||||
'Rejected' => 'bg-red-50 text-red-800 ring-1 ring-red-100',
|
||||
'Waiting' => 'bg-amber-50 text-amber-900 ring-1 ring-amber-100',
|
||||
'Cancelled' => 'bg-gray-100 text-gray-600 ring-1 ring-gray-200',
|
||||
default => 'bg-gray-100 text-gray-700 ring-1 ring-gray-200',
|
||||
};
|
||||
?>
|
||||
<tr class="transition-colors hover:bg-gray-50">
|
||||
<td class="px-4 py-2.5">
|
||||
<div class="font-medium text-gray-900"><?= esc((string) ($c['nama_lengkap'] ?? '')) ?></div>
|
||||
<div class="text-xs text-gray-500"><?= esc((string) ($c['nip'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-2.5 text-gray-800"><?= esc(cuti_tanggal_label_from_row($c)) ?></td>
|
||||
<td class="px-4 py-2.5 text-gray-700"><?= esc((string) ($c['tipe_cuti'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5">
|
||||
<span class="inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold <?= $badge ?>"><?= esc($st) ?></span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-2.5">
|
||||
<a href="<?= site_url('admin/cuti/detail/' . (int) ($c['id_cuti'] ?? 0)) ?>" class="mr-2 text-xs font-semibold text-blue-600 hover:text-blue-800">Detail</a>
|
||||
<?php if ($st === 'Waiting') : ?>
|
||||
<form method="post" action="<?= site_url('admin/cuti/approve/' . (int) ($c['id_cuti'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Setujui permohonan ini?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-xs font-semibold text-emerald-700 hover:text-emerald-900 hover:underline">Approve</button>
|
||||
</form>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="flex flex-col gap-3 border-t border-gray-200 bg-gray-50/80 px-5 py-3 text-xs text-gray-600 sm:flex-row sm:items-center sm:justify-between">
|
||||
<span>Total: <strong class="text-gray-900"><?= number_format($total, 0, ',', '.') ?></strong></span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<?php if ($curPage > 1) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium text-gray-800 shadow-sm hover:bg-gray-50" href="<?= site_url('admin/cuti?' . http_build_query(['status' => $status, 'page' => $curPage - 1])) ?>">Sebelumnya</a>
|
||||
<?php endif ?>
|
||||
<?php if ($curPage < $totalPage) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium text-gray-800 shadow-sm hover:bg-gray-50" href="<?= site_url('admin/cuti?' . http_build_query(['status' => $status, 'page' => $curPage + 1])) ?>">Berikutnya</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
218
app/Views/admin/dashboard/_profil_ringkas.php
Normal file
218
app/Views/admin/dashboard/_profil_ringkas.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array<string, mixed>|object|null $profil */
|
||||
$p = $profil ?? null;
|
||||
if (is_object($p)) {
|
||||
$json = json_encode($p);
|
||||
$p = is_string($json) ? json_decode($json, true) : [];
|
||||
}
|
||||
$p = is_array($p) ? $p : [];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
$nestedNama = static function (mixed $row, string ...$keys): string {
|
||||
if (! is_array($row)) {
|
||||
return '';
|
||||
}
|
||||
foreach ($keys as $k) {
|
||||
if (isset($row[$k]) && is_string($row[$k]) && $row[$k] !== '') {
|
||||
return $row[$k];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$fmtDate = static function (mixed $v): string {
|
||||
if ($v === null || $v === '') {
|
||||
return '—';
|
||||
}
|
||||
$s = trim((string) $v);
|
||||
if ($s === '' || str_starts_with($s, '0000-00-00')) {
|
||||
return '—';
|
||||
}
|
||||
if (preg_match('/^(\d{4}-\d{1,2}-\d{1,2})/', $s, $m)) {
|
||||
$t = strtotime($m[1]);
|
||||
|
||||
return $t !== false ? date('d/m/Y', $t) : '—';
|
||||
}
|
||||
|
||||
return '—';
|
||||
};
|
||||
|
||||
$fmtDateTime = static function (mixed $v): string {
|
||||
if ($v === null || $v === '') {
|
||||
return '—';
|
||||
}
|
||||
$s = trim((string) $v);
|
||||
$t = strtotime($s);
|
||||
|
||||
return $t !== false ? date('d/m/Y H:i', $t) : $s;
|
||||
};
|
||||
|
||||
$fmtBool = static function (mixed $v): string {
|
||||
if ($v === null || $v === '') {
|
||||
return '—';
|
||||
}
|
||||
if (is_bool($v)) {
|
||||
return $v ? 'Ya' : 'Tidak';
|
||||
}
|
||||
$f = filter_var($v, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
|
||||
return $f === null ? (string) $v : ($f ? 'Ya' : 'Tidak');
|
||||
};
|
||||
|
||||
$scalarOrDash = static function (mixed $v): string {
|
||||
if ($v === null) {
|
||||
return '—';
|
||||
}
|
||||
if (is_bool($v)) {
|
||||
return $v ? 'Ya' : 'Tidak';
|
||||
}
|
||||
if (is_scalar($v)) {
|
||||
$s = trim((string) $v);
|
||||
|
||||
return $s === '' ? '—' : $s;
|
||||
}
|
||||
|
||||
return '—';
|
||||
};
|
||||
|
||||
$lemburLabel = static function (mixed $v): string {
|
||||
if ($v === null || $v === false || $v === '') {
|
||||
return 'Tidak ada';
|
||||
}
|
||||
if (is_string($v) || is_int($v) || is_float($v)) {
|
||||
$s = trim((string) $v);
|
||||
|
||||
return $s === '' ? 'Tidak ada' : $s;
|
||||
}
|
||||
if (is_array($v)) {
|
||||
return $v !== [] ? 'Ada (hari ini)' : 'Tidak ada';
|
||||
}
|
||||
if (is_object($v)) {
|
||||
$a = (array) $v;
|
||||
|
||||
return $a !== [] ? 'Ada (hari ini)' : 'Tidak ada';
|
||||
}
|
||||
|
||||
return '—';
|
||||
};
|
||||
|
||||
$rows = [
|
||||
['key' => 'nip', 'label' => 'NIP', 'fmt' => 'scalar'],
|
||||
['key' => 'jenis_kelamin', 'label' => 'Jenis kelamin', 'fmt' => 'scalar'],
|
||||
['key' => 'tempat_lahir', 'label' => 'Tempat lahir', 'fmt' => 'scalar'],
|
||||
['key' => 'tanggal_lahir', 'label' => 'Tanggal lahir', 'fmt' => 'date'],
|
||||
['key' => 'email', 'label' => 'Email', 'fmt' => 'scalar'],
|
||||
['key' => 'kantor', 'label' => 'Kantor', 'fmt' => 'nested', 'nested' => ['nama_kantor', 'nama']],
|
||||
['key' => 'jabatan', 'label' => 'Jabatan', 'fmt' => 'nested', 'nested' => ['nama_jabatan', 'nama']],
|
||||
['key' => 'unit_kerja', 'label' => 'Unit kerja', 'fmt' => 'nested', 'nested' => ['nama_unit_kerja', 'nama']],
|
||||
['key' => 'golongan_pekerjaan', 'label' => 'Golongan (ID)', 'fmt' => 'scalar'],
|
||||
['key' => 'status_kepegawaian', 'label' => 'Status kepegawaian', 'fmt' => 'scalar'],
|
||||
['key' => 'tanggal_bergabung', 'label' => 'Tanggal bergabung', 'fmt' => 'date'],
|
||||
['key' => 'super_akses', 'label' => 'Super akses', 'fmt' => 'bool'],
|
||||
['key' => 'last_login', 'label' => 'Terakhir login', 'fmt' => 'dt'],
|
||||
['key' => 'lembur', 'label' => 'Lembur hari ini', 'fmt' => 'lembur'],
|
||||
['key' => 'dilapangan', 'label' => 'Tugas luar / lapangan', 'fmt' => 'bool'],
|
||||
];
|
||||
|
||||
$nama = (string) ($p['nama_lengkap'] ?? '');
|
||||
$nip = (string) ($p['nip'] ?? '');
|
||||
$idPeg = isset($p['id_pegawai']) ? (string) $p['id_pegawai'] : '';
|
||||
$photo = trim((string) ($p['photo'] ?? ''));
|
||||
$jadwal = $p['jadwal'] ?? null;
|
||||
?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500">Profil pegawai</h2>
|
||||
|
||||
<?php if ($p === []) : ?>
|
||||
<div class="mt-4">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-user',
|
||||
'title' => 'Belum ada data profil',
|
||||
'hint' => 'Setelah login pegawai valid, ringkasan profil akan tampil di sini.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="mt-4 flex flex-col gap-4 border-b border-gray-100 pb-4 sm:flex-row sm:items-start sm:gap-5">
|
||||
<div class="flex h-16 w-16 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-gray-200 bg-gray-50 text-gray-400">
|
||||
<?php if ($photo !== '') : ?>
|
||||
<img src="<?= esc($photo) ?>" alt="" class="h-full w-full object-cover" loading="lazy" decoding="async" onerror="this.style.display='none'; this.nextElementSibling?.classList.remove('hidden');">
|
||||
<i class="fa-solid fa-user hidden text-2xl"></i>
|
||||
<?php else : ?>
|
||||
<i class="fa-solid fa-user text-2xl"></i>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-lg font-semibold tracking-tight text-gray-900"><?= esc($nama !== '' ? $nama : '—') ?></p>
|
||||
<?php if ($nip !== '') : ?>
|
||||
<p class="mt-0.5 font-mono text-sm text-gray-600"><?= esc($nip) ?></p>
|
||||
<?php endif ?>
|
||||
<?php if ($idPeg !== '') : ?>
|
||||
<p class="mt-1 text-xs text-gray-400">ID pegawai <?= esc($idPeg) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="mt-4 grid gap-x-6 gap-y-3 text-sm sm:grid-cols-2">
|
||||
<?php foreach ($rows as $spec) : ?>
|
||||
<?php
|
||||
$key = $spec['key'];
|
||||
$raw = $p[$key] ?? null;
|
||||
$label = $spec['label'];
|
||||
$fmt = $spec['fmt'];
|
||||
$out = '—';
|
||||
if ($fmt === 'scalar') {
|
||||
$out = $scalarOrDash($raw);
|
||||
} elseif ($fmt === 'date') {
|
||||
$out = $fmtDate($raw);
|
||||
} elseif ($fmt === 'dt') {
|
||||
$out = $fmtDateTime($raw);
|
||||
} elseif ($fmt === 'bool') {
|
||||
$out = $fmtBool($raw);
|
||||
} elseif ($fmt === 'lembur') {
|
||||
$out = $lemburLabel($raw);
|
||||
} elseif ($fmt === 'nested' && isset($spec['nested'])) {
|
||||
$out = $nestedNama(is_array($raw) ? $raw : (is_object($raw) ? (array) $raw : []), ...$spec['nested']);
|
||||
$out = $out !== '' ? $out : '—';
|
||||
}
|
||||
if ($key === 'email' && $out === '—') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="flex flex-col gap-0.5 border-b border-gray-50 pb-3 sm:border-0 sm:pb-0">
|
||||
<dt class="text-xs font-medium text-gray-500"><?= esc($label) ?></dt>
|
||||
<dd class="font-medium text-gray-900"><?= esc((string) $out) ?></dd>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</dl>
|
||||
|
||||
<?php if (is_array($jadwal) && $jadwal !== []) : ?>
|
||||
<div class="mt-5 rounded-xl border border-gray-100 bg-gray-50/80 px-4 py-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Jadwal hari ini</p>
|
||||
<p class="mt-1 text-sm text-gray-800">
|
||||
<?php
|
||||
$hari = (string) ($jadwal['hari'] ?? '');
|
||||
$masuk = (string) ($jadwal['masuk'] ?? '');
|
||||
$pulang = (string) ($jadwal['pulang'] ?? '');
|
||||
$ket = (string) ($jadwal['ket_libur'] ?? '');
|
||||
$libur = ! empty($jadwal['libur']);
|
||||
?>
|
||||
<?php if ($hari !== '') : ?><span class="font-medium"><?= esc($hari) ?></span><?php endif ?>
|
||||
<?php if (! $libur && ($masuk !== '' || $pulang !== '')) : ?>
|
||||
<span class="text-gray-600"> · Masuk <?= esc($masuk !== '' ? $masuk : '—') ?>, pulang <?= esc($pulang !== '' ? $pulang : '—') ?></span>
|
||||
<?php endif ?>
|
||||
<?php if ($ket !== '') : ?>
|
||||
<span class="mt-1 block text-xs text-amber-800"><?= esc($ket) ?></span>
|
||||
<?php elseif ($libur && $ket === '') : ?>
|
||||
<span class="mt-1 block text-xs text-gray-600">Hari libur / tidak ada jadwal masuk</span>
|
||||
<?php endif ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
208
app/Views/admin/dashboard/index.php
Normal file
208
app/Views/admin/dashboard/index.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Dashboard Presensi<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<?php
|
||||
$s = is_array($summary ?? null) ? $summary : [];
|
||||
$belumList = is_array($s['belum_rekam_pegawai'] ?? null) ? $s['belum_rekam_pegawai'] : [];
|
||||
?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Dashboard Presensi</h1>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">Statistik harian dan antrian pengajuan cuti.</p>
|
||||
</div>
|
||||
<span class="inline-flex w-fit shrink-0 items-center rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm"><?= esc(date('d F Y, H:i')) ?></span>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<?php if (! empty($errors) && empty($token ?? null)) : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-4 text-sm text-gray-700 shadow-sm">
|
||||
<a href="<?= site_url('admin/login') ?>" class="inline-flex items-center rounded-lg bg-blue-600 px-4 py-2 text-xs font-semibold text-white shadow-sm hover:bg-blue-700">Login ulang</a>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($summary !== null) : ?>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Total Pegawai</p>
|
||||
<p class="mt-2 text-3xl font-bold text-gray-900"><?= number_format((int) ($s['total_pegawai'] ?? 0), 0, ',', '.') ?></p>
|
||||
<p class="mt-2 text-xs text-gray-600">
|
||||
Pria: <?= number_format((int) ($s['pegawai_laki'] ?? 0), 0, ',', '.') ?>
|
||||
<span class="text-gray-400">(<?= esc((string) ($s['persen_laki'] ?? '0')) ?>%)</span>
|
||||
· Wanita: <?= number_format((int) ($s['pegawai_perempuan'] ?? 0), 0, ',', '.') ?>
|
||||
<span class="text-gray-400">(<?= esc((string) ($s['persen_perempuan'] ?? '0')) ?>%)</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Presensi hari ini</p>
|
||||
<p class="mt-2 text-3xl font-bold text-blue-600"><?= number_format((int) ($s['presensi_hari_ini'] ?? 0), 0, ',', '.') ?></p>
|
||||
<p class="mt-2 text-xs text-gray-600">Kehadiran <?= esc((string) ($s['persen_presensi'] ?? '0')) ?>%</p>
|
||||
<p class="mt-1 text-xs text-amber-700"><?= number_format((int) ($s['belum_rekam'] ?? 0), 0, ',', '.') ?> belum rekam presensi</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Cuti hari ini (Approve)</p>
|
||||
<p class="mt-2 text-3xl font-bold text-emerald-600"><?= number_format((int) ($s['cuti_hari_ini'] ?? 0), 0, ',', '.') ?></p>
|
||||
<p class="mt-2 text-xs text-gray-600">Pegawai cuti <?= esc((string) ($s['persen_cuti'] ?? '0')) ?>% · status disetujui</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-sm font-semibold text-gray-900">Ringkasan kehadiran hari ini</h2>
|
||||
<p class="mt-0.5 text-xs text-gray-500">Proporsi hadir, cuti disetujui, dan belum rekam.</p>
|
||||
<?php
|
||||
$hadir = (int) ($s['presensi_hari_ini'] ?? 0);
|
||||
$cuti = (int) ($s['cuti_hari_ini'] ?? 0);
|
||||
$blm = (int) ($s['belum_rekam'] ?? 0);
|
||||
$tot = max(1, (int) ($s['total_pegawai'] ?? 1));
|
||||
$pH = round(($hadir / $tot) * 100, 1);
|
||||
$pC = round(($cuti / $tot) * 100, 1);
|
||||
$pB = round(($blm / $tot) * 100, 1);
|
||||
?>
|
||||
<div class="mt-4 flex h-11 w-full overflow-hidden rounded-xl text-xs font-semibold text-white shadow-inner">
|
||||
<span class="flex min-w-0 items-center justify-center bg-emerald-500 px-1" style="width: <?= esc((string) max(0, min(100, $pH))) ?>%"><?= $hadir > 0 ? 'Hadir ' . $hadir : '' ?></span>
|
||||
<span class="flex min-w-0 items-center justify-center bg-amber-500 px-1" style="width: <?= esc((string) max(0, min(100, $pC))) ?>%"><?= $cuti > 0 ? 'Cuti ' . $cuti : '' ?></span>
|
||||
<span class="flex min-w-0 items-center justify-center bg-gray-400 px-1" style="width: <?= esc((string) max(0, min(100, $pB))) ?>%"><?= $blm > 0 ? 'Belum ' . $blm : '' ?></span>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap items-baseline gap-x-6 gap-y-2 text-xs text-gray-600">
|
||||
<span><strong class="text-gray-900">Hadir</strong> <?= $hadir ?> (<?= $pH ?>%)</span>
|
||||
<span><strong class="text-gray-900">Cuti</strong> <?= $cuti ?> (<?= $pC ?>%)</span>
|
||||
<?php if ($blm > 0) : ?>
|
||||
<details class="relative max-w-full [&_summary::-webkit-details-marker]:hidden">
|
||||
<summary class="flex cursor-pointer list-none flex-wrap items-baseline gap-x-1 text-gray-600 underline decoration-dotted decoration-gray-400 underline-offset-2 hover:text-blue-700">
|
||||
<strong class="text-gray-900">Belum rekam</strong>
|
||||
<span><?= $blm ?> (<?= $pB ?>%)</span>
|
||||
<span class="text-[10px] font-normal text-blue-600">klik</span>
|
||||
</summary>
|
||||
<div class="absolute left-0 z-20 mt-2 max-h-64 w-[min(calc(100vw-2rem),20rem)] overflow-y-auto rounded-lg border border-gray-200 bg-white p-3 text-left shadow-lg">
|
||||
<p class="mb-2 font-semibold text-gray-800">Yang belum rekam presensi hari ini</p>
|
||||
<p class="mb-2 text-[11px] leading-snug text-gray-500">Hanya informasi; tidak termasuk yang cuti disetujui.</p>
|
||||
<?php if ($belumList === []) : ?>
|
||||
<p class="text-gray-500">Daftar nama tidak tersedia. Coba muat ulang halaman setelah pembaruan server.</p>
|
||||
<?php else : ?>
|
||||
<ol class="list-decimal space-y-1.5 pl-4 text-gray-700">
|
||||
<?php foreach ($belumList as $bp) : ?>
|
||||
<?php if (! is_array($bp)) {
|
||||
continue;
|
||||
} ?>
|
||||
<li class="leading-snug">
|
||||
<span class="font-medium text-gray-900"><?= esc((string) ($bp['nama_lengkap'] ?? '')) ?></span>
|
||||
<?php if (($bp['nip'] ?? '') !== '') : ?>
|
||||
<span class="text-gray-500"> · NIP <?= esc((string) $bp['nip']) ?></span>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ol>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php else : ?>
|
||||
<span><strong class="text-gray-900">Belum rekam</strong> <?= $blm ?> (<?= $pB ?>%)</span>
|
||||
<?php endif ?>
|
||||
<span><strong class="text-gray-900">Total</strong> <?= (int) ($s['total_pegawai'] ?? 0) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$pending = is_array($s['permohonan_cuti'] ?? null) ? $s['permohonan_cuti'] : [];
|
||||
$nPending = (int) ($s['total_permohonan_cuti'] ?? 0);
|
||||
?>
|
||||
<section class="overflow-hidden rounded-2xl border border-amber-200/80 bg-white shadow-sm">
|
||||
<div class="border-b border-amber-100 bg-amber-50 px-5 py-4">
|
||||
<h2 class="text-sm font-semibold text-amber-950">Permohonan cuti menunggu persetujuan</h2>
|
||||
<p class="mt-0.5 text-xs text-amber-900/80"><?= number_format($nPending, 0, ',', '.') ?> permohonan menunggu, seluruhnya pengajuan lihat di Data Cuti.</p>
|
||||
</div>
|
||||
<?php if ($pending === []) : ?>
|
||||
<div class="p-6">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-clipboard-check',
|
||||
'title' => 'Tidak ada antrian Waiting',
|
||||
'hint' => 'Permohonan baru akan muncul di sini. Kelola semua cuti lewat menu Pegawai → Data Cuti.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-5 py-3">Pegawai</th>
|
||||
<th class="px-5 py-3">Tanggal cuti</th>
|
||||
<th class="px-5 py-3">Tipe</th>
|
||||
<th class="px-5 py-3">Alasan</th>
|
||||
<?php if (canAccess('cuti')) : ?>
|
||||
<th class="px-5 py-3">Aksi</th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($pending as $row) : ?>
|
||||
<?php if (! is_array($row)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="transition-colors hover:bg-gray-50">
|
||||
<td class="px-5 py-3">
|
||||
<div class="font-medium text-gray-900"><?= esc((string) ($row['nama_lengkap'] ?? '')) ?></div>
|
||||
<div class="text-xs text-gray-500">NIP: <?= esc((string) ($row['nip'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-5 py-3 text-gray-800"><?= esc(cuti_tanggal_label_from_row($row)) ?></td>
|
||||
<td class="px-5 py-3 text-gray-700"><?= esc((string) ($row['tipe_cuti'] ?? '')) ?></td>
|
||||
<td class="max-w-xs truncate px-5 py-3 text-gray-600" title="<?= esc((string) ($row['alasan_cuti'] ?? '')) ?>"><?= esc((string) ($row['alasan_cuti'] ?? '')) ?></td>
|
||||
<?php if (canAccess('cuti')) : ?>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<form method="post" action="<?= site_url('admin/cuti/approve/' . (int) ($row['id_cuti'] ?? 0)) ?>" class="inline">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex rounded-lg bg-emerald-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-emerald-700">Approve</button>
|
||||
</form>
|
||||
<a href="<?= site_url('admin/cuti/detail/' . (int) ($row['id_cuti'] ?? 0)) ?>" class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Tolak…</a>
|
||||
</div>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if (canAccess('cuti')) : ?>
|
||||
<div class="border-t border-gray-100 bg-gray-50/80 px-5 py-3">
|
||||
<a href="<?= site_url('admin/cuti') ?>" class="text-sm font-semibold text-blue-600 hover:text-blue-800">Kelola semua cuti →</a>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<?= view('admin/dashboard/_profil_ringkas', ['profil' => $profil ?? null]) ?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500">Berita terbaru</h2>
|
||||
<?php if (! empty($berita) && is_array($berita)) : ?>
|
||||
<ul class="mt-4 divide-y divide-gray-100">
|
||||
<?php foreach ($berita as $row) : ?>
|
||||
<li class="py-3 text-sm">
|
||||
<?php if (is_array($row)) : ?>
|
||||
<span class="font-medium text-gray-900"><?= esc((string) ($row['judul'] ?? $row['title'] ?? 'Item')) ?></span>
|
||||
<?php if (! empty($row['tanggal'] ?? $row['tgl'] ?? null)) : ?>
|
||||
<span class="mt-0.5 block text-xs text-gray-400"><?= esc((string) ($row['tanggal'] ?? $row['tgl'])) ?></span>
|
||||
<?php endif ?>
|
||||
<?php else : ?>
|
||||
<?= esc((string) $row) ?>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php else : ?>
|
||||
<div class="mt-4">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-newspaper',
|
||||
'title' => 'Tidak ada berita',
|
||||
'hint' => 'Pengumuman dari sistem akan tampil di blok ini bila tersedia.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
95
app/Views/admin/laporan/cuti.php
Normal file
95
app/Views/admin/laporan/cuti.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Laporan cuti<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Laporan cuti pegawai</h1>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">Filter menurut tanggal mulai cuti. Untuk mencetak halaman ini, gunakan <kbd class="rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 text-[10px] font-sans">Ctrl</kbd>+<kbd class="rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 text-[10px] font-sans">P</kbd> di browser.</p>
|
||||
</div>
|
||||
<button type="button" onclick="window.print()" class="hidden h-10 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-white px-4 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50 print:hidden sm:inline-flex">
|
||||
<i class="fa-solid fa-print mr-2 text-gray-500"></i> Cetak
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<form method="get" action="<?= site_url('admin/laporan/cuti') ?>" class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Dari</label>
|
||||
<input type="date" name="dari" value="<?= esc($dari) ?>" class="mt-1 rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Sampai</label>
|
||||
<input type="date" name="sampai" value="<?= esc($sampai) ?>" class="mt-1 rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Tampilkan</button>
|
||||
<a href="<?= site_url('admin/laporan') ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">Ringkasan</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Daftar cuti</h2>
|
||||
<p class="mt-0.5 text-xs text-gray-500"><?= count($rows) ?> baris · <?= esc($dari) ?> s/d <?= esc($sampai) ?></p>
|
||||
</div>
|
||||
<?php if ($rows === []) : ?>
|
||||
<div class="p-6">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-file-lines',
|
||||
'title' => 'Tidak ada cuti pada rentang ini',
|
||||
'hint' => 'Perlebar rentang tanggal atau kembali ke ringkasan laporan.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Tanggal</th>
|
||||
<th class="px-4 py-3">Nama</th>
|
||||
<th class="px-4 py-3">NIP</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
<th class="px-4 py-3">Jenis / keterangan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php
|
||||
$st = (string) ($r['status'] ?? '');
|
||||
$badgeClass = 'bg-gray-100 text-gray-700 ring-1 ring-gray-200';
|
||||
if ($st === 'Approve') {
|
||||
$badgeClass = 'bg-emerald-50 text-emerald-800 ring-1 ring-emerald-100';
|
||||
} elseif ($st === 'Waiting') {
|
||||
$badgeClass = 'bg-amber-50 text-amber-900 ring-1 ring-amber-100';
|
||||
} elseif ($st === 'Rejected') {
|
||||
$badgeClass = 'bg-red-50 text-red-800 ring-1 ring-red-100';
|
||||
} elseif ($st === 'Cancelled') {
|
||||
$badgeClass = 'bg-gray-100 text-gray-600 ring-1 ring-gray-200';
|
||||
}
|
||||
$jenis = (string) ($r['jenis_cuti'] ?? $r['jenis'] ?? $r['keterangan'] ?? '');
|
||||
?>
|
||||
<tr class="transition-colors hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-4 py-2.5 text-gray-800"><?= esc((string) ($r['tanggal_cuti'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 font-medium text-gray-900"><?= esc((string) ($r['nama_lengkap'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-4 py-2.5 font-mono text-xs text-gray-600"><?= esc((string) ($r['nip'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5">
|
||||
<span class="inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold <?= $badgeClass ?>"><?= esc($st !== '' ? $st : '—') ?></span>
|
||||
</td>
|
||||
<td class="max-w-xs truncate px-4 py-2.5 text-gray-600" title="<?= esc($jenis) ?>"><?= esc($jenis !== '' ? $jenis : '—') ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
86
app/Views/admin/laporan/index.php
Normal file
86
app/Views/admin/laporan/index.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Laporan<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Ringkasan laporan</h1>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">Ringkasan angka untuk rentang tanggal yang dipilih. Untuk daftar cuti per periode, buka <strong>Laporan → Cuti pegawai</strong>.</p>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<form method="get" action="<?= site_url('admin/laporan') ?>" class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Dari</label>
|
||||
<input type="date" name="dari" value="<?= esc($dari) ?>" class="mt-1 rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Sampai</label>
|
||||
<input type="date" name="sampai" value="<?= esc($sampai) ?>" class="mt-1 rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Tampilkan</button>
|
||||
<a href="<?= site_url('admin/laporan/cuti') ?>?dari=<?= esc(urlencode($dari)) ?>&sampai=<?= esc(urlencode($sampai)) ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">Laporan cuti</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (is_array($summary)) : ?>
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Total pegawai</p>
|
||||
<p class="mt-2 text-3xl font-bold text-gray-900"><?= number_format((int) ($summary['total_pegawai'] ?? 0), 0, ',', '.') ?></p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Presensi (rekam) rentang</p>
|
||||
<p class="mt-2 text-3xl font-bold text-emerald-700"><?= number_format((int) ($summary['presensi_rekam'] ?? 0), 0, ',', '.') ?></p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Cuti disetujui rentang</p>
|
||||
<p class="mt-2 text-3xl font-bold text-sky-700"><?= number_format((int) ($summary['cuti_approve'] ?? 0), 0, ',', '.') ?></p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Presensi hari ini</p>
|
||||
<p class="mt-2 text-2xl font-bold text-gray-900"><?= number_format((int) ($summary['presensi_hari_ini'] ?? 0), 0, ',', '.') ?></p>
|
||||
<p class="text-xs text-gray-500"><?= esc((string) ($summary['hari_ini'] ?? '')) ?></p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Cuti (Approve) hari ini</p>
|
||||
<p class="mt-2 text-2xl font-bold text-gray-900"><?= number_format((int) ($summary['cuti_hari_ini'] ?? 0), 0, ',', '.') ?></p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Belum rekam (estimasi)</p>
|
||||
<p class="mt-2 text-2xl font-bold text-amber-700"><?= number_format((int) ($summary['belum_rekam_hari_ini'] ?? 0), 0, ',', '.') ?></p>
|
||||
<p class="text-xs text-gray-500">Total − presensi hari ini − cuti approve hari ini</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Laporan terkait</p>
|
||||
<p class="mt-1 text-sm font-semibold text-gray-900">Cuti pegawai (rentang tanggal)</p>
|
||||
<p class="mt-1 text-xs text-gray-500">Tabel detail per pegawai; cetak dari browser (Ctrl+P) di halaman laporan cuti.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/laporan/cuti') ?>?dari=<?= esc(urlencode($dari)) ?>&sampai=<?= esc(urlencode($sampai)) ?>" class="inline-flex shrink-0 items-center justify-center rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
|
||||
Buka laporan cuti
|
||||
</a>
|
||||
</div>
|
||||
<p class="mt-5 border-t border-gray-100 pt-4 text-xs text-gray-500">Integrasi: <code class="rounded bg-gray-100 px-1">GET /api/admin/laporan</code> · <code class="rounded bg-gray-100 px-1">GET /api/admin/laporan/cuti</code></p>
|
||||
</section>
|
||||
<?php elseif (empty($errors)) : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-chart-simple',
|
||||
'title' => 'Belum ada ringkasan',
|
||||
'hint' => 'Pilih tanggal lalu klik Tampilkan untuk memuat angka agregat.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
38
app/Views/admin/panel/group_create.php
Normal file
38
app/Views/admin/panel/group_create.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Tambah grup admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Tambah grup</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Nama unik, huruf/angka/garis bawah, maks. 20 karakter (disimpan huruf kecil). Untuk pembatasan satu cabang, gunakan nama <code class="rounded bg-gray-100 px-1 text-xs">supervisor</code> dan pastikan pengguna punya assign pegawai dengan <code class="rounded bg-gray-100 px-1 text-xs">kantor</code> benar.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/panel/groups') ?>" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left mr-2 text-gray-500"></i> Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/panel/groups/store') ?>" class="grid max-w-xl gap-4">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama grup *</label>
|
||||
<input name="name" value="<?= esc(old('name', '')) ?>" required maxlength="20" pattern="[a-zA-Z0-9_]+" autocomplete="off" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm" placeholder="supervisor">
|
||||
<p class="mt-1 text-xs text-gray-500">Hanya a-z, 0-9, underscore. Tidak boleh <code class="rounded bg-gray-100 px-1">webmaster</code>.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Deskripsi *</label>
|
||||
<input name="description" value="<?= esc(old('description', '')) ?>" required maxlength="100" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm" placeholder="Supervisor cabang">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3 pt-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
<a href="<?= site_url('admin/panel/groups') ?>" class="inline-flex items-center rounded-lg border border-gray-200 px-5 py-2.5 text-sm font-medium text-gray-800 hover:bg-gray-50">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
43
app/Views/admin/panel/group_edit.php
Normal file
43
app/Views/admin/panel/group_edit.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Ubah grup admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Ubah grup</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">ID <?= (int) ($id ?? 0) ?>. Nama webmaster tidak boleh diganti.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/panel/groups') ?>" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left mr-2 text-gray-500"></i> Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<?php if (($row ?? null) !== null && is_array($row)) : ?>
|
||||
<?php $gname = strtolower((string) ($row['name'] ?? '')); ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/panel/groups/update/' . (int) ($row['id'] ?? 0)) ?>" class="grid max-w-xl gap-4">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama grup *</label>
|
||||
<input name="name" value="<?= esc(old('name', (string) ($row['name'] ?? ''))) ?>" required maxlength="20" pattern="[a-zA-Z0-9_]+" <?= $gname === 'webmaster' ? 'readonly class="w-full rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 text-sm text-gray-600"' : 'class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm"' ?>>
|
||||
<?php if ($gname === 'webmaster') : ?>
|
||||
<p class="mt-1 text-xs text-amber-700">Nama webmaster dikunci.</p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Deskripsi *</label>
|
||||
<input name="description" value="<?= esc(old('description', (string) ($row['description'] ?? ''))) ?>" required maxlength="100" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3 pt-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
<a href="<?= site_url('admin/panel/groups') ?>" class="inline-flex items-center rounded-lg border border-gray-200 px-5 py-2.5 text-sm font-medium text-gray-800 hover:bg-gray-50">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
83
app/Views/admin/panel/groups.php
Normal file
83
app/Views/admin/panel/groups.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Grup akses admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Grup akses</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Grup menentukan izin menu di panel. Contoh: <code class="rounded bg-gray-100 px-1 text-xs">hrd</code>, <code class="rounded bg-gray-100 px-1 text-xs">supervisor</code> (beberapa grup hanya melihat data sesuai kantor pegawai yang sedang login).</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="<?= site_url('admin/panel/users') ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-users mr-2 text-gray-500"></i> Pengguna
|
||||
</a>
|
||||
<a href="<?= site_url('admin/panel/groups/create') ?>" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
|
||||
<i class="fa-solid fa-plus mr-2"></i> Tambah grup
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Daftar grup</h2>
|
||||
<p class="text-xs text-gray-500">Grup <strong>webmaster</strong> tidak bisa dihapus. Tombol <strong>Hapus</strong> nonaktif jika grup masih dipakai pengguna — pindahkan pengguna ke grup lain dulu.</p>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">ID</th>
|
||||
<th class="px-4 py-3">Nama</th>
|
||||
<th class="px-4 py-3">Deskripsi</th>
|
||||
<th class="px-4 py-3">Pengguna</th>
|
||||
<th class="px-4 py-3 w-40">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php if (($rows ?? []) === []) : ?>
|
||||
<tr>
|
||||
<td colspan="5" class="px-4 py-10 text-center text-gray-500">Tidak ada grup atau gagal memuat.</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php
|
||||
$gid = (int) ($r['id'] ?? 0);
|
||||
$gname = strtolower((string) ($r['name'] ?? ''));
|
||||
$isWm = $gname === 'webmaster';
|
||||
$uc = (int) ($r['user_count'] ?? 0);
|
||||
?>
|
||||
<tr class="hover:bg-gray-50/80">
|
||||
<td class="whitespace-nowrap px-4 py-2 font-mono text-xs text-gray-600"><?= $gid ?></td>
|
||||
<td class="whitespace-nowrap px-4 py-2 font-medium text-gray-900"><?= esc((string) ($r['name'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2 text-gray-600"><?= esc((string) ($r['description'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2 text-gray-600"><?= $uc ?></td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<a href="<?= site_url('admin/panel/groups/edit/' . $gid) ?>" class="inline-flex rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Ubah</a>
|
||||
<?php if (! $isWm) : ?>
|
||||
<form method="post" action="<?= site_url('admin/panel/groups/delete/' . $gid) ?>" class="inline" onsubmit="<?= $uc > 0 ? 'return false;' : "return confirm('Hapus grup ini?');" ?>">
|
||||
<?= csrf_field() ?>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex rounded-lg border px-2 py-1 text-xs font-semibold shadow-sm <?= $uc > 0 ? 'cursor-not-allowed border-gray-200 bg-gray-100 text-gray-400' : 'border-red-200 bg-red-50 text-red-800 hover:bg-red-100' ?>"
|
||||
<?= $uc > 0 ? 'disabled title="Grup masih dipakai ' . $uc . ' pengguna. Ubah pengguna lewat menu Pengguna → hapus dari grup ini dulu."' : 'title="Hapus grup ini"' ?>
|
||||
>Hapus</button>
|
||||
</form>
|
||||
<?php else : ?>
|
||||
<span class="text-xs text-gray-400" title="Grup sistem">—</span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
99
app/Views/admin/panel/user_create.php
Normal file
99
app/Views/admin/panel/user_create.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Tambah pengguna admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Tambah pengguna admin</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Grup webmaster tidak bisa dipilih di sini. Akun bertipe pegawai menghubungkan login admin ke data pegawai tertentu.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/panel/users') ?>" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left mr-2 text-gray-500"></i> Kembali ke daftar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<?php
|
||||
$groupChoices = 0;
|
||||
foreach ($groups as $g) {
|
||||
if (strtolower((string) ($g['name'] ?? '')) !== 'webmaster') {
|
||||
$groupChoices++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if ($groupChoices === 0) : ?>
|
||||
<div class="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900">
|
||||
Tidak ada grup yang bisa dipilih (semua grup mungkin webmaster, atau belum ada grup). Tambah grup lewat <a href="<?= site_url('admin/panel/groups/create') ?>" class="font-medium text-amber-950 underline">Tambah grup</a> atau hubungi webmaster.
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h2 class="text-base font-semibold text-gray-900">Form pengguna</h2>
|
||||
<form method="post" action="<?= site_url('admin/panel/users/store') ?>" class="mt-6 grid gap-4 sm:grid-cols-2">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Username *</label>
|
||||
<input name="username" value="<?= esc(old('username', '')) ?>" required autocomplete="username" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Password *</label>
|
||||
<input type="password" name="password" required autocomplete="new-password" minlength="6" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama lengkap *</label>
|
||||
<input name="nama_lengkap" value="<?= esc(old('nama_lengkap', '')) ?>" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Email</label>
|
||||
<input type="email" name="email" value="<?= esc(old('email', '')) ?>" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">No. telepon</label>
|
||||
<input name="no_telepon" value="<?= esc(old('no_telepon', '')) ?>" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Pegawai untuk token API (opsional)</label>
|
||||
<select name="id_pegawai" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
<option value="">— Default (urutan .env / super_akses) —</option>
|
||||
<?php foreach ($pegawai_rows ?? [] as $p) : ?>
|
||||
<?php
|
||||
$pid = (int) ($p['id_pegawai'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$label = trim((string) ($p['nama_lengkap'] ?? '')) . ' — NIP ' . (string) ($p['nip'] ?? '');
|
||||
?>
|
||||
<option value="<?= $pid ?>" <?= (string) old('id_pegawai') === (string) $pid ? 'selected' : '' ?>><?= esc($label) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-gray-500">Kosongkan jika satu akun admin boleh memakai aturan fallback lama. Isi agar login panel memakai pegawai ini sebagai proxy.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Grup *</label>
|
||||
<select name="group_id" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
<option value="">— Pilih grup —</option>
|
||||
<?php foreach ($groups as $g) : ?>
|
||||
<?php
|
||||
$gid = (int) ($g['id'] ?? 0);
|
||||
$gname = strtolower((string) ($g['name'] ?? ''));
|
||||
if ($gname === 'webmaster') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<option value="<?= $gid ?>" <?= (string) old('group_id') === (string) $gid ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($g['name'] ?? '')) ?><?= isset($g['description']) && (string) $g['description'] !== '' ? ' — ' . esc((string) $g['description']) : '' ?>
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-2 flex flex-wrap gap-3 pt-2">
|
||||
<button type="submit" <?= $groupChoices === 0 ? 'disabled' : '' ?> class="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50">Simpan</button>
|
||||
<a href="<?= site_url('admin/panel/users') ?>" class="inline-flex items-center rounded-lg border border-gray-200 px-5 py-2.5 text-sm font-medium text-gray-800 hover:bg-gray-50">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
139
app/Views/admin/panel/user_edit.php
Normal file
139
app/Views/admin/panel/user_edit.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Ubah pengguna admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<?php
|
||||
$user = $user ?? null;
|
||||
$isWm = false;
|
||||
if (is_array($user)) {
|
||||
$gnames = $user['groups'] ?? [];
|
||||
$gnames = is_array($gnames) ? $gnames : [];
|
||||
foreach ($gnames as $gn) {
|
||||
if (strtolower((string) $gn) === 'webmaster') {
|
||||
$isWm = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Ubah pengguna admin</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">ID <?= (int) ($id ?? 0) ?>. Password diubah lewat <strong>Reset password</strong>. Akun webmaster: grup tetap webmaster.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/panel/users') ?>" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left mr-2 text-gray-500"></i> Kembali ke daftar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<?php if (is_array($user)) : ?>
|
||||
<?php
|
||||
$groupChoices = 0;
|
||||
foreach ($groups as $g) {
|
||||
$gname = strtolower((string) ($g['name'] ?? ''));
|
||||
if ($isWm) {
|
||||
if ($gname === 'webmaster') {
|
||||
$groupChoices++;
|
||||
}
|
||||
} elseif ($gname !== 'webmaster') {
|
||||
$groupChoices++;
|
||||
}
|
||||
}
|
||||
$gidCurrent = (int) ($user['group_id'] ?? 0);
|
||||
if ($isWm && $gidCurrent <= 0) {
|
||||
foreach ($groups as $gw) {
|
||||
if (strtolower((string) ($gw['name'] ?? '')) === 'webmaster') {
|
||||
$gidCurrent = (int) ($gw['id'] ?? 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$pegawaiKey = array_key_exists('id_pegawai', $user);
|
||||
$pidCurrent = $pegawaiKey ? (int) ($user['id_pegawai'] ?? 0) : 0;
|
||||
$actCurrent = (int) ($user['active'] ?? 1) === 1 ? '1' : '0';
|
||||
?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/panel/users/update/' . (int) ($user['id'] ?? 0)) ?>" class="mt-0 grid gap-4 sm:grid-cols-2">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Username *</label>
|
||||
<input name="username" value="<?= esc(old('username', (string) ($user['username'] ?? ''))) ?>" required autocomplete="username" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Status *</label>
|
||||
<select name="active" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
<option value="1" <?= (string) old('active', $actCurrent) === '1' ? 'selected' : '' ?>>Aktif</option>
|
||||
<option value="0" <?= (string) old('active', $actCurrent) === '0' ? 'selected' : '' ?>>Nonaktif</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama lengkap *</label>
|
||||
<input name="nama_lengkap" value="<?= esc(old('nama_lengkap', (string) ($user['nama_lengkap'] ?? ''))) ?>" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Email</label>
|
||||
<input type="email" name="email" value="<?= esc(old('email', (string) ($user['email'] ?? ''))) ?>" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">No. telepon</label>
|
||||
<input name="no_telepon" value="<?= esc(old('no_telepon', (string) ($user['no_telepon'] ?? ''))) ?>" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<?php if ($pegawaiKey) : ?>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Pegawai untuk token API (opsional)</label>
|
||||
<select name="id_pegawai" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
<option value="">— Default (urutan .env / super_akses) —</option>
|
||||
<?php foreach ($pegawai_rows ?? [] as $p) : ?>
|
||||
<?php
|
||||
$pid = (int) ($p['id_pegawai'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$label = trim((string) ($p['nama_lengkap'] ?? '')) . ' — NIP ' . (string) ($p['nip'] ?? '');
|
||||
$sel = (string) old('id_pegawai', (string) ($pidCurrent > 0 ? $pidCurrent : '')) === (string) $pid;
|
||||
?>
|
||||
<option value="<?= $pid ?>" <?= $sel ? 'selected' : '' ?>><?= esc($label) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="<?= $pegawaiKey ? '' : 'sm:col-span-2' ?>">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Grup *</label>
|
||||
<select name="group_id" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm" <?= $isWm ? 'disabled' : '' ?>>
|
||||
<?php foreach ($groups as $g) : ?>
|
||||
<?php
|
||||
$gid = (int) ($g['id'] ?? 0);
|
||||
$gname = strtolower((string) ($g['name'] ?? ''));
|
||||
if ($isWm) {
|
||||
if ($gname !== 'webmaster') {
|
||||
continue;
|
||||
}
|
||||
} elseif ($gname === 'webmaster') {
|
||||
continue;
|
||||
}
|
||||
$sel = (string) old('group_id', (string) $gidCurrent) === (string) $gid;
|
||||
?>
|
||||
<option value="<?= $gid ?>" <?= $sel ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($g['name'] ?? '')) ?><?= isset($g['description']) && (string) $g['description'] !== '' ? ' — ' . esc((string) $g['description']) : '' ?>
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
<?php if ($isWm) : ?>
|
||||
<input type="hidden" name="group_id" value="<?= $gidCurrent > 0 ? $gidCurrent : '' ?>">
|
||||
<p class="mt-1 text-xs text-amber-800">Grup webmaster tidak bisa diganti dari sini.</p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="sm:col-span-2 flex flex-wrap gap-3 pt-2">
|
||||
<button type="submit" <?= $groupChoices === 0 ? 'disabled' : '' ?> class="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50">Simpan</button>
|
||||
<a href="<?= site_url('admin/panel/users/reset/' . (int) ($user['id'] ?? 0)) ?>" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">Reset password</a>
|
||||
<a href="<?= site_url('admin/panel/users') ?>" class="inline-flex items-center rounded-lg border border-gray-200 px-5 py-2.5 text-sm font-medium text-gray-800 hover:bg-gray-50">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
54
app/Views/admin/panel/user_reset.php
Normal file
54
app/Views/admin/panel/user_reset.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Reset password admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Reset password</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Atur ulang kata sandi untuk pengguna ID <span class="font-mono font-semibold text-gray-800"><?= (int) $id ?></span>.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/panel/users') ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left mr-2 text-gray-500"></i> Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php $flashErr = session()->getFlashdata('error'); ?>
|
||||
<?php if (is_string($flashErr) && $flashErr !== '') : ?>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => [$flashErr]]) ?>
|
||||
<?php endif ?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h2 class="text-base font-semibold text-gray-900">Password baru</h2>
|
||||
<form method="post" action="<?= site_url('admin/panel/users/reset_password/' . (int) $id) ?>" class="mt-6 max-w-md space-y-4" id="form-reset-admin">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Password baru *</label>
|
||||
<input type="password" id="pw1" name="password" required minlength="6" autocomplete="new-password" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Ulangi password *</label>
|
||||
<input type="password" id="pw2" required minlength="6" autocomplete="new-password" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3 pt-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan password</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var f = document.getElementById('form-reset-admin');
|
||||
if (!f) return;
|
||||
f.addEventListener('submit', function (e) {
|
||||
var a = document.getElementById('pw1');
|
||||
var b = document.getElementById('pw2');
|
||||
if (!a || !b || a.value !== b.value) {
|
||||
e.preventDefault();
|
||||
alert('Kedua password harus sama.');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
102
app/Views/admin/panel/users.php
Normal file
102
app/Views/admin/panel/users.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Pengguna admin<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Daftar pengguna admin</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Daftar akun yang dapat masuk ke panel admin.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="<?= site_url('admin/panel/groups') ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-layer-group mr-2 text-gray-500"></i> Kelola grup
|
||||
</a>
|
||||
<a href="<?= site_url('admin/panel/users/create') ?>" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
|
||||
<i class="fa-solid fa-user-plus mr-2"></i> Tambah pengguna
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Pengguna</h2>
|
||||
<p class="text-xs text-gray-500">Reset password untuk akun yang sudah ada. Pembuatan akun tidak mengizinkan grup webmaster.</p>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">ID</th>
|
||||
<th class="px-4 py-3">Username</th>
|
||||
<th class="px-4 py-3">Nama</th>
|
||||
<th class="px-4 py-3">Email</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
<th class="px-4 py-3">Grup</th>
|
||||
<th class="px-4 py-3">Pegawai (API)</th>
|
||||
<th class="px-4 py-3 w-52">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php if ($rows === []) : ?>
|
||||
<tr>
|
||||
<td colspan="8" class="px-4 py-10 text-center text-gray-500">Tidak ada pengguna atau tabel belum tersedia.</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php
|
||||
$uid = (int) ($r['id'] ?? 0);
|
||||
$act = (int) ($r['active'] ?? 0) === 1;
|
||||
$grups = $r['groups'] ?? [];
|
||||
$glist = is_array($grups) ? $grups : [];
|
||||
?>
|
||||
<tr class="hover:bg-gray-50/80">
|
||||
<td class="whitespace-nowrap px-4 py-2 font-mono text-xs text-gray-600"><?= $uid ?></td>
|
||||
<td class="whitespace-nowrap px-4 py-2 font-medium text-gray-900"><?= esc((string) ($r['username'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2"><?= esc((string) ($r['nama_lengkap'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2 text-gray-600"><?= esc((string) ($r['email'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2">
|
||||
<?php if ($act) : ?>
|
||||
<span class="inline-flex rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-semibold text-emerald-800">Aktif</span>
|
||||
<?php else : ?>
|
||||
<span class="inline-flex rounded-full bg-gray-100 px-2 py-0.5 text-xs font-semibold text-gray-600">Nonaktif</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<?php foreach ($glist as $gn) : ?>
|
||||
<span class="inline-flex rounded-md bg-sky-50 px-2 py-0.5 text-xs font-medium text-sky-800"><?= esc((string) $gn) ?></span>
|
||||
<?php endforeach ?>
|
||||
<?php if ($glist === []) : ?>
|
||||
<span class="text-xs text-gray-400">—</span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="max-w-xs px-4 py-2 text-xs text-gray-700">
|
||||
<?php
|
||||
$pp = $r['pegawai_proxy'] ?? null;
|
||||
?>
|
||||
<?php if (is_string($pp) && $pp !== '') : ?>
|
||||
<?= esc($pp) ?>
|
||||
<?php else : ?>
|
||||
<span class="text-gray-400">Default</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-col gap-1 sm:flex-row sm:flex-wrap">
|
||||
<a href="<?= site_url('admin/panel/users/edit/' . $uid) ?>" class="inline-flex justify-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Ubah</a>
|
||||
<a href="<?= site_url('admin/panel/users/reset/' . $uid) ?>" class="inline-flex justify-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Reset password</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
175
app/Views/admin/pegawai/form.php
Normal file
175
app/Views/admin/pegawai/form.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?><?= $mode === 'create' ? 'Tambah' : 'Edit' ?> Pegawai<?= $this->endSection() ?>
|
||||
|
||||
<?php
|
||||
/** @var string $mode */
|
||||
/** @var array<string,mixed>|null $row */
|
||||
/** @var array<string,mixed>|null $refs */
|
||||
$r = $row ?? [];
|
||||
$jab = $refs['jabatan'] ?? [];
|
||||
$uk = $refs['unit_kerja'] ?? [];
|
||||
$gol = $refs['golongan'] ?? [];
|
||||
$kan = $refs['kantor'] ?? [];
|
||||
$jad = $refs['jadwal'] ?? [];
|
||||
$ph = (string) ($r['photo'] ?? '');
|
||||
$phDisp = $ph !== '' && $ph !== '-';
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="mx-auto max-w-3xl space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900"><?= $mode === 'create' ? 'Tambah pegawai' : 'Edit pegawai' ?></h1>
|
||||
<p class="mt-1 text-sm text-gray-500"><?= $mode === 'create' ? 'Isi data pegawai baru lalu simpan.' : 'Perbarui data pegawai lalu simpan.' ?></p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/pegawai') ?>" class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left text-xs text-gray-500"></i> Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<?php if ($refs === null) : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-triangle-exclamation',
|
||||
'title' => 'Referensi form tidak dimuat',
|
||||
'hint' => 'Periksa koneksi API atau login ulang. Tanpa jabatan/unit/kantor, form tidak dapat digunakan.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
$action = $mode === 'create' ? site_url('admin/pegawai/store') : site_url('admin/pegawai/update/' . (int) ($r['id_pegawai'] ?? 0));
|
||||
?>
|
||||
<form action="<?= $action ?>" method="post" enctype="multipart/form-data" class="space-y-4 rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<?= csrf_field() ?>
|
||||
<?php if ($mode === 'edit') : ?>
|
||||
<input type="hidden" name="photo_existing" value="<?= esc(old('photo_existing', $ph), 'attr') ?>">
|
||||
<?php endif ?>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600">NIP / NIK *</label>
|
||||
<input name="nip" required value="<?= esc(old('nip', (string) ($r['nip'] ?? ''))) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600">Nama lengkap *</label>
|
||||
<input name="nama_lengkap" required value="<?= esc(old('nama_lengkap', (string) ($r['nama_lengkap'] ?? ''))) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Jenis kelamin *</label>
|
||||
<select name="jenis_kelamin" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach (['Pria', 'Wanita'] as $jk) : ?>
|
||||
<option value="<?= esc($jk) ?>" <?= old('jenis_kelamin', (string) ($r['jenis_kelamin'] ?? 'Pria')) === $jk ? 'selected' : '' ?>><?= esc($jk) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Status kepegawaian *</label>
|
||||
<select name="status_kepegawaian" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach (['Pegawai Tetap', 'Kontrak'] as $st) : ?>
|
||||
<option value="<?= esc($st) ?>" <?= old('status_kepegawaian', (string) ($r['status_kepegawaian'] ?? 'Pegawai Tetap')) === $st ? 'selected' : '' ?>><?= esc($st) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Tempat lahir</label>
|
||||
<input name="tempat_lahir" value="<?= esc(old('tempat_lahir', (string) ($r['tempat_lahir'] ?? ''))) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Tanggal lahir</label>
|
||||
<input type="date" name="tanggal_lahir" value="<?= esc(old('tanggal_lahir', ($r['tanggal_lahir'] ?? '') && ($r['tanggal_lahir'] ?? '') !== '0000-00-00' ? (string) $r['tanggal_lahir'] : '')) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600">Email</label>
|
||||
<input name="email" type="email" value="<?= esc(old('email', (string) ($r['email'] ?? ''))) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Jabatan *</label>
|
||||
<select name="jabatan" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach ($jab as $j) : ?>
|
||||
<option value="<?= (int) ($j['id_jabatan'] ?? 0) ?>" <?= (int) old('jabatan', (string) ($r['jabatan'] ?? '0')) === (int) ($j['id_jabatan'] ?? 0) ? 'selected' : '' ?>><?= esc((string) ($j['nama_jabatan'] ?? '')) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Unit kerja *</label>
|
||||
<select name="unit_kerja" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach ($uk as $u) : ?>
|
||||
<option value="<?= (int) ($u['id_unit_kerja'] ?? 0) ?>" <?= (int) old('unit_kerja', (string) ($r['unit_kerja'] ?? '0')) === (int) ($u['id_unit_kerja'] ?? 0) ? 'selected' : '' ?>><?= esc((string) ($u['nama_unit_kerja'] ?? '')) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Golongan *</label>
|
||||
<select name="golongan_pekerjaan" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach ($gol as $g) : ?>
|
||||
<option value="<?= (int) ($g['id_golongan'] ?? 0) ?>" <?= (int) old('golongan_pekerjaan', (string) ($r['golongan_pekerjaan'] ?? '0')) === (int) ($g['id_golongan'] ?? 0) ? 'selected' : '' ?>><?= esc((string) ($g['nama_golongan'] ?? '')) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Kantor *</label>
|
||||
<select name="kantor" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach ($kan as $k) : ?>
|
||||
<option value="<?= (int) ($k['id_kantor'] ?? 0) ?>" <?= (int) old('kantor', (string) ($r['kantor'] ?? '0')) === (int) ($k['id_kantor'] ?? 0) ? 'selected' : '' ?>><?= esc((string) ($k['nama_kantor'] ?? '')) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Jadwal *</label>
|
||||
<select name="jadwal" required class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach ($jad as $jd) : ?>
|
||||
<option value="<?= (int) ($jd['id_jadwal'] ?? 0) ?>" <?= (int) old('jadwal', (string) ($r['jadwal'] ?? '1')) === (int) ($jd['id_jadwal'] ?? 0) ? 'selected' : '' ?>><?= esc((string) ($jd['nama_jadwal'] ?? '')) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Tanggal bergabung *</label>
|
||||
<input type="date" name="tanggal_bergabung" required value="<?= esc(old('tanggal_bergabung', ($r['tanggal_bergabung'] ?? '') && ($r['tanggal_bergabung'] ?? '') !== '0000-00-00' ? (string) $r['tanggal_bergabung'] : date('Y-m-d'))) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Super akses</label>
|
||||
<select name="super_akses" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm">
|
||||
<?php foreach (['false', 'true'] as $sa) : ?>
|
||||
<option value="<?= esc($sa) ?>" <?= old('super_akses', (string) ($r['super_akses'] ?? 'false')) === $sa ? 'selected' : '' ?>><?= esc($sa) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Username *</label>
|
||||
<input name="username" required value="<?= esc(old('username', (string) ($r['username'] ?? ''))) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm" autocomplete="username">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600">Password <?= $mode === 'edit' ? '(kosongkan jika tidak diubah)' : '(opsional, default = MD5(NIP))' ?></label>
|
||||
<input type="password" name="password" value="<?= esc(old('password', '')) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="sm:col-span-2 rounded-xl border border-gray-100 bg-gray-50/80 p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Foto profil</p>
|
||||
<p class="mt-1 text-xs text-gray-500">Unggah gambar (maks. 2 MB) atau isi nama file yang sudah ada di folder <code class="rounded bg-white px-1">assets/uploads/pengguna/</code>. Unggah file menggantikan isian nama file.</p>
|
||||
<?php if ($phDisp) : ?>
|
||||
<div class="mt-3 flex items-center gap-4">
|
||||
<img src="<?= esc(base_url('assets/uploads/pengguna/' . rawurlencode(basename(str_replace('\\', '/', $ph))))) ?>" alt="Foto pegawai" class="h-20 w-20 shrink-0 rounded-lg border border-gray-200 object-cover shadow-sm">
|
||||
<span class="text-xs text-gray-600">File saat ini: <strong class="font-mono text-gray-800"><?= esc(basename(str_replace('\\', '/', $ph))) ?></strong></span>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600">Unggah foto</label>
|
||||
<input type="file" name="photo_file" accept="image/jpeg,image/png,image/gif,image/webp" class="mt-1 block w-full text-sm text-gray-600 file:mr-3 file:rounded-lg file:border-0 file:bg-blue-50 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-blue-700 hover:file:bg-blue-100">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600">Nama file di server (opsional)</label>
|
||||
<input type="text" name="photo" value="<?= esc(old('photo', $ph)) ?>" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm font-mono" placeholder="contoh: 67a1b2c3d4e5f-photo.jpg" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col-reverse gap-2 border-t border-gray-100 pt-4 sm:flex-row sm:justify-end">
|
||||
<a href="<?= site_url('admin/pegawai') ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">Batal</a>
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
110
app/Views/admin/pegawai/index.php
Normal file
110
app/Views/admin/pegawai/index.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Data Pegawai<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-start">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Data Pegawai</h1>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">Daftar pegawai. Gunakan pencarian untuk memfilter nama atau NIP.</p>
|
||||
</div>
|
||||
<?php if (canAccess('pegawai_tambah')) : ?>
|
||||
<a href="<?= site_url('admin/pegawai/create') ?>" class="inline-flex shrink-0 items-center justify-center rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
|
||||
<i class="fa-solid fa-plus mr-2"></i> Tambah pegawai
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<form method="get" action="<?= site_url('admin/pegawai') ?>" class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
<label class="block text-xs font-medium text-gray-600">Cari (nama / NIP / username)</label>
|
||||
<input type="search" name="q" value="<?= esc($q) ?>" placeholder="Ketik lalu cari…" class="mt-1 w-full max-w-xl rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
<div class="flex shrink-0 gap-2">
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Cari</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
$rows = is_array($payload) ? ($payload['rows'] ?? []) : [];
|
||||
$total = is_array($payload) ? (int) ($payload['total'] ?? 0) : 0;
|
||||
$totalPage = is_array($payload) ? max(1, (int) ($payload['total_page'] ?? 1)) : 1;
|
||||
$curPage = is_array($payload) ? (int) ($payload['page'] ?? $page) : $page;
|
||||
?>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Daftar pegawai</h2>
|
||||
<p class="mt-0.5 text-xs text-gray-500">Edit, reset password (NIP MD5), atau hapus baris.</p>
|
||||
</div>
|
||||
<?php if ($rows === []) : ?>
|
||||
<div class="p-6">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-users',
|
||||
'title' => 'Tidak ada pegawai',
|
||||
'hint' => 'Sesuaikan kata kunci pencarian atau tambah pegawai baru dengan tombol di atas.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">NIP</th>
|
||||
<th class="px-4 py-3">Nama</th>
|
||||
<th class="px-4 py-3">JK</th>
|
||||
<th class="px-4 py-3">Jabatan</th>
|
||||
<th class="px-4 py-3">Unit</th>
|
||||
<th class="px-4 py-3">Golongan</th>
|
||||
<th class="px-4 py-3">Kantor</th>
|
||||
<th class="px-4 py-3 w-44">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $p) : ?>
|
||||
<tr class="transition-colors hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-4 py-2.5 font-mono text-xs text-gray-800"><?= esc((string) ($p['nip'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 font-medium text-gray-900"><?= esc((string) ($p['nama_lengkap'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 text-gray-700"><?= esc((string) ($p['jenis_kelamin'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 text-gray-700"><?= esc((string) ($p['nama_jabatan'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 text-gray-700"><?= esc((string) ($p['nama_unit_kerja'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 text-gray-700"><?= esc((string) ($p['nama_golongan'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5 text-gray-700"><?= esc((string) ($p['nama_kantor'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2.5">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<a href="<?= site_url('admin/pegawai/edit/' . (int) ($p['id_pegawai'] ?? 0)) ?>" class="inline-flex rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Edit</a>
|
||||
<form action="<?= site_url('admin/pegawai/reset/' . (int) ($p['id_pegawai'] ?? 0)) ?>" method="post" class="inline" onsubmit="return confirm('Reset password ke NIP (MD5) dan kosongkan token?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex rounded-lg border border-amber-200 bg-amber-50 px-2 py-1 text-xs font-semibold text-amber-900 hover:bg-amber-100">Reset</button>
|
||||
</form>
|
||||
<form action="<?= site_url('admin/pegawai/delete/' . (int) ($p['id_pegawai'] ?? 0)) ?>" method="post" class="inline" onsubmit="return confirm('Hapus pegawai ini?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex rounded-lg border border-red-200 bg-red-50 px-2 py-1 text-xs font-semibold text-red-800 hover:bg-red-100">Hapus</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="flex flex-col gap-3 border-t border-gray-200 bg-gray-50/80 px-5 py-3 text-xs text-gray-600 sm:flex-row sm:items-center sm:justify-between">
|
||||
<span>Total <strong class="text-gray-900"><?= number_format($total, 0, ',', '.') ?></strong> pegawai</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<?php if ($curPage > 1) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium text-gray-800 shadow-sm hover:bg-gray-50" href="<?= site_url('admin/pegawai?' . http_build_query(['page' => $curPage - 1, 'q' => $q])) ?>">Sebelumnya</a>
|
||||
<?php endif ?>
|
||||
<?php if ($curPage < $totalPage) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium text-gray-800 shadow-sm hover:bg-gray-50" href="<?= site_url('admin/pegawai?' . http_build_query(['page' => $curPage + 1, 'q' => $q])) ?>">Berikutnya</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
146
app/Views/admin/perusahaan/berita.php
Normal file
146
app/Views/admin/perusahaan/berita.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Berita<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
<?php
|
||||
$beritaPhotoUrl = static function (string $photo): ?string {
|
||||
$p = trim($photo);
|
||||
if ($p === '' || $p === '-') {
|
||||
return null;
|
||||
}
|
||||
$base = basename(str_replace('\\', '/', $p));
|
||||
|
||||
return base_url('assets/uploads/berita/' . rawurlencode($base));
|
||||
};
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Berita / pengumuman</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Foto disimpan di <code class="rounded bg-gray-100 px-1 text-xs">assets/uploads/berita/</code> (sama lokasi dengan aplikasi mobile). Bisa unggah file atau isi nama file yang sudah ada.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/berita/save') ?>" enctype="multipart/form-data" class="grid gap-4">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_berita" id="f_id" value="">
|
||||
<input type="hidden" name="photo_existing" id="f_photo_existing" value="">
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Tanggal *</label>
|
||||
<input type="date" name="tanggal" id="f_tgl" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2 rounded-xl border border-gray-100 bg-gray-50/80 p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">Gambar berita</p>
|
||||
<div id="f_preview_wrap" class="mt-2 hidden">
|
||||
<img id="f_preview_img" src="" alt="Pratinjau" class="h-20 w-20 rounded-lg border border-gray-200 object-cover shadow-sm">
|
||||
</div>
|
||||
<div class="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Unggah foto</label>
|
||||
<input type="file" name="photo_file" id="f_photo_file" accept="image/jpeg,image/png,image/gif,image/webp" class="block w-full text-sm text-gray-600 file:mr-3 file:rounded-lg file:border-0 file:bg-blue-50 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-blue-700 hover:file:bg-blue-100">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama file di server (opsional)</label>
|
||||
<input name="photo" id="f_photo" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm font-mono shadow-sm" placeholder="-" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Judul *</label>
|
||||
<input name="judul" id="f_judul" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Isi *</label>
|
||||
<textarea name="isi" id="f_isi" rows="5" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm"></textarea>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
<button type="button" id="f_clear" class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Bersihkan</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Tanggal</th><th class="px-5 py-3 w-16">Foto</th><th class="px-5 py-3">Judul</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php if (! is_array($r)) {
|
||||
continue;
|
||||
} ?>
|
||||
<?php
|
||||
$purl = $beritaPhotoUrl((string) ($r['photo'] ?? ''));
|
||||
?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-5 py-3"><?= esc((string) ($r['tanggal'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3">
|
||||
<?php if ($purl !== null) : ?>
|
||||
<img src="<?= esc($purl) ?>" alt="" class="h-10 w-10 rounded-md border border-gray-100 object-cover">
|
||||
<?php else : ?>
|
||||
<span class="text-xs text-gray-400">—</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="px-5 py-3 font-medium text-gray-900"><?= esc((string) ($r['judul'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold btn-edit-berita" data-row="<?= esc(json_encode($r, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/berita/delete/' . (int) ($r['id_berita'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var base = <?= json_encode(base_url('assets/uploads/berita/')) ?>;
|
||||
function showPreview(filename) {
|
||||
var wrap = document.getElementById('f_preview_wrap');
|
||||
var img = document.getElementById('f_preview_img');
|
||||
if (!wrap || !img) return;
|
||||
if (!filename || filename === '-') {
|
||||
wrap.classList.add('hidden');
|
||||
img.removeAttribute('src');
|
||||
return;
|
||||
}
|
||||
img.src = base + encodeURIComponent(filename.split(/[/\\]/).pop());
|
||||
wrap.classList.remove('hidden');
|
||||
}
|
||||
document.querySelectorAll('.btn-edit-berita').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var o = JSON.parse(this.getAttribute('data-row'));
|
||||
document.getElementById('f_id').value = o.id_berita || '';
|
||||
document.getElementById('f_tgl').value = o.tanggal || '';
|
||||
document.getElementById('f_photo').value = (o.photo && o.photo !== '-') ? o.photo : '';
|
||||
document.getElementById('f_photo_existing').value = o.photo || '';
|
||||
document.getElementById('f_judul').value = o.judul || '';
|
||||
document.getElementById('f_isi').value = o.isi || '';
|
||||
var pf = document.getElementById('f_photo_file');
|
||||
if (pf) pf.value = '';
|
||||
showPreview((o.photo && o.photo !== '-') ? o.photo : '');
|
||||
});
|
||||
});
|
||||
document.getElementById('f_clear').addEventListener('click', function () {
|
||||
document.getElementById('f_id').value = '';
|
||||
document.getElementById('f_tgl').value = '';
|
||||
document.getElementById('f_photo').value = '';
|
||||
document.getElementById('f_photo_existing').value = '';
|
||||
document.getElementById('f_judul').value = '';
|
||||
document.getElementById('f_isi').value = '';
|
||||
var pf = document.getElementById('f_photo_file');
|
||||
if (pf) pf.value = '';
|
||||
showPreview('');
|
||||
});
|
||||
document.getElementById('f_photo').addEventListener('input', function () {
|
||||
var v = this.value.trim();
|
||||
showPreview(v && v !== '-' ? v : '');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
48
app/Views/admin/perusahaan/golongan.php
Normal file
48
app/Views/admin/perusahaan/golongan.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Golongan<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Golongan</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Master golongan.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/golongan/save') ?>" class="flex flex-wrap items-end gap-3">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_golongan" id="f_id" value="">
|
||||
<div class="min-w-[16rem] flex-1">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama golongan</label>
|
||||
<input name="nama_golongan" id="f_nama" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Nama</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php if (! is_array($r)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3"><?= esc((string) ($r['nama_golongan'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold" data-row="<?= esc(json_encode($r, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="var o=JSON.parse(this.dataset.row);document.getElementById('f_id').value=o.id_golongan||'';document.getElementById('f_nama').value=o.nama_golongan||'';">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/golongan/delete/' . (int) ($r['id_golongan'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
48
app/Views/admin/perusahaan/jabatan.php
Normal file
48
app/Views/admin/perusahaan/jabatan.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Jabatan<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Jabatan</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Master jabatan.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/jabatan/save') ?>" class="flex flex-wrap items-end gap-3">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_jabatan" id="f_id" value="">
|
||||
<div class="min-w-[16rem] flex-1">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama jabatan *</label>
|
||||
<input name="nama_jabatan" id="f_nama" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Nama</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php if (! is_array($r)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3 font-medium text-gray-900"><?= esc((string) ($r['nama_jabatan'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold" data-row="<?= esc(json_encode($r, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="var o=JSON.parse(this.dataset.row);document.getElementById('f_id').value=o.id_jabatan||'';document.getElementById('f_nama').value=o.nama_jabatan||'';">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/jabatan/delete/' . (int) ($r['id_jabatan'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
121
app/Views/admin/perusahaan/kantor.php
Normal file
121
app/Views/admin/perusahaan/kantor.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Lokasi kerja<?= $this->endSection() ?>
|
||||
|
||||
<?php
|
||||
$rows = is_array($payload) ? ($payload['rows'] ?? []) : [];
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Lokasi kerja (Kantor)</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Kelola data kantor atau cabang perusahaan.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-base font-semibold text-gray-900">Tambah / ubah</h2>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/kantor/save') ?>" class="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_kantor" id="f_id_kantor" value="">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Kode kantor *</label>
|
||||
<input name="kode_kantor" id="f_kode" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama *</label>
|
||||
<input name="nama_kantor" id="f_nama" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Tipe *</label>
|
||||
<input name="tipe" id="f_tipe" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Alamat *</label>
|
||||
<input name="alamat_kantor" id="f_alamat" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Latitude *</label>
|
||||
<input name="lat" id="f_lat" required step="any" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Longitude *</label>
|
||||
<input name="lng" id="f_lng" required step="any" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Jarak rekam (m)</label>
|
||||
<input type="number" name="jarak_rekam_presensi" id="f_jarak" value="30" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
<button type="button" onclick="document.getElementById('f_id_kantor').value='';this.form.reset();" class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Bersihkan</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Daftar kantor</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr>
|
||||
<th class="px-5 py-3">Kode</th>
|
||||
<th class="px-5 py-3">Nama</th>
|
||||
<th class="px-5 py-3">Tipe</th>
|
||||
<th class="px-5 py-3">Peta</th>
|
||||
<th class="px-5 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php if ($rows === []) : ?>
|
||||
<tr><td colspan="5" class="px-5 py-10 text-center text-gray-500">Belum ada data.</td></tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($rows as $k) : ?>
|
||||
<?php if (! is_array($k)) {
|
||||
continue;
|
||||
} ?>
|
||||
<?php
|
||||
$lat = (float) ($k['lat'] ?? 0);
|
||||
$lng = (float) ($k['lng'] ?? 0);
|
||||
$map = ($lat != 0.0 || $lng != 0.0) ? 'https://www.google.com/maps/place/' . rawurlencode((string) $lat) . ',' . rawurlencode((string) $lng) : '';
|
||||
?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3 font-mono text-xs"><?= esc((string) ($k['kode_kantor'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3 font-medium text-gray-900"><?= esc((string) ($k['nama_kantor'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3"><?= esc((string) ($k['tipe'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3"><?= $map !== '' ? '<a href="' . esc($map) . '" target="_blank" rel="noopener" class="text-blue-600 hover:underline">Lihat</a>' : '—' ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="edit-kantor rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50" data-row="<?= esc(json_encode($k, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/kantor/delete/' . (int) ($k['id_kantor'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus kantor ini?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700 hover:bg-red-100">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelectorAll('.edit-kantor').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var row = JSON.parse(this.getAttribute('data-row'));
|
||||
document.getElementById('f_id_kantor').value = row.id_kantor || '';
|
||||
document.getElementById('f_kode').value = row.kode_kantor || '';
|
||||
document.getElementById('f_nama').value = row.nama_kantor || '';
|
||||
document.getElementById('f_tipe').value = row.tipe || '';
|
||||
document.getElementById('f_alamat').value = row.alamat_kantor || '';
|
||||
document.getElementById('f_lat').value = row.lat || '';
|
||||
document.getElementById('f_lng').value = row.lng || '';
|
||||
document.getElementById('f_jarak').value = row.jarak_rekam_presensi || 30;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
54
app/Views/admin/perusahaan/unit_kerja.php
Normal file
54
app/Views/admin/perusahaan/unit_kerja.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Unit kerja<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Unit kerja</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Master unit kerja pegawai.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-base font-semibold text-gray-900">Tambah / ubah</h2>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/unit_kerja/save') ?>" class="mt-4 flex flex-wrap items-end gap-3">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_unit_kerja" id="f_id" value="">
|
||||
<div class="min-w-[16rem] flex-1">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama unit *</label>
|
||||
<input name="nama_unit_kerja" id="f_nama" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
<button type="button" onclick="document.getElementById('f_id').value='';document.getElementById('f_nama').value='';" class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Bersihkan</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Nama</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php if ($rows === []) : ?>
|
||||
<tr><td colspan="2" class="px-5 py-10 text-center text-gray-500">Kosong.</td></tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<?php if (! is_array($r)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3 font-medium text-gray-900"><?= esc((string) ($r['nama_unit_kerja'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold shadow-sm hover:bg-gray-50" data-row="<?= esc(json_encode($r, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="var o=JSON.parse(this.dataset.row);document.getElementById('f_id').value=o.id_unit_kerja||'';document.getElementById('f_nama').value=o.nama_unit_kerja||'';">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/perusahaan/unit_kerja/delete/' . (int) ($r['id_unit_kerja'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
76
app/Views/admin/presensi/aktivitas.php
Normal file
76
app/Views/admin/presensi/aktivitas.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Aktivitas harian<?= $this->endSection() ?>
|
||||
<?php
|
||||
$rows = is_array($payload) ? ($payload['rows'] ?? []) : [];
|
||||
$total = is_array($payload) ? (int) ($payload['total'] ?? 0) : 0;
|
||||
$totalPage = is_array($payload) ? max(1, (int) ($payload['total_page'] ?? 1)) : 1;
|
||||
$curPage = is_array($payload) ? (int) ($payload['page'] ?? $page) : $page;
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Rekaman aktivitas harian</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Log aktivitas dari aplikasi pegawai. Hanya dapat dilihat atau dihapus di sini.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Daftar</h2>
|
||||
<span class="text-xs text-gray-500">Total <?= number_format($total, 0, ',', '.') ?></span>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr>
|
||||
<th class="px-5 py-3">Waktu</th>
|
||||
<th class="px-5 py-3">Pegawai</th>
|
||||
<th class="px-5 py-3">Deskripsi</th>
|
||||
<th class="px-5 py-3">Gambar</th>
|
||||
<th class="px-5 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php if ($rows === []) : ?>
|
||||
<tr><td colspan="5" class="px-5 py-10 text-center text-gray-500">Tidak ada data.</td></tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($rows as $x) : ?>
|
||||
<?php if (! is_array($x)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-5 py-3 text-xs"><?= esc((string) ($x['waktu_aktifitas'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3">
|
||||
<div class="font-medium text-gray-900"><?= esc((string) ($x['nama_lengkap'] ?? '')) ?></div>
|
||||
<div class="text-xs text-gray-500"><?= esc((string) ($x['nip'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td class="max-w-xs truncate px-5 py-3" title="<?= esc((string) ($x['deskripsi'] ?? '')) ?>"><?= esc((string) ($x['deskripsi'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3 text-xs"><?= esc((string) ($x['image'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<form method="post" action="<?= site_url('admin/presensi/aktivitas/delete/' . (int) ($x['id_aktifitas_harian'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus rekaman ini?');">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="page" value="<?= (int) $curPage ?>">
|
||||
<button type="submit" class="rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700 hover:bg-red-100">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-t border-gray-200 bg-gray-50/80 px-5 py-3 text-xs text-gray-600">
|
||||
<span>Halaman <?= (int) $curPage ?> / <?= (int) $totalPage ?></span>
|
||||
<div class="flex gap-2">
|
||||
<?php if ($curPage > 1) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium shadow-sm hover:bg-gray-50" href="<?= site_url('admin/presensi/aktivitas?' . http_build_query(['page' => $curPage - 1])) ?>">Sebelumnya</a>
|
||||
<?php endif ?>
|
||||
<?php if ($curPage < $totalPage) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium shadow-sm hover:bg-gray-50" href="<?= site_url('admin/presensi/aktivitas?' . http_build_query(['page' => $curPage + 1])) ?>">Berikutnya</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
65
app/Views/admin/presensi/detail.php
Normal file
65
app/Views/admin/presensi/detail.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Detail presensi<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="mx-auto max-w-3xl space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Detail presensi</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Rekap satu baris presensi.</p>
|
||||
</div>
|
||||
<a href="<?= site_url('admin/presensi') ?>" class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">
|
||||
<i class="fa-solid fa-arrow-left text-xs text-gray-500"></i> Kembali ke daftar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<?php if (is_array($row)) : ?>
|
||||
<dl class="grid gap-4 rounded-2xl border border-gray-200 bg-white p-6 text-sm shadow-sm sm:grid-cols-2">
|
||||
<?php foreach ($row as $k => $v) : ?>
|
||||
<?php if ($k === 'jadwal') : ?>
|
||||
<div class="sm:col-span-2">
|
||||
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500"><?= esc((string) $k) ?></dt>
|
||||
<dd class="mt-1 break-all rounded-xl bg-gray-900 p-3 font-mono text-xs text-gray-100"><?= esc(is_scalar($v) ? (string) $v : json_encode($v, JSON_UNESCAPED_UNICODE)) ?></dd>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500"><?= esc((string) $k) ?></dt>
|
||||
<dd class="mt-1 font-medium text-gray-900"><?= esc(is_scalar($v) || $v === null ? (string) ($v ?? '') : json_encode($v, JSON_UNESCAPED_UNICODE)) ?></dd>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</dl>
|
||||
<?php
|
||||
$photoM = $row['photo_masuk'] ?? '';
|
||||
$photoP = $row['photo_pulang'] ?? '';
|
||||
?>
|
||||
<?php if ($photoM !== '' || $photoP !== '') : ?>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<?php if ($photoM !== '') : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-sm font-semibold text-gray-900">Foto masuk</h2>
|
||||
<img src="<?= esc(site_url('admin/presensi/foto/masuk/' . rawurlencode($photoM))) ?>" alt="Foto masuk" class="mt-3 max-h-64 rounded-xl border border-gray-100 object-contain">
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if ($photoP !== '') : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-sm font-semibold text-gray-900">Foto pulang</h2>
|
||||
<img src="<?= esc(site_url('admin/presensi/foto/pulang/' . rawurlencode($photoP))) ?>" alt="Foto pulang" class="mt-3 max-h-64 rounded-xl border border-gray-100 object-contain">
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php else : ?>
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-circle-question',
|
||||
'title' => 'Data tidak ditemukan',
|
||||
'hint' => 'Presensi mungkin sudah dihapus atau ID tidak valid.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
153
app/Views/admin/presensi/index.php
Normal file
153
app/Views/admin/presensi/index.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Presensi<?= $this->endSection() ?>
|
||||
|
||||
<?php
|
||||
$rows = is_array($payload) ? ($payload['rows'] ?? []) : [];
|
||||
$total = is_array($payload) ? (int) ($payload['total'] ?? 0) : 0;
|
||||
$totalPage = is_array($payload) ? max(1, (int) ($payload['total_page'] ?? 1)) : 1;
|
||||
$curPage = is_array($payload) ? (int) ($payload['page'] ?? $page) : $page;
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Data presensi pegawai</h1>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">Filter rentang tanggal (default hari ini) dan pencarian nama atau NIP.</p>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<form method="get" action="<?= site_url('admin/presensi') ?>" class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Tanggal dari</label>
|
||||
<input type="date" name="tanggal_dari" value="<?= esc($dari) ?>" class="mt-1 rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600">Tanggal sampai</label>
|
||||
<input type="date" name="tanggal_sampai" value="<?= esc($sampai) ?>" class="mt-1 rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
<div class="min-w-[min(100%,14rem)] flex-1 sm:min-w-[16rem]">
|
||||
<label class="block text-xs font-medium text-gray-600">Cari nama / NIP</label>
|
||||
<input type="search" name="q" value="<?= esc($q ?? '') ?>" placeholder="Opsional" class="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-shrink-0 flex-wrap items-center gap-2">
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Terapkan</button>
|
||||
<a href="<?= site_url('admin/presensi?' . http_build_query(['tanggal_dari' => date('Y-m-d'), 'tanggal_sampai' => date('Y-m-d')])) ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-800 shadow-sm hover:bg-gray-50">Hari ini</a>
|
||||
<?php $exportQs = array_filter(['tanggal_dari' => $dari, 'tanggal_sampai' => $sampai, 'q' => $q ?? '']); ?>
|
||||
<a href="<?= site_url('admin/presensi/export-xlsx?' . http_build_query($exportQs)) ?>" class="inline-flex items-center justify-center rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-900 shadow-sm hover:bg-emerald-100">
|
||||
<i class="fa-solid fa-file-excel mr-2"></i> Export Excel
|
||||
</a>
|
||||
<a href="<?= site_url('admin/presensi/export?' . http_build_query($exportQs)) ?>" class="inline-flex items-center justify-center rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-medium text-gray-600 shadow-sm hover:bg-gray-50" title="Format CSV untuk aplikasi lain">CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">Rekaman presensi</h2>
|
||||
<p class="mt-0.5 text-xs text-gray-500">Hanya menampilkan pegawai yang sudah punya minimal satu rekam (masuk / pulang / istirahat atau foto). Baris kosong dari aplikasi (buka app tanpa absen) tidak ditampilkan. Status: lengkap (hijau), sebagian (kuning), belum rekam (abu) untuk baris yang tampil. <strong>Export Excel</strong> (dan CSV) memakai filter tanggal dan pencarian yang sama (maks. 20.000 baris); file .xlsx sudah berheader, filter, dan lebar kolom.</p>
|
||||
</div>
|
||||
<?php if ($rows === []) : ?>
|
||||
<div class="p-6">
|
||||
<?= view('layouts/partials/empty_state', [
|
||||
'icon' => 'fa-calendar-xmark',
|
||||
'title' => 'Tidak ada data presensi',
|
||||
'hint' => 'Ubah rentang tanggal atau kata kunci pencarian, lalu klik Terapkan.',
|
||||
]) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-5 py-3">Tanggal</th>
|
||||
<th class="px-5 py-3">Pegawai</th>
|
||||
<th class="px-5 py-3">Status</th>
|
||||
<th class="px-5 py-3">Masuk</th>
|
||||
<th class="px-5 py-3">Pulang</th>
|
||||
<th class="px-5 py-3">Istirahat</th>
|
||||
<th class="px-5 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $pr) : ?>
|
||||
<?php
|
||||
$jm = $pr['jam_masuk'] ?? null;
|
||||
$jp = $pr['jam_pulang'] ?? null;
|
||||
$mi = $pr['mulai_istirahat'] ?? null;
|
||||
$bi = $pr['beres_istirahat'] ?? null;
|
||||
$hadirMasuk = ! empty($jm);
|
||||
$hadirPulang = ! empty($jp);
|
||||
if ($hadirMasuk && $hadirPulang) {
|
||||
$stLabel = 'Lengkap';
|
||||
$stClass = 'bg-emerald-50 text-emerald-800 ring-1 ring-emerald-100';
|
||||
} elseif ($hadirMasuk || $hadirPulang) {
|
||||
$stLabel = 'Sebagian';
|
||||
$stClass = 'bg-amber-50 text-amber-900 ring-1 ring-amber-100';
|
||||
} else {
|
||||
$stLabel = 'Belum rekam';
|
||||
$stClass = 'bg-gray-100 text-gray-600 ring-1 ring-gray-200';
|
||||
}
|
||||
?>
|
||||
<tr class="transition-colors hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-5 py-3 text-gray-800"><?= esc((string) ($pr['tanggal'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3">
|
||||
<div class="font-medium text-gray-900"><?= esc((string) ($pr['nama_lengkap'] ?? '')) ?></div>
|
||||
<div class="text-xs text-gray-500"><?= esc((string) ($pr['nip'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<span class="inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold <?= $stClass ?>"><?= esc($stLabel) ?></span>
|
||||
</td>
|
||||
<td class="px-5 py-3">
|
||||
<?php if (! empty($jm)) : ?>
|
||||
<span class="font-mono text-xs text-gray-900"><?= esc(date('H:i', strtotime((string) $jm))) ?></span>
|
||||
<div class="text-xs text-gray-500"><?= esc((string) ($pr['ket_masuk'] ?? '')) ?></div>
|
||||
<?php else : ?>
|
||||
<span class="text-gray-400">—</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="px-5 py-3">
|
||||
<?php if (! empty($jp)) : ?>
|
||||
<span class="font-mono text-xs text-gray-900"><?= esc(date('H:i', strtotime((string) $jp))) ?></span>
|
||||
<div class="text-xs text-gray-500"><?= esc((string) ($pr['ket_pulang'] ?? '')) ?></div>
|
||||
<?php else : ?>
|
||||
<span class="text-gray-400">—</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-700">
|
||||
<?php if (! empty($mi) || ! empty($bi)) : ?>
|
||||
<?php
|
||||
$s = (empty($mi) ? '—' : date('H:i', strtotime((string) $mi))) . ' → ' . (empty($bi) ? '—' : date('H:i', strtotime((string) $bi)));
|
||||
?>
|
||||
<?= esc($s) ?>
|
||||
<?php else : ?>
|
||||
<span class="text-gray-400">—</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<a href="<?= site_url('admin/presensi/detail/' . (int) ($pr['id_presensi'] ?? 0)) ?>" class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Detail</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="flex flex-col gap-3 border-t border-gray-200 bg-gray-50/80 px-5 py-3 text-xs text-gray-600 sm:flex-row sm:items-center sm:justify-between">
|
||||
<span>Total baris: <strong class="text-gray-900"><?= number_format($total, 0, ',', '.') ?></strong></span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<?php if ($curPage > 1) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium text-gray-800 shadow-sm hover:bg-gray-50" href="<?= site_url('admin/presensi?' . http_build_query(array_filter(['tanggal_dari' => $dari, 'tanggal_sampai' => $sampai, 'q' => $q ?? '', 'page' => $curPage - 1]))) ?>">Sebelumnya</a>
|
||||
<?php endif ?>
|
||||
<?php if ($curPage < $totalPage) : ?>
|
||||
<a class="inline-flex rounded-lg border border-gray-200 bg-white px-3 py-1.5 font-medium text-gray-800 shadow-sm hover:bg-gray-50" href="<?= site_url('admin/presensi?' . http_build_query(array_filter(['tanggal_dari' => $dari, 'tanggal_sampai' => $sampai, 'q' => $q ?? '', 'page' => $curPage + 1]))) ?>">Berikutnya</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
79
app/Views/admin/presensi/jadwal.php
Normal file
79
app/Views/admin/presensi/jadwal.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Jadwal presensi<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Management jadwal</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Atur jam masuk dan pulang standar per hari dalam minggu.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/presensi/jadwal/save') ?>" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_jadwal" id="f_id" value="">
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Nama jadwal *</label>
|
||||
<input name="nama_jadwal" id="f_nama" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Toleransi terlambat (mnt) *</label>
|
||||
<input type="number" name="toleransi_terlambat" id="f_tol_t" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Toleransi pulang cepat *</label>
|
||||
<input type="number" name="toleransi_pulang_cepat" id="f_tol_p" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
$days = [1 => 'Senin', 2 => 'Selasa', 3 => 'Rabu', 4 => 'Kamis', 5 => 'Jumat', 6 => 'Sabtu', 7 => 'Minggu'];
|
||||
?>
|
||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<?php foreach ($days as $d => $label) : ?>
|
||||
<div class="rounded-xl border border-gray-100 bg-gray-50/80 p-3">
|
||||
<p class="mb-2 text-xs font-semibold text-gray-700"><?= esc($label) ?></p>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<label class="text-[10px] text-gray-500">Masuk</label>
|
||||
<input name="<?= $d ?>_in" id="f_<?= $d ?>_in" class="w-full rounded border border-gray-300 px-2 py-1 text-xs" placeholder="08:00">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-[10px] text-gray-500">Pulang</label>
|
||||
<input name="<?= $d ?>_out" id="f_<?= $d ?>_out" class="w-full rounded border border-gray-300 px-2 py-1 text-xs" placeholder="17:00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan jadwal</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Nama</th><th class="px-5 py-3">Sen–Min (masuk)</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $x) : ?>
|
||||
<?php if (! is_array($x)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3 font-medium text-gray-900"><?= esc((string) ($x['nama_jadwal'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3 font-mono text-xs text-gray-600"><?= esc((string) ($x['1_in'] ?? '')) ?> … <?= esc((string) ($x['5_out'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold" data-row="<?= esc(json_encode($x, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="(function(o){document.getElementById('f_id').value=o.id_jadwal||'';document.getElementById('f_nama').value=o.nama_jadwal||'';document.getElementById('f_tol_t').value=o.toleransi_terlambat||0;document.getElementById('f_tol_p').value=o.toleransi_pulang_cepat||0;for(var d=1;d<=7;d++){document.getElementById('f_'+d+'_in').value=o[d+'_in']||'';document.getElementById('f_'+d+'_out').value=o[d+'_out']||'';}})(JSON.parse(this.dataset.row))">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/presensi/jadwal/delete/' . (int) ($x['id_jadwal'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus jadwal? Pastikan tidak dipakai pegawai.');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
68
app/Views/admin/presensi/lapangan.php
Normal file
68
app/Views/admin/presensi/lapangan.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Tugas luar / lapangan<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Pegawai dilapangan</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Catat pegawai yang bertugas di luar kantor.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-base font-semibold text-gray-900">Tambah / ubah</h2>
|
||||
<form method="post" action="<?= site_url('admin/presensi/lapangan/save') ?>" class="mt-4 grid gap-4 sm:grid-cols-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_dilapangan" id="f_id" value="">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Pegawai *</label>
|
||||
<select name="pegawai" id="f_pegawai" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
<option value="">— pilih —</option>
|
||||
<?php foreach ($pegawai as $pg) : ?>
|
||||
<?php if (! is_array($pg)) {
|
||||
continue;
|
||||
} ?>
|
||||
<option value="<?= (int) ($pg['id_pegawai'] ?? 0) ?>"><?= esc((string) ($pg['nama_lengkap'] ?? '')) ?> (<?= esc((string) ($pg['nip'] ?? '')) ?>)</option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Alasan *</label>
|
||||
<input name="alasan" id="f_alasan" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2 flex gap-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
<button type="button" onclick="document.getElementById('f_id').value='';document.getElementById('f_pegawai').value='';document.getElementById('f_alasan').value='';" class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Bersihkan</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Pegawai</th><th class="px-5 py-3">Alasan</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $x) : ?>
|
||||
<?php if (! is_array($x)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3">
|
||||
<div class="font-medium text-gray-900"><?= esc((string) ($x['nama_lengkap'] ?? '')) ?></div>
|
||||
<div class="text-xs text-gray-500"><?= esc((string) ($x['nip'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td class="px-5 py-3"><?= esc((string) ($x['alasan'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold" data-row="<?= esc(json_encode($x, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="var o=JSON.parse(this.dataset.row);document.getElementById('f_id').value=o.id_dilapangan||'';document.getElementById('f_pegawai').value=String(o.pegawai||'');document.getElementById('f_alasan').value=o.alasan||'';">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/presensi/lapangan/delete/' . (int) ($x['id_dilapangan'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
77
app/Views/admin/presensi/lembur.php
Normal file
77
app/Views/admin/presensi/lembur.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Jadwal lembur<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Jadwal lembur</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Kelola pencatatan lembur pegawai.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/presensi/lembur/save') ?>" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_lembur" id="f_id" value="">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Tanggal *</label>
|
||||
<input type="date" name="tanggal_lembur" id="f_tgl" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Pegawai *</label>
|
||||
<select name="pegawai" id="f_pegawai" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
<option value="">—</option>
|
||||
<?php foreach ($pegawai as $pg) : ?>
|
||||
<?php if (! is_array($pg)) {
|
||||
continue;
|
||||
} ?>
|
||||
<option value="<?= (int) ($pg['id_pegawai'] ?? 0) ?>"><?= esc((string) ($pg['nama_lengkap'] ?? '')) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Masuk (HH:MM) *</label>
|
||||
<input name="masuk" id="f_masuk" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm" placeholder="18:00">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Beres (HH:MM) *</label>
|
||||
<input name="beres" id="f_beres" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm" placeholder="22:00">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Keterangan *</label>
|
||||
<input name="keterangan" id="f_ket" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Tanggal</th><th class="px-5 py-3">Pegawai</th><th class="px-5 py-3">Jam</th><th class="px-5 py-3">Ket</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $x) : ?>
|
||||
<?php if (! is_array($x)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-5 py-3"><?= esc((string) ($x['tanggal_lembur'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3"><?= esc((string) ($x['nama_lengkap'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3 font-mono text-xs"><?= esc((string) ($x['masuk'] ?? '')) ?>–<?= esc((string) ($x['beres'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3"><?= esc((string) ($x['keterangan'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold" data-row="<?= esc(json_encode($x, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="var o=JSON.parse(this.dataset.row);document.getElementById('f_id').value=o.id_lembur||'';document.getElementById('f_tgl').value=o.tanggal_lembur||'';document.getElementById('f_pegawai').value=String(o.pegawai||'');document.getElementById('f_masuk').value=o.masuk||'';document.getElementById('f_beres').value=o.beres||'';document.getElementById('f_ket').value=o.keterangan||'';">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/presensi/lembur/delete/' . (int) ($x['id_lembur'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
53
app/Views/admin/presensi/libur.php
Normal file
53
app/Views/admin/presensi/libur.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Hari libur<?= $this->endSection() ?>
|
||||
<?php $rows = is_array($payload) ? ($payload['rows'] ?? []) : []; ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Hari libur perusahaan</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Atur hari libur perusahaan yang mempengaruhi presensi.</p>
|
||||
</div>
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<form method="post" action="<?= site_url('admin/presensi/libur/save') ?>" class="grid gap-4 sm:grid-cols-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id_libur" id="f_id" value="">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Tanggal libur *</label>
|
||||
<input type="date" name="tanggal_libur" id="f_tgl" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600">Keterangan *</label>
|
||||
<input name="keterangan_libur" id="f_ket" required class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm">
|
||||
</div>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">Simpan</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase text-gray-500">
|
||||
<tr><th class="px-5 py-3">Tanggal</th><th class="px-5 py-3">Keterangan</th><th class="px-5 py-3"></th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php foreach ($rows as $x) : ?>
|
||||
<?php if (! is_array($x)) {
|
||||
continue;
|
||||
} ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="whitespace-nowrap px-5 py-3"><?= esc((string) ($x['tanggal_libur'] ?? '')) ?></td>
|
||||
<td class="px-5 py-3"><?= esc((string) ($x['keterangan_libur'] ?? '')) ?></td>
|
||||
<td class="whitespace-nowrap px-5 py-3">
|
||||
<button type="button" class="rounded-lg border border-gray-200 px-2 py-1 text-xs font-semibold" data-row="<?= esc(json_encode($x, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 'attr') ?>" onclick="var o=JSON.parse(this.dataset.row);document.getElementById('f_id').value=o.id_libur||'';document.getElementById('f_tgl').value=o.tanggal_libur||'';document.getElementById('f_ket').value=o.keterangan_libur||'';">Edit</button>
|
||||
<form method="post" action="<?= site_url('admin/presensi/libur/delete/' . (int) ($x['id_libur'] ?? 0)) ?>" class="inline" onsubmit="return confirm('Hapus?');"><?= csrf_field() ?><button type="submit" class="ml-1 rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-700">Hapus</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
87
app/Views/admin/util/backup.php
Normal file
87
app/Views/admin/util/backup.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('title') ?>Backup database<?= $this->endSection() ?>
|
||||
|
||||
<?php
|
||||
$files = is_array($payload) && isset($payload['files']) && is_array($payload['files']) ? $payload['files'] : [];
|
||||
$dir = is_array($payload) ? (string) ($payload['directory'] ?? '') : '';
|
||||
?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Backup database</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">File cadangan basis data disimpan di folder aplikasi (writable). Unduh atau hapus dari daftar di bawah.</p>
|
||||
<?php if ($dir !== '') : ?>
|
||||
<p class="mt-1 font-mono text-xs text-gray-400 break-all"><?= esc($dir) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?= view('layouts/partials/admin_alert', ['errors' => $errors ?? []]) ?>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h2 class="text-base font-semibold text-gray-900">Buat backup baru</h2>
|
||||
<p class="mt-1 text-xs text-gray-500">Cadangan basis data dalam format SQL. Proses bisa memakan waktu beberapa detik.</p>
|
||||
<form method="post" action="<?= site_url('admin/util/backup/run') ?>" class="mt-4 flex flex-wrap items-center gap-4">
|
||||
<?= csrf_field() ?>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700">
|
||||
<input type="checkbox" name="save_latest" value="1" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
Juga tulis salinan <code class="rounded bg-gray-100 px-1 text-xs">latest.sql</code>
|
||||
</label>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700" onclick="return confirm('Jalankan backup sekarang?');">
|
||||
<i class="fa-solid fa-database mr-2"></i> Jalankan backup
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 px-5 py-4">
|
||||
<h2 class="text-base font-semibold text-gray-900">File backup</h2>
|
||||
<p class="text-xs text-gray-500">Unduh untuk arsip; hapus jika sudah tidak diperlukan.</p>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="border-y border-gray-200 bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Nama file</th>
|
||||
<th class="px-4 py-3">Ukuran</th>
|
||||
<th class="px-4 py-3">Diubah</th>
|
||||
<th class="px-4 py-3 w-48">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<?php if ($files === []) : ?>
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-10 text-center text-gray-500">Belum ada file backup.</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($files as $f) : ?>
|
||||
<?php
|
||||
$name = (string) ($f['name'] ?? '');
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$enc = rawurlencode($name);
|
||||
?>
|
||||
<tr class="hover:bg-gray-50/80">
|
||||
<td class="px-4 py-2 font-mono text-xs text-gray-900"><?= esc($name) ?></td>
|
||||
<td class="whitespace-nowrap px-4 py-2 text-gray-600"><?= number_format((int) ($f['size'] ?? 0), 0, ',', '.') ?> B</td>
|
||||
<td class="whitespace-nowrap px-4 py-2 text-gray-600"><?= esc((string) ($f['mtime'] ?? '')) ?></td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="<?= site_url('admin/util/backup/download/' . $enc) ?>" class="inline-flex rounded-lg border border-gray-200 bg-white px-2 py-1 text-xs font-semibold text-gray-800 shadow-sm hover:bg-gray-50">Unduh</a>
|
||||
<form method="post" action="<?= site_url('admin/util/backup/delete/' . $enc) ?>" class="inline" onsubmit="return confirm('Hapus file backup ini?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex rounded-lg bg-red-50 px-2 py-1 text-xs font-semibold text-red-800 hover:bg-red-100">Hapus</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
7
app/Views/errors/cli/error_404.php
Normal file
7
app/Views/errors/cli/error_404.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
CLI::error('ERROR: ' . $code);
|
||||
CLI::write($message);
|
||||
CLI::newLine();
|
||||
65
app/Views/errors/cli/error_exception.php
Normal file
65
app/Views/errors/cli/error_exception.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
// The main Exception
|
||||
CLI::write('[' . $exception::class . ']', 'light_gray', 'red');
|
||||
CLI::write($message);
|
||||
CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green'));
|
||||
CLI::newLine();
|
||||
|
||||
$last = $exception;
|
||||
|
||||
while ($prevException = $last->getPrevious()) {
|
||||
$last = $prevException;
|
||||
|
||||
CLI::write(' Caused by:');
|
||||
CLI::write(' [' . $prevException::class . ']', 'red');
|
||||
CLI::write(' ' . $prevException->getMessage());
|
||||
CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
// The backtrace
|
||||
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
|
||||
$backtraces = $last->getTrace();
|
||||
|
||||
if ($backtraces) {
|
||||
CLI::write('Backtrace:', 'green');
|
||||
}
|
||||
|
||||
foreach ($backtraces as $i => $error) {
|
||||
$padFile = ' '; // 4 spaces
|
||||
$padClass = ' '; // 7 spaces
|
||||
$c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);
|
||||
|
||||
if (isset($error['file'])) {
|
||||
$filepath = clean_path($error['file']) . ':' . $error['line'];
|
||||
|
||||
CLI::write($c . $padFile . CLI::color($filepath, 'yellow'));
|
||||
} else {
|
||||
CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow'));
|
||||
}
|
||||
|
||||
$function = '';
|
||||
|
||||
if (isset($error['class'])) {
|
||||
$type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type'];
|
||||
$function .= $padClass . $error['class'] . $type . $error['function'];
|
||||
} elseif (! isset($error['class']) && isset($error['function'])) {
|
||||
$function .= $padClass . $error['function'];
|
||||
}
|
||||
|
||||
$args = implode(', ', array_map(static fn ($value): string => match (true) {
|
||||
is_object($value) => 'Object(' . $value::class . ')',
|
||||
is_array($value) => $value !== [] ? '[...]' : '[]',
|
||||
$value === null => 'null', // return the lowercased version
|
||||
default => var_export($value, true),
|
||||
}, array_values($error['args'] ?? [])));
|
||||
|
||||
$function .= '(' . $args . ')';
|
||||
|
||||
CLI::write($function);
|
||||
CLI::newLine();
|
||||
}
|
||||
}
|
||||
5
app/Views/errors/cli/production.php
Normal file
5
app/Views/errors/cli/production.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
// On the CLI, we still want errors in productions
|
||||
// so just use the exception template.
|
||||
include __DIR__ . '/error_exception.php';
|
||||
194
app/Views/errors/html/debug.css
Normal file
194
app/Views/errors/html/debug.css
Normal file
@@ -0,0 +1,194 @@
|
||||
:root {
|
||||
--main-bg-color: #fff;
|
||||
--main-text-color: #555;
|
||||
--dark-text-color: #222;
|
||||
--light-text-color: #c7c7c7;
|
||||
--brand-primary-color: #DC4814;
|
||||
--light-bg-color: #ededee;
|
||||
--dark-bg-color: #404040;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
background: var(--main-bg-color);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
color: var(--main-text-color);
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-weight: lighter;
|
||||
font-size: 3rem;
|
||||
color: var(--dark-text-color);
|
||||
margin: 0;
|
||||
}
|
||||
h1.headline {
|
||||
margin-top: 20%;
|
||||
font-size: 5rem;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
p.lead {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
.container {
|
||||
max-width: 75rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
.header {
|
||||
background: var(--light-bg-color);
|
||||
color: var(--dark-text-color);
|
||||
margin-top: 2.17rem;
|
||||
}
|
||||
.header .container {
|
||||
padding: 1rem;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.header p {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
line-height: 2.5;
|
||||
}
|
||||
.header a {
|
||||
color: var(--brand-primary-color);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
.header:hover a {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.environment {
|
||||
background: var(--brand-primary-color);
|
||||
color: var(--main-bg-color);
|
||||
text-align: center;
|
||||
padding: calc(4px + 0.2083vw);
|
||||
width: 100%;
|
||||
top: 0;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.source {
|
||||
background: #343434;
|
||||
color: var(--light-text-color);
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 5px;
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.source span.line {
|
||||
line-height: 1.4;
|
||||
}
|
||||
.source span.line .number {
|
||||
color: #666;
|
||||
}
|
||||
.source .line .highlight {
|
||||
display: block;
|
||||
background: var(--dark-text-color);
|
||||
color: var(--light-text-color);
|
||||
}
|
||||
.source span.highlight .number {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
list-style: none;
|
||||
list-style-position: inside;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.tabs li {
|
||||
display: inline;
|
||||
}
|
||||
.tabs a:link,
|
||||
.tabs a:visited {
|
||||
padding: 0 1rem;
|
||||
line-height: 2.7;
|
||||
text-decoration: none;
|
||||
color: var(--dark-text-color);
|
||||
background: var(--light-bg-color);
|
||||
border: 1px solid rgba(0,0,0,0.15);
|
||||
border-bottom: 0;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
.tabs a:hover {
|
||||
background: var(--light-bg-color);
|
||||
border-color: rgba(0,0,0,0.15);
|
||||
}
|
||||
.tabs a.active {
|
||||
background: var(--main-bg-color);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
.tab-content {
|
||||
background: var(--main-bg-color);
|
||||
border: 1px solid rgba(0,0,0,0.15);
|
||||
}
|
||||
.content {
|
||||
padding: 1rem;
|
||||
}
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-top: 2rem;
|
||||
display: block;
|
||||
text-align: center;
|
||||
line-height: 3.0;
|
||||
background: #d9edf7;
|
||||
border: 1px solid #bcdff1;
|
||||
border-radius: 5px;
|
||||
color: #31708f;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
td {
|
||||
padding: 0.2rem 0.5rem 0.2rem 0;
|
||||
}
|
||||
tr:hover td {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
td pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.trace a {
|
||||
color: inherit;
|
||||
}
|
||||
.trace table {
|
||||
width: auto;
|
||||
}
|
||||
.trace tr td:first-child {
|
||||
min-width: 5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.trace td {
|
||||
background: var(--light-bg-color);
|
||||
padding: 0 1rem;
|
||||
}
|
||||
.trace td pre {
|
||||
margin: 0;
|
||||
}
|
||||
.args {
|
||||
display: none;
|
||||
}
|
||||
116
app/Views/errors/html/debug.js
Normal file
116
app/Views/errors/html/debug.js
Normal file
@@ -0,0 +1,116 @@
|
||||
var tabLinks = new Array();
|
||||
var contentDivs = new Array();
|
||||
|
||||
function init()
|
||||
{
|
||||
// Grab the tab links and content divs from the page
|
||||
var tabListItems = document.getElementById('tabs').childNodes;
|
||||
console.log(tabListItems);
|
||||
for (var i = 0; i < tabListItems.length; i ++)
|
||||
{
|
||||
if (tabListItems[i].nodeName == "LI")
|
||||
{
|
||||
var tabLink = getFirstChildWithTagName(tabListItems[i], 'A');
|
||||
var id = getHash(tabLink.getAttribute('href'));
|
||||
tabLinks[id] = tabLink;
|
||||
contentDivs[id] = document.getElementById(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign onclick events to the tab links, and
|
||||
// highlight the first tab
|
||||
var i = 0;
|
||||
|
||||
for (var id in tabLinks)
|
||||
{
|
||||
tabLinks[id].onclick = showTab;
|
||||
tabLinks[id].onfocus = function () {
|
||||
this.blur()
|
||||
};
|
||||
if (i == 0)
|
||||
{
|
||||
tabLinks[id].className = 'active';
|
||||
}
|
||||
i ++;
|
||||
}
|
||||
|
||||
// Hide all content divs except the first
|
||||
var i = 0;
|
||||
|
||||
for (var id in contentDivs)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
console.log(contentDivs[id]);
|
||||
contentDivs[id].className = 'content hide';
|
||||
}
|
||||
i ++;
|
||||
}
|
||||
}
|
||||
|
||||
function showTab()
|
||||
{
|
||||
var selectedId = getHash(this.getAttribute('href'));
|
||||
|
||||
// Highlight the selected tab, and dim all others.
|
||||
// Also show the selected content div, and hide all others.
|
||||
for (var id in contentDivs)
|
||||
{
|
||||
if (id == selectedId)
|
||||
{
|
||||
tabLinks[id].className = 'active';
|
||||
contentDivs[id].className = 'content';
|
||||
}
|
||||
else
|
||||
{
|
||||
tabLinks[id].className = '';
|
||||
contentDivs[id].className = 'content hide';
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the browser following the link
|
||||
return false;
|
||||
}
|
||||
|
||||
function getFirstChildWithTagName(element, tagName)
|
||||
{
|
||||
for (var i = 0; i < element.childNodes.length; i ++)
|
||||
{
|
||||
if (element.childNodes[i].nodeName == tagName)
|
||||
{
|
||||
return element.childNodes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getHash(url)
|
||||
{
|
||||
var hashPos = url.lastIndexOf('#');
|
||||
return url.substring(hashPos + 1);
|
||||
}
|
||||
|
||||
function toggle(elem)
|
||||
{
|
||||
elem = document.getElementById(elem);
|
||||
|
||||
if (elem.style && elem.style['display'])
|
||||
{
|
||||
// Only works with the "style" attr
|
||||
var disp = elem.style['display'];
|
||||
}
|
||||
else if (elem.currentStyle)
|
||||
{
|
||||
// For MSIE, naturally
|
||||
var disp = elem.currentStyle['display'];
|
||||
}
|
||||
else if (window.getComputedStyle)
|
||||
{
|
||||
// For most other browsers
|
||||
var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
|
||||
}
|
||||
|
||||
// Toggle the state of the "display" style
|
||||
elem.style.display = disp == 'block' ? 'none' : 'block';
|
||||
|
||||
return false;
|
||||
}
|
||||
84
app/Views/errors/html/error_400.php
Normal file
84
app/Views/errors/html/error_400.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?= lang('Errors.badRequest') ?></title>
|
||||
|
||||
<style>
|
||||
div.logo {
|
||||
height: 200px;
|
||||
width: 155px;
|
||||
display: inline-block;
|
||||
opacity: 0.08;
|
||||
position: absolute;
|
||||
top: 2rem;
|
||||
left: 50%;
|
||||
margin-left: -73px;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
background: #fafafa;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #777;
|
||||
font-weight: 300;
|
||||
}
|
||||
h1 {
|
||||
font-weight: lighter;
|
||||
letter-spacing: normal;
|
||||
font-size: 3rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
color: #222;
|
||||
}
|
||||
.wrap {
|
||||
max-width: 1024px;
|
||||
margin: 5rem auto;
|
||||
padding: 2rem;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
border: 1px solid #efefef;
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
pre {
|
||||
white-space: normal;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
code {
|
||||
background: #fafafa;
|
||||
border: 1px solid #efefef;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
}
|
||||
p {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
border-top: 1px solid #efefef;
|
||||
padding: 1em 2em 0 2em;
|
||||
font-size: 85%;
|
||||
color: #999;
|
||||
}
|
||||
a:active,
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #dd4814;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>400</h1>
|
||||
|
||||
<p>
|
||||
<?php if (ENVIRONMENT !== 'production') : ?>
|
||||
<?= nl2br(esc($message)) ?>
|
||||
<?php else : ?>
|
||||
<?= lang('Errors.sorryBadRequest') ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
84
app/Views/errors/html/error_404.php
Normal file
84
app/Views/errors/html/error_404.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?= lang('Errors.pageNotFound') ?></title>
|
||||
|
||||
<style>
|
||||
div.logo {
|
||||
height: 200px;
|
||||
width: 155px;
|
||||
display: inline-block;
|
||||
opacity: 0.08;
|
||||
position: absolute;
|
||||
top: 2rem;
|
||||
left: 50%;
|
||||
margin-left: -73px;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
background: #fafafa;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #777;
|
||||
font-weight: 300;
|
||||
}
|
||||
h1 {
|
||||
font-weight: lighter;
|
||||
letter-spacing: normal;
|
||||
font-size: 3rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
color: #222;
|
||||
}
|
||||
.wrap {
|
||||
max-width: 1024px;
|
||||
margin: 5rem auto;
|
||||
padding: 2rem;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
border: 1px solid #efefef;
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
pre {
|
||||
white-space: normal;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
code {
|
||||
background: #fafafa;
|
||||
border: 1px solid #efefef;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
}
|
||||
p {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
border-top: 1px solid #efefef;
|
||||
padding: 1em 2em 0 2em;
|
||||
font-size: 85%;
|
||||
color: #999;
|
||||
}
|
||||
a:active,
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #dd4814;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>404</h1>
|
||||
|
||||
<p>
|
||||
<?php if (ENVIRONMENT !== 'production') : ?>
|
||||
<?= nl2br(esc($message)) ?>
|
||||
<?php else : ?>
|
||||
<?= lang('Errors.sorryCannotFind') ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
429
app/Views/errors/html/error_exception.php
Normal file
429
app/Views/errors/html/error_exception.php
Normal file
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
use CodeIgniter\HTTP\Header;
|
||||
use CodeIgniter\CodeIgniter;
|
||||
|
||||
$errorId = uniqid('error', true);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex">
|
||||
|
||||
<title><?= esc($title) ?></title>
|
||||
<style>
|
||||
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="environment">
|
||||
Displayed at <?= esc(date('H:i:s')) ?> —
|
||||
PHP: <?= esc(PHP_VERSION) ?> —
|
||||
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
|
||||
Environment: <?= ENVIRONMENT ?>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
|
||||
<p>
|
||||
<?= nl2br(esc($exception->getMessage())) ?>
|
||||
<a href="https://www.duckduckgo.com/?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
|
||||
rel="noreferrer" target="_blank">search →</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source -->
|
||||
<div class="container">
|
||||
<p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>
|
||||
|
||||
<?php if (is_file($file)) : ?>
|
||||
<div class="source">
|
||||
<?= static::highlightFile($file, $line, 15); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<?php
|
||||
$last = $exception;
|
||||
|
||||
while ($prevException = $last->getPrevious()) {
|
||||
$last = $prevException;
|
||||
?>
|
||||
|
||||
<pre>
|
||||
Caused by:
|
||||
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
|
||||
|
||||
<?= nl2br(esc($prevException->getMessage())) ?>
|
||||
<a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
|
||||
rel="noreferrer" target="_blank">search →</a>
|
||||
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
|
||||
</pre>
|
||||
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) : ?>
|
||||
<div class="container">
|
||||
|
||||
<ul class="tabs" id="tabs">
|
||||
<li><a href="#backtrace">Backtrace</a></li>
|
||||
<li><a href="#server">Server</a></li>
|
||||
<li><a href="#request">Request</a></li>
|
||||
<li><a href="#response">Response</a></li>
|
||||
<li><a href="#files">Files</a></li>
|
||||
<li><a href="#memory">Memory</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
<!-- Backtrace -->
|
||||
<div class="content" id="backtrace">
|
||||
|
||||
<ol class="trace">
|
||||
<?php foreach ($trace as $index => $row) : ?>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<!-- Trace info -->
|
||||
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
|
||||
<?php
|
||||
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
|
||||
echo esc($row['function'] . ' ' . clean_path($row['file']));
|
||||
} else {
|
||||
echo esc(clean_path($row['file']) . ' : ' . $row['line']);
|
||||
}
|
||||
?>
|
||||
<?php else: ?>
|
||||
{PHP internal code}
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Class/Method -->
|
||||
<?php if (isset($row['class'])) : ?>
|
||||
— <?= esc($row['class'] . $row['type'] . $row['function']) ?>
|
||||
<?php if (! empty($row['args'])) : ?>
|
||||
<?php $argsId = $errorId . 'args' . $index ?>
|
||||
( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
|
||||
<div class="args" id="<?= esc($argsId, 'attr') ?>">
|
||||
<table cellspacing="0">
|
||||
|
||||
<?php
|
||||
$params = null;
|
||||
// Reflection by name is not available for closure function
|
||||
if (! str_ends_with($row['function'], '}')) {
|
||||
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
|
||||
$params = $mirror->getParameters();
|
||||
}
|
||||
|
||||
foreach ($row['args'] as $key => $value) : ?>
|
||||
<tr>
|
||||
<td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
|
||||
<td><pre><?= esc(print_r($value, true)) ?></pre></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
()
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
|
||||
— <?= esc($row['function']) ?>()
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<!-- Source? -->
|
||||
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
|
||||
<div class="source">
|
||||
<?= static::highlightFile($row['file'], $row['line']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Server -->
|
||||
<div class="content" id="server">
|
||||
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
|
||||
<?php
|
||||
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
|
||||
continue;
|
||||
} ?>
|
||||
|
||||
<h3>$<?= esc($var) ?></h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
|
||||
<tr>
|
||||
<td><?= esc($key) ?></td>
|
||||
<td>
|
||||
<?php if (is_string($value)) : ?>
|
||||
<?= esc($value) ?>
|
||||
<?php else: ?>
|
||||
<pre><?= esc(print_r($value, true)) ?></pre>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php endforeach ?>
|
||||
|
||||
<!-- Constants -->
|
||||
<?php $constants = get_defined_constants(true); ?>
|
||||
<?php if (! empty($constants['user'])) : ?>
|
||||
<h3>Constants</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($constants['user'] as $key => $value) : ?>
|
||||
<tr>
|
||||
<td><?= esc($key) ?></td>
|
||||
<td>
|
||||
<?php if (is_string($value)) : ?>
|
||||
<?= esc($value) ?>
|
||||
<?php else: ?>
|
||||
<pre><?= esc(print_r($value, true)) ?></pre>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Request -->
|
||||
<div class="content" id="request">
|
||||
<?php $request = service('request'); ?>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 10em">Path</td>
|
||||
<td><?= esc($request->getUri()) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTTP Method</td>
|
||||
<td><?= esc($request->getMethod()) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP Address</td>
|
||||
<td><?= esc($request->getIPAddress()) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 10em">Is AJAX Request?</td>
|
||||
<td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is CLI Request?</td>
|
||||
<td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is Secure Request?</td>
|
||||
<td><?= $request->isSecure() ? 'yes' : 'no' ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>User Agent</td>
|
||||
<td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<?php $empty = true; ?>
|
||||
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
|
||||
<?php
|
||||
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
|
||||
continue;
|
||||
} ?>
|
||||
|
||||
<?php $empty = false; ?>
|
||||
|
||||
<h3>$<?= esc($var) ?></h3>
|
||||
|
||||
<table style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
|
||||
<tr>
|
||||
<td><?= esc($key) ?></td>
|
||||
<td>
|
||||
<?php if (is_string($value)) : ?>
|
||||
<?= esc($value) ?>
|
||||
<?php else: ?>
|
||||
<pre><?= esc(print_r($value, true)) ?></pre>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php if ($empty) : ?>
|
||||
|
||||
<div class="alert">
|
||||
No $_GET, $_POST, or $_COOKIE Information to show.
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $headers = $request->headers(); ?>
|
||||
<?php if (! empty($headers)) : ?>
|
||||
|
||||
<h3>Headers</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($headers as $name => $value) : ?>
|
||||
<tr>
|
||||
<td><?= esc($name, 'html') ?></td>
|
||||
<td>
|
||||
<?php
|
||||
if ($value instanceof Header) {
|
||||
echo esc($value->getValueLine(), 'html');
|
||||
} else {
|
||||
foreach ($value as $i => $header) {
|
||||
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Response -->
|
||||
<?php
|
||||
$response = service('response');
|
||||
$response->setStatusCode(http_response_code());
|
||||
?>
|
||||
<div class="content" id="response">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="width: 15em">Response Status</td>
|
||||
<td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?php $headers = $response->headers(); ?>
|
||||
<?php if (! empty($headers)) : ?>
|
||||
<h3>Headers</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($headers as $name => $value) : ?>
|
||||
<tr>
|
||||
<td><?= esc($name, 'html') ?></td>
|
||||
<td>
|
||||
<?php
|
||||
if ($value instanceof Header) {
|
||||
echo esc($response->getHeaderLine($name), 'html');
|
||||
} else {
|
||||
foreach ($value as $i => $header) {
|
||||
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Files -->
|
||||
<div class="content" id="files">
|
||||
<?php $files = get_included_files(); ?>
|
||||
|
||||
<ol>
|
||||
<?php foreach ($files as $file) :?>
|
||||
<li><?= esc(clean_path($file)) ?></li>
|
||||
<?php endforeach ?>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- Memory -->
|
||||
<div class="content" id="memory">
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Memory Usage</td>
|
||||
<td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 12em">Peak Memory Usage:</td>
|
||||
<td><?= esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memory Limit:</td>
|
||||
<td><?= esc(ini_get('memory_limit')) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div> <!-- /tab-content -->
|
||||
|
||||
</div> <!-- /container -->
|
||||
<?php endif; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
25
app/Views/errors/html/production.php
Normal file
25
app/Views/errors/html/production.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex">
|
||||
|
||||
<title><?= lang('Errors.whoops') ?></title>
|
||||
|
||||
<style>
|
||||
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container text-center">
|
||||
|
||||
<h1 class="headline"><?= lang('Errors.whoops') ?></h1>
|
||||
|
||||
<p class="lead"><?= lang('Errors.weHitASnag') ?></p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
26
app/Views/layouts/auth.php
Normal file
26
app/Views/layouts/auth.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= esc($this->renderSection('title') ?: 'Login') ?> — BIJ Admin</title>
|
||||
<link rel="icon" type="image/png" href="<?= base_url('assets/images/bij_logo.png') ?>">
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script>
|
||||
<link rel="stylesheet" href="<?= base_url('assets/tailadmin/fa-7.1.0-web/css/all.min.css') ?>">
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen bg-gradient-to-br from-gray-100 via-gray-50 to-gray-200 text-gray-900 antialiased">
|
||||
<div class="flex min-h-screen flex-col items-center justify-center px-4 py-10">
|
||||
<div class="mb-8 flex flex-col items-center text-center">
|
||||
<img src="<?= base_url('assets/images/bpr-logo.png') ?>" alt="Logo BPR" class="h-12 w-auto max-h-16 max-w-[220px] object-contain drop-shadow-sm">
|
||||
<p class="mt-3 text-sm font-medium text-gray-600">Panel administrasi BIJ</p>
|
||||
</div>
|
||||
<div class="w-full max-w-md">
|
||||
<?= $this->renderSection('content') ?>
|
||||
</div>
|
||||
<p class="mt-10 text-center text-xs text-gray-500">CodeIgniter 4 · Presensi & kepegawaian</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
3
app/Views/layouts/footer.php
Normal file
3
app/Views/layouts/footer.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<footer class="mt-auto border-t border-gray-200 bg-gray-50/80 px-4 py-3 text-center text-[11px] text-gray-500 md:px-6 2xl:px-8">
|
||||
© <?= date('Y') ?> bank BIJ — PT. Wira Pratama Indonesia.
|
||||
</footer>
|
||||
31
app/Views/layouts/header.php
Normal file
31
app/Views/layouts/header.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<header class="sticky top-0 z-30 flex w-full flex-col gap-2 border-b border-gray-200 bg-white/95 px-4 py-3 shadow-sm backdrop-blur-md supports-[backdrop-filter]:bg-white/90 md:h-16 md:flex-row md:items-center md:justify-between md:gap-3 md:px-6 md:py-0 2xl:px-8">
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<button type="button" id="btn-mobile-sidebar" class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-gray-200 bg-white text-gray-700 shadow-sm hover:bg-gray-50 lg:hidden" aria-label="Buka menu">
|
||||
<i class="fa-solid fa-bars text-lg"></i>
|
||||
</button>
|
||||
<button type="button" id="btn-desktop-sidebar-toggle" class="hidden h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-gray-200 bg-white text-gray-700 shadow-sm hover:bg-gray-50 lg:inline-flex" aria-label="Ciutkan sidebar">
|
||||
<i class="fa-solid fa-table-columns text-sm"></i>
|
||||
</button>
|
||||
<div class="min-w-0 leading-tight">
|
||||
<p class="truncate text-sm font-semibold tracking-tight text-gray-900">Panel administrasi</p>
|
||||
<?php if (session()->get('admin_auth_source') === 'admin_users' && is_array(session()->get('admin_ion_groups'))) : ?>
|
||||
<p class="truncate text-xs text-gray-500">
|
||||
<?= esc(session('admin_username') ?? '') ?>
|
||||
<span class="text-gray-300">·</span>
|
||||
<?= esc(implode(', ', session('admin_ion_groups'))) ?>
|
||||
</p>
|
||||
<?php elseif (session()->get('admin_auth_source') === 'pegawai') : ?>
|
||||
<p class="truncate text-xs text-gray-500"><?= esc(session('admin_username') ?? '') ?> (pegawai)</p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-end gap-2 md:max-w-[50%]">
|
||||
<?php if (session()->getFlashdata('message')) : ?>
|
||||
<span class="inline-flex max-w-full items-center rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1.5 text-xs font-medium text-emerald-800"><?= esc(session()->getFlashdata('message')) ?></span>
|
||||
<?php endif ?>
|
||||
<?php if (session()->getFlashdata('error')) : ?>
|
||||
<span class="inline-flex max-w-full items-center rounded-full border border-red-200 bg-red-50 px-3 py-1.5 text-xs font-medium text-red-800"><?= esc(session()->getFlashdata('error')) ?></span>
|
||||
<?php endif ?>
|
||||
<span class="hidden shrink-0 text-[11px] font-medium uppercase tracking-wide text-gray-400 sm:inline">Presensi Admin</span>
|
||||
</div>
|
||||
</header>
|
||||
128
app/Views/layouts/main.php
Normal file
128
app/Views/layouts/main.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= esc($this->renderSection('title') ?: 'Admin') ?> — BIJ</title>
|
||||
<link rel="icon" type="image/png" href="<?= base_url('assets/images/bij_logo.png') ?>">
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script>
|
||||
<link rel="stylesheet" href="<?= base_url('assets/tailadmin/fa-7.1.0-web/css/all.min.css') ?>">
|
||||
<style>
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #d1d5db transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: #d1d5db;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
/* Shell: mobile drawer + desktop sidebar width (TailAdmin-style) */
|
||||
#admin-sidebar {
|
||||
transition: transform 0.2s ease, width 0.2s ease;
|
||||
}
|
||||
@media (max-width: 1023px) {
|
||||
#admin-sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
#admin-shell.mobile-sidebar-open #admin-sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
#admin-sidebar {
|
||||
transform: none !important;
|
||||
}
|
||||
#sidebar-overlay {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
#admin-shell.sidebar-narrow #admin-sidebar {
|
||||
width: 90px !important;
|
||||
min-width: 90px;
|
||||
}
|
||||
#admin-shell.sidebar-narrow #admin-sidebar .sidebar-text,
|
||||
#admin-shell.sidebar-narrow #admin-sidebar .nav-section-label {
|
||||
display: none !important;
|
||||
}
|
||||
#admin-shell.sidebar-narrow #admin-sidebar details > summary .fa-chevron-down {
|
||||
display: none;
|
||||
}
|
||||
#admin-shell.sidebar-narrow #admin-sidebar .sidebar-brand {
|
||||
justify-content: center;
|
||||
}
|
||||
#admin-shell.sidebar-narrow #admin-sidebar .sidebar-logo-expanded {
|
||||
display: none !important;
|
||||
}
|
||||
#admin-shell.sidebar-narrow #admin-sidebar .sidebar-logo-collapsed {
|
||||
display: block !important;
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
outline: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-50 font-sans text-gray-900 antialiased">
|
||||
<?php helper('rbac'); ?>
|
||||
<div id="admin-shell" class="flex h-screen w-full overflow-hidden">
|
||||
<div id="sidebar-overlay" class="fixed inset-0 z-40 hidden bg-black/50 lg:hidden" aria-hidden="true"></div>
|
||||
<?= $this->include('layouts/sidebar') ?>
|
||||
<div class="flex min-w-0 flex-1 flex-col overflow-hidden bg-gray-50">
|
||||
<?= $this->include('layouts/header') ?>
|
||||
<main class="custom-scrollbar relative flex flex-1 flex-col overflow-x-hidden overflow-y-auto">
|
||||
<div class="mx-auto w-full max-w-screen-2xl flex-1 p-4 pb-10 md:p-6 md:pb-12 2xl:p-8 2xl:pb-14">
|
||||
<?= $this->renderSection('content') ?>
|
||||
</div>
|
||||
<?= $this->include('layouts/footer') ?>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var shell = document.getElementById('admin-shell');
|
||||
var overlay = document.getElementById('sidebar-overlay');
|
||||
var btnMobile = document.getElementById('btn-mobile-sidebar');
|
||||
var btnDesktop = document.getElementById('btn-desktop-sidebar-toggle');
|
||||
function openMobile() {
|
||||
if (!shell) return;
|
||||
shell.classList.add('mobile-sidebar-open');
|
||||
if (overlay) overlay.classList.remove('hidden');
|
||||
}
|
||||
function closeMobile() {
|
||||
if (!shell) return;
|
||||
shell.classList.remove('mobile-sidebar-open');
|
||||
if (overlay) overlay.classList.add('hidden');
|
||||
}
|
||||
if (btnMobile) btnMobile.addEventListener('click', function () {
|
||||
if (shell && shell.classList.contains('mobile-sidebar-open')) closeMobile(); else openMobile();
|
||||
});
|
||||
if (overlay) overlay.addEventListener('click', closeMobile);
|
||||
window.addEventListener('resize', function () {
|
||||
if (window.matchMedia('(min-width: 1024px)').matches) closeMobile();
|
||||
});
|
||||
if (btnDesktop && shell) {
|
||||
btnDesktop.addEventListener('click', function () {
|
||||
shell.classList.toggle('sidebar-narrow');
|
||||
try {
|
||||
localStorage.setItem('adminSidebarNarrow', shell.classList.contains('sidebar-narrow') ? '1' : '0');
|
||||
} catch (e) {}
|
||||
});
|
||||
try {
|
||||
if (localStorage.getItem('adminSidebarNarrow') === '1') {
|
||||
shell.classList.add('sidebar-narrow');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
15
app/Views/layouts/partials/admin_alert.php
Normal file
15
app/Views/layouts/partials/admin_alert.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/** @var list<string>|array<int, string>|null $errors */
|
||||
$errors = is_array($errors ?? null) ? $errors : [];
|
||||
if ($errors === []) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900 shadow-sm" role="alert">
|
||||
<p class="font-medium text-amber-950">Perlu perhatian</p>
|
||||
<ul class="mt-2 list-inside list-disc space-y-1">
|
||||
<?php foreach ($errors as $e) : ?>
|
||||
<li><?= esc((string) $e) ?></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
14
app/Views/layouts/partials/empty_state.php
Normal file
14
app/Views/layouts/partials/empty_state.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
$icon = isset($icon) && is_string($icon) ? $icon : 'fa-inbox';
|
||||
$title = isset($title) && is_string($title) ? $title : 'Tidak ada data';
|
||||
$hint = isset($hint) && is_string($hint) ? $hint : '';
|
||||
?>
|
||||
<div class="flex flex-col items-center justify-center rounded-2xl border border-dashed border-gray-200 bg-gray-50 px-6 py-12 text-center">
|
||||
<span class="flex h-14 w-14 items-center justify-center rounded-full bg-white text-2xl text-gray-400 shadow-sm ring-1 ring-gray-100">
|
||||
<i class="fa-solid <?= esc($icon) ?>"></i>
|
||||
</span>
|
||||
<p class="mt-4 text-sm font-semibold text-gray-900"><?= esc($title) ?></p>
|
||||
<?php if ($hint !== '') : ?>
|
||||
<p class="mt-2 max-w-md text-sm text-gray-500"><?= esc($hint) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
179
app/Views/layouts/sidebar.php
Normal file
179
app/Views/layouts/sidebar.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* Menu panel admin + visibilitas menurut AdminAccess::canAccess().
|
||||
*/
|
||||
$u = uri_string();
|
||||
$is = static function (string $prefix) use ($u): bool {
|
||||
return $u === $prefix || str_starts_with($u, $prefix . '/');
|
||||
};
|
||||
$exact = static function (string $path) use ($u): bool {
|
||||
return $u === $path || $u === $path . '/';
|
||||
};
|
||||
$dashActive = $exact('admin');
|
||||
$linkBase = 'flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors';
|
||||
$linkIdle = 'text-gray-700 hover:bg-gray-100';
|
||||
$linkActive = 'bg-gray-100 text-gray-900';
|
||||
$subWrap = 'mt-1 space-y-0.5 border-l border-gray-200 py-1 pl-3 ml-3';
|
||||
$subLink = 'block rounded-md px-2 py-2 text-xs font-medium transition-colors';
|
||||
$subIdle = 'text-gray-600 hover:bg-gray-50 hover:text-gray-900';
|
||||
$subActive = 'bg-gray-100 text-gray-900';
|
||||
|
||||
$presensiOpen = $is('admin/presensi');
|
||||
$presensiData = $exact('admin/presensi') || str_starts_with($u, 'admin/presensi/detail');
|
||||
$presLapangan = str_starts_with($u, 'admin/presensi/lapangan');
|
||||
$presLembur = str_starts_with($u, 'admin/presensi/lembur');
|
||||
$presJadwal = str_starts_with($u, 'admin/presensi/jadwal');
|
||||
$presLibur = str_starts_with($u, 'admin/presensi/libur');
|
||||
$presAktiv = str_starts_with($u, 'admin/presensi/aktivitas');
|
||||
|
||||
$perusahaanOpen = $is('admin/perusahaan');
|
||||
$laporanOpen = $is('admin/laporan');
|
||||
$laporanRingkas = $exact('admin/laporan');
|
||||
$laporanCuti = str_starts_with($u, 'admin/laporan/cuti');
|
||||
|
||||
$panelOpen = $is('admin/panel');
|
||||
$panelUserList = $exact('admin/panel/users') || str_starts_with($u, 'admin/panel/users/reset') || str_starts_with($u, 'admin/panel/users/edit');
|
||||
$panelUserCreate = str_starts_with($u, 'admin/panel/users/create');
|
||||
$panelGroupsIndex = $exact('admin/panel/groups');
|
||||
$panelGroupCreate = str_starts_with($u, 'admin/panel/groups/create');
|
||||
$panelGroupEdit = str_starts_with($u, 'admin/panel/groups/edit');
|
||||
|
||||
$utilOpen = $is('admin/util');
|
||||
?>
|
||||
<aside id="admin-sidebar" class="fixed inset-y-0 left-0 z-50 flex h-screen w-[290px] shrink-0 flex-col border-r border-gray-200 bg-white shadow-sm lg:static lg:shadow-none">
|
||||
<div class="sidebar-brand flex h-16 shrink-0 items-center gap-3 border-b border-gray-200 px-5">
|
||||
<img src="<?= base_url('assets/images/bpr-logo.png') ?>" alt="Logo BPR" width="144" height="36" decoding="async" class="sidebar-logo-expanded h-9 w-auto max-w-[9rem] shrink-0 object-contain object-left">
|
||||
<img src="<?= base_url('assets/images/bij_logo.png') ?>" alt="BIJ" width="36" height="36" decoding="async" class="sidebar-logo-collapsed hidden h-9 w-9 shrink-0 rounded-none border-0 bg-transparent object-contain shadow-none ring-0 outline-none">
|
||||
</div>
|
||||
<nav class="custom-scrollbar flex-1 overflow-y-auto overflow-x-hidden px-3 py-5 text-sm">
|
||||
<p class="nav-section-label mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-gray-400">Menu</p>
|
||||
|
||||
<a href="<?= site_url('admin') ?>" title="Beranda" class="<?= $linkBase ?> <?= $dashActive ? $linkActive : $linkIdle ?>">
|
||||
<i class="fa-solid fa-house w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text">Beranda</span>
|
||||
</a>
|
||||
|
||||
<?php if (canAccess('presensi')) : ?>
|
||||
<details class="group mb-1" <?= $presensiOpen ? 'open' : '' ?>>
|
||||
<summary class="<?= $linkBase ?> cursor-pointer list-none text-gray-700 marker:hidden hover:bg-gray-100 [&::-webkit-details-marker]:hidden">
|
||||
<i class="fa-solid fa-clock w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text flex-1 text-left">Presensi</span>
|
||||
<i class="fa-solid fa-chevron-down text-[10px] text-gray-400 transition group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="<?= $subWrap ?>">
|
||||
<a href="<?= site_url('admin/presensi') ?>" title="Data Presensi" class="<?= $subLink ?> <?= $presensiData ? $subActive : $subIdle ?>">Data Presensi</a>
|
||||
<a href="<?= site_url('admin/presensi/lapangan') ?>" title="Tugas luar / lapangan" class="<?= $subLink ?> <?= $presLapangan ? $subActive : $subIdle ?>">Tugas Luar / Lapangan</a>
|
||||
<a href="<?= site_url('admin/presensi/lembur') ?>" title="Jadwal lembur" class="<?= $subLink ?> <?= $presLembur ? $subActive : $subIdle ?>">Jadwal Lembur</a>
|
||||
<?php if (canAccess('presensi_jadwal')) : ?>
|
||||
<a href="<?= site_url('admin/presensi/jadwal') ?>" title="Management jadwal" class="<?= $subLink ?> <?= $presJadwal ? $subActive : $subIdle ?>">Management Jadwal</a>
|
||||
<?php endif ?>
|
||||
<?php if (canAccess('presensi_libur')) : ?>
|
||||
<a href="<?= site_url('admin/presensi/libur') ?>" title="Hari libur" class="<?= $subLink ?> <?= $presLibur ? $subActive : $subIdle ?>">Hari Libur</a>
|
||||
<?php endif ?>
|
||||
<a href="<?= site_url('admin/presensi/aktivitas') ?>" title="Rekaman aktivitas" class="<?= $subLink ?> <?= $presAktiv ? $subActive : $subIdle ?>">Rekaman Aktivitas</a>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (canAccess('perusahaan')) : ?>
|
||||
<details class="group mb-1" <?= $perusahaanOpen ? 'open' : '' ?>>
|
||||
<summary class="<?= $linkBase ?> cursor-pointer list-none text-gray-700 marker:hidden hover:bg-gray-100 [&::-webkit-details-marker]:hidden">
|
||||
<i class="fa-solid fa-building w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text flex-1 text-left">Perusahaan</span>
|
||||
<i class="fa-solid fa-chevron-down text-[10px] text-gray-400 transition group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="<?= $subWrap ?>">
|
||||
<a href="<?= site_url('admin/perusahaan/kantor') ?>" title="Lokasi kerja" class="<?= $subLink ?> <?= str_starts_with($u, 'admin/perusahaan/kantor') ? $subActive : $subIdle ?>">Lokasi Kerja</a>
|
||||
<a href="<?= site_url('admin/perusahaan/unit_kerja') ?>" title="Unit kerja" class="<?= $subLink ?> <?= str_starts_with($u, 'admin/perusahaan/unit_kerja') ? $subActive : $subIdle ?>">Unit Kerja</a>
|
||||
<a href="<?= site_url('admin/perusahaan/jabatan') ?>" title="Jabatan" class="<?= $subLink ?> <?= str_starts_with($u, 'admin/perusahaan/jabatan') ? $subActive : $subIdle ?>">Jabatan</a>
|
||||
<a href="<?= site_url('admin/perusahaan/golongan') ?>" title="Golongan" class="<?= $subLink ?> <?= str_starts_with($u, 'admin/perusahaan/golongan') ? $subActive : $subIdle ?>">Golongan</a>
|
||||
<a href="<?= site_url('admin/perusahaan/berita') ?>" title="Berita dan pengumuman" class="<?= $subLink ?> <?= str_starts_with($u, 'admin/perusahaan/berita') ? $subActive : $subIdle ?>">Berita / Pengumuman</a>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (canAccess('pegawai')) : ?>
|
||||
<details class="group mb-1" <?= ($is('admin/pegawai') || $is('admin/cuti')) ? 'open' : '' ?>>
|
||||
<summary class="<?= $linkBase ?> cursor-pointer list-none text-gray-700 marker:hidden hover:bg-gray-100 [&::-webkit-details-marker]:hidden">
|
||||
<i class="fa-solid fa-users w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text flex-1 text-left">Pegawai</span>
|
||||
<i class="fa-solid fa-chevron-down text-[10px] text-gray-400 transition group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="<?= $subWrap ?>">
|
||||
<a href="<?= site_url('admin/pegawai') ?>" title="Data pegawai" class="<?= $subLink ?> <?= $is('admin/pegawai') && ! $is('admin/cuti') ? $subActive : $subIdle ?>">Data Pegawai</a>
|
||||
<?php if (canAccess('cuti')) : ?>
|
||||
<a href="<?= site_url('admin/cuti') ?>" title="Data cuti" class="<?= $subLink ?> <?= $is('admin/cuti') ? $subActive : $subIdle ?>">Data Cuti</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (canAccess('laporan')) : ?>
|
||||
<details class="group mb-1" <?= $laporanOpen ? 'open' : '' ?>>
|
||||
<summary class="<?= $linkBase ?> cursor-pointer list-none text-gray-700 marker:hidden hover:bg-gray-100 [&::-webkit-details-marker]:hidden">
|
||||
<i class="fa-solid fa-print w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text flex-1 text-left">Laporan</span>
|
||||
<i class="fa-solid fa-chevron-down text-[10px] text-gray-400 transition group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="<?= $subWrap ?>">
|
||||
<a href="<?= site_url('admin/laporan') ?>" title="Statistik dan ringkasan" class="<?= $subLink ?> <?= $laporanRingkas ? $subActive : $subIdle ?>">Statistik / Ringkasan</a>
|
||||
<a href="<?= site_url('admin/laporan/cuti') ?>" title="Laporan cuti pegawai" class="<?= $subLink ?> <?= $laporanCuti ? $subActive : $subIdle ?>">Cuti Pegawai</a>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (canAccess('panel')) : ?>
|
||||
<details class="group mb-1" <?= $panelOpen ? 'open' : '' ?>>
|
||||
<summary class="<?= $linkBase ?> cursor-pointer list-none text-gray-700 marker:hidden hover:bg-gray-100 [&::-webkit-details-marker]:hidden">
|
||||
<i class="fa-solid fa-user-shield w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text flex-1 text-left">Akses Pengguna</span>
|
||||
<i class="fa-solid fa-chevron-down text-[10px] text-gray-400 transition group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="<?= $subWrap ?>">
|
||||
<a href="<?= site_url('admin/panel/users') ?>" title="Daftar pengguna admin" class="<?= $subLink ?> <?= $panelUserList && ! $panelUserCreate ? $subActive : $subIdle ?>">Daftar Pengguna</a>
|
||||
<a href="<?= site_url('admin/panel/users/create') ?>" title="Tambah pengguna admin" class="<?= $subLink ?> <?= $panelUserCreate ? $subActive : $subIdle ?>">Tambah Pengguna</a>
|
||||
<a href="<?= site_url('admin/panel/groups') ?>" title="Grup akses" class="<?= $subLink ?> <?= ($panelGroupsIndex || $panelGroupCreate || $panelGroupEdit) && ! $panelGroupCreate && ! $panelGroupEdit ? $subActive : $subIdle ?>">Kelola Grup</a>
|
||||
<a href="<?= site_url('admin/panel/groups/create') ?>" title="Tambah grup" class="<?= $subLink ?> <?= $panelGroupCreate ? $subActive : $subIdle ?>">Tambah Grup</a>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (canAccess('utilitas')) : ?>
|
||||
<details class="group mb-1" <?= $utilOpen ? 'open' : '' ?>>
|
||||
<summary class="<?= $linkBase ?> cursor-pointer list-none text-gray-700 marker:hidden hover:bg-gray-100 [&::-webkit-details-marker]:hidden">
|
||||
<i class="fa-solid fa-gears w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text flex-1 text-left">Utilitas</span>
|
||||
<i class="fa-solid fa-chevron-down text-[10px] text-gray-400 transition group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="<?= $subWrap ?>">
|
||||
<a href="<?= site_url('admin/util/backup') ?>" title="Backup database" class="<?= $subLink ?> <?= str_starts_with($u, 'admin/util/backup') ? $subActive : $subIdle ?>">Daftar File Backup</a>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
|
||||
<?php
|
||||
$apk = env('ANDROID_APP_APK_URL', 'https://bij.mwp.co.id/assets/uploads/bij_apps_v_1_1_0.apk');
|
||||
?>
|
||||
<div class="mt-4 border-t border-gray-200 pt-4">
|
||||
<p class="nav-section-label mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-gray-400">Tautan</p>
|
||||
<?php if (is_string($apk) && $apk !== '' && canAccess('apk_link')) : ?>
|
||||
<a href="<?= esc($apk) ?>" target="_blank" rel="noopener noreferrer" title="Unduh aplikasi Android" class="<?= $linkBase ?> text-emerald-700 hover:bg-emerald-50">
|
||||
<i class="fa-brands fa-android w-5 shrink-0 text-center"></i>
|
||||
<span class="sidebar-text">Aplikasi Android</span>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
<?php if (session()->get('admin_mobile_token')) : ?>
|
||||
<p class="sidebar-text mb-1 truncate px-3 text-xs text-gray-500"><?= esc(session('admin_username') ?? '') ?></p>
|
||||
<a href="<?= site_url('admin/logout') ?>" title="Keluar dari panel" class="<?= $linkBase ?> text-red-600 hover:bg-red-50">
|
||||
<i class="fa-solid fa-right-from-bracket w-5 shrink-0 text-center"></i>
|
||||
<span class="sidebar-text">Sign Out</span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<a href="<?= site_url('admin/login') ?>" title="Masuk ke panel admin" class="<?= $linkBase ?> border border-gray-200 bg-gray-50 text-gray-900 hover:bg-white">
|
||||
<i class="fa-solid fa-key w-5 shrink-0 text-center text-gray-500"></i>
|
||||
<span class="sidebar-text">Login</span>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
19
app/Views/pages/dashboard.php
Normal file
19
app/Views/pages/dashboard.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php $this->extend('layouts/main'); ?>
|
||||
|
||||
<?php $this->section('title'); ?>
|
||||
Dashboard
|
||||
<?php $this->endSection(); ?>
|
||||
|
||||
<?php $this->section('content'); ?>
|
||||
<div class="mx-auto max-w-4xl rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Dashboard</h1>
|
||||
<p class="mt-2 text-slate-600">
|
||||
Layout admin memakai struktur partial CI4 (<code>extend</code> / <code>section</code>) dan aset TailAdmin di
|
||||
<code><?= esc(base_url('assets/tailadmin/')) ?></code>.
|
||||
</p>
|
||||
<ul class="mt-4 list-inside list-disc text-sm text-slate-600">
|
||||
<li>API mobile: <code class="rounded bg-slate-100 px-1">POST <?= esc(site_url('api/mobile/login')) ?></code></li>
|
||||
<li>Endpoint login JSON (opsional): <code class="rounded bg-slate-100 px-1">POST <?= esc(site_url('json/login')) ?></code></li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php $this->endSection(); ?>
|
||||
331
app/Views/welcome_message.php
Normal file
331
app/Views/welcome_message.php
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user